From 373928808951f01bfcbffc377979978b39f7761c Mon Sep 17 00:00:00 2001 From: djgreen Date: Thu, 4 Jun 2026 16:34:09 -0700 Subject: [PATCH 1/5] adding danny's branch for procedure documentation --- docs/assets/test_procedure.hermes.md | 97 + docs/exec/author.md | 84 + docs/exec/execution.md | 22 + docs/exec/procs_flow.md | 45 + .../procedure_templates/asrun_generator.py | 72 + venv/bin/Activate.ps1 | 247 + venv/bin/activate | 69 + venv/bin/activate.csh | 26 + venv/bin/activate.fish | 69 + venv/bin/python | 1 + venv/bin/python3 | 1 + venv/bin/python3.10 | 1 + .../site-packages/_distutils_hack/__init__.py | 132 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 5131 bytes .../__pycache__/override.cpython-310.pyc | Bin 0 -> 256 bytes .../site-packages/_distutils_hack/override.py | 1 + .../site-packages/distutils-precedence.pth | 1 + .../pip-22.0.2.dist-info/LICENSE.txt | 20 + .../pip-22.0.2.dist-info/METADATA | 92 + .../site-packages/pip-22.0.2.dist-info/RECORD | 520 ++ .../site-packages/pip-22.0.2.dist-info/WHEEL | 5 + .../pip-22.0.2.dist-info/entry_points.txt | 5 + .../pip-22.0.2.dist-info/top_level.txt | 1 + .../python3.10/site-packages/pip/__init__.py | 13 + .../python3.10/site-packages/pip/__main__.py | 31 + .../site-packages/pip/_internal/__init__.py | 19 + .../site-packages/pip/_internal/build_env.py | 296 + .../site-packages/pip/_internal/cache.py | 264 + .../pip/_internal/cli/__init__.py | 4 + .../pip/_internal/cli/autocompletion.py | 171 + .../pip/_internal/cli/base_command.py | 220 + .../pip/_internal/cli/cmdoptions.py | 1018 +++ .../pip/_internal/cli/command_context.py | 27 + .../site-packages/pip/_internal/cli/main.py | 70 + .../pip/_internal/cli/main_parser.py | 87 + .../site-packages/pip/_internal/cli/parser.py | 292 + .../pip/_internal/cli/progress_bars.py | 321 + .../pip/_internal/cli/req_command.py | 506 ++ .../pip/_internal/cli/spinners.py | 157 + .../pip/_internal/cli/status_codes.py | 6 + .../pip/_internal/commands/__init__.py | 127 + .../pip/_internal/commands/cache.py | 223 + .../pip/_internal/commands/check.py | 53 + .../pip/_internal/commands/completion.py | 96 + .../pip/_internal/commands/configuration.py | 266 + .../pip/_internal/commands/debug.py | 202 + .../pip/_internal/commands/download.py | 140 + .../pip/_internal/commands/freeze.py | 97 + .../pip/_internal/commands/hash.py | 59 + .../pip/_internal/commands/help.py | 41 + .../pip/_internal/commands/index.py | 0 .../site-packages/pkg_resources/__init__.py | 3303 ++++++++++ .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 100619 bytes .../pkg_resources/_vendor/__init__.py | 0 .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 211 bytes .../__pycache__/appdirs.cpython-310.pyc | Bin 0 -> 20272 bytes .../__pycache__/pyparsing.cpython-310.pyc | Bin 0 -> 198764 bytes .../pkg_resources/_vendor/appdirs.py | 608 ++ .../_vendor/packaging/__about__.py | 26 + .../_vendor/packaging/__init__.py | 25 + .../__pycache__/__about__.cpython-310.pyc | Bin 0 -> 618 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 474 bytes .../__pycache__/_manylinux.cpython-310.pyc | Bin 0 -> 7328 bytes .../__pycache__/_musllinux.cpython-310.pyc | Bin 0 -> 4640 bytes .../__pycache__/_structures.cpython-310.pyc | Bin 0 -> 2998 bytes .../__pycache__/markers.cpython-310.pyc | Bin 0 -> 9324 bytes .../__pycache__/requirements.cpython-310.pyc | Bin 0 -> 4012 bytes .../__pycache__/specifiers.cpython-310.pyc | Bin 0 -> 22216 bytes .../__pycache__/tags.cpython-310.pyc | Bin 0 -> 12242 bytes .../__pycache__/utils.cpython-310.pyc | Bin 0 -> 3603 bytes .../__pycache__/version.cpython-310.pyc | Bin 0 -> 12953 bytes .../_vendor/packaging/_manylinux.py | 301 + .../_vendor/packaging/_musllinux.py | 136 + .../_vendor/packaging/_structures.py | 67 + .../_vendor/packaging/markers.py | 304 + .../_vendor/packaging/requirements.py | 146 + .../_vendor/packaging/specifiers.py | 828 +++ .../pkg_resources/_vendor/packaging/tags.py | 484 ++ .../pkg_resources/_vendor/packaging/utils.py | 136 + .../_vendor/packaging/version.py | 504 ++ .../pkg_resources/_vendor/pyparsing.py | 5742 +++++++++++++++++ .../pkg_resources/extern/__init__.py | 73 + .../data/my-test-package-source/setup.py | 6 + .../setuptools-59.6.0.dist-info/LICENSE | 19 + .../setuptools-59.6.0.dist-info/METADATA | 125 + .../setuptools-59.6.0.dist-info/RECORD | 157 + .../setuptools-59.6.0.dist-info/WHEEL | 5 + .../entry_points.txt | 56 + .../setuptools-59.6.0.dist-info/top_level.txt | 4 + .../site-packages/setuptools/__init__.py | 242 + .../setuptools/_deprecation_warning.py | 7 + .../setuptools/_distutils/__init__.py | 24 + .../setuptools/_distutils/_msvccompiler.py | 561 ++ .../setuptools/_distutils/archive_util.py | 256 + .../setuptools/_distutils/bcppcompiler.py | 393 ++ .../setuptools/_distutils/ccompiler.py | 1123 ++++ .../setuptools/_distutils/cmd.py | 403 ++ .../setuptools/_distutils/command/__init__.py | 31 + .../setuptools/_distutils/command/bdist.py | 143 + .../_distutils/command/bdist_dumb.py | 123 + .../_distutils/command/bdist_msi.py | 749 +++ .../_distutils/command/bdist_rpm.py | 579 ++ .../_distutils/command/bdist_wininst.py | 377 ++ .../setuptools/_distutils/command/build.py | 157 + .../_distutils/command/build_clib.py | 209 + .../_distutils/command/build_ext.py | 755 +++ .../setuptools/_distutils/command/build_py.py | 392 ++ .../_distutils/command/build_scripts.py | 152 + .../setuptools/_distutils/command/check.py | 148 + .../setuptools/_distutils/command/clean.py | 76 + .../setuptools/_distutils/command/config.py | 344 + .../setuptools/_distutils/command/install.py | 721 +++ .../_distutils/command/install_data.py | 79 + .../_distutils/command/install_egg_info.py | 84 + .../_distutils/command/install_headers.py | 47 + .../_distutils/command/install_lib.py | 217 + .../_distutils/command/install_scripts.py | 60 + .../_distutils/command/py37compat.py | 30 + .../setuptools/_distutils/command/register.py | 304 + .../setuptools/_distutils/command/sdist.py | 494 ++ .../setuptools/_distutils/command/upload.py | 214 + .../setuptools/_distutils/config.py | 130 + .../setuptools/_distutils/core.py | 249 + .../setuptools/_distutils/cygwinccompiler.py | 425 ++ .../setuptools/_distutils/debug.py | 5 + .../setuptools/_distutils/dep_util.py | 92 + .../setuptools/_distutils/dir_util.py | 210 + .../setuptools/_distutils/dist.py | 1257 ++++ .../setuptools/_distutils/errors.py | 97 + .../setuptools/_distutils/extension.py | 240 + .../setuptools/_distutils/fancy_getopt.py | 457 ++ .../setuptools/_distutils/file_util.py | 238 + .../setuptools/_distutils/filelist.py | 355 + .../setuptools/_distutils/log.py | 77 + .../setuptools/_distutils/msvc9compiler.py | 788 +++ .../setuptools/_distutils/msvccompiler.py | 643 ++ .../setuptools/_distutils/py35compat.py | 19 + .../setuptools/_distutils/py38compat.py | 7 + .../setuptools/_distutils/spawn.py | 106 + .../setuptools/_distutils/sysconfig.py | 601 ++ .../setuptools/_distutils/text_file.py | 286 + .../setuptools/_distutils/unixccompiler.py | 325 + .../setuptools/_distutils/util.py | 548 ++ .../setuptools/_distutils/version.py | 363 ++ .../setuptools/_distutils/versionpredicate.py | 169 + .../site-packages/setuptools/_imp.py | 82 + .../setuptools/_vendor/__init__.py | 0 .../_vendor/more_itertools/__init__.py | 4 + .../setuptools/_vendor/more_itertools/more.py | 3825 +++++++++++ .../_vendor/more_itertools/recipes.py | 620 ++ .../setuptools/_vendor/ordered_set.py | 488 ++ .../setuptools/_vendor/packaging/__about__.py | 26 + .../setuptools/_vendor/packaging/__init__.py | 25 + .../_vendor/packaging/_manylinux.py | 301 + .../_vendor/packaging/_musllinux.py | 136 + .../_vendor/packaging/_structures.py | 67 + .../setuptools/_vendor/packaging/markers.py | 304 + .../_vendor/packaging/requirements.py | 146 + .../_vendor/packaging/specifiers.py | 828 +++ .../setuptools/_vendor/packaging/tags.py | 484 ++ .../setuptools/_vendor/packaging/utils.py | 136 + .../setuptools/_vendor/packaging/version.py | 504 ++ .../setuptools/_vendor/pyparsing.py | 5742 +++++++++++++++++ .../site-packages/setuptools/archive_util.py | 205 + .../site-packages/setuptools/build_meta.py | 290 + .../site-packages/setuptools/cli-32.exe | Bin 0 -> 65536 bytes .../site-packages/setuptools/cli-64.exe | Bin 0 -> 74752 bytes .../site-packages/setuptools/cli-arm64.exe | Bin 0 -> 137216 bytes .../site-packages/setuptools/cli.exe | Bin 0 -> 65536 bytes .../setuptools/command/__init__.py | 8 + .../site-packages/setuptools/command/alias.py | 78 + .../setuptools/command/bdist_egg.py | 456 ++ .../setuptools/command/bdist_rpm.py | 40 + .../setuptools/command/build_clib.py | 101 + .../setuptools/command/build_ext.py | 328 + .../setuptools/command/build_py.py | 242 + .../setuptools/command/develop.py | 193 + .../setuptools/command/dist_info.py | 36 + .../setuptools/command/easy_install.py | 2354 +++++++ .../setuptools/command/egg_info.py | 755 +++ .../setuptools/command/install.py | 132 + .../setuptools/command/install_egg_info.py | 82 + .../setuptools/command/install_lib.py | 148 + .../setuptools/command/install_scripts.py | 69 + .../setuptools/command/launcher manifest.xml | 15 + .../setuptools/command/py36compat.py | 134 + .../setuptools/command/register.py | 18 + .../setuptools/command/rotate.py | 64 + .../setuptools/command/saveopts.py | 22 + .../site-packages/setuptools/command/sdist.py | 196 + .../setuptools/command/setopt.py | 149 + .../site-packages/setuptools/command/test.py | 252 + .../setuptools/command/upload.py | 17 + .../setuptools/command/upload_docs.py | 202 + .../site-packages/setuptools/config.py | 751 +++ .../site-packages/setuptools/dep_util.py | 25 + .../site-packages/setuptools/depends.py | 176 + .../site-packages/setuptools/dist.py | 1156 ++++ .../site-packages/setuptools/errors.py | 40 + .../site-packages/setuptools/extension.py | 55 + .../setuptools/extern/__init__.py | 73 + .../site-packages/setuptools/glob.py | 167 + .../site-packages/setuptools/gui-32.exe | Bin 0 -> 65536 bytes .../site-packages/setuptools/gui-64.exe | Bin 0 -> 75264 bytes .../site-packages/setuptools/gui-arm64.exe | Bin 0 -> 137728 bytes .../site-packages/setuptools/gui.exe | Bin 0 -> 65536 bytes .../site-packages/setuptools/installer.py | 104 + .../site-packages/setuptools/launch.py | 36 + .../site-packages/setuptools/monkey.py | 177 + .../site-packages/setuptools/msvc.py | 1805 ++++++ .../site-packages/setuptools/namespaces.py | 107 + .../site-packages/setuptools/package_index.py | 1176 ++++ .../site-packages/setuptools/py34compat.py | 13 + .../site-packages/setuptools/sandbox.py | 530 ++ .../setuptools/script (dev).tmpl | 6 + .../site-packages/setuptools/script.tmpl | 3 + .../site-packages/setuptools/unicode_utils.py | 42 + .../site-packages/setuptools/version.py | 6 + .../site-packages/setuptools/wheel.py | 213 + .../setuptools/windows_support.py | 29 + venv/lib64 | 1 + venv/pyvenv.cfg | 3 + zensical.toml | 5 + 223 files changed, 63556 insertions(+) create mode 100644 docs/assets/test_procedure.hermes.md create mode 100644 docs/exec/author.md create mode 100644 docs/exec/execution.md create mode 100644 docs/exec/procs_flow.md create mode 100644 docs/procedures/procedure_templates/asrun_generator.py create mode 100644 venv/bin/Activate.ps1 create mode 100644 venv/bin/activate create mode 100644 venv/bin/activate.csh create mode 100644 venv/bin/activate.fish create mode 120000 venv/bin/python create mode 120000 venv/bin/python3 create mode 120000 venv/bin/python3.10 create mode 100644 venv/lib/python3.10/site-packages/_distutils_hack/__init__.py create mode 100644 venv/lib/python3.10/site-packages/_distutils_hack/__pycache__/__init__.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/_distutils_hack/__pycache__/override.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/_distutils_hack/override.py create mode 100644 venv/lib/python3.10/site-packages/distutils-precedence.pth create mode 100644 venv/lib/python3.10/site-packages/pip-22.0.2.dist-info/LICENSE.txt create mode 100644 venv/lib/python3.10/site-packages/pip-22.0.2.dist-info/METADATA create mode 100644 venv/lib/python3.10/site-packages/pip-22.0.2.dist-info/RECORD create mode 100644 venv/lib/python3.10/site-packages/pip-22.0.2.dist-info/WHEEL create mode 100644 venv/lib/python3.10/site-packages/pip-22.0.2.dist-info/entry_points.txt create mode 100644 venv/lib/python3.10/site-packages/pip-22.0.2.dist-info/top_level.txt create mode 100644 venv/lib/python3.10/site-packages/pip/__init__.py create mode 100644 venv/lib/python3.10/site-packages/pip/__main__.py create mode 100644 venv/lib/python3.10/site-packages/pip/_internal/__init__.py create mode 100644 venv/lib/python3.10/site-packages/pip/_internal/build_env.py create mode 100644 venv/lib/python3.10/site-packages/pip/_internal/cache.py create mode 100644 venv/lib/python3.10/site-packages/pip/_internal/cli/__init__.py create mode 100644 venv/lib/python3.10/site-packages/pip/_internal/cli/autocompletion.py create mode 100644 venv/lib/python3.10/site-packages/pip/_internal/cli/base_command.py create mode 100644 venv/lib/python3.10/site-packages/pip/_internal/cli/cmdoptions.py create mode 100644 venv/lib/python3.10/site-packages/pip/_internal/cli/command_context.py create mode 100644 venv/lib/python3.10/site-packages/pip/_internal/cli/main.py create mode 100644 venv/lib/python3.10/site-packages/pip/_internal/cli/main_parser.py create mode 100644 venv/lib/python3.10/site-packages/pip/_internal/cli/parser.py create mode 100644 venv/lib/python3.10/site-packages/pip/_internal/cli/progress_bars.py create mode 100644 venv/lib/python3.10/site-packages/pip/_internal/cli/req_command.py create mode 100644 venv/lib/python3.10/site-packages/pip/_internal/cli/spinners.py create mode 100644 venv/lib/python3.10/site-packages/pip/_internal/cli/status_codes.py create mode 100644 venv/lib/python3.10/site-packages/pip/_internal/commands/__init__.py create mode 100644 venv/lib/python3.10/site-packages/pip/_internal/commands/cache.py create mode 100644 venv/lib/python3.10/site-packages/pip/_internal/commands/check.py create mode 100644 venv/lib/python3.10/site-packages/pip/_internal/commands/completion.py create mode 100644 venv/lib/python3.10/site-packages/pip/_internal/commands/configuration.py create mode 100644 venv/lib/python3.10/site-packages/pip/_internal/commands/debug.py create mode 100644 venv/lib/python3.10/site-packages/pip/_internal/commands/download.py create mode 100644 venv/lib/python3.10/site-packages/pip/_internal/commands/freeze.py create mode 100644 venv/lib/python3.10/site-packages/pip/_internal/commands/hash.py create mode 100644 venv/lib/python3.10/site-packages/pip/_internal/commands/help.py create mode 100644 venv/lib/python3.10/site-packages/pip/_internal/commands/index.py create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/__init__.py create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/__pycache__/__init__.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/__init__.py create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/__pycache__/__init__.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/__pycache__/appdirs.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/__pycache__/pyparsing.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/appdirs.py create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__about__.py create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__init__.py create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__pycache__/__about__.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__pycache__/__init__.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__pycache__/_manylinux.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__pycache__/_musllinux.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__pycache__/_structures.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__pycache__/markers.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__pycache__/requirements.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__pycache__/specifiers.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__pycache__/tags.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__pycache__/utils.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__pycache__/version.cpython-310.pyc create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/_manylinux.py create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/_musllinux.py create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/_structures.py create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/markers.py create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/requirements.py create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/specifiers.py create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/tags.py create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/utils.py create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/version.py create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing.py create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/extern/__init__.py create mode 100644 venv/lib/python3.10/site-packages/pkg_resources/tests/data/my-test-package-source/setup.py create mode 100644 venv/lib/python3.10/site-packages/setuptools-59.6.0.dist-info/LICENSE create mode 100644 venv/lib/python3.10/site-packages/setuptools-59.6.0.dist-info/METADATA create mode 100644 venv/lib/python3.10/site-packages/setuptools-59.6.0.dist-info/RECORD create mode 100644 venv/lib/python3.10/site-packages/setuptools-59.6.0.dist-info/WHEEL create mode 100644 venv/lib/python3.10/site-packages/setuptools-59.6.0.dist-info/entry_points.txt create mode 100644 venv/lib/python3.10/site-packages/setuptools-59.6.0.dist-info/top_level.txt create mode 100644 venv/lib/python3.10/site-packages/setuptools/__init__.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_deprecation_warning.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/__init__.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/_msvccompiler.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/archive_util.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/bcppcompiler.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/ccompiler.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/cmd.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/command/__init__.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/command/bdist.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/command/bdist_dumb.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/command/bdist_msi.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/command/bdist_rpm.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/command/bdist_wininst.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/command/build.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/command/build_clib.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/command/build_ext.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/command/build_py.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/command/build_scripts.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/command/check.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/command/clean.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/command/config.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/command/install.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/command/install_data.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/command/install_egg_info.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/command/install_headers.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/command/install_lib.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/command/install_scripts.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/command/py37compat.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/command/register.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/command/sdist.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/command/upload.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/config.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/core.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/cygwinccompiler.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/debug.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/dep_util.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/dir_util.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/dist.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/errors.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/extension.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/fancy_getopt.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/file_util.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/filelist.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/log.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/msvc9compiler.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/msvccompiler.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/py35compat.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/py38compat.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/spawn.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/sysconfig.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/text_file.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/unixccompiler.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/util.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/version.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_distutils/versionpredicate.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_imp.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_vendor/__init__.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_vendor/more_itertools/__init__.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_vendor/more_itertools/more.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_vendor/more_itertools/recipes.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_vendor/ordered_set.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/__about__.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/__init__.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/_manylinux.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/_musllinux.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/_structures.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/markers.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/requirements.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/specifiers.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/tags.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/utils.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/version.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/_vendor/pyparsing.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/archive_util.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/build_meta.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/cli-32.exe create mode 100644 venv/lib/python3.10/site-packages/setuptools/cli-64.exe create mode 100644 venv/lib/python3.10/site-packages/setuptools/cli-arm64.exe create mode 100644 venv/lib/python3.10/site-packages/setuptools/cli.exe create mode 100644 venv/lib/python3.10/site-packages/setuptools/command/__init__.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/command/alias.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/command/bdist_egg.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/command/bdist_rpm.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/command/build_clib.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/command/build_ext.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/command/build_py.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/command/develop.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/command/dist_info.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/command/easy_install.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/command/egg_info.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/command/install.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/command/install_egg_info.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/command/install_lib.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/command/install_scripts.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/command/launcher manifest.xml create mode 100644 venv/lib/python3.10/site-packages/setuptools/command/py36compat.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/command/register.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/command/rotate.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/command/saveopts.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/command/sdist.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/command/setopt.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/command/test.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/command/upload.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/command/upload_docs.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/config.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/dep_util.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/depends.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/dist.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/errors.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/extension.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/extern/__init__.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/glob.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/gui-32.exe create mode 100644 venv/lib/python3.10/site-packages/setuptools/gui-64.exe create mode 100644 venv/lib/python3.10/site-packages/setuptools/gui-arm64.exe create mode 100644 venv/lib/python3.10/site-packages/setuptools/gui.exe create mode 100644 venv/lib/python3.10/site-packages/setuptools/installer.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/launch.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/monkey.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/msvc.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/namespaces.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/package_index.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/py34compat.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/sandbox.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/script (dev).tmpl create mode 100644 venv/lib/python3.10/site-packages/setuptools/script.tmpl create mode 100644 venv/lib/python3.10/site-packages/setuptools/unicode_utils.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/version.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/wheel.py create mode 100644 venv/lib/python3.10/site-packages/setuptools/windows_support.py create mode 120000 venv/lib64 create mode 100644 venv/pyvenv.cfg diff --git a/docs/assets/test_procedure.hermes.md b/docs/assets/test_procedure.hermes.md new file mode 100644 index 00000000..4d23e35f --- /dev/null +++ b/docs/assets/test_procedure.hermes.md @@ -0,0 +1,97 @@ + + +--- +# Procedure Name: + + **Test Procedure Author:** + + **Test_Description:** + + **Date Created:** + + + + + +**Standard Comments:** + +*SECTION:* denotes a new section of the procedure + + **MANUAL INSTRUCT:** denotes a manual action to be taken by the operator + + **RECORD:** denotes a manual recording of a value in the procedure to be written in by the operator +- (record at the end of the next line) +- **RECORD HERE>>:** + + **CAUTION:** denotes a particularly important note for the operator to read or take action on, likely related to hardware or personnel safety. + + + +Redline template below can be copy and pasted into the appropriate procedure location. The section title of the start is intentional so that the outline feature of VS Code shows new redlines in the outline. The end redline is not a section title so that the start redline captures + + +##### **REDLINE_START:** denotes the start of a redlined section of a procedure that has been added during the asrun + + + **REDLINE_STOP:** denotes the end of a redlined section of a procedure that has been added during the asrun + + +--- + + +# Startup + + + + **MANUAL INSTRUCT:** + +* Complete the steps for start up + + + +```fprime +R00:00:00 CdhCore.cmdDisp.CMD_NO_OP +``` + + +```fprime +R00:00:00 CdhCore.cmdDisp.CMD_NO_OP_STRING "Example Text to Print to the Terminal" + +``` + + +ADD A TABLE + + +ADD A PICTURE + + +# Test Run + + + + **MANUAL INSTRUCT:** + +* Complete the steps for runnng the test + + + +# Shutdown + + + + **MANUAL INSTRUCT:** + +* Complete the steps for shut down + + + +# Appendix + + + + **MANUAL INSTRUCT:** + +* This section contains additional diagrams, tables, and references that can be called out throughout the procedure. + + \ No newline at end of file diff --git a/docs/exec/author.md b/docs/exec/author.md new file mode 100644 index 00000000..15ef7c69 --- /dev/null +++ b/docs/exec/author.md @@ -0,0 +1,84 @@ +--- +icon: fontawesome/solid/pen-fancy +--- + +# Authoring + +Authoring the .hermes.md procedure templates is recommended in VS Code. +Terms: +* Markdown Block: Manual Instructions or notes for the test operators +* Code Block: Instructions that can be scripts, logic, calculations, or commands to the software like FPrime. +* Section: Uses # to denote different levels of section headers. +* Outline (VS Code): The left pane in VS Code has a feature to show the user the section headers as a way of outlining a document/procedure. + +There .md file extension is markdown and follows markdown formating. + +Markdown is not the only format used in the procedure. Python, bash, FPrime, and more are all capabilities built into the procedure capability. + +When using a FPrime code block, there are additional features that allow for integrated command dictionaries giving suthors real time commands, agruments, argument range validation, and additional argument details. + +## :fontawesome-solid-cube: Common Markdown Blocks + + +### :fontawesome-solid-table-columns: Tables + +### :fontawesome-solid-camera: Pictures + +### :fontawesome-solid-link: URL Links + +### :fontawesome-solid-stairs: Manual Instructs + +* Denotes a manual action to be taken by the operator +* Examples: Take Picture with camera, open hand valve, move hardware, etc. + +``` py + **MANUAL INSTRUCT:** +``` +:fontawesome-solid-arrow-down: Looks like :fontawesome-solid-arrow-down: + + **MANUAL INSTRUCT:** + +### :fontawesome-solid-text-slash: Redlines + +* Red Lines are used to document a deviation from the approved procedural steps. These are critical to repeating tests effectively and understanding if an anomaly is due to test response, procedural error, or operator error. +* The Start Redline has a title header while the End Redline does not. + * This is to accomodate the collapse-hierarchy in the outline function in VS Code. + +``` py +##### **REDLINE_START:** +``` +``` py + + **REDLINE_STOP:** + +``` +:fontawesome-solid-arrow-down: Looks like :fontawesome-solid-arrow-down: + + **REDLINE_START:** + + **REDLINE_STOP:** + +### :fontawesome-solid-note-sticky: Notes + +### :fontawesome-solid-compact-disc: Record + +## :fontawesome-solid-terminal: Common Code Blocks + +### :fontawesome-solid-satellite-dish: Flight Commands + +### :fontawesome-brands-python: Python Scripts + +### :fontawesome-solid-code: Bash Scripts + + + +!!! question "FAQs" + +!!! warning "Documentation In Progress" + + This documentation is incomplete while we are migrating from our internal documentation store to the public GitHub. + + + + +This is new Content \ No newline at end of file diff --git a/docs/exec/execution.md b/docs/exec/execution.md new file mode 100644 index 00000000..ddb32fd6 --- /dev/null +++ b/docs/exec/execution.md @@ -0,0 +1,22 @@ +--- +icon: fontawesome/solid/play +--- + +# Execution + +Add a flow diagram of the process expected for the procedure usage + + +!!! abstract Authoring +!!! example "Test Execution" + +!!! question "FAQs" + +!!! warning "Documentation In Progress" + + This documentation is incomplete while we are migrating from our internal documentation store to the public GitHub. + + + + +This is new Content \ No newline at end of file diff --git a/docs/exec/procs_flow.md b/docs/exec/procs_flow.md new file mode 100644 index 00000000..edb1192f --- /dev/null +++ b/docs/exec/procs_flow.md @@ -0,0 +1,45 @@ +--- +icon: fontawesome/solid/scroll +--- + +# Procedures + +Documentation of a test is crucial to repeatable data collection. Procedures are used to record the steps taken for each test performed. Procedures can have different use cases, formats, and requirements. + +An architechture that has proven successful and effetive has been included as a recommended method of authoring, executing, and tracking procedures. + +Terms + +* Template: A standard document that can be used multiple times +* As-Run: The procedure used to record a specific test instance. +* Test-ID: The unique identification of a test or a condition + +``` mermaid +classDiagram + AuthorProcedure --|> Template Procedure + Template Procedure --|> procedure_name_TEST_1.hermes.md:asrun-generator.py TEST_1 + Template Procedure --|> procedure_name_TEST_2.hermes.md:asrun-generator.py TEST_2 + Template Procedure --|> procedure_name_TEST_3.hermes.md:asrun-generator.py TEST_3 + + class AuthorProcedure { + VS Code as wutoring tool + Github as tracking of templates, as-runs, notes, etc. + + } + class Template Procedure { + procedure_name.hermes.md + Different Templates for different campaings/conditions/etc. + } + class procedure_name_TEST_1.hermes.md { + Use this to execute the test condition 1 + } + class procedure_name_TEST_2.hermes.md { + Use this to execute the test condition 2 + } + class procedure_name_TEST_3.hermes.md { + Use this to execute the test condition 3 + } + +``` + +!!! question "FAQs" \ No newline at end of file diff --git a/docs/procedures/procedure_templates/asrun_generator.py b/docs/procedures/procedure_templates/asrun_generator.py new file mode 100644 index 00000000..b4f44679 --- /dev/null +++ b/docs/procedures/procedure_templates/asrun_generator.py @@ -0,0 +1,72 @@ +import os +import sys +import shutil +from pathlib import Path +import glob + +# ---- Check for argument ---- +if len(sys.argv) != 2: + print("Usage: python3 asrun_generator_test-name.py ") + sys.exit(1) + +test_id = sys.argv[1] + +# ---- Constants ---- +as_run_folder = "campaign_1" +test_procedure_name = "test_procedure" + +#Optional Capability +#if there is a string that should be replaced for each use of the procedure, +#use this variable to replace it in the as run +#var_REPLACEPROCEDURETEXT = "" + +# ---- Paths ---- +home = str(Path.home()) +procedure_dir = Path(__file__).resolve().parent.parent +as_run_dir = Path(os.path.join(procedure_dir, "as_runs", as_run_folder, test_id)) +template_dir = Path(os.path.join(procedure_dir, "procedure_templates")) +procedure_pics_dir = Path(os.path.join(template_dir, "procedure_pictures")) + + +# ---- Create directory if it doesn't exist ---- +os.makedirs(as_run_dir, exist_ok=True) + +# ---- Copy Test Procedure ---- +test_proc_pattern = os.path.join(as_run_dir, f"*{test_procedure_name}*") +test_proc_files = glob.glob(test_proc_pattern) + +file_names = [f.name for f in template_dir.iterdir() if f.is_file()] + +# Find the first file whose name contains the search text +procedure_dir = next( + (f for f in template_dir.iterdir() + if f.is_file() and test_procedure_name.lower() in f.name.lower()), + None +) + +if len(test_proc_files) == 0: + dst = os.path.join(as_run_dir, f"{test_procedure_name}_{test_id}.hermes.md") +else: + dst = os.path.join(as_run_dir, f"{test_procedure_name}_{test_id}_{len(test_proc_files) + 1}.hermes.md") + +src = os.path.join(template_dir, f"{test_procedure_name}.hermes.md") +print(src, dst) +shutil.copy(src, dst) + +# ---- Copy Procedure Pictures if Not Already Present ---- +pictures_path = os.path.join(home, "seqs/test_procedure_templates", "procedure_pictures", as_run_folder, test_id) +if not os.path.exists(procedure_pics_dir) or len(os.listdir(procedure_pics_dir)) == 0: + shutil.copytree(procedure_pics_dir, as_run_dir, dirs_exist_ok=True) + +for file_path in glob.glob(os.path.join(as_run_dir, "*.hermes.md")): + with open(file_path, "r", encoding="utf-8") as f: + content = f.read() + + new_content = content.replace("REPLACEPROCEDURETEXT", var_REPLACEPROCEDURETEXT) + + if new_content != content: + with open(file_path, "w", encoding="utf-8") as f: + f.write(new_content) + print(f"Updated REPLACEWITHTESTID in file: {os.path.basename(file_path)}") + else: + print(f"No placeholders of REPLACEWITHTESTID found in: {os.path.basename(file_path)}") diff --git a/venv/bin/Activate.ps1 b/venv/bin/Activate.ps1 new file mode 100644 index 00000000..b49d77ba --- /dev/null +++ b/venv/bin/Activate.ps1 @@ -0,0 +1,247 @@ +<# +.Synopsis +Activate a Python virtual environment for the current PowerShell session. + +.Description +Pushes the python executable for a virtual environment to the front of the +$Env:PATH environment variable and sets the prompt to signify that you are +in a Python virtual environment. Makes use of the command line switches as +well as the `pyvenv.cfg` file values present in the virtual environment. + +.Parameter VenvDir +Path to the directory that contains the virtual environment to activate. The +default value for this is the parent of the directory that the Activate.ps1 +script is located within. + +.Parameter Prompt +The prompt prefix to display when this virtual environment is activated. By +default, this prompt is the name of the virtual environment folder (VenvDir) +surrounded by parentheses and followed by a single space (ie. '(.venv) '). + +.Example +Activate.ps1 +Activates the Python virtual environment that contains the Activate.ps1 script. + +.Example +Activate.ps1 -Verbose +Activates the Python virtual environment that contains the Activate.ps1 script, +and shows extra information about the activation as it executes. + +.Example +Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv +Activates the Python virtual environment located in the specified location. + +.Example +Activate.ps1 -Prompt "MyPython" +Activates the Python virtual environment that contains the Activate.ps1 script, +and prefixes the current prompt with the specified string (surrounded in +parentheses) while the virtual environment is active. + +.Notes +On Windows, it may be required to enable this Activate.ps1 script by setting the +execution policy for the user. You can do this by issuing the following PowerShell +command: + +PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + +For more information on Execution Policies: +https://go.microsoft.com/fwlink/?LinkID=135170 + +#> +Param( + [Parameter(Mandatory = $false)] + [String] + $VenvDir, + [Parameter(Mandatory = $false)] + [String] + $Prompt +) + +<# Function declarations --------------------------------------------------- #> + +<# +.Synopsis +Remove all shell session elements added by the Activate script, including the +addition of the virtual environment's Python executable from the beginning of +the PATH variable. + +.Parameter NonDestructive +If present, do not remove this function from the global namespace for the +session. + +#> +function global:deactivate ([switch]$NonDestructive) { + # Revert to original values + + # The prior prompt: + if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) { + Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt + Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT + } + + # The prior PYTHONHOME: + if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) { + Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME + Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME + } + + # The prior PATH: + if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) { + Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH + Remove-Item -Path Env:_OLD_VIRTUAL_PATH + } + + # Just remove the VIRTUAL_ENV altogether: + if (Test-Path -Path Env:VIRTUAL_ENV) { + Remove-Item -Path env:VIRTUAL_ENV + } + + # Just remove VIRTUAL_ENV_PROMPT altogether. + if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) { + Remove-Item -Path env:VIRTUAL_ENV_PROMPT + } + + # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether: + if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) { + Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force + } + + # Leave deactivate function in the global namespace if requested: + if (-not $NonDestructive) { + Remove-Item -Path function:deactivate + } +} + +<# +.Description +Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the +given folder, and returns them in a map. + +For each line in the pyvenv.cfg file, if that line can be parsed into exactly +two strings separated by `=` (with any amount of whitespace surrounding the =) +then it is considered a `key = value` line. The left hand string is the key, +the right hand is the value. + +If the value starts with a `'` or a `"` then the first and last character is +stripped from the value before being captured. + +.Parameter ConfigDir +Path to the directory that contains the `pyvenv.cfg` file. +#> +function Get-PyVenvConfig( + [String] + $ConfigDir +) { + Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg" + + # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue). + $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue + + # An empty map will be returned if no config file is found. + $pyvenvConfig = @{ } + + if ($pyvenvConfigPath) { + + Write-Verbose "File exists, parse `key = value` lines" + $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath + + $pyvenvConfigContent | ForEach-Object { + $keyval = $PSItem -split "\s*=\s*", 2 + if ($keyval[0] -and $keyval[1]) { + $val = $keyval[1] + + # Remove extraneous quotations around a string value. + if ("'""".Contains($val.Substring(0, 1))) { + $val = $val.Substring(1, $val.Length - 2) + } + + $pyvenvConfig[$keyval[0]] = $val + Write-Verbose "Adding Key: '$($keyval[0])'='$val'" + } + } + } + return $pyvenvConfig +} + + +<# Begin Activate script --------------------------------------------------- #> + +# Determine the containing directory of this script +$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition +$VenvExecDir = Get-Item -Path $VenvExecPath + +Write-Verbose "Activation script is located in path: '$VenvExecPath'" +Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)" +Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)" + +# Set values required in priority: CmdLine, ConfigFile, Default +# First, get the location of the virtual environment, it might not be +# VenvExecDir if specified on the command line. +if ($VenvDir) { + Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values" +} +else { + Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir." + $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/") + Write-Verbose "VenvDir=$VenvDir" +} + +# Next, read the `pyvenv.cfg` file to determine any required value such +# as `prompt`. +$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir + +# Next, set the prompt from the command line, or the config file, or +# just use the name of the virtual environment folder. +if ($Prompt) { + Write-Verbose "Prompt specified as argument, using '$Prompt'" +} +else { + Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value" + if ($pyvenvCfg -and $pyvenvCfg['prompt']) { + Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'" + $Prompt = $pyvenvCfg['prompt']; + } + else { + Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)" + Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'" + $Prompt = Split-Path -Path $venvDir -Leaf + } +} + +Write-Verbose "Prompt = '$Prompt'" +Write-Verbose "VenvDir='$VenvDir'" + +# Deactivate any currently active virtual environment, but leave the +# deactivate function in place. +deactivate -nondestructive + +# Now set the environment variable VIRTUAL_ENV, used by many tools to determine +# that there is an activated venv. +$env:VIRTUAL_ENV = $VenvDir + +if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) { + + Write-Verbose "Setting prompt to '$Prompt'" + + # Set the prompt to include the env name + # Make sure _OLD_VIRTUAL_PROMPT is global + function global:_OLD_VIRTUAL_PROMPT { "" } + Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT + New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt + + function global:prompt { + Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) " + _OLD_VIRTUAL_PROMPT + } + $env:VIRTUAL_ENV_PROMPT = $Prompt +} + +# Clear PYTHONHOME +if (Test-Path -Path Env:PYTHONHOME) { + Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME + Remove-Item -Path Env:PYTHONHOME +} + +# Add the venv to the PATH +Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH +$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH" diff --git a/venv/bin/activate b/venv/bin/activate new file mode 100644 index 00000000..f583216c --- /dev/null +++ b/venv/bin/activate @@ -0,0 +1,69 @@ +# This file must be used with "source bin/activate" *from bash* +# you cannot run it directly + +deactivate () { + # reset old environment variables + if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then + PATH="${_OLD_VIRTUAL_PATH:-}" + export PATH + unset _OLD_VIRTUAL_PATH + fi + if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then + PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}" + export PYTHONHOME + unset _OLD_VIRTUAL_PYTHONHOME + fi + + # This should detect bash and zsh, which have a hash command that must + # be called to get it to forget past commands. Without forgetting + # past commands the $PATH changes we made may not be respected + if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then + hash -r 2> /dev/null + fi + + if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then + PS1="${_OLD_VIRTUAL_PS1:-}" + export PS1 + unset _OLD_VIRTUAL_PS1 + fi + + unset VIRTUAL_ENV + unset VIRTUAL_ENV_PROMPT + if [ ! "${1:-}" = "nondestructive" ] ; then + # Self destruct! + unset -f deactivate + fi +} + +# unset irrelevant variables +deactivate nondestructive + +VIRTUAL_ENV=/mnt/c/Users/djgreen/Desktop/RSVP_Lite/hermes_docs/venv +export VIRTUAL_ENV + +_OLD_VIRTUAL_PATH="$PATH" +PATH="$VIRTUAL_ENV/"bin":$PATH" +export PATH + +# unset PYTHONHOME if set +# this will fail if PYTHONHOME is set to the empty string (which is bad anyway) +# could use `if (set -u; : $PYTHONHOME) ;` in bash +if [ -n "${PYTHONHOME:-}" ] ; then + _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}" + unset PYTHONHOME +fi + +if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then + _OLD_VIRTUAL_PS1="${PS1:-}" + PS1='(venv) '"${PS1:-}" + export PS1 + VIRTUAL_ENV_PROMPT='(venv) ' + export VIRTUAL_ENV_PROMPT +fi + +# This should detect bash and zsh, which have a hash command that must +# be called to get it to forget past commands. Without forgetting +# past commands the $PATH changes we made may not be respected +if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then + hash -r 2> /dev/null +fi diff --git a/venv/bin/activate.csh b/venv/bin/activate.csh new file mode 100644 index 00000000..bfb77b7b --- /dev/null +++ b/venv/bin/activate.csh @@ -0,0 +1,26 @@ +# This file must be used with "source bin/activate.csh" *from csh*. +# You cannot run it directly. +# Created by Davide Di Blasi . +# Ported to Python 3.3 venv by Andrew Svetlov + +alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate' + +# Unset irrelevant variables. +deactivate nondestructive + +setenv VIRTUAL_ENV /mnt/c/Users/djgreen/Desktop/RSVP_Lite/hermes_docs/venv + +set _OLD_VIRTUAL_PATH="$PATH" +setenv PATH "$VIRTUAL_ENV/"bin":$PATH" + + +set _OLD_VIRTUAL_PROMPT="$prompt" + +if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then + set prompt = '(venv) '"$prompt" + setenv VIRTUAL_ENV_PROMPT '(venv) ' +endif + +alias pydoc python -m pydoc + +rehash diff --git a/venv/bin/activate.fish b/venv/bin/activate.fish new file mode 100644 index 00000000..94aed0e7 --- /dev/null +++ b/venv/bin/activate.fish @@ -0,0 +1,69 @@ +# This file must be used with "source /bin/activate.fish" *from fish* +# (https://fishshell.com/); you cannot run it directly. + +function deactivate -d "Exit virtual environment and return to normal shell environment" + # reset old environment variables + if test -n "$_OLD_VIRTUAL_PATH" + set -gx PATH $_OLD_VIRTUAL_PATH + set -e _OLD_VIRTUAL_PATH + end + if test -n "$_OLD_VIRTUAL_PYTHONHOME" + set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME + set -e _OLD_VIRTUAL_PYTHONHOME + end + + if test -n "$_OLD_FISH_PROMPT_OVERRIDE" + set -e _OLD_FISH_PROMPT_OVERRIDE + # prevents error when using nested fish instances (Issue #93858) + if functions -q _old_fish_prompt + functions -e fish_prompt + functions -c _old_fish_prompt fish_prompt + functions -e _old_fish_prompt + end + end + + set -e VIRTUAL_ENV + set -e VIRTUAL_ENV_PROMPT + if test "$argv[1]" != "nondestructive" + # Self-destruct! + functions -e deactivate + end +end + +# Unset irrelevant variables. +deactivate nondestructive + +set -gx VIRTUAL_ENV /mnt/c/Users/djgreen/Desktop/RSVP_Lite/hermes_docs/venv + +set -gx _OLD_VIRTUAL_PATH $PATH +set -gx PATH "$VIRTUAL_ENV/"bin $PATH + +# Unset PYTHONHOME if set. +if set -q PYTHONHOME + set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME + set -e PYTHONHOME +end + +if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" + # fish uses a function instead of an env var to generate the prompt. + + # Save the current fish_prompt function as the function _old_fish_prompt. + functions -c fish_prompt _old_fish_prompt + + # With the original prompt function renamed, we can override with our own. + function fish_prompt + # Save the return status of the last command. + set -l old_status $status + + # Output the venv prompt; color taken from the blue of the Python logo. + printf "%s%s%s" (set_color 4B8BBE) '(venv) ' (set_color normal) + + # Restore the return status of the previous command. + echo "exit $old_status" | . + # Output the original/"old" prompt. + _old_fish_prompt + end + + set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" + set -gx VIRTUAL_ENV_PROMPT '(venv) ' +end diff --git a/venv/bin/python b/venv/bin/python new file mode 120000 index 00000000..b8a0adbb --- /dev/null +++ b/venv/bin/python @@ -0,0 +1 @@ +python3 \ No newline at end of file diff --git a/venv/bin/python3 b/venv/bin/python3 new file mode 120000 index 00000000..ae65fdaa --- /dev/null +++ b/venv/bin/python3 @@ -0,0 +1 @@ +/usr/bin/python3 \ No newline at end of file diff --git a/venv/bin/python3.10 b/venv/bin/python3.10 new file mode 120000 index 00000000..b8a0adbb --- /dev/null +++ b/venv/bin/python3.10 @@ -0,0 +1 @@ +python3 \ No newline at end of file diff --git a/venv/lib/python3.10/site-packages/_distutils_hack/__init__.py b/venv/lib/python3.10/site-packages/_distutils_hack/__init__.py new file mode 100644 index 00000000..f7074162 --- /dev/null +++ b/venv/lib/python3.10/site-packages/_distutils_hack/__init__.py @@ -0,0 +1,132 @@ +import sys +import os +import re +import importlib +import warnings + + +is_pypy = '__pypy__' in sys.builtin_module_names + + +warnings.filterwarnings('ignore', + r'.+ distutils\b.+ deprecated', + DeprecationWarning) + + +def warn_distutils_present(): + if 'distutils' not in sys.modules: + return + if is_pypy and sys.version_info < (3, 7): + # PyPy for 3.6 unconditionally imports distutils, so bypass the warning + # https://foss.heptapod.net/pypy/pypy/-/blob/be829135bc0d758997b3566062999ee8b23872b4/lib-python/3/site.py#L250 + return + warnings.warn( + "Distutils was imported before Setuptools, but importing Setuptools " + "also replaces the `distutils` module in `sys.modules`. This may lead " + "to undesirable behaviors or errors. To avoid these issues, avoid " + "using distutils directly, ensure that setuptools is installed in the " + "traditional way (e.g. not an editable install), and/or make sure " + "that setuptools is always imported before distutils.") + + +def clear_distutils(): + if 'distutils' not in sys.modules: + return + warnings.warn("Setuptools is replacing distutils.") + mods = [name for name in sys.modules if re.match(r'distutils\b', name)] + for name in mods: + del sys.modules[name] + + +def enabled(): + """ + Allow selection of distutils by environment variable. + """ + which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'stdlib') + return which == 'local' + + +def ensure_local_distutils(): + clear_distutils() + + # With the DistutilsMetaFinder in place, + # perform an import to cause distutils to be + # loaded from setuptools._distutils. Ref #2906. + add_shim() + importlib.import_module('distutils') + remove_shim() + + # check that submodules load as expected + core = importlib.import_module('distutils.core') + assert '_distutils' in core.__file__, core.__file__ + + +def do_override(): + """ + Ensure that the local copy of distutils is preferred over stdlib. + + See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401 + for more motivation. + """ + if enabled(): + warn_distutils_present() + ensure_local_distutils() + + +class DistutilsMetaFinder: + def find_spec(self, fullname, path, target=None): + if path is not None: + return + + method_name = 'spec_for_{fullname}'.format(**locals()) + method = getattr(self, method_name, lambda: None) + return method() + + def spec_for_distutils(self): + import importlib.abc + import importlib.util + + class DistutilsLoader(importlib.abc.Loader): + + def create_module(self, spec): + return importlib.import_module('setuptools._distutils') + + def exec_module(self, module): + pass + + return importlib.util.spec_from_loader('distutils', DistutilsLoader()) + + def spec_for_pip(self): + """ + Ensure stdlib distutils when running under pip. + See pypa/pip#8761 for rationale. + """ + if self.pip_imported_during_build(): + return + clear_distutils() + self.spec_for_distutils = lambda: None + + @staticmethod + def pip_imported_during_build(): + """ + Detect if pip is being imported in a build script. Ref #2355. + """ + import traceback + return any( + frame.f_globals['__file__'].endswith('setup.py') + for frame, line in traceback.walk_stack(None) + ) + + +DISTUTILS_FINDER = DistutilsMetaFinder() + + +def add_shim(): + sys.meta_path.insert(0, DISTUTILS_FINDER) + + +def remove_shim(): + try: + sys.meta_path.remove(DISTUTILS_FINDER) + except ValueError: + pass diff --git a/venv/lib/python3.10/site-packages/_distutils_hack/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/_distutils_hack/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3c4a172f5856733e4e2a8b20c0542a6ee4f1492a GIT binary patch literal 5131 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

#%Wr#5SRb} literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/_distutils_hack/__pycache__/override.cpython-310.pyc b/venv/lib/python3.10/site-packages/_distutils_hack/__pycache__/override.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..135450c028c6f2fff3cb5b41f092e3b4bb5d6d9b GIT binary patch literal 256 zcmZ8cv1$TA5Zx7vpd9)ODFf;l(u$-=DH0TI2Xi}kt9Q4{&U#>-ABm;kkura9ZT`Z_ zIUx{yFmHInFmIkql8zGmd_V36(tjKAA1x8DQrs6JWz?ts(kG>r(M$Ezs(WZG$3wI^ zW41~6bD4DzZQ)&rh;`REbQOhQ4V_hJ%at2lJQQVw6jn@u!R)qysDwA-eRLic%k?|X zY$V7i6vSv;%1}{RL2f_5A7kdqm(lYWSeAxfoHtuyxcdhFtVjM>KTHZ|4mnMAlcx#R FS65^@OE~}l literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/_distutils_hack/override.py b/venv/lib/python3.10/site-packages/_distutils_hack/override.py new file mode 100644 index 00000000..2cc433a4 --- /dev/null +++ b/venv/lib/python3.10/site-packages/_distutils_hack/override.py @@ -0,0 +1 @@ +__import__('_distutils_hack').do_override() diff --git a/venv/lib/python3.10/site-packages/distutils-precedence.pth b/venv/lib/python3.10/site-packages/distutils-precedence.pth new file mode 100644 index 00000000..6de4198f --- /dev/null +++ b/venv/lib/python3.10/site-packages/distutils-precedence.pth @@ -0,0 +1 @@ +import os; var = 'SETUPTOOLS_USE_DISTUTILS'; enabled = os.environ.get(var, 'stdlib') == 'local'; enabled and __import__('_distutils_hack').add_shim(); diff --git a/venv/lib/python3.10/site-packages/pip-22.0.2.dist-info/LICENSE.txt b/venv/lib/python3.10/site-packages/pip-22.0.2.dist-info/LICENSE.txt new file mode 100644 index 00000000..8e7b65ea --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip-22.0.2.dist-info/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2008-present The pip developers (see AUTHORS.txt file) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/venv/lib/python3.10/site-packages/pip-22.0.2.dist-info/METADATA b/venv/lib/python3.10/site-packages/pip-22.0.2.dist-info/METADATA new file mode 100644 index 00000000..29392cd5 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip-22.0.2.dist-info/METADATA @@ -0,0 +1,92 @@ +Metadata-Version: 2.1 +Name: pip +Version: 22.0.2 +Summary: The PyPA recommended tool for installing Python packages. +Home-page: https://pip.pypa.io/ +Author: The pip developers +Author-email: distutils-sig@python.org +License: MIT +Project-URL: Documentation, https://pip.pypa.io +Project-URL: Source, https://github.com/pypa/pip +Project-URL: Changelog, https://pip.pypa.io/en/stable/news/ +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Topic :: Software Development :: Build Tools +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Requires-Python: >=3.7 +License-File: LICENSE.txt + +pip - The Python Package Installer +================================== + +.. image:: https://img.shields.io/pypi/v/pip.svg + :target: https://pypi.org/project/pip/ + +.. image:: https://readthedocs.org/projects/pip/badge/?version=latest + :target: https://pip.pypa.io/en/latest + +pip is the `package installer`_ for Python. You can use pip to install packages from the `Python Package Index`_ and other indexes. + +Please take a look at our documentation for how to install and use pip: + +* `Installation`_ +* `Usage`_ + +We release updates regularly, with a new version every 3 months. Find more details in our documentation: + +* `Release notes`_ +* `Release process`_ + +In pip 20.3, we've `made a big improvement to the heart of pip`_; `learn more`_. We want your input, so `sign up for our user experience research studies`_ to help us do it right. + +**Note**: pip 21.0, in January 2021, removed Python 2 support, per pip's `Python 2 support policy`_. Please migrate to Python 3. + +If you find bugs, need help, or want to talk to the developers, please use our mailing lists or chat rooms: + +* `Issue tracking`_ +* `Discourse channel`_ +* `User IRC`_ + +If you want to get involved head over to GitHub to get the source code, look at our development documentation and feel free to jump on the developer mailing lists and chat rooms: + +* `GitHub page`_ +* `Development documentation`_ +* `Development mailing list`_ +* `Development IRC`_ + +Code of Conduct +--------------- + +Everyone interacting in the pip project's codebases, issue trackers, chat +rooms, and mailing lists is expected to follow the `PSF Code of Conduct`_. + +.. _package installer: https://packaging.python.org/guides/tool-recommendations/ +.. _Python Package Index: https://pypi.org +.. _Installation: https://pip.pypa.io/en/stable/installation/ +.. _Usage: https://pip.pypa.io/en/stable/ +.. _Release notes: https://pip.pypa.io/en/stable/news.html +.. _Release process: https://pip.pypa.io/en/latest/development/release-process/ +.. _GitHub page: https://github.com/pypa/pip +.. _Development documentation: https://pip.pypa.io/en/latest/development +.. _made a big improvement to the heart of pip: https://pyfound.blogspot.com/2020/11/pip-20-3-new-resolver.html +.. _learn more: https://pip.pypa.io/en/latest/user_guide/#changes-to-the-pip-dependency-resolver-in-20-3-2020 +.. _sign up for our user experience research studies: https://pyfound.blogspot.com/2020/03/new-pip-resolver-to-roll-out-this-year.html +.. _Python 2 support policy: https://pip.pypa.io/en/latest/development/release-process/#python-2-support +.. _Issue tracking: https://github.com/pypa/pip/issues +.. _Discourse channel: https://discuss.python.org/c/packaging +.. _Development mailing list: https://mail.python.org/mailman3/lists/distutils-sig.python.org/ +.. _User IRC: https://kiwiirc.com/nextclient/#ircs://irc.libera.chat:+6697/pypa +.. _Development IRC: https://kiwiirc.com/nextclient/#ircs://irc.libera.chat:+6697/pypa-dev +.. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md + + diff --git a/venv/lib/python3.10/site-packages/pip-22.0.2.dist-info/RECORD b/venv/lib/python3.10/site-packages/pip-22.0.2.dist-info/RECORD new file mode 100644 index 00000000..f4d766ad --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip-22.0.2.dist-info/RECORD @@ -0,0 +1,520 @@ +pip/__init__.py,sha256=PZBF-ESk5Q0DZxQd4HHmTU_wX8y1ynzxBCRdu_fxHSI,357 +pip/__main__.py,sha256=mXwWDftNLMKfwVqKFWGE_uuBZvGSIiUELhLkeysIuZc,1198 +pip/py.typed,sha256=EBVvvPRTn_eIpz5e5QztSCdrMX7Qwd7VP93RSoIlZ2I,286 +pip/_internal/__init__.py,sha256=nnFCuxrPMgALrIDxSoy-H6Zj4W4UY60D-uL1aJyq0pc,573 +pip/_internal/build_env.py,sha256=QAsnxJFvj74jS2cZUcxk7zXLvrtAYiRL0EkSPkpSJTo,9739 +pip/_internal/cache.py,sha256=71eaYwrls34HJ6gzbmmYiotiKhPNFTM_tqYJXD5nf3s,9441 +pip/_internal/configuration.py,sha256=qmCX3uuVM73PQeAuWQHic22bhops8s31B8k02nFAoiQ,13171 +pip/_internal/exceptions.py,sha256=U-dV1ixkSz6NAU6Aw9dosKi2EzZ5D3BA7ilYZuTLKeU,20912 +pip/_internal/main.py,sha256=r-UnUe8HLo5XFJz8inTcOOTiu_sxNhgHb6VwlGUllOI,340 +pip/_internal/pyproject.py,sha256=Wm2ljdT6spC-tSdf1LBRaMYSJaXr1xUxV3OwdHCW9jc,6722 +pip/_internal/self_outdated_check.py,sha256=GKSatNlt2cz_CMGxu72FbUzuPaXpWOnIVKOOYIk0gvY,6849 +pip/_internal/wheel_builder.py,sha256=65rOA8FSYt3c3HyqEw17uujjlCgqmoKEIv6rv9xN2NM,12307 +pip/_internal/cli/__init__.py,sha256=FkHBgpxxb-_gd6r1FjnNhfMOzAUYyXoXKJ6abijfcFU,132 +pip/_internal/cli/autocompletion.py,sha256=wY2JPZY2Eji1vhR7bVo-yCBPJ9LCy6P80iOAhZD1Vi8,6676 +pip/_internal/cli/base_command.py,sha256=6IVFmOjObv0ILip28QcgP8glhXHiGRvU_9kO35Hr7Z0,8037 +pip/_internal/cli/cmdoptions.py,sha256=GT2G2YKBj-851qGseugn2Veq7fJe3FA30gWdcziPQvo,28525 +pip/_internal/cli/command_context.py,sha256=a1pBBvvGLDiZ1Kw64_4tT6HmRTwYDoYy8JFgG5Czn7s,760 +pip/_internal/cli/main.py,sha256=ioJ8IVlb2K1qLOxR-tXkee9lURhYV89CDM71MKag7YY,2472 +pip/_internal/cli/main_parser.py,sha256=Q9TnytfuC5Z2JSjBFWVGtEdYLFy7rukNIb04movHdAo,2614 +pip/_internal/cli/parser.py,sha256=CDXTuFr2UD8ozOlZYf1KDziQdo9-X_IaYOiUcyJQwrA,10788 +pip/_internal/cli/progress_bars.py,sha256=_52w11WoZrvDSR3oItLWvLrEZFUKAfLf4Y6I6WtOnIU,10339 +pip/_internal/cli/req_command.py,sha256=VwqonOy18QwZsRsVjHhp-6w15fG9x3Ltwoa8yJqQno8,18669 +pip/_internal/cli/spinners.py,sha256=TFhjxtOnLeNJ5YmRvQm4eKPgPbJNkZiqO8jOXuxRaYU,5076 +pip/_internal/cli/status_codes.py,sha256=sEFHUaUJbqv8iArL3HAtcztWZmGOFX01hTesSytDEh0,116 +pip/_internal/commands/__init__.py,sha256=Vc1HjsLEtyCh7506OozPHPKXe2Hk-z9cFkFF3BMj1lM,3736 +pip/_internal/commands/cache.py,sha256=p9gvc6W_xgxE2zO0o8NXqO1gGJEinEK42qEC-a7Cnuk,7524 +pip/_internal/commands/check.py,sha256=0gjXR7j36xJT5cs2heYU_dfOfpnFfzX8OoPNNoKhqdM,1685 +pip/_internal/commands/completion.py,sha256=kTG_I1VR3N5kGC4Ma9pQTSoY9Q1URCrNyseHSQ-rCL4,2958 +pip/_internal/commands/configuration.py,sha256=arE8vLstjBg-Ar1krXF-bBmT1qBtnL7Fpk-NVh38a0U,8944 +pip/_internal/commands/debug.py,sha256=krET-y45CnQzXwKR1qA3M_tJE4LE2vnQtm3yfGyDSnE,6629 +pip/_internal/commands/download.py,sha256=gVIAEOcpWolhRj9hl89Qzn52G2b_pcZ8naXhxaXobdo,4942 +pip/_internal/commands/freeze.py,sha256=PaJJB9mT_3vHeZ3mbFL_m1fzTYL-_Or3kDtXwTdZZ-A,2968 +pip/_internal/commands/hash.py,sha256=EVVOuvGtoPEdFi8SNnmdqlCQrhCxV-kJsdwtdcCnXGQ,1703 +pip/_internal/commands/help.py,sha256=gcc6QDkcgHMOuAn5UxaZwAStsRBrnGSn_yxjS57JIoM,1132 +pip/_internal/commands/index.py,sha256=8pYkICUJlccjm3E83b7UuZ5DtOfLh1N7ZHXAgkajjHo,4849 +pip/_internal/commands/install.py,sha256=YVygBF6vfrNi0jmdNBCM6bcoWb7vaALEGG1--8Mmf88,27893 +pip/_internal/commands/list.py,sha256=aKt1PP7enTiNLD_1qDXXaIKQ2QvLmUDfoQU6SYxJ8Ek,12318 +pip/_internal/commands/search.py,sha256=sbBZiARRc050QquOKcCvOr2K3XLsoYebLKZGRi__iUI,5697 +pip/_internal/commands/show.py,sha256=2VicM3jF0YWgn4O1jG_QF5oxOT0ln57VDu1NE6hqWcM,5859 +pip/_internal/commands/uninstall.py,sha256=DNTYAGJNljMO_YYBxrpcwj0FEl7lo_P55_98O6g2TNk,3526 +pip/_internal/commands/wheel.py,sha256=7HAjLclZxIzBrX6JmhmGBVxH5xrjaBYCtSdpQi1pWCE,6206 +pip/_internal/distributions/__init__.py,sha256=Hq6kt6gXBgjNit5hTTWLAzeCNOKoB-N0pGYSqehrli8,858 +pip/_internal/distributions/base.py,sha256=3FUYD8Gb4YuSu3pggC_FRctZBDbpm5ZK89tPksIUjoE,1172 +pip/_internal/distributions/installed.py,sha256=HzfNRu3smoOm54m8H2iK6LHzBx6_DEnka4OPEsizbXg,680 +pip/_internal/distributions/sdist.py,sha256=0nJvU1RhZtbwaeYtLbzSwYrbGRcY6IgNsWdEhAHROK8,5499 +pip/_internal/distributions/wheel.py,sha256=-NgzdIs-w_hcer_U81yzgpVTljJRg5m79xufqvbjv0s,1115 +pip/_internal/index/__init__.py,sha256=vpt-JeTZefh8a-FC22ZeBSXFVbuBcXSGiILhQZJaNpQ,30 +pip/_internal/index/collector.py,sha256=8kXlmlnZ-qAknyxd0duCn5mxFHX-zr468ykutk8WOwo,21392 +pip/_internal/index/package_finder.py,sha256=9UVg-7582nYNEWa0cIIl8otzPm4mlfyrQVuozAcssLo,36783 +pip/_internal/index/sources.py,sha256=SVyPitv08-Qalh2_Bk5diAJ9GAA_d-a93koouQodAG0,6557 +pip/_internal/locations/__init__.py,sha256=ergvPwlfNTmQYFmaRYbj--ZwTN5izgTL9KE5d0FB7-8,17362 +pip/_internal/locations/_distutils.py,sha256=Sk7tw8ZP1DWMYJ8MibABsa8IME2Ejv1PKeGlYQCBTZc,5871 +pip/_internal/locations/_sysconfig.py,sha256=LQNKTJKyjVqxXaPntlBwdUqTG1xwYf6GVCKMbyRJx5M,7918 +pip/_internal/locations/base.py,sha256=x5D1ONktmPJd8nnUTh-ELsAJ7fiXA-k-0a_vhfi2_Us,1579 +pip/_internal/metadata/__init__.py,sha256=iGoDbe_iTXQTIAEVy9f7dm-VQfZANO8kkwFr1CpqxqI,2036 +pip/_internal/metadata/base.py,sha256=SCRPtShrtPy0lfFxuaFTgJJHsRXToGFToQUAZoBBbeA,19429 +pip/_internal/metadata/pkg_resources.py,sha256=wAnEtrcgH9YtV996MfoBjR2hGLHvi3uxk0vUOHbqBak,9456 +pip/_internal/models/__init__.py,sha256=3DHUd_qxpPozfzouoqa9g9ts1Czr5qaHfFxbnxriepM,63 +pip/_internal/models/candidate.py,sha256=6pcABsaR7CfIHlbJbr2_kMkVJFL_yrYjTx6SVWUnCPQ,990 +pip/_internal/models/direct_url.py,sha256=7XtGQSLLDQb5ZywI2EMnnLcddtf5CJLx44lMtTHPxFw,6350 +pip/_internal/models/format_control.py,sha256=DJpMYjxeYKKQdwNcML2_F0vtAh-qnKTYe-CpTxQe-4g,2520 +pip/_internal/models/index.py,sha256=tYnL8oxGi4aSNWur0mG8DAP7rC6yuha_MwJO8xw0crI,1030 +pip/_internal/models/link.py,sha256=hoT_qsOBAgLBm9GKqpBrNF_mrEXeGXQE-aH_RX2cGgg,9817 +pip/_internal/models/scheme.py,sha256=3EFQp_ICu_shH1-TBqhl0QAusKCPDFOlgHFeN4XowWs,738 +pip/_internal/models/search_scope.py,sha256=LwloG0PJAmtI1hFXIypsD95kWE9xfR5hf_a2v1Vw7sk,4520 +pip/_internal/models/selection_prefs.py,sha256=KZdi66gsR-_RUXUr9uejssk3rmTHrQVJWeNA2sV-VSY,1907 +pip/_internal/models/target_python.py,sha256=qKpZox7J8NAaPmDs5C_aniwfPDxzvpkrCKqfwndG87k,3858 +pip/_internal/models/wheel.py,sha256=wlyz23BcZ40nBLX3rXKtrV6tmc8-8RxHyV-hq5zJ74Q,3525 +pip/_internal/network/__init__.py,sha256=jf6Tt5nV_7zkARBrKojIXItgejvoegVJVKUbhAa5Ioc,50 +pip/_internal/network/auth.py,sha256=a3C7Xaa8kTJjXkdi_wrUjqaySc8Z9Yz7U6QIbXfzMyc,12190 +pip/_internal/network/cache.py,sha256=FJ3uTUo3wgf2KHmeZ3ltN9x3tQoy_0X6qNsRtNXsuL0,2131 +pip/_internal/network/download.py,sha256=12Ef_L7MlhNUN_0-n_3DggozWJER8c9J0us16cbvkKA,6062 +pip/_internal/network/lazy_wheel.py,sha256=1b8ZJ1w4bSBzpGzGwJR_CL2yQ6AFIwWQkS1vbPPw2XU,7627 +pip/_internal/network/session.py,sha256=38IKGKC64MTVUIH5XOR1hr2pOCzp39RccykdmGAvqRU,16729 +pip/_internal/network/utils.py,sha256=igLlTu_-q0LmL8FdJKq-Uj7AT_owrQ-T9FfyarkhK5U,4059 +pip/_internal/network/xmlrpc.py,sha256=AzQgG4GgS152_cqmGr_Oz2MIXsCal-xfsis7fA7nmU0,1791 +pip/_internal/operations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_internal/operations/check.py,sha256=ca4O9CkPt9Em9sLCf3H0iVt1GIcW7M8C0U5XooaBuT4,5109 +pip/_internal/operations/freeze.py,sha256=ZiYw5GlUpLVx4VJHz4S1AP2JFNyvH0iq5kpcYj2ovyw,9770 +pip/_internal/operations/prepare.py,sha256=LJP97jsuiCAaTGVIRrcINvxc1ntVsB45MoRbyMIukg4,24145 +pip/_internal/operations/build/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_internal/operations/build/metadata.py,sha256=ES_uRmAvhrNm_nDTpZxshBfUsvnXtkj-g_4rZrH9Rww,1404 +pip/_internal/operations/build/metadata_editable.py,sha256=_Rai0VZjxoeJUkjkuICrq45LtjwFoDOveosMYH43rKc,1456 +pip/_internal/operations/build/metadata_legacy.py,sha256=o-eU21As175hDC7dluM1fJJ_FqokTIShyWpjKaIpHZw,2198 +pip/_internal/operations/build/wheel.py,sha256=AO9XnTGhTgHtZmU8Dkbfo1OGr41rBuSDjIgAa4zUKgE,1063 +pip/_internal/operations/build/wheel_editable.py,sha256=TVETY-L_M_dSEKBhTIcQOP75zKVXw8tuq1U354Mm30A,1405 +pip/_internal/operations/build/wheel_legacy.py,sha256=C9j6rukgQI1n_JeQLoZGuDdfUwzCXShyIdPTp6edbMQ,3064 +pip/_internal/operations/install/__init__.py,sha256=mX7hyD2GNBO2mFGokDQ30r_GXv7Y_PLdtxcUv144e-s,51 +pip/_internal/operations/install/editable_legacy.py,sha256=ee4kfJHNuzTdKItbfAsNOSEwq_vD7DRPGkBdK48yBhU,1354 +pip/_internal/operations/install/legacy.py,sha256=x7BG8kBm0K3JO6AR4sBl0zh2LOrfUaz7EdNt-keHBv4,4091 +pip/_internal/operations/install/wheel.py,sha256=QuQyCZE-XjuJjDYRixo40oUt2ucFhNmSrCbcXY7A9aE,27412 +pip/_internal/req/__init__.py,sha256=A7mUvT1KAcCYP3H7gUOTx2GRMlgoDur3H68Q0OJqM5A,2793 +pip/_internal/req/constructors.py,sha256=fXmtNI_J77JFP_HRvYcQW-1nKw3AiUu6Q3b1Nm8aMm0,16094 +pip/_internal/req/req_file.py,sha256=5N8OTouPCof-305StC2YK9HBxQMw-xO46skRoBPbkZo,17421 +pip/_internal/req/req_install.py,sha256=jU1HQBT_DnXZean7jY8wPNMhb6_CzdKHcilHFY_o-Fc,32524 +pip/_internal/req/req_set.py,sha256=kHYiLvkKRx21WaLTwOI-54Ng0SSzZZ9SE7FD0PsfvYA,7584 +pip/_internal/req/req_tracker.py,sha256=jK7JDu-Wt73X-gqozrFtgJVlUlnQo0P4IQ4x4_gPlfM,4117 +pip/_internal/req/req_uninstall.py,sha256=K2BHYRRJAfkSpFqcPzc9XfX2EvbhaRtQIPRFmMtUdfo,23814 +pip/_internal/resolution/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_internal/resolution/base.py,sha256=qlmh325SBVfvG6Me9gc5Nsh5sdwHBwzHBq6aEXtKsLA,583 +pip/_internal/resolution/legacy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_internal/resolution/legacy/resolver.py,sha256=b7bf5qL1ROg73sl8dhTvLdD1w5XF8xybBAF6eF_kz7c,18288 +pip/_internal/resolution/resolvelib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_internal/resolution/resolvelib/base.py,sha256=u1O4fkvCO4mhmu5i32xrDv9AX5NgUci_eYVyBDQhTIM,5220 +pip/_internal/resolution/resolvelib/candidates.py,sha256=KR5jxZRSahByOABXbwrX-zNoawa7Gm9Iss-HrvrcvNw,18357 +pip/_internal/resolution/resolvelib/factory.py,sha256=0bbxnUSSjaeTmtIEgeeKtEqhEFfNhv3xpq7j9IaMq2c,28298 +pip/_internal/resolution/resolvelib/found_candidates.py,sha256=hvL3Hoa9VaYo-qEOZkBi2Iqw251UDxPz-uMHVaWmLpE,5705 +pip/_internal/resolution/resolvelib/provider.py,sha256=LzQQyzMVaZYAwLgKInbq-it6mbQL1gX0hGohz5Cr5wg,9915 +pip/_internal/resolution/resolvelib/reporter.py,sha256=3ZVVYrs5PqvLFJkGLcuXoMK5mTInFzl31xjUpDBpZZk,2526 +pip/_internal/resolution/resolvelib/requirements.py,sha256=B1ndvKPSuyyyTEXt9sKhbwminViSWnBrJa7qO2ln4Z0,5455 +pip/_internal/resolution/resolvelib/resolver.py,sha256=ucoVKHtwH6gkZjcfIVJbUiOIHLqJxeYlrKTMIJciYwM,11335 +pip/_internal/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_internal/utils/_log.py,sha256=-jHLOE_THaZz5BFcCnoSL9EYAtJ0nXem49s9of4jvKw,1015 +pip/_internal/utils/appdirs.py,sha256=swgcTKOm3daLeXTW6v5BUS2Ti2RvEnGRQYH_yDXklAo,1665 +pip/_internal/utils/compat.py,sha256=ACyBfLgj3_XG-iA5omEDrXqDM0cQKzi8h8HRBInzG6Q,1884 +pip/_internal/utils/compatibility_tags.py,sha256=ydin8QG8BHqYRsPY4OL6cmb44CbqXl1T0xxS97VhHkk,5377 +pip/_internal/utils/datetime.py,sha256=m21Y3wAtQc-ji6Veb6k_M5g6A0ZyFI4egchTdnwh-pQ,242 +pip/_internal/utils/deprecation.py,sha256=NKo8VqLioJ4nnXXGmW4KdasxF90EFHkZaHeX1fT08C8,3627 +pip/_internal/utils/direct_url_helpers.py,sha256=6F1tc2rcKaCZmgfVwsE6ObIe_Pux23mUVYA-2D9wCFc,3206 +pip/_internal/utils/distutils_args.py,sha256=mcAscyp80vTt3xAGTipnpgc83V-_wCvydNELVXLq7JI,1249 +pip/_internal/utils/egg_link.py,sha256=5MVlpz5LirT4iLQq86OYzjXaYF0D4Qk1dprEI7ThST4,2203 +pip/_internal/utils/encoding.py,sha256=bdZ3YgUpaOEBI5MP4-DEXiQarCW3V0rxw1kRz-TaU1Q,1169 +pip/_internal/utils/entrypoints.py,sha256=aPvCnQVi9Hdk35Kloww_D5ibjUpqxgqcJP8O9VuMZek,1055 +pip/_internal/utils/filesystem.py,sha256=rrl-rY1w8TYyKYndUyZlE9ffkQyA4-jI9x_59zXkn5s,5893 +pip/_internal/utils/filetypes.py,sha256=i8XAQ0eFCog26Fw9yV0Yb1ygAqKYB1w9Cz9n0fj8gZU,716 +pip/_internal/utils/glibc.py,sha256=tDfwVYnJCOC0BNVpItpy8CGLP9BjkxFHdl0mTS0J7fc,3110 +pip/_internal/utils/hashes.py,sha256=anpZfFGIT6HcIj2td9NHtE8AWg6GeAIhwpP8GPvZE0E,4811 +pip/_internal/utils/inject_securetransport.py,sha256=o-QRVMGiENrTJxw3fAhA7uxpdEdw6M41TjHYtSVRrcg,795 +pip/_internal/utils/logging.py,sha256=Rvght-fDXL70VWib1cpgZ3iU-kXODV98bNeLUlbqVto,11522 +pip/_internal/utils/misc.py,sha256=MdUB12BMhj73sEmskEutmPyWFaJB7asoPCfLzs_YeT0,19359 +pip/_internal/utils/models.py,sha256=5GoYU586SrxURMvDn_jBMJInitviJg4O5-iOU-6I0WY,1193 +pip/_internal/utils/packaging.py,sha256=5Wm6_x7lKrlqVjPI5MBN_RurcRHwVYoQ7Ksrs84de7s,2108 +pip/_internal/utils/setuptools_build.py,sha256=vNH9hQB9wT6d-h1hVQhBKw91jNeT42meHpVeii-urOI,5652 +pip/_internal/utils/subprocess.py,sha256=vIWGpet5ARBmZ2Qn4NEHNgzCOduqbPIuByZmhhmr6mM,9182 +pip/_internal/utils/temp_dir.py,sha256=zob3PYMVevONkheOMUp_4jDofrEY3HIu5DHK78cSspI,7662 +pip/_internal/utils/unpacking.py,sha256=HUFlMEyCa9dPwdLh6sWeh95DeKytV8rsOyKShEw9y6g,8906 +pip/_internal/utils/urls.py,sha256=AhaesUGl-9it6uvG6fsFPOr9ynFpGaTMk4t5XTX7Z_Q,1759 +pip/_internal/utils/virtualenv.py,sha256=4_48qMzCwB_F5jIK5BC_ua7uiAMVifmQWU9NdaGUoVA,3459 +pip/_internal/utils/wheel.py,sha256=lXOgZyTlOm5HmK8tw5iw0A3_5A6wRzsXHOaQkIvvloU,4549 +pip/_internal/vcs/__init__.py,sha256=UAqvzpbi0VbZo3Ub6skEeZAw-ooIZR-zX_WpCbxyCoU,596 +pip/_internal/vcs/bazaar.py,sha256=IGb5ca1xSZfgegRD2_JeyoZPrQQHs7lEYEIgpVsKpoU,3047 +pip/_internal/vcs/git.py,sha256=mjhwudCx9WlLNkxZ6_kOKmueF0rLoU2i1xeASKF6yiQ,18116 +pip/_internal/vcs/mercurial.py,sha256=Bzbd518Jsx-EJI0IhIobiQqiRsUv5TWYnrmRIFWE0Gw,5238 +pip/_internal/vcs/subversion.py,sha256=TEMRdwECvMcXakZX0pTNUep79kmBYkWDkWFkrYmcmac,11718 +pip/_internal/vcs/versioncontrol.py,sha256=KUOc-hN51em9jrqxKwUR3JnkgSE-xSOqMiiJcSaL6B8,22811 +pip/_vendor/__init__.py,sha256=xjcBX0EP50pkaMdCssrsBXoZgo2hTtYxlcH1CIyA3T4,4708 +pip/_vendor/distro.py,sha256=O1EeHMq1-xAO373JI2_6pYEtd09yEkxtmrYkdY-9S-w,48414 +pip/_vendor/six.py,sha256=TOOfQi7nFGfMrIvtdr6wX4wyHH8M7aknmuLfo2cBBrM,34549 +pip/_vendor/typing_extensions.py,sha256=1uqi_RSlI7gos4eJB_NEV3d5wQwzTUQHd3_jrkbTo8Q,87149 +pip/_vendor/vendor.txt,sha256=H-9fScoah7nx4K8O4Uft0l5iH2P_mVo4RqyuMVOTJEc,496 +pip/_vendor/cachecontrol/__init__.py,sha256=1j_YQfjmiix6YyouLrftC6NzksAm8e8xGSjMKMRPIkM,465 +pip/_vendor/cachecontrol/_cmd.py,sha256=lxUXqfNTVx84zf6tcWbkLZHA6WVBRtJRpfeA9ZqhaAY,1379 +pip/_vendor/cachecontrol/adapter.py,sha256=ew9OYEQHEOjvGl06ZsuX8W3DAvHWsQKHwWAxISyGug8,5033 +pip/_vendor/cachecontrol/cache.py,sha256=eMS9Bn9JWQkHiIYA5GPRBqKVU95uS-yXkxrzpoafRig,917 +pip/_vendor/cachecontrol/compat.py,sha256=LNx7vqBndYdHU8YuJt53ab_8rzMGTXVrvMb7CZJkxG0,778 +pip/_vendor/cachecontrol/controller.py,sha256=9DSEiV58Gx7Ce69fLCrRcpN-_sHzXTY4ol9bEviatR0,15625 +pip/_vendor/cachecontrol/filewrapper.py,sha256=X4BAQOO26GNOR7nH_fhTzAfeuct2rBQcx_15MyFBpcs,3946 +pip/_vendor/cachecontrol/heuristics.py,sha256=8kAyuZLSCyEIgQr6vbUwfhpqg9ows4mM0IV6DWazevI,4154 +pip/_vendor/cachecontrol/serialize.py,sha256=dlySaeA5U7Q5eHvjiObgo1M8j8_huVjfWjid7Aq-r8c,6783 +pip/_vendor/cachecontrol/wrapper.py,sha256=X3-KMZ20Ho3VtqyVaXclpeQpFzokR5NE8tZSfvKVaB8,774 +pip/_vendor/cachecontrol/caches/__init__.py,sha256=gGFOtIH8QDRvkP4YAfGIh-u9YYcGZVxwLM1-6e1mPNI,170 +pip/_vendor/cachecontrol/caches/file_cache.py,sha256=P2KHcNXiqxEW7fCq5KC-NYHGSk0nNR9NIKuN-vBTn-E,4251 +pip/_vendor/cachecontrol/caches/redis_cache.py,sha256=tu_YBV7EV8vdBRGazUErkoRqYYjSBmNcB8dZ7BNomqk,940 +pip/_vendor/certifi/__init__.py,sha256=xWdRgntT3j1V95zkRipGOg_A1UfEju2FcpujhysZLRI,62 +pip/_vendor/certifi/__main__.py,sha256=1k3Cr95vCxxGRGDljrW3wMdpZdL3Nhf0u1n-k2qdsCY,255 +pip/_vendor/certifi/cacert.pem,sha256=-og4Keu4zSpgL5shwfhd4kz0eUnVILzrGCi0zRy2kGw,265969 +pip/_vendor/certifi/core.py,sha256=CcwptmiI-3M50jIdO0HT6Fh6W_wqGsf8QcX9yfzvyuc,2791 +pip/_vendor/chardet/__init__.py,sha256=mWZaWmvZkhwfBEAT9O1Y6nRTfKzhT7FHhQTTAujbqUA,3271 +pip/_vendor/chardet/big5freq.py,sha256=D_zK5GyzoVsRes0HkLJziltFQX0bKCLOrFe9_xDvO_8,31254 +pip/_vendor/chardet/big5prober.py,sha256=kBxHbdetBpPe7xrlb-e990iot64g_eGSLd32lB7_h3M,1757 +pip/_vendor/chardet/chardistribution.py,sha256=3woWS62KrGooKyqz4zQSnjFbJpa6V7g02daAibTwcl8,9411 +pip/_vendor/chardet/charsetgroupprober.py,sha256=GZLReHP6FRRn43hvSOoGCxYamErKzyp6RgOQxVeC3kg,3839 +pip/_vendor/chardet/charsetprober.py,sha256=KSmwJErjypyj0bRZmC5F5eM7c8YQgLYIjZXintZNstg,5110 +pip/_vendor/chardet/codingstatemachine.py,sha256=VYp_6cyyki5sHgXDSZnXW4q1oelHc3cu9AyQTX7uug8,3590 +pip/_vendor/chardet/compat.py,sha256=40zr6wICZwknxyuLGGcIOPyve8DTebBCbbvttvnmp5Q,1200 +pip/_vendor/chardet/cp949prober.py,sha256=TZ434QX8zzBsnUvL_8wm4AQVTZ2ZkqEEQL_lNw9f9ow,1855 +pip/_vendor/chardet/enums.py,sha256=Aimwdb9as1dJKZaFNUH2OhWIVBVd6ZkJJ_WK5sNY8cU,1661 +pip/_vendor/chardet/escprober.py,sha256=kkyqVg1Yw3DIOAMJ2bdlyQgUFQhuHAW8dUGskToNWSc,3950 +pip/_vendor/chardet/escsm.py,sha256=RuXlgNvTIDarndvllNCk5WZBIpdCxQ0kcd9EAuxUh84,10510 +pip/_vendor/chardet/eucjpprober.py,sha256=iD8Jdp0ISRjgjiVN7f0e8xGeQJ5GM2oeZ1dA8nbSeUw,3749 +pip/_vendor/chardet/euckrfreq.py,sha256=-7GdmvgWez4-eO4SuXpa7tBiDi5vRXQ8WvdFAzVaSfo,13546 +pip/_vendor/chardet/euckrprober.py,sha256=MqFMTQXxW4HbzIpZ9lKDHB3GN8SP4yiHenTmf8g_PxY,1748 +pip/_vendor/chardet/euctwfreq.py,sha256=No1WyduFOgB5VITUA7PLyC5oJRNzRyMbBxaKI1l16MA,31621 +pip/_vendor/chardet/euctwprober.py,sha256=13p6EP4yRaxqnP4iHtxHOJ6R2zxHq1_m8hTRjzVZ95c,1747 +pip/_vendor/chardet/gb2312freq.py,sha256=JX8lsweKLmnCwmk8UHEQsLgkr_rP_kEbvivC4qPOrlc,20715 +pip/_vendor/chardet/gb2312prober.py,sha256=gGvIWi9WhDjE-xQXHvNIyrnLvEbMAYgyUSZ65HUfylw,1754 +pip/_vendor/chardet/hebrewprober.py,sha256=c3SZ-K7hvyzGY6JRAZxJgwJ_sUS9k0WYkvMY00YBYFo,13838 +pip/_vendor/chardet/jisfreq.py,sha256=vpmJv2Bu0J8gnMVRPHMFefTRvo_ha1mryLig8CBwgOg,25777 +pip/_vendor/chardet/jpcntx.py,sha256=PYlNqRUQT8LM3cT5FmHGP0iiscFlTWED92MALvBungo,19643 +pip/_vendor/chardet/langbulgarianmodel.py,sha256=rk9CJpuxO0bObboJcv6gNgWuosYZmd8qEEds5y7DS_Y,105697 +pip/_vendor/chardet/langgreekmodel.py,sha256=S-uNQ1ihC75yhBvSux24gLFZv3QyctMwC6OxLJdX-bw,99571 +pip/_vendor/chardet/langhebrewmodel.py,sha256=DzPP6TPGG_-PV7tqspu_d8duueqm7uN-5eQ0aHUw1Gg,98776 +pip/_vendor/chardet/langhungarianmodel.py,sha256=RtJH7DZdsmaHqyK46Kkmnk5wQHiJwJPPJSqqIlpeZRc,102498 +pip/_vendor/chardet/langrussianmodel.py,sha256=THqJOhSxiTQcHboDNSc5yofc2koXXQFHFyjtyuntUfM,131180 +pip/_vendor/chardet/langthaimodel.py,sha256=R1wXHnUMtejpw0JnH_JO8XdYasME6wjVqp1zP7TKLgg,103312 +pip/_vendor/chardet/langturkishmodel.py,sha256=rfwanTptTwSycE4-P-QasPmzd-XVYgevytzjlEzBBu8,95946 +pip/_vendor/chardet/latin1prober.py,sha256=S2IoORhFk39FEFOlSFWtgVybRiP6h7BlLldHVclNkU8,5370 +pip/_vendor/chardet/mbcharsetprober.py,sha256=AR95eFH9vuqSfvLQZN-L5ijea25NOBCoXqw8s5O9xLQ,3413 +pip/_vendor/chardet/mbcsgroupprober.py,sha256=h6TRnnYq2OxG1WdD5JOyxcdVpn7dG0q-vB8nWr5mbh4,2012 +pip/_vendor/chardet/mbcssm.py,sha256=SY32wVIF3HzcjY3BaEspy9metbNSKxIIB0RKPn7tjpI,25481 +pip/_vendor/chardet/sbcharsetprober.py,sha256=nmyMyuxzG87DN6K3Rk2MUzJLMLR69MrWpdnHzOwVUwQ,6136 +pip/_vendor/chardet/sbcsgroupprober.py,sha256=hqefQuXmiFyDBArOjujH6hd6WFXlOD1kWCsxDhjx5Vc,4309 +pip/_vendor/chardet/sjisprober.py,sha256=IIt-lZj0WJqK4rmUZzKZP4GJlE8KUEtFYVuY96ek5MQ,3774 +pip/_vendor/chardet/universaldetector.py,sha256=DpZTXCX0nUHXxkQ9sr4GZxGB_hveZ6hWt3uM94cgWKs,12503 +pip/_vendor/chardet/utf8prober.py,sha256=IdD8v3zWOsB8OLiyPi-y_fqwipRFxV9Nc1eKBLSuIEw,2766 +pip/_vendor/chardet/version.py,sha256=A4CILFAd8MRVG1HoXPp45iK9RLlWyV73a1EtwE8Tvn8,242 +pip/_vendor/chardet/cli/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1 +pip/_vendor/chardet/cli/chardetect.py,sha256=XK5zqjUG2a4-y6eLHZ8ThYcp6WWUrdlmELxNypcc2SE,2747 +pip/_vendor/chardet/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/chardet/metadata/languages.py,sha256=41tLq3eLSrBEbEVVQpVGFq9K7o1ln9b1HpY1l0hCUQo,19474 +pip/_vendor/colorama/__init__.py,sha256=pCdErryzLSzDW5P-rRPBlPLqbBtIRNJB6cMgoeJns5k,239 +pip/_vendor/colorama/ansi.py,sha256=Top4EeEuaQdBWdteKMEcGOTeKeF19Q-Wo_6_Cj5kOzQ,2522 +pip/_vendor/colorama/ansitowin32.py,sha256=yV7CEmCb19MjnJKODZEEvMH_fnbJhwnpzo4sxZuGXmA,10517 +pip/_vendor/colorama/initialise.py,sha256=PprovDNxMTrvoNHFcL2NZjpH2XzDc8BLxLxiErfUl4k,1915 +pip/_vendor/colorama/win32.py,sha256=bJ8Il9jwaBN5BJ8bmN6FoYZ1QYuMKv2j8fGrXh7TJjw,5404 +pip/_vendor/colorama/winterm.py,sha256=2y_2b7Zsv34feAsP67mLOVc-Bgq51mdYGo571VprlrM,6438 +pip/_vendor/distlib/__init__.py,sha256=y-rKDBB99QJ3N1PJGAXQo89ou615aAeBjV2brBxKgM8,581 +pip/_vendor/distlib/compat.py,sha256=tfoMrj6tujk7G4UC2owL6ArgDuCKabgBxuJRGZSmpko,41259 +pip/_vendor/distlib/database.py,sha256=hBO2dgvDF7W3BqX8Ecns6p_RPerCaIbNKbdUOuJ1a14,51456 +pip/_vendor/distlib/index.py,sha256=UfcimNW19AB7IKWam4VaJbXuCBvArKfSxhV16EwavzE,20739 +pip/_vendor/distlib/locators.py,sha256=4D2hEcHePNuW4mXEZ3Cuw12eW-vbO-4WuAlbf4h5K7w,51963 +pip/_vendor/distlib/manifest.py,sha256=nQEhYmgoreaBZzyFzwYsXxJARu3fo4EkunU163U16iE,14811 +pip/_vendor/distlib/markers.py,sha256=TpHHHLgkzyT7YHbwj-2i6weRaq-Ivy2-MUnrDkjau-U,5058 +pip/_vendor/distlib/metadata.py,sha256=vatoxFdmBr6ie-sTVXVNPOPG3uwMDWJTnEECnm7xDCw,39109 +pip/_vendor/distlib/resources.py,sha256=LwbPksc0A1JMbi6XnuPdMBUn83X7BPuFNWqPGEKI698,10820 +pip/_vendor/distlib/scripts.py,sha256=tjSwENINeV91ROZxec5zTSMRg2jEeKc4enyCHDzNvEE,17720 +pip/_vendor/distlib/util.py,sha256=31dPXn3Rfat0xZLeVoFpuniyhe6vsbl9_QN-qd9Lhlk,66262 +pip/_vendor/distlib/version.py,sha256=WG__LyAa2GwmA6qSoEJtvJE8REA1LZpbSizy8WvhJLk,23513 +pip/_vendor/distlib/wheel.py,sha256=pj5VVCjqZMcHvgizORWwAFPS7hOk61CZ59dxP8laQ4E,42943 +pip/_vendor/html5lib/__init__.py,sha256=BYzcKCqeEii52xDrqBFruhnmtmkiuHXFyFh-cglQ8mk,1160 +pip/_vendor/html5lib/_ihatexml.py,sha256=ifOwF7pXqmyThIXc3boWc96s4MDezqRrRVp7FwDYUFs,16728 +pip/_vendor/html5lib/_inputstream.py,sha256=jErNASMlkgs7MpOM9Ve_VdLDJyFFweAjLuhVutZz33U,32353 +pip/_vendor/html5lib/_tokenizer.py,sha256=04mgA2sNTniutl2fxFv-ei5bns4iRaPxVXXHh_HrV_4,77040 +pip/_vendor/html5lib/_utils.py,sha256=Dx9AKntksRjFT1veBj7I362pf5OgIaT0zglwq43RnfU,4931 +pip/_vendor/html5lib/constants.py,sha256=Ll-yzLU_jcjyAI_h57zkqZ7aQWE5t5xA4y_jQgoUUhw,83464 +pip/_vendor/html5lib/html5parser.py,sha256=anr-aXre_ImfrkQ35c_rftKXxC80vJCREKe06Tq15HA,117186 +pip/_vendor/html5lib/serializer.py,sha256=_PpvcZF07cwE7xr9uKkZqh5f4UEaI8ltCU2xPJzaTpk,15759 +pip/_vendor/html5lib/_trie/__init__.py,sha256=nqfgO910329BEVJ5T4psVwQtjd2iJyEXQ2-X8c1YxwU,109 +pip/_vendor/html5lib/_trie/_base.py,sha256=CaybYyMro8uERQYjby2tTeSUatnWDfWroUN9N7ety5w,1013 +pip/_vendor/html5lib/_trie/py.py,sha256=wXmQLrZRf4MyWNyg0m3h81m9InhLR7GJ002mIIZh-8o,1775 +pip/_vendor/html5lib/filters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/html5lib/filters/alphabeticalattributes.py,sha256=lViZc2JMCclXi_5gduvmdzrRxtO5Xo9ONnbHBVCsykU,919 +pip/_vendor/html5lib/filters/base.py,sha256=z-IU9ZAYjpsVsqmVt7kuWC63jR11hDMr6CVrvuao8W0,286 +pip/_vendor/html5lib/filters/inject_meta_charset.py,sha256=egDXUEHXmAG9504xz0K6ALDgYkvUrC2q15YUVeNlVQg,2945 +pip/_vendor/html5lib/filters/lint.py,sha256=jk6q56xY0ojiYfvpdP-OZSm9eTqcAdRqhCoPItemPYA,3643 +pip/_vendor/html5lib/filters/optionaltags.py,sha256=8lWT75J0aBOHmPgfmqTHSfPpPMp01T84NKu0CRedxcE,10588 +pip/_vendor/html5lib/filters/sanitizer.py,sha256=m6oGmkBhkGAnn2nV6D4hE78SCZ6WEnK9rKdZB3uXBIc,26897 +pip/_vendor/html5lib/filters/whitespace.py,sha256=8eWqZxd4UC4zlFGW6iyY6f-2uuT8pOCSALc3IZt7_t4,1214 +pip/_vendor/html5lib/treeadapters/__init__.py,sha256=A0rY5gXIe4bJOiSGRO_j_tFhngRBO8QZPzPtPw5dFzo,679 +pip/_vendor/html5lib/treeadapters/genshi.py,sha256=CH27pAsDKmu4ZGkAUrwty7u0KauGLCZRLPMzaO3M5vo,1715 +pip/_vendor/html5lib/treeadapters/sax.py,sha256=BKS8woQTnKiqeffHsxChUqL4q2ZR_wb5fc9MJ3zQC8s,1776 +pip/_vendor/html5lib/treebuilders/__init__.py,sha256=AysSJyvPfikCMMsTVvaxwkgDieELD5dfR8FJIAuq7hY,3592 +pip/_vendor/html5lib/treebuilders/base.py,sha256=z-o51vt9r_l2IDG5IioTOKGzZne4Fy3_Fc-7ztrOh4I,14565 +pip/_vendor/html5lib/treebuilders/dom.py,sha256=22whb0C71zXIsai5mamg6qzBEiigcBIvaDy4Asw3at0,8925 +pip/_vendor/html5lib/treebuilders/etree.py,sha256=w5ZFpKk6bAxnrwD2_BrF5EVC7vzz0L3LMi9Sxrbc_8w,12836 +pip/_vendor/html5lib/treebuilders/etree_lxml.py,sha256=9gqDjs-IxsPhBYa5cpvv2FZ1KZlG83Giusy2lFmvIkE,14766 +pip/_vendor/html5lib/treewalkers/__init__.py,sha256=OBPtc1TU5mGyy18QDMxKEyYEz0wxFUUNj5v0-XgmYhY,5719 +pip/_vendor/html5lib/treewalkers/base.py,sha256=ouiOsuSzvI0KgzdWP8PlxIaSNs9falhbiinAEc_UIJY,7476 +pip/_vendor/html5lib/treewalkers/dom.py,sha256=EHyFR8D8lYNnyDU9lx_IKigVJRyecUGua0mOi7HBukc,1413 +pip/_vendor/html5lib/treewalkers/etree.py,sha256=xo1L5m9VtkfpFJK0pFmkLVajhqYYVisVZn3k9kYpPkI,4551 +pip/_vendor/html5lib/treewalkers/etree_lxml.py,sha256=_b0LAVWLcVu9WaU_-w3D8f0IRSpCbjf667V-3NRdhTw,6357 +pip/_vendor/html5lib/treewalkers/genshi.py,sha256=4D2PECZ5n3ZN3qu3jMl9yY7B81jnQApBQSVlfaIuYbA,2309 +pip/_vendor/idna/__init__.py,sha256=KJQN1eQBr8iIK5SKrJ47lXvxG0BJ7Lm38W4zT0v_8lk,849 +pip/_vendor/idna/codec.py,sha256=6ly5odKfqrytKT9_7UrlGklHnf1DSK2r9C6cSM4sa28,3374 +pip/_vendor/idna/compat.py,sha256=0_sOEUMT4CVw9doD3vyRhX80X19PwqFoUBs7gWsFME4,321 +pip/_vendor/idna/core.py,sha256=pNlI-XmwJ8ZseGTa0PLGxI6SOyaaAMwgfWvi7zwVNpE,12658 +pip/_vendor/idna/idnadata.py,sha256=TmP9_NBUtQ0pCZDjJxCjYtflgfroWC-LA9ueaNNtT90,78019 +pip/_vendor/idna/intranges.py,sha256=YBr4fRYuWH7kTKS2tXlFjM24ZF1Pdvcir-aywniInqg,1881 +pip/_vendor/idna/package_data.py,sha256=szxQhV0ZD0nKJ84Kuobw3l8q4_KeCyXjFRdpwIpKZmw,21 +pip/_vendor/idna/uts46data.py,sha256=o-D7V-a0fOLZNd7tvxof6MYfUd0TBZzE2bLR5XO67xU,204400 +pip/_vendor/msgpack/__init__.py,sha256=2gJwcsTIaAtCM0GMi2rU-_Y6kILeeQuqRkrQ22jSANc,1118 +pip/_vendor/msgpack/_version.py,sha256=JpTcnRd3YUioA24NDtDZbLW0Nhl2yA-N1Rq2lLDBB-g,20 +pip/_vendor/msgpack/exceptions.py,sha256=dCTWei8dpkrMsQDcjQk74ATl9HsIBH0ybt8zOPNqMYc,1081 +pip/_vendor/msgpack/ext.py,sha256=4l356Y4sVEcvCla2dh_cL57vh4GMhZfa3kuWHFHYz6A,6088 +pip/_vendor/msgpack/fallback.py,sha256=L5jriXysURbf6rPbbHbvXgvoFrKZiryIBmujMTcrf3A,34475 +pip/_vendor/packaging/__about__.py,sha256=ugASIO2w1oUyH8_COqQ2X_s0rDhjbhQC3yJocD03h2c,661 +pip/_vendor/packaging/__init__.py,sha256=b9Kk5MF7KxhhLgcDmiUWukN-LatWFxPdNug0joPhHSk,497 +pip/_vendor/packaging/_manylinux.py,sha256=XcbiXB-qcjv3bcohp6N98TMpOP4_j3m-iOA8ptK2GWY,11488 +pip/_vendor/packaging/_musllinux.py,sha256=_KGgY_qc7vhMGpoqss25n2hiLCNKRtvz9mCrS7gkqyc,4378 +pip/_vendor/packaging/_structures.py,sha256=q3eVNmbWJGG_S0Dit_S3Ao8qQqz_5PYTXFAKBZe5yr4,1431 +pip/_vendor/packaging/markers.py,sha256=AJBOcY8Oq0kYc570KuuPTkvuqjAlhufaE2c9sCUbm64,8487 +pip/_vendor/packaging/requirements.py,sha256=NtDlPBtojpn1IUC85iMjPNsUmufjpSlwnNA-Xb4m5NA,4676 +pip/_vendor/packaging/specifiers.py,sha256=LRQ0kFsHrl5qfcFNEEJrIFYsnIHQUJXY9fIsakTrrqE,30110 +pip/_vendor/packaging/tags.py,sha256=lmsnGNiJ8C4D_Pf9PbM0qgbZvD9kmB9lpZBQUZa3R_Y,15699 +pip/_vendor/packaging/utils.py,sha256=dJjeat3BS-TYn1RrUFVwufUMasbtzLfYRoy_HXENeFQ,4200 +pip/_vendor/packaging/version.py,sha256=_fLRNrFrxYcHVfyo8vk9j8s6JM8N_xsSxVFr6RJyco8,14665 +pip/_vendor/pep517/__init__.py,sha256=Y1bATL2qbFNN6M_DQa4yyrwqjpIiL-j9T6kBmR0DS14,130 +pip/_vendor/pep517/build.py,sha256=2bar6EdjwIz2Dlfy94qdxn3oA9mVnnny40mfoT5f-qI,3457 +pip/_vendor/pep517/check.py,sha256=bCORq1WrHjhpTONa-zpAqG0EB9rHNuhO1ORu6DsDuL8,6084 +pip/_vendor/pep517/colorlog.py,sha256=Tk9AuYm_cLF3BKTBoSTJt9bRryn0aFojIQOwbfVUTxQ,4098 +pip/_vendor/pep517/compat.py,sha256=NmLImE5oiDT3gbEhJ4w7xeoMFcpAPrGu_NltBytSJUY,1253 +pip/_vendor/pep517/dirtools.py,sha256=2mkAkAL0mRz_elYFjRKuekTJVipH1zTn4tbf1EDev84,1129 +pip/_vendor/pep517/envbuild.py,sha256=zFde--rmzjXMLXcm7SA_3hDtgk5VCTA8hjpk88RbF6E,6100 +pip/_vendor/pep517/meta.py,sha256=8mnM5lDnT4zXQpBTliJbRGfesH7iioHwozbDxALPS9Y,2463 +pip/_vendor/pep517/wrappers.py,sha256=impq7Cz_LL1iDF1iiOzYWB4MaEu6O6Gps7TJ5qsJz1Q,13429 +pip/_vendor/pep517/in_process/__init__.py,sha256=MyWoAi8JHdcBv7yXuWpUSVADbx6LSB9rZh7kTIgdA8Y,563 +pip/_vendor/pep517/in_process/_in_process.py,sha256=D3waguyNSGcwosociD5USfcycYr2RCzCjYtxX5UHQmQ,11201 +pip/_vendor/pkg_resources/__init__.py,sha256=NnpQ3g6BCHzpMgOR_OLBmYtniY4oOzdKpwqghfq_6ug,108287 +pip/_vendor/pkg_resources/py31compat.py,sha256=CRk8fkiPRDLsbi5pZcKsHI__Pbmh_94L8mr9Qy9Ab2U,562 +pip/_vendor/platformdirs/__init__.py,sha256=Aizpxewwd4nY63Gqw-Od1Rso9Ah4bSoc6rkx-GBRu2Y,12676 +pip/_vendor/platformdirs/__main__.py,sha256=ZmsnTxEOxtTvwa-Y_Vfab_JN3X4XCVeN8X0yyy9-qnc,1176 +pip/_vendor/platformdirs/android.py,sha256=xhlD4NmrKCARe5lgnpBGYo4lOYxEOBOByNDNYy91gEE,4012 +pip/_vendor/platformdirs/api.py,sha256=MXKHXOL3eh_-trSok-JUTjAR_zjmmKF3rjREVABjP8s,4910 +pip/_vendor/platformdirs/macos.py,sha256=-3UXQewbT0yMhMdkzRXfXGAntmLIH7Qt4a9Hlf8I5_Y,2655 +pip/_vendor/platformdirs/unix.py,sha256=b4aVYTz0qZ50HntwOXo8r6tp82jAa3qTjxw-WlnC2yc,6910 +pip/_vendor/platformdirs/version.py,sha256=bXzLJCe23FNQRQrf7ZRWKejxWnct_wft7dxdkMGT33E,80 +pip/_vendor/platformdirs/windows.py,sha256=ISruopR5UGBePC0BxCxXevkZYfjJsIZc49YWU5iYfQ4,6439 +pip/_vendor/progress/__init__.py,sha256=1HejNZtv2ouUNQeStUDAtZrtwkz_3FmYKQ476hJ7zOs,5294 +pip/_vendor/progress/bar.py,sha256=GbedY0oZ-Q1duXjmvVLO0tSf-uTSH7hJ3zzyI91Esws,2942 +pip/_vendor/progress/colors.py,sha256=cCYXQnYFYVmQKKmYEbQ_lj6SPSFzdw4FN98F2x2kR-U,2655 +pip/_vendor/progress/counter.py,sha256=zYt9DWH0_05s8Q9TrJwHVud-WwsyyaR3PwYtk5hxwwQ,1613 +pip/_vendor/progress/spinner.py,sha256=u5ElzW94XEiLGH-aAlr54VJtKfeK745xr6UfGvvflzU,1461 +pip/_vendor/pygments/__init__.py,sha256=CAmA9UthykwxvtutUcH0IxqtiyQcSg6CmYdM-jKlcRY,3002 +pip/_vendor/pygments/__main__.py,sha256=X7rGLMUC54EXgO14FZ9goKXZDmhPzKXTsUglmb_McIU,353 +pip/_vendor/pygments/cmdline.py,sha256=XpsyWgErcSqHC7rXiYKLF3Y61Uy8SR2DNQDDhZGuezg,23408 +pip/_vendor/pygments/console.py,sha256=QZXBUAkyl4dPLQ1e6XHjQu3mmXBWvuGQwsQT2q1mtCY,1697 +pip/_vendor/pygments/filter.py,sha256=35iMZiB1rcuogxokm92kViB2DPXPp_wWoxWuMmwvvzY,1938 +pip/_vendor/pygments/formatter.py,sha256=zSBbX2U_OOriy7SJvSTK6OAxjuXtROWxQlNpJEJZjBA,2917 +pip/_vendor/pygments/lexer.py,sha256=ECXWlEsbRnKs_njozZns6BGQ4riTMzct_BzAr3zV6dY,31937 +pip/_vendor/pygments/modeline.py,sha256=37fen3cf1moCz4vMVJqX41eAQCmj8pzUchikgPcHp-U,986 +pip/_vendor/pygments/plugin.py,sha256=zGSig3S7QX-3o6RDxd4_Uvice_t25l_BN9aQQ9k8vmU,1727 +pip/_vendor/pygments/regexopt.py,sha256=mj8Fgu3sT0d5PZwRwDLexEvVOQbuHeosubQnqVwgiqs,3072 +pip/_vendor/pygments/scanner.py,sha256=nGoHy-Npk2ylUd4bws_CJN1hK785Xqo8e0teRmNX2jo,3091 +pip/_vendor/pygments/sphinxext.py,sha256=FZ2puvLe2Bztqtj6UJvQd7D8TvtOZ1GsfRJObvH59tE,4630 +pip/_vendor/pygments/style.py,sha256=lGyan5bU42q1kGMfFqafwL3g1j5EurTvfkv8vdP7NzQ,6257 +pip/_vendor/pygments/token.py,sha256=lNPgeaQTzu2DEUi6n_lxAIU7uy4DVj8LMI3nSVnTjks,6143 +pip/_vendor/pygments/unistring.py,sha256=Xs0FzOzE0l0iWRoTlcgi-Q_kAMdF5Gt5FL_goGKJc98,63188 +pip/_vendor/pygments/util.py,sha256=s9n8BQXIxG3lIwCPWv5-ci8yhaqq5JbEVK9v8Z-8_3I,9123 +pip/_vendor/pygments/filters/__init__.py,sha256=-veOimzCyYGEARru2Dfo6ofSYcZ8tGsIVuMprtaZQ24,40292 +pip/_vendor/pygments/formatters/__init__.py,sha256=fjkYDy5-F998XczKi0ymHFayr5ObIRLHF8cgp9k8kpA,5119 +pip/_vendor/pygments/formatters/_mapping.py,sha256=3A1rYSjYN9MLduCFWy2_mYhllPVpwlw55anRYnPXX8w,6516 +pip/_vendor/pygments/formatters/bbcode.py,sha256=cSKMOioUnE4TzvCCsK4IbJ6G78W07ZwHtkz4V1Wte0U,3314 +pip/_vendor/pygments/formatters/groff.py,sha256=ULgMKvGeLswX0KZn3IBp0p0U3rruiSHBtpl6O5qbqLs,5005 +pip/_vendor/pygments/formatters/html.py,sha256=0jM7Jc4xA4tsjmPq35uklm_En_OVdcNb0__SEXp2pDQ,35330 +pip/_vendor/pygments/formatters/img.py,sha256=r4iag_jCfyv_LhIt-1fRDeVEEoAfVJzkD9nZChIwiS8,21819 +pip/_vendor/pygments/formatters/irc.py,sha256=gi_IeIZeNaTfTMtvseLigZdS6lNicN7r7O7rnI6myo0,5871 +pip/_vendor/pygments/formatters/latex.py,sha256=qZUerrHt2Nn2aB4gJcdqj99qBkIxl_1v1ukYsf230Gk,18930 +pip/_vendor/pygments/formatters/other.py,sha256=Q01LtkqPZ8m_EYdgMVzXPUGjHoL00lXI3By97wzytYU,5073 +pip/_vendor/pygments/formatters/pangomarkup.py,sha256=ZpjALTSuGFwviJd5kOYwr-1NgqxCX3XRJrjXC7x1UbQ,2212 +pip/_vendor/pygments/formatters/rtf.py,sha256=qh7-z_wbUsTY6z7fZUGrYECYBlWB0wEdBwIZVEVybL0,5014 +pip/_vendor/pygments/formatters/svg.py,sha256=T7Jj004I3JUPOr48aAhQ368K2qWCciUyMQ2tdU-LB-4,7335 +pip/_vendor/pygments/formatters/terminal.py,sha256=cRD5hitINOkYlGZo9ma252vpJYPSGNgLivrsm6zGyec,4674 +pip/_vendor/pygments/formatters/terminal256.py,sha256=Bvz9zZL3UWc94TDm1GhKMI4x0BTit0XplhyRL0zmtkw,11753 +pip/_vendor/pygments/lexers/__init__.py,sha256=6Ds0GVBP3jrIU02wmjRdpoL4eFGhwT2IVD1zf3cV5_Y,11307 +pip/_vendor/pygments/lexers/_mapping.py,sha256=jAxmvh5wvNkD-p3Fh6E7hY_B0sGbcxWRfseT6iq7ex4,70032 +pip/_vendor/pygments/lexers/python.py,sha256=LXnk43Lcngqn9xj6eRqdk2f73oF4kHZWiwgHMM_RlVM,52776 +pip/_vendor/pygments/styles/__init__.py,sha256=Qx2zq6ufbDNE2cTp51M-s9zW-sDE-KLIqFw31qr3Bhg,3252 +pip/_vendor/pyparsing/__init__.py,sha256=jXheGTFT1b6r_4WxuOE0uVUqiouLJ3WHzOScpLieRgQ,9107 +pip/_vendor/pyparsing/actions.py,sha256=60v7mETOBzc01YPH_qQD5isavgcSJpAfIKpzgjM3vaU,6429 +pip/_vendor/pyparsing/common.py,sha256=lFL97ooIeR75CmW5hjURZqwDCTgruqltcTCZ-ulLO2Q,12936 +pip/_vendor/pyparsing/core.py,sha256=GtQsD06HlwKPc7M8K8hyOuOW-cRnd87AxAHq-ad5lEk,212248 +pip/_vendor/pyparsing/exceptions.py,sha256=H4D9gqMavqmAFSsdrU_J6bO-jA-T-A7yvtXWZpooIUA,9030 +pip/_vendor/pyparsing/helpers.py,sha256=kqpIZFG-y0fQ3g_TmloYllo9we6YCYiewZMXIK0y5wc,38299 +pip/_vendor/pyparsing/results.py,sha256=4D-oURF1cLeL7k0d3zMqUuWH_gTjop_OrZwik9O0HXU,25339 +pip/_vendor/pyparsing/testing.py,sha256=szs8AKZREZMhL0y0vsMfaTVAnpqPHetg6VKJBNmc4QY,13388 +pip/_vendor/pyparsing/unicode.py,sha256=IR-ioeGY29cZ49tG8Ts7ITPWWNP5G2DcZs58oa8zn44,10381 +pip/_vendor/pyparsing/util.py,sha256=kq772O5YSeXOSdP-M31EWpbH_ayj7BMHImBYo9xPD5M,6805 +pip/_vendor/pyparsing/diagram/__init__.py,sha256=h0gsUwmo5N3shgvfXVQTtqvTpUAv-ZdQjSQ6IUJmsxY,22165 +pip/_vendor/requests/__init__.py,sha256=6IUFQM6K9V2NIu4fe4LtUsN21-TFbw_w3EfPpdUN-qc,5130 +pip/_vendor/requests/__version__.py,sha256=q8miOQaomOv3S74lK4eQs1zZ5jwcnOusyEU-M2idhts,441 +pip/_vendor/requests/_internal_utils.py,sha256=Zx3PnEUccyfsB-ie11nZVAW8qClJy0gx1qNME7rgT18,1096 +pip/_vendor/requests/adapters.py,sha256=WazYJQ_b2LHhNDb_y0hscNlWVsSe5ca5I3pymPrer5w,21861 +pip/_vendor/requests/api.py,sha256=hjuoP79IAEmX6Dysrw8t032cLfwLHxbI_wM4gC5G9t0,6402 +pip/_vendor/requests/auth.py,sha256=OMoJIVKyRLy9THr91y8rxysZuclwPB-K1Xg1zBomUhQ,10207 +pip/_vendor/requests/certs.py,sha256=nXRVq9DtGmv_1AYbwjTu9UrgAcdJv05ZvkNeaoLOZxY,465 +pip/_vendor/requests/compat.py,sha256=N1281mkcTluMjKqCSLf88LR6HNOygEhS1TbR9LLsoVY,2114 +pip/_vendor/requests/cookies.py,sha256=Y-bKX6TvW3FnYlE6Au0SXtVVWcaNdFvuAwQxw-G0iTI,18430 +pip/_vendor/requests/exceptions.py,sha256=VcpBXOL-9JYhNbK8OZxCIImBgpQSXJlUelDPf1f-pmM,3446 +pip/_vendor/requests/help.py,sha256=dyhe3lcmHXnFCzDiZVjcGmVvvO_jtsfAm-AC542ndw8,3972 +pip/_vendor/requests/hooks.py,sha256=QReGyy0bRcr5rkwCuObNakbYsc7EkiKeBwG4qHekr2Q,757 +pip/_vendor/requests/models.py,sha256=7pzscX_47qxx7-zEaBWGxMoB33Vdf6HLoUKZh1ktEvM,35116 +pip/_vendor/requests/packages.py,sha256=njJmVifY4aSctuW3PP5EFRCxjEwMRDO6J_feG2dKWsI,695 +pip/_vendor/requests/sessions.py,sha256=HiFx6LQ-pINGxzmdfKbU3C5o_-NF1OViwFoWKjSILG4,30028 +pip/_vendor/requests/status_codes.py,sha256=gT79Pbs_cQjBgp-fvrUgg1dn2DQO32bDj4TInjnMPSc,4188 +pip/_vendor/requests/structures.py,sha256=msAtr9mq1JxHd-JRyiILfdFlpbJwvvFuP3rfUQT_QxE,3005 +pip/_vendor/requests/utils.py,sha256=4-8IEAnpoRvepu6y5qk1aTI_NkfUqO4yxProY71MkZQ,33030 +pip/_vendor/resolvelib/__init__.py,sha256=UL-B2BDI0_TRIqkfGwLHKLxY-LjBlomz7941wDqzB1I,537 +pip/_vendor/resolvelib/providers.py,sha256=roVmFBItQJ0TkhNua65h8LdNny7rmeqVEXZu90QiP4o,5872 +pip/_vendor/resolvelib/reporters.py,sha256=fW91NKf-lK8XN7i6Yd_rczL5QeOT3sc6AKhpaTEnP3E,1583 +pip/_vendor/resolvelib/resolvers.py,sha256=2wYzVGBGerbmcIpH8cFmgSKgLSETz8jmwBMGjCBMHG4,17592 +pip/_vendor/resolvelib/structs.py,sha256=IVIYof6sA_N4ZEiE1C1UhzTX495brCNnyCdgq6CYq28,4794 +pip/_vendor/resolvelib/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/resolvelib/compat/collections_abc.py,sha256=uy8xUZ-NDEw916tugUXm8HgwCGiMO0f-RcdnpkfXfOs,156 +pip/_vendor/rich/__init__.py,sha256=wF1th4JGBCVC02xfaw8j6P2MrFcJaQJL72scKtEmDYQ,5804 +pip/_vendor/rich/__main__.py,sha256=vd1PP-o7_1un-ThdgMU9LHV-D8z56yz_-fryczn38eE,8810 +pip/_vendor/rich/_cell_widths.py,sha256=2n4EiJi3X9sqIq0O16kUZ_zy6UYMd3xFfChlKfnW1Hc,10096 +pip/_vendor/rich/_emoji_codes.py,sha256=hu1VL9nbVdppJrVoijVshRlcRRe_v3dju3Mmd2sKZdY,140235 +pip/_vendor/rich/_emoji_replace.py,sha256=n-kcetsEUx2ZUmhQrfeMNc-teeGhpuSQ5F8VPBsyvDo,1064 +pip/_vendor/rich/_extension.py,sha256=Xt47QacCKwYruzjDi-gOBq724JReDj9Cm9xUi5fr-34,265 +pip/_vendor/rich/_inspect.py,sha256=vq6BjewwEvddjcBTr_lCcjYQBsKi92aTNpcXyaA5ERA,7444 +pip/_vendor/rich/_log_render.py,sha256=1ByI0PA1ZpxZY3CGJOK54hjlq4X-Bz_boIjIqCd8Kns,3225 +pip/_vendor/rich/_loop.py,sha256=hV_6CLdoPm0va22Wpw4zKqM0RYsz3TZxXj0PoS-9eDQ,1236 +pip/_vendor/rich/_lru_cache.py,sha256=M7H1ZQF32o6SxrpOur9zTIhEHlNXT9XnrcdhruUmG5I,1246 +pip/_vendor/rich/_palettes.py,sha256=cdev1JQKZ0JvlguV9ipHgznTdnvlIzUFDBb0It2PzjI,7063 +pip/_vendor/rich/_pick.py,sha256=evDt8QN4lF5CiwrUIXlOJCntitBCOsI3ZLPEIAVRLJU,423 +pip/_vendor/rich/_ratio.py,sha256=2lLSliL025Y-YMfdfGbutkQDevhcyDqc-DtUYW9mU70,5472 +pip/_vendor/rich/_spinners.py,sha256=huT1biTlwyp9Lm8S7bLfVzg1psUaIH5xHDwTaWEHVh0,26521 +pip/_vendor/rich/_stack.py,sha256=-C8OK7rxn3sIUdVwxZBBpeHhIzX0eI-VM3MemYfaXm0,351 +pip/_vendor/rich/_timer.py,sha256=zelxbT6oPFZnNrwWPpc1ktUeAT-Vc4fuFcRZLQGLtMI,417 +pip/_vendor/rich/_windows.py,sha256=nBO71icHMIHlzT7hg6fkoIdh1mT-5MvDdPDwunkshyw,2065 +pip/_vendor/rich/_wrap.py,sha256=OtnSxnERkuNlSM1d_MYtNg8KIYTcTBk3peg16dCZH_U,1804 +pip/_vendor/rich/abc.py,sha256=ON-E-ZqSSheZ88VrKX2M3PXpFbGEUUZPMa_Af0l-4f0,890 +pip/_vendor/rich/align.py,sha256=2zRHV8SzR5eP-vQkSDgjmgsBLBluCBwykgejAW6oRD0,10425 +pip/_vendor/rich/ansi.py,sha256=QaVVkfvVL6C3OsuWI9iQ-iJFkMsMohjYlxgMLnVTEPo,6676 +pip/_vendor/rich/bar.py,sha256=a7UD303BccRCrEhGjfMElpv5RFYIinaAhAuqYqhUvmw,3264 +pip/_vendor/rich/box.py,sha256=o0ywz1iW0WjGLPrRVDAZPh1CVPEgAOaWsn8Bf3sf43g,9069 +pip/_vendor/rich/cells.py,sha256=NadN20gFxE8Aj-2S3Drn7qgn-ZpsRZcNnTNtweRL7rA,4285 +pip/_vendor/rich/color.py,sha256=SD3yTf3t8japb-jOv8GYCMCDqyzpipzXS_0rAXhSlU4,17285 +pip/_vendor/rich/color_triplet.py,sha256=3lhQkdJbvWPoLDO-AnYImAWmJvV5dlgYNCVZ97ORaN4,1054 +pip/_vendor/rich/columns.py,sha256=HUX0KcMm9dsKNi11fTbiM_h2iDtl8ySCaVcxlalEzq8,7131 +pip/_vendor/rich/console.py,sha256=bioCy8012eZ8PIOBxMyyqxYPltKk2pGEG9jmwylNCQk,81236 +pip/_vendor/rich/constrain.py,sha256=1VIPuC8AgtKWrcncQrjBdYqA3JVWysu6jZo1rrh7c7Q,1288 +pip/_vendor/rich/containers.py,sha256=aKgm5UDHn5Nmui6IJaKdsZhbHClh_X7D-_Wg8Ehrr7s,5497 +pip/_vendor/rich/control.py,sha256=qxg6Yjd78XuF0VxthlT8O4dpvpACYwKkBfm2S4-IvHA,5298 +pip/_vendor/rich/default_styles.py,sha256=At42PcWzmnYWcx5fUOKyOUpI8HK5m4ItZqxkgHToaMs,7614 +pip/_vendor/rich/diagnose.py,sha256=4L8SZfbqjIRotzJ39QzD9-d4I80FyV1mNKHryg1eArE,183 +pip/_vendor/rich/emoji.py,sha256=omTF9asaAnsM4yLY94eR_9dgRRSm1lHUszX20D1yYCQ,2501 +pip/_vendor/rich/errors.py,sha256=5pP3Kc5d4QJ_c0KFsxrfyhjiPVe7J1zOqSFbFAzcV-Y,642 +pip/_vendor/rich/file_proxy.py,sha256=fHeReSO3VJ7IbH_9ri-OrPYbFC3UYOzeTNjngiiWOcY,1613 +pip/_vendor/rich/filesize.py,sha256=oQJnM5_7ygkpzt3GtNq5l3F6gmB7YahBA5vpdQVKLwI,2511 +pip/_vendor/rich/highlighter.py,sha256=AdhjC0meTYswZ_xKgka0cRYdNjLABLUzHAbyF3QpPWo,4894 +pip/_vendor/rich/json.py,sha256=RCm4lXBXrjvXHpqrWPH8wdGP0jEo4IohLmkddlhRY18,5051 +pip/_vendor/rich/jupyter.py,sha256=4sxNAwJs4g3dYfWy_enPw9fp0Tdn-82tV4T9uh9vAOM,3025 +pip/_vendor/rich/layout.py,sha256=b64KMDP2EPiC103P-v-_VZKGY13oWiiGS418P_KRRlc,14048 +pip/_vendor/rich/live.py,sha256=OKxMaFU5sFfuR--cJftGYjSvg1VPQri1U_DNZUjCsvI,13711 +pip/_vendor/rich/live_render.py,sha256=zElm3PrfSIvjOce28zETHMIUf9pFYSUA5o0AflgUP64,3667 +pip/_vendor/rich/logging.py,sha256=YNcCSK6pCo2Wg6JKqScAe6VgFqebHBnS5nDnBO4gXAA,10868 +pip/_vendor/rich/markup.py,sha256=hsVW_k1TIvj5OPPQ12ihAii9HSVa8N1TStvA5B2GGpo,8058 +pip/_vendor/rich/measure.py,sha256=Z74XvzIgLZm0xH-QIo1uX5d4oahavHe8D8MKyxLNqPQ,5258 +pip/_vendor/rich/padding.py,sha256=kTFGsdGe0os7tXLnHKpwTI90CXEvrceeZGCshmJy5zw,4970 +pip/_vendor/rich/pager.py,sha256=VK_2EfH0JduZWdyV-KZma06bvi_V5PWmHG6W7BoiaTg,838 +pip/_vendor/rich/palette.py,sha256=lInvR1ODDT2f3UZMfL1grq7dY_pDdKHw4bdUgOGaM4Y,3396 +pip/_vendor/rich/panel.py,sha256=O6ORyIhDcOLSEasTjpcDvmhvIcppPGCeQoXpoycIUT8,8637 +pip/_vendor/rich/pretty.py,sha256=HAB68BpYysaL1EXeV4X5Tt-U2hDlcLpbFz06fkojWWE,32572 +pip/_vendor/rich/progress.py,sha256=jcgi7aMnQ_YjSpAmQkalwtNsgVn9i56SeZGprr7tuOk,35926 +pip/_vendor/rich/progress_bar.py,sha256=ELiBaxJOgsRYKpNIrot7BC0bFXvmf8cTd6nxI02BbK0,7762 +pip/_vendor/rich/prompt.py,sha256=gKVd13YWv6jedzwcRPZGUINBjC-xcJhJ_xz_NvMW80c,11307 +pip/_vendor/rich/protocol.py,sha256=Vx6n4fEoSDhzSup8t3KH0iK2RWyssIOks5E0S1qw1GA,1401 +pip/_vendor/rich/region.py,sha256=rNT9xZrVZTYIXZC0NYn41CJQwYNbR-KecPOxTgQvB8Y,166 +pip/_vendor/rich/repr.py,sha256=1A0U0_ibG_bZbw71pUBIctO9Az-CQUuyOTbiKcJOwyw,4309 +pip/_vendor/rich/rule.py,sha256=cPK6NYo4kzh-vM_8a-rXajXplsbaHa6ahErYvGSsrJ0,4197 +pip/_vendor/rich/scope.py,sha256=HX13XsJfqzQHpPfw4Jn9JmJjCsRj9uhHxXQEqjkwyLA,2842 +pip/_vendor/rich/screen.py,sha256=YoeReESUhx74grqb0mSSb9lghhysWmFHYhsbMVQjXO8,1591 +pip/_vendor/rich/segment.py,sha256=MBBAWaHyqCQFCfiNbrTW4BGaFR1uU31XktJ1S3Taqb4,23916 +pip/_vendor/rich/spinner.py,sha256=V6dW0jIk5IO0_2MyxyftQf5VjCHI0T2cRhJ4F31hPIQ,4312 +pip/_vendor/rich/status.py,sha256=gJsIXIZeSo3urOyxRUjs6VrhX5CZrA0NxIQ-dxhCnwo,4425 +pip/_vendor/rich/style.py,sha256=AD1I7atfclsFCtGeL8ronH1Jj-02WLp9ZQ2VYqmpBjM,26469 +pip/_vendor/rich/styled.py,sha256=eZNnzGrI4ki_54pgY3Oj0T-x3lxdXTYh4_ryDB24wBU,1258 +pip/_vendor/rich/syntax.py,sha256=pJAD08ywowg5xVwTGCqUOMpDYskjoMoDYEV-hryEX5s,26994 +pip/_vendor/rich/table.py,sha256=oQAEBaV4zMUPyg_tSA93_GrCirdIf-osolxf9wb3pEo,36757 +pip/_vendor/rich/tabulate.py,sha256=nl0oeNbiXectEgTHyj3K7eN4NZMISpaogpOdZyEOGbs,1700 +pip/_vendor/rich/terminal_theme.py,sha256=E0nI_ycFpvflamt-KVCY4J52LmUjRi1Y6ICB-Ef3gMo,1459 +pip/_vendor/rich/text.py,sha256=auX3LpY-I6PBiNyxB3o3LyMEx7lna2cx9IbNQJDwtw8,44424 +pip/_vendor/rich/theme.py,sha256=GKNtQhDBZKAzDaY0vQVQQFzbc0uWfFe6CJXA-syT7zQ,3627 +pip/_vendor/rich/themes.py,sha256=0xgTLozfabebYtcJtDdC5QkX5IVUEaviqDUJJh4YVFk,102 +pip/_vendor/rich/traceback.py,sha256=hAU3IR295eFuup_px2NU4aCEWu7KQs1qpZbnqoHCtR0,25935 +pip/_vendor/rich/tree.py,sha256=JxyWbc27ZuwoLQnd7I-rSsRsqI9lzaVKlfTLJXla9U0,9122 +pip/_vendor/tenacity/__init__.py,sha256=GLLsTFD4Bd5VDgTR6mU_FxyOsrxc48qONorVaRebeD4,18257 +pip/_vendor/tenacity/_asyncio.py,sha256=HEb0BVJEeBJE9P-m9XBxh1KcaF96BwoeqkJCL5sbVcQ,3314 +pip/_vendor/tenacity/_utils.py,sha256=-y68scDcyoqvTJuJJ0GTfjdSCljEYlbCYvgk7nM4NdM,1944 +pip/_vendor/tenacity/after.py,sha256=dlmyxxFy2uqpLXDr838DiEd7jgv2AGthsWHGYcGYsaI,1496 +pip/_vendor/tenacity/before.py,sha256=7XtvRmO0dRWUp8SVn24OvIiGFj8-4OP5muQRUiWgLh0,1376 +pip/_vendor/tenacity/before_sleep.py,sha256=ThyDvqKU5yle_IvYQz_b6Tp6UjUS0PhVp6zgqYl9U6Y,1908 +pip/_vendor/tenacity/nap.py,sha256=fRWvnz1aIzbIq9Ap3gAkAZgDH6oo5zxMrU6ZOVByq0I,1383 +pip/_vendor/tenacity/retry.py,sha256=62R71W59bQjuNyFKsDM7hE2aEkEPtwNBRA0tnsEvgSk,6645 +pip/_vendor/tenacity/stop.py,sha256=sKHmHaoSaW6sKu3dTxUVKr1-stVkY7lw4Y9yjZU30zQ,2790 +pip/_vendor/tenacity/tornadoweb.py,sha256=E8lWO2nwe6dJgoB-N2HhQprYLDLB_UdSgFnv-EN6wKE,2145 +pip/_vendor/tenacity/wait.py,sha256=e_Saa6I2tsNLpCL1t9897wN2fGb0XQMQlE4bU2t9V2w,6691 +pip/_vendor/tomli/__init__.py,sha256=z1Elt0nLAqU5Y0DOn9p__8QnLWavlEOpRyQikdYgKro,230 +pip/_vendor/tomli/_parser.py,sha256=50BD4o9YbzFAGAYyZLqZC8F81DQ7iWWyJnrHNwBKa6A,22415 +pip/_vendor/tomli/_re.py,sha256=5GPfgXKteg7wRFCF-DzlkAPI2ilHbkMK2-JC49F-AJQ,2681 +pip/_vendor/urllib3/__init__.py,sha256=j3yzHIbmW7CS-IKQJ9-PPQf_YKO8EOAey_rMW0UR7us,2763 +pip/_vendor/urllib3/_collections.py,sha256=pyASJJhW7wdOpqJj9QJA8FyGRfr8E8uUUhqUvhF0728,11372 +pip/_vendor/urllib3/_version.py,sha256=_NdMUQaeBvFHAX2z3zAIX2Wum58A6rVtY1f7ByHsQ4g,63 +pip/_vendor/urllib3/connection.py,sha256=6zokyboYYKm9VkyrQvVVLgxMyCZK7n9Vmg_2ZK6pbhc,20076 +pip/_vendor/urllib3/connectionpool.py,sha256=eQ1jWJ2dDdRADuCj9Yx7RCpzY2iM8P32jGHbjYBkAIk,39308 +pip/_vendor/urllib3/exceptions.py,sha256=0Mnno3KHTNfXRfY7638NufOPkUb6mXOm-Lqj-4x2w8A,8217 +pip/_vendor/urllib3/fields.py,sha256=kvLDCg_JmH1lLjUUEY_FLS8UhY7hBvDPuVETbY8mdrM,8579 +pip/_vendor/urllib3/filepost.py,sha256=5b_qqgRHVlL7uLtdAYBzBh-GHmU5AfJVt_2N0XS3PeY,2440 +pip/_vendor/urllib3/poolmanager.py,sha256=0yYas7w5XQzsHvypcd1hstw4uPlNZ56cFwSA4Lg10XI,20954 +pip/_vendor/urllib3/request.py,sha256=ZFSIqX0C6WizixecChZ3_okyu7BEv0lZu1VT0s6h4SM,5985 +pip/_vendor/urllib3/response.py,sha256=hGhGBh7TkEkh_IQg5C1W_xuPNrgIKv5BUXPyE-q0LuE,28203 +pip/_vendor/urllib3/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/urllib3/contrib/_appengine_environ.py,sha256=bDbyOEhW2CKLJcQqAKAyrEHN-aklsyHFKq6vF8ZFsmk,957 +pip/_vendor/urllib3/contrib/appengine.py,sha256=lfzpHFmJiO82shClLEm3QB62SYgHWnjpZOH_2JhU5Tc,11034 +pip/_vendor/urllib3/contrib/ntlmpool.py,sha256=ej9gGvfAb2Gt00lafFp45SIoRz-QwrQ4WChm6gQmAlM,4538 +pip/_vendor/urllib3/contrib/pyopenssl.py,sha256=DD4pInv_3OEEGffEFynBoirc8ldR789sLmGSKukzA0E,16900 +pip/_vendor/urllib3/contrib/securetransport.py,sha256=4qUKo7PUV-vVIqXmr2BD-sH7qplB918jiD5eNsRI9vU,34449 +pip/_vendor/urllib3/contrib/socks.py,sha256=aRi9eWXo9ZEb95XUxef4Z21CFlnnjbEiAo9HOseoMt4,7097 +pip/_vendor/urllib3/contrib/_securetransport/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/urllib3/contrib/_securetransport/bindings.py,sha256=4Xk64qIkPBt09A5q-RIFUuDhNc9mXilVapm7WnYnzRw,17632 +pip/_vendor/urllib3/contrib/_securetransport/low_level.py,sha256=B2JBB2_NRP02xK6DCa1Pa9IuxrPwxzDzZbixQkb7U9M,13922 +pip/_vendor/urllib3/packages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/urllib3/packages/six.py,sha256=1LVW7ljqRirFlfExjwl-v1B7vSAUNTmzGMs-qays2zg,34666 +pip/_vendor/urllib3/packages/backports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pip/_vendor/urllib3/packages/backports/makefile.py,sha256=nbzt3i0agPVP07jqqgjhaYjMmuAi_W5E0EywZivVO8E,1417 +pip/_vendor/urllib3/util/__init__.py,sha256=JEmSmmqqLyaw8P51gUImZh8Gwg9i1zSe-DoqAitn2nc,1155 +pip/_vendor/urllib3/util/connection.py,sha256=5Lx2B1PW29KxBn2T0xkN1CBgRBa3gGVJBKoQoRogEVk,4901 +pip/_vendor/urllib3/util/proxy.py,sha256=zUvPPCJrp6dOF0N4GAVbOcl6o-4uXKSrGiTkkr5vUS4,1605 +pip/_vendor/urllib3/util/queue.py,sha256=nRgX8_eX-_VkvxoX096QWoz8Ps0QHUAExILCY_7PncM,498 +pip/_vendor/urllib3/util/request.py,sha256=NnzaEKQ1Pauw5MFMV6HmgEMHITf0Aua9fQuzi2uZzGc,4123 +pip/_vendor/urllib3/util/response.py,sha256=GJpg3Egi9qaJXRwBh5wv-MNuRWan5BIu40oReoxWP28,3510 +pip/_vendor/urllib3/util/retry.py,sha256=J39USWUTcYYHibxaBy2ZLlXnBZX0N1leKok6cCdtUMw,22048 +pip/_vendor/urllib3/util/ssl_.py,sha256=X4-AqW91aYPhPx6-xbf66yHFQKbqqfC_5Zt4WkLX1Hc,17177 +pip/_vendor/urllib3/util/ssl_match_hostname.py,sha256=w01jCYuwvQ038p9mhc1P1gF8IiTN1qHakThpoukOlbw,5751 +pip/_vendor/urllib3/util/ssltransport.py,sha256=NA-u5rMTrDFDFC8QzRKUEKMG0561hOD4qBTr3Z4pv6E,6895 +pip/_vendor/urllib3/util/timeout.py,sha256=QSbBUNOB9yh6AnDn61SrLQ0hg5oz0I9-uXEG91AJuIg,10003 +pip/_vendor/urllib3/util/url.py,sha256=QVEzcbHipbXyCWwH6R4K4TR-N8T4LM55WEMwNUTBmLE,14047 +pip/_vendor/urllib3/util/wait.py,sha256=3MUKRSAUJDB2tgco7qRUskW0zXGAWYvRRE4Q1_6xlLs,5404 +pip/_vendor/webencodings/__init__.py,sha256=qOBJIuPy_4ByYH6W_bNgJF-qYQ2DoU-dKsDu5yRWCXg,10579 +pip/_vendor/webencodings/labels.py,sha256=4AO_KxTddqGtrL9ns7kAPjb0CcN6xsCIxbK37HY9r3E,8979 +pip/_vendor/webencodings/mklabels.py,sha256=GYIeywnpaLnP0GSic8LFWgd0UVvO_l1Nc6YoF-87R_4,1305 +pip/_vendor/webencodings/tests.py,sha256=OtGLyjhNY1fvkW1GvLJ_FV9ZoqC9Anyjr7q3kxTbzNs,6563 +pip/_vendor/webencodings/x_user_defined.py,sha256=yOqWSdmpytGfUgh_Z6JYgDNhoc-BAHyyeeT15Fr42tM,4307 +pip-22.0.2.dist-info/LICENSE.txt,sha256=Y0MApmnUmurmWxLGxIySTFGkzfPR_whtw0VtyLyqIQQ,1093 +pip-22.0.2.dist-info/METADATA,sha256=Yixa0LKkyzjT2N5JQO5qYDgZcmTs6Z6dg4UbwBNyT2A,4166 +pip-22.0.2.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92 +pip-22.0.2.dist-info/entry_points.txt,sha256=vUvIlB_ga0fFQuWvFEq6uJKftMG_HNuoe4kgXkb5rNY,126 +pip-22.0.2.dist-info/top_level.txt,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +pip-22.0.2.dist-info/RECORD,, diff --git a/venv/lib/python3.10/site-packages/pip-22.0.2.dist-info/WHEEL b/venv/lib/python3.10/site-packages/pip-22.0.2.dist-info/WHEEL new file mode 100644 index 00000000..becc9a66 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip-22.0.2.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.1) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/venv/lib/python3.10/site-packages/pip-22.0.2.dist-info/entry_points.txt b/venv/lib/python3.10/site-packages/pip-22.0.2.dist-info/entry_points.txt new file mode 100644 index 00000000..c4ad5212 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip-22.0.2.dist-info/entry_points.txt @@ -0,0 +1,5 @@ +[console_scripts] +pip = pip._internal.cli.main:main +pip3 = pip._internal.cli.main:main +pip3.10 = pip._internal.cli.main:main + diff --git a/venv/lib/python3.10/site-packages/pip-22.0.2.dist-info/top_level.txt b/venv/lib/python3.10/site-packages/pip-22.0.2.dist-info/top_level.txt new file mode 100644 index 00000000..a1b589e3 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip-22.0.2.dist-info/top_level.txt @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.10/site-packages/pip/__init__.py b/venv/lib/python3.10/site-packages/pip/__init__.py new file mode 100644 index 00000000..8a50472b --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip/__init__.py @@ -0,0 +1,13 @@ +from typing import List, Optional + +__version__ = "22.0.2" + + +def main(args: Optional[List[str]] = None) -> int: + """This is an internal API only meant for use by pip's own console scripts. + + For additional details, see https://github.com/pypa/pip/issues/7498. + """ + from pip._internal.utils.entrypoints import _wrapper + + return _wrapper(args) diff --git a/venv/lib/python3.10/site-packages/pip/__main__.py b/venv/lib/python3.10/site-packages/pip/__main__.py new file mode 100644 index 00000000..fe34a7b7 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip/__main__.py @@ -0,0 +1,31 @@ +import os +import sys +import warnings + +# Remove '' and current working directory from the first entry +# of sys.path, if present to avoid using current directory +# in pip commands check, freeze, install, list and show, +# when invoked as python -m pip +if sys.path[0] in ("", os.getcwd()): + sys.path.pop(0) + +# If we are running from a wheel, add the wheel to sys.path +# This allows the usage python pip-*.whl/pip install pip-*.whl +if __package__ == "": + # __file__ is pip-*.whl/pip/__main__.py + # first dirname call strips of '/__main__.py', second strips off '/pip' + # Resulting path is the name of the wheel itself + # Add that to sys.path so we can import pip + path = os.path.dirname(os.path.dirname(__file__)) + sys.path.insert(0, path) + +if __name__ == "__main__": + # Work around the error reported in #9540, pending a proper fix. + # Note: It is essential the warning filter is set *before* importing + # pip, as the deprecation happens at import time, not runtime. + warnings.filterwarnings( + "ignore", category=DeprecationWarning, module=".*packaging\\.version" + ) + from pip._internal.cli.main import main as _main + + sys.exit(_main()) diff --git a/venv/lib/python3.10/site-packages/pip/_internal/__init__.py b/venv/lib/python3.10/site-packages/pip/_internal/__init__.py new file mode 100644 index 00000000..6afb5c62 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip/_internal/__init__.py @@ -0,0 +1,19 @@ +from typing import List, Optional + +import pip._internal.utils.inject_securetransport # noqa +from pip._internal.utils import _log + +# init_logging() must be called before any call to logging.getLogger() +# which happens at import of most modules. +_log.init_logging() + + +def main(args: (Optional[List[str]]) = None) -> int: + """This is preserved for old console scripts that may still be referencing + it. + + For additional details, see https://github.com/pypa/pip/issues/7498. + """ + from pip._internal.utils.entrypoints import _wrapper + + return _wrapper(args) diff --git a/venv/lib/python3.10/site-packages/pip/_internal/build_env.py b/venv/lib/python3.10/site-packages/pip/_internal/build_env.py new file mode 100644 index 00000000..daeb7fbc --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip/_internal/build_env.py @@ -0,0 +1,296 @@ +"""Build Environment used for isolation during sdist building +""" + +import contextlib +import logging +import os +import pathlib +import sys +import textwrap +import zipfile +from collections import OrderedDict +from sysconfig import get_paths +from types import TracebackType +from typing import TYPE_CHECKING, Iterable, Iterator, List, Optional, Set, Tuple, Type + +from pip._vendor.certifi import where +from pip._vendor.packaging.requirements import Requirement +from pip._vendor.packaging.version import Version + +from pip import __file__ as pip_location +from pip._internal.cli.spinners import open_spinner +from pip._internal.locations import get_platlib, get_prefixed_libs, get_purelib +from pip._internal.metadata import get_environment +from pip._internal.utils.subprocess import call_subprocess +from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds + +if TYPE_CHECKING: + from pip._internal.index.package_finder import PackageFinder + +logger = logging.getLogger(__name__) + + +class _Prefix: + def __init__(self, path: str) -> None: + self.path = path + self.setup = False + self.bin_dir = get_paths( + "nt" if os.name == "nt" else "posix_prefix", + vars={"base": path, "platbase": path}, + )["scripts"] + self.lib_dirs = get_prefixed_libs(path) + + +@contextlib.contextmanager +def _create_standalone_pip() -> Iterator[str]: + """Create a "standalone pip" zip file. + + The zip file's content is identical to the currently-running pip. + It will be used to install requirements into the build environment. + """ + source = pathlib.Path(pip_location).resolve().parent + + # Return the current instance if `source` is not a directory. We can't build + # a zip from this, and it likely means the instance is already standalone. + if not source.is_dir(): + yield str(source) + return + + with TempDirectory(kind="standalone-pip") as tmp_dir: + pip_zip = os.path.join(tmp_dir.path, "__env_pip__.zip") + kwargs = {} + if sys.version_info >= (3, 8): + kwargs["strict_timestamps"] = False + with zipfile.ZipFile(pip_zip, "w", **kwargs) as zf: + for child in source.rglob("*"): + zf.write(child, child.relative_to(source.parent).as_posix()) + yield os.path.join(pip_zip, "pip") + + +class BuildEnvironment: + """Creates and manages an isolated environment to install build deps""" + + def __init__(self) -> None: + temp_dir = TempDirectory(kind=tempdir_kinds.BUILD_ENV, globally_managed=True) + + self._prefixes = OrderedDict( + (name, _Prefix(os.path.join(temp_dir.path, name))) + for name in ("normal", "overlay") + ) + + self._bin_dirs: List[str] = [] + self._lib_dirs: List[str] = [] + for prefix in reversed(list(self._prefixes.values())): + self._bin_dirs.append(prefix.bin_dir) + self._lib_dirs.extend(prefix.lib_dirs) + + # Customize site to: + # - ensure .pth files are honored + # - prevent access to system site packages + system_sites = { + os.path.normcase(site) for site in (get_purelib(), get_platlib()) + } + self._site_dir = os.path.join(temp_dir.path, "site") + if not os.path.exists(self._site_dir): + os.mkdir(self._site_dir) + with open( + os.path.join(self._site_dir, "sitecustomize.py"), "w", encoding="utf-8" + ) as fp: + fp.write( + textwrap.dedent( + """ + import os, site, sys + + # First, drop system-sites related paths. + original_sys_path = sys.path[:] + known_paths = set() + for path in {system_sites!r}: + site.addsitedir(path, known_paths=known_paths) + system_paths = set( + os.path.normcase(path) + for path in sys.path[len(original_sys_path):] + ) + original_sys_path = [ + path for path in original_sys_path + if os.path.normcase(path) not in system_paths + ] + sys.path = original_sys_path + + # Second, add lib directories. + # ensuring .pth file are processed. + for path in {lib_dirs!r}: + assert not path in sys.path + site.addsitedir(path) + """ + ).format(system_sites=system_sites, lib_dirs=self._lib_dirs) + ) + + def __enter__(self) -> None: + self._save_env = { + name: os.environ.get(name, None) + for name in ("PATH", "PYTHONNOUSERSITE", "PYTHONPATH") + } + + path = self._bin_dirs[:] + old_path = self._save_env["PATH"] + if old_path: + path.extend(old_path.split(os.pathsep)) + + pythonpath = [self._site_dir] + + os.environ.update( + { + "PATH": os.pathsep.join(path), + "PYTHONNOUSERSITE": "1", + "PYTHONPATH": os.pathsep.join(pythonpath), + } + ) + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + for varname, old_value in self._save_env.items(): + if old_value is None: + os.environ.pop(varname, None) + else: + os.environ[varname] = old_value + + def check_requirements( + self, reqs: Iterable[str] + ) -> Tuple[Set[Tuple[str, str]], Set[str]]: + """Return 2 sets: + - conflicting requirements: set of (installed, wanted) reqs tuples + - missing requirements: set of reqs + """ + missing = set() + conflicting = set() + if reqs: + env = get_environment(self._lib_dirs) + for req_str in reqs: + req = Requirement(req_str) + dist = env.get_distribution(req.name) + if not dist: + missing.add(req_str) + continue + if isinstance(dist.version, Version): + installed_req_str = f"{req.name}=={dist.version}" + else: + installed_req_str = f"{req.name}==={dist.version}" + if dist.version not in req.specifier: + conflicting.add((installed_req_str, req_str)) + # FIXME: Consider direct URL? + return conflicting, missing + + def install_requirements( + self, + finder: "PackageFinder", + requirements: Iterable[str], + prefix_as_string: str, + *, + kind: str, + ) -> None: + prefix = self._prefixes[prefix_as_string] + assert not prefix.setup + prefix.setup = True + if not requirements: + return + with contextlib.ExitStack() as ctx: + pip_runnable = ctx.enter_context(_create_standalone_pip()) + self._install_requirements( + pip_runnable, + finder, + requirements, + prefix, + kind=kind, + ) + + @staticmethod + def _install_requirements( + pip_runnable: str, + finder: "PackageFinder", + requirements: Iterable[str], + prefix: _Prefix, + *, + kind: str, + ) -> None: + args: List[str] = [ + sys.executable, + pip_runnable, + "install", + "--ignore-installed", + "--no-user", + "--prefix", + prefix.path, + "--no-warn-script-location", + ] + if logger.getEffectiveLevel() <= logging.DEBUG: + args.append("-v") + for format_control in ("no_binary", "only_binary"): + formats = getattr(finder.format_control, format_control) + args.extend( + ( + "--" + format_control.replace("_", "-"), + ",".join(sorted(formats or {":none:"})), + ) + ) + + index_urls = finder.index_urls + if index_urls: + args.extend(["-i", index_urls[0]]) + for extra_index in index_urls[1:]: + args.extend(["--extra-index-url", extra_index]) + else: + args.append("--no-index") + for link in finder.find_links: + args.extend(["--find-links", link]) + + for host in finder.trusted_hosts: + args.extend(["--trusted-host", host]) + if finder.allow_all_prereleases: + args.append("--pre") + if finder.prefer_binary: + args.append("--prefer-binary") + args.append("--") + args.extend(requirements) + extra_environ = {"_PIP_STANDALONE_CERT": where()} + with open_spinner(f"Installing {kind}") as spinner: + call_subprocess( + args, + command_desc=f"pip subprocess to install {kind}", + spinner=spinner, + extra_environ=extra_environ, + ) + + +class NoOpBuildEnvironment(BuildEnvironment): + """A no-op drop-in replacement for BuildEnvironment""" + + def __init__(self) -> None: + pass + + def __enter__(self) -> None: + pass + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + pass + + def cleanup(self) -> None: + pass + + def install_requirements( + self, + finder: "PackageFinder", + requirements: Iterable[str], + prefix_as_string: str, + *, + kind: str, + ) -> None: + raise NotImplementedError() diff --git a/venv/lib/python3.10/site-packages/pip/_internal/cache.py b/venv/lib/python3.10/site-packages/pip/_internal/cache.py new file mode 100644 index 00000000..1d6df220 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip/_internal/cache.py @@ -0,0 +1,264 @@ +"""Cache Management +""" + +import hashlib +import json +import logging +import os +from typing import Any, Dict, List, Optional, Set + +from pip._vendor.packaging.tags import Tag, interpreter_name, interpreter_version +from pip._vendor.packaging.utils import canonicalize_name + +from pip._internal.exceptions import InvalidWheelFilename +from pip._internal.models.format_control import FormatControl +from pip._internal.models.link import Link +from pip._internal.models.wheel import Wheel +from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds +from pip._internal.utils.urls import path_to_url + +logger = logging.getLogger(__name__) + + +def _hash_dict(d: Dict[str, str]) -> str: + """Return a stable sha224 of a dictionary.""" + s = json.dumps(d, sort_keys=True, separators=(",", ":"), ensure_ascii=True) + return hashlib.sha224(s.encode("ascii")).hexdigest() + + +class Cache: + """An abstract class - provides cache directories for data from links + + + :param cache_dir: The root of the cache. + :param format_control: An object of FormatControl class to limit + binaries being read from the cache. + :param allowed_formats: which formats of files the cache should store. + ('binary' and 'source' are the only allowed values) + """ + + def __init__( + self, cache_dir: str, format_control: FormatControl, allowed_formats: Set[str] + ) -> None: + super().__init__() + assert not cache_dir or os.path.isabs(cache_dir) + self.cache_dir = cache_dir or None + self.format_control = format_control + self.allowed_formats = allowed_formats + + _valid_formats = {"source", "binary"} + assert self.allowed_formats.union(_valid_formats) == _valid_formats + + def _get_cache_path_parts(self, link: Link) -> List[str]: + """Get parts of part that must be os.path.joined with cache_dir""" + + # We want to generate an url to use as our cache key, we don't want to + # just re-use the URL because it might have other items in the fragment + # and we don't care about those. + key_parts = {"url": link.url_without_fragment} + if link.hash_name is not None and link.hash is not None: + key_parts[link.hash_name] = link.hash + if link.subdirectory_fragment: + key_parts["subdirectory"] = link.subdirectory_fragment + + # Include interpreter name, major and minor version in cache key + # to cope with ill-behaved sdists that build a different wheel + # depending on the python version their setup.py is being run on, + # and don't encode the difference in compatibility tags. + # https://github.com/pypa/pip/issues/7296 + key_parts["interpreter_name"] = interpreter_name() + key_parts["interpreter_version"] = interpreter_version() + + # Encode our key url with sha224, we'll use this because it has similar + # security properties to sha256, but with a shorter total output (and + # thus less secure). However the differences don't make a lot of + # difference for our use case here. + hashed = _hash_dict(key_parts) + + # We want to nest the directories some to prevent having a ton of top + # level directories where we might run out of sub directories on some + # FS. + parts = [hashed[:2], hashed[2:4], hashed[4:6], hashed[6:]] + + return parts + + def _get_candidates(self, link: Link, canonical_package_name: str) -> List[Any]: + can_not_cache = not self.cache_dir or not canonical_package_name or not link + if can_not_cache: + return [] + + formats = self.format_control.get_allowed_formats(canonical_package_name) + if not self.allowed_formats.intersection(formats): + return [] + + candidates = [] + path = self.get_path_for_link(link) + if os.path.isdir(path): + for candidate in os.listdir(path): + candidates.append((candidate, path)) + return candidates + + def get_path_for_link(self, link: Link) -> str: + """Return a directory to store cached items in for link.""" + raise NotImplementedError() + + def get( + self, + link: Link, + package_name: Optional[str], + supported_tags: List[Tag], + ) -> Link: + """Returns a link to a cached item if it exists, otherwise returns the + passed link. + """ + raise NotImplementedError() + + +class SimpleWheelCache(Cache): + """A cache of wheels for future installs.""" + + def __init__(self, cache_dir: str, format_control: FormatControl) -> None: + super().__init__(cache_dir, format_control, {"binary"}) + + def get_path_for_link(self, link: Link) -> str: + """Return a directory to store cached wheels for link + + Because there are M wheels for any one sdist, we provide a directory + to cache them in, and then consult that directory when looking up + cache hits. + + We only insert things into the cache if they have plausible version + numbers, so that we don't contaminate the cache with things that were + not unique. E.g. ./package might have dozens of installs done for it + and build a version of 0.0...and if we built and cached a wheel, we'd + end up using the same wheel even if the source has been edited. + + :param link: The link of the sdist for which this will cache wheels. + """ + parts = self._get_cache_path_parts(link) + assert self.cache_dir + # Store wheels within the root cache_dir + return os.path.join(self.cache_dir, "wheels", *parts) + + def get( + self, + link: Link, + package_name: Optional[str], + supported_tags: List[Tag], + ) -> Link: + candidates = [] + + if not package_name: + return link + + canonical_package_name = canonicalize_name(package_name) + for wheel_name, wheel_dir in self._get_candidates(link, canonical_package_name): + try: + wheel = Wheel(wheel_name) + except InvalidWheelFilename: + continue + if canonicalize_name(wheel.name) != canonical_package_name: + logger.debug( + "Ignoring cached wheel %s for %s as it " + "does not match the expected distribution name %s.", + wheel_name, + link, + package_name, + ) + continue + if not wheel.supported(supported_tags): + # Built for a different python/arch/etc + continue + candidates.append( + ( + wheel.support_index_min(supported_tags), + wheel_name, + wheel_dir, + ) + ) + + if not candidates: + return link + + _, wheel_name, wheel_dir = min(candidates) + return Link(path_to_url(os.path.join(wheel_dir, wheel_name))) + + +class EphemWheelCache(SimpleWheelCache): + """A SimpleWheelCache that creates it's own temporary cache directory""" + + def __init__(self, format_control: FormatControl) -> None: + self._temp_dir = TempDirectory( + kind=tempdir_kinds.EPHEM_WHEEL_CACHE, + globally_managed=True, + ) + + super().__init__(self._temp_dir.path, format_control) + + +class CacheEntry: + def __init__( + self, + link: Link, + persistent: bool, + ): + self.link = link + self.persistent = persistent + + +class WheelCache(Cache): + """Wraps EphemWheelCache and SimpleWheelCache into a single Cache + + This Cache allows for gracefully degradation, using the ephem wheel cache + when a certain link is not found in the simple wheel cache first. + """ + + def __init__(self, cache_dir: str, format_control: FormatControl) -> None: + super().__init__(cache_dir, format_control, {"binary"}) + self._wheel_cache = SimpleWheelCache(cache_dir, format_control) + self._ephem_cache = EphemWheelCache(format_control) + + def get_path_for_link(self, link: Link) -> str: + return self._wheel_cache.get_path_for_link(link) + + def get_ephem_path_for_link(self, link: Link) -> str: + return self._ephem_cache.get_path_for_link(link) + + def get( + self, + link: Link, + package_name: Optional[str], + supported_tags: List[Tag], + ) -> Link: + cache_entry = self.get_cache_entry(link, package_name, supported_tags) + if cache_entry is None: + return link + return cache_entry.link + + def get_cache_entry( + self, + link: Link, + package_name: Optional[str], + supported_tags: List[Tag], + ) -> Optional[CacheEntry]: + """Returns a CacheEntry with a link to a cached item if it exists or + None. The cache entry indicates if the item was found in the persistent + or ephemeral cache. + """ + retval = self._wheel_cache.get( + link=link, + package_name=package_name, + supported_tags=supported_tags, + ) + if retval is not link: + return CacheEntry(retval, persistent=True) + + retval = self._ephem_cache.get( + link=link, + package_name=package_name, + supported_tags=supported_tags, + ) + if retval is not link: + return CacheEntry(retval, persistent=False) + + return None diff --git a/venv/lib/python3.10/site-packages/pip/_internal/cli/__init__.py b/venv/lib/python3.10/site-packages/pip/_internal/cli/__init__.py new file mode 100644 index 00000000..e589bb91 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip/_internal/cli/__init__.py @@ -0,0 +1,4 @@ +"""Subpackage containing all of pip's command line interface related code +""" + +# This file intentionally does not import submodules diff --git a/venv/lib/python3.10/site-packages/pip/_internal/cli/autocompletion.py b/venv/lib/python3.10/site-packages/pip/_internal/cli/autocompletion.py new file mode 100644 index 00000000..226fe84d --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip/_internal/cli/autocompletion.py @@ -0,0 +1,171 @@ +"""Logic that powers autocompletion installed by ``pip completion``. +""" + +import optparse +import os +import sys +from itertools import chain +from typing import Any, Iterable, List, Optional + +from pip._internal.cli.main_parser import create_main_parser +from pip._internal.commands import commands_dict, create_command +from pip._internal.metadata import get_default_environment + + +def autocomplete() -> None: + """Entry Point for completion of main and subcommand options.""" + # Don't complete if user hasn't sourced bash_completion file. + if "PIP_AUTO_COMPLETE" not in os.environ: + return + cwords = os.environ["COMP_WORDS"].split()[1:] + cword = int(os.environ["COMP_CWORD"]) + try: + current = cwords[cword - 1] + except IndexError: + current = "" + + parser = create_main_parser() + subcommands = list(commands_dict) + options = [] + + # subcommand + subcommand_name: Optional[str] = None + for word in cwords: + if word in subcommands: + subcommand_name = word + break + # subcommand options + if subcommand_name is not None: + # special case: 'help' subcommand has no options + if subcommand_name == "help": + sys.exit(1) + # special case: list locally installed dists for show and uninstall + should_list_installed = not current.startswith("-") and subcommand_name in [ + "show", + "uninstall", + ] + if should_list_installed: + env = get_default_environment() + lc = current.lower() + installed = [ + dist.canonical_name + for dist in env.iter_installed_distributions(local_only=True) + if dist.canonical_name.startswith(lc) + and dist.canonical_name not in cwords[1:] + ] + # if there are no dists installed, fall back to option completion + if installed: + for dist in installed: + print(dist) + sys.exit(1) + + should_list_installables = ( + not current.startswith("-") and subcommand_name == "install" + ) + if should_list_installables: + for path in auto_complete_paths(current, "path"): + print(path) + sys.exit(1) + + subcommand = create_command(subcommand_name) + + for opt in subcommand.parser.option_list_all: + if opt.help != optparse.SUPPRESS_HELP: + for opt_str in opt._long_opts + opt._short_opts: + options.append((opt_str, opt.nargs)) + + # filter out previously specified options from available options + prev_opts = [x.split("=")[0] for x in cwords[1 : cword - 1]] + options = [(x, v) for (x, v) in options if x not in prev_opts] + # filter options by current input + options = [(k, v) for k, v in options if k.startswith(current)] + # get completion type given cwords and available subcommand options + completion_type = get_path_completion_type( + cwords, + cword, + subcommand.parser.option_list_all, + ) + # get completion files and directories if ``completion_type`` is + # ````, ``

`` or ```` + if completion_type: + paths = auto_complete_paths(current, completion_type) + options = [(path, 0) for path in paths] + for option in options: + opt_label = option[0] + # append '=' to options which require args + if option[1] and option[0][:2] == "--": + opt_label += "=" + print(opt_label) + else: + # show main parser options only when necessary + + opts = [i.option_list for i in parser.option_groups] + opts.append(parser.option_list) + flattened_opts = chain.from_iterable(opts) + if current.startswith("-"): + for opt in flattened_opts: + if opt.help != optparse.SUPPRESS_HELP: + subcommands += opt._long_opts + opt._short_opts + else: + # get completion type given cwords and all available options + completion_type = get_path_completion_type(cwords, cword, flattened_opts) + if completion_type: + subcommands = list(auto_complete_paths(current, completion_type)) + + print(" ".join([x for x in subcommands if x.startswith(current)])) + sys.exit(1) + + +def get_path_completion_type( + cwords: List[str], cword: int, opts: Iterable[Any] +) -> Optional[str]: + """Get the type of path completion (``file``, ``dir``, ``path`` or None) + + :param cwords: same as the environmental variable ``COMP_WORDS`` + :param cword: same as the environmental variable ``COMP_CWORD`` + :param opts: The available options to check + :return: path completion type (``file``, ``dir``, ``path`` or None) + """ + if cword < 2 or not cwords[cword - 2].startswith("-"): + return None + for opt in opts: + if opt.help == optparse.SUPPRESS_HELP: + continue + for o in str(opt).split("/"): + if cwords[cword - 2].split("=")[0] == o: + if not opt.metavar or any( + x in ("path", "file", "dir") for x in opt.metavar.split("/") + ): + return opt.metavar + return None + + +def auto_complete_paths(current: str, completion_type: str) -> Iterable[str]: + """If ``completion_type`` is ``file`` or ``path``, list all regular files + and directories starting with ``current``; otherwise only list directories + starting with ``current``. + + :param current: The word to be completed + :param completion_type: path completion type(``file``, ``path`` or ``dir``) + :return: A generator of regular files and/or directories + """ + directory, filename = os.path.split(current) + current_path = os.path.abspath(directory) + # Don't complete paths if they can't be accessed + if not os.access(current_path, os.R_OK): + return + filename = os.path.normcase(filename) + # list all files that start with ``filename`` + file_list = ( + x for x in os.listdir(current_path) if os.path.normcase(x).startswith(filename) + ) + for f in file_list: + opt = os.path.join(current_path, f) + comp_file = os.path.normcase(os.path.join(directory, f)) + # complete regular files when there is not ```` after option + # complete directories when there is ````, ```` or + # ````after option + if completion_type != "dir" and os.path.isfile(opt): + yield comp_file + elif os.path.isdir(opt): + yield os.path.join(comp_file, "") diff --git a/venv/lib/python3.10/site-packages/pip/_internal/cli/base_command.py b/venv/lib/python3.10/site-packages/pip/_internal/cli/base_command.py new file mode 100644 index 00000000..f5dc0fec --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip/_internal/cli/base_command.py @@ -0,0 +1,220 @@ +"""Base Command class, and related routines""" + +import functools +import logging +import logging.config +import optparse +import os +import sys +import traceback +from optparse import Values +from typing import Any, Callable, List, Optional, Tuple + +from pip._internal.cli import cmdoptions +from pip._internal.cli.command_context import CommandContextMixIn +from pip._internal.cli.parser import ConfigOptionParser, UpdatingDefaultsHelpFormatter +from pip._internal.cli.status_codes import ( + ERROR, + PREVIOUS_BUILD_DIR_ERROR, + UNKNOWN_ERROR, + VIRTUALENV_NOT_FOUND, +) +from pip._internal.exceptions import ( + BadCommand, + CommandError, + DiagnosticPipError, + InstallationError, + NetworkConnectionError, + PreviousBuildDirError, + UninstallationError, +) +from pip._internal.utils.filesystem import check_path_owner +from pip._internal.utils.logging import BrokenStdoutLoggingError, setup_logging +from pip._internal.utils.misc import get_prog, normalize_path +from pip._internal.utils.temp_dir import TempDirectoryTypeRegistry as TempDirRegistry +from pip._internal.utils.temp_dir import global_tempdir_manager, tempdir_registry +from pip._internal.utils.virtualenv import running_under_virtualenv + +__all__ = ["Command"] + +logger = logging.getLogger(__name__) + + +class Command(CommandContextMixIn): + usage: str = "" + ignore_require_venv: bool = False + + def __init__(self, name: str, summary: str, isolated: bool = False) -> None: + super().__init__() + + self.name = name + self.summary = summary + self.parser = ConfigOptionParser( + usage=self.usage, + prog=f"{get_prog()} {name}", + formatter=UpdatingDefaultsHelpFormatter(), + add_help_option=False, + name=name, + description=self.__doc__, + isolated=isolated, + ) + + self.tempdir_registry: Optional[TempDirRegistry] = None + + # Commands should add options to this option group + optgroup_name = f"{self.name.capitalize()} Options" + self.cmd_opts = optparse.OptionGroup(self.parser, optgroup_name) + + # Add the general options + gen_opts = cmdoptions.make_option_group( + cmdoptions.general_group, + self.parser, + ) + self.parser.add_option_group(gen_opts) + + self.add_options() + + def add_options(self) -> None: + pass + + def handle_pip_version_check(self, options: Values) -> None: + """ + This is a no-op so that commands by default do not do the pip version + check. + """ + # Make sure we do the pip version check if the index_group options + # are present. + assert not hasattr(options, "no_index") + + def run(self, options: Values, args: List[str]) -> int: + raise NotImplementedError + + def parse_args(self, args: List[str]) -> Tuple[Values, List[str]]: + # factored out for testability + return self.parser.parse_args(args) + + def main(self, args: List[str]) -> int: + try: + with self.main_context(): + return self._main(args) + finally: + logging.shutdown() + + def _main(self, args: List[str]) -> int: + # We must initialize this before the tempdir manager, otherwise the + # configuration would not be accessible by the time we clean up the + # tempdir manager. + self.tempdir_registry = self.enter_context(tempdir_registry()) + # Intentionally set as early as possible so globally-managed temporary + # directories are available to the rest of the code. + self.enter_context(global_tempdir_manager()) + + options, args = self.parse_args(args) + + # Set verbosity so that it can be used elsewhere. + self.verbosity = options.verbose - options.quiet + + level_number = setup_logging( + verbosity=self.verbosity, + no_color=options.no_color, + user_log_file=options.log, + ) + + # TODO: Try to get these passing down from the command? + # without resorting to os.environ to hold these. + # This also affects isolated builds and it should. + + if options.no_input: + os.environ["PIP_NO_INPUT"] = "1" + + if options.exists_action: + os.environ["PIP_EXISTS_ACTION"] = " ".join(options.exists_action) + + if options.require_venv and not self.ignore_require_venv: + # If a venv is required check if it can really be found + if not running_under_virtualenv(): + logger.critical("Could not find an activated virtualenv (required).") + sys.exit(VIRTUALENV_NOT_FOUND) + + if options.cache_dir: + options.cache_dir = normalize_path(options.cache_dir) + if not check_path_owner(options.cache_dir): + logger.warning( + "The directory '%s' or its parent directory is not owned " + "or is not writable by the current user. The cache " + "has been disabled. Check the permissions and owner of " + "that directory. If executing pip with sudo, you should " + "use sudo's -H flag.", + options.cache_dir, + ) + options.cache_dir = None + + if "2020-resolver" in options.features_enabled: + logger.warning( + "--use-feature=2020-resolver no longer has any effect, " + "since it is now the default dependency resolver in pip. " + "This will become an error in pip 21.0." + ) + + def intercepts_unhandled_exc( + run_func: Callable[..., int] + ) -> Callable[..., int]: + @functools.wraps(run_func) + def exc_logging_wrapper(*args: Any) -> int: + try: + status = run_func(*args) + assert isinstance(status, int) + return status + except DiagnosticPipError as exc: + logger.error("[present-diagnostic] %s", exc) + logger.debug("Exception information:", exc_info=True) + + return ERROR + except PreviousBuildDirError as exc: + logger.critical(str(exc)) + logger.debug("Exception information:", exc_info=True) + + return PREVIOUS_BUILD_DIR_ERROR + except ( + InstallationError, + UninstallationError, + BadCommand, + NetworkConnectionError, + ) as exc: + logger.critical(str(exc)) + logger.debug("Exception information:", exc_info=True) + + return ERROR + except CommandError as exc: + logger.critical("%s", exc) + logger.debug("Exception information:", exc_info=True) + + return ERROR + except BrokenStdoutLoggingError: + # Bypass our logger and write any remaining messages to + # stderr because stdout no longer works. + print("ERROR: Pipe to stdout was broken", file=sys.stderr) + if level_number <= logging.DEBUG: + traceback.print_exc(file=sys.stderr) + + return ERROR + except KeyboardInterrupt: + logger.critical("Operation cancelled by user") + logger.debug("Exception information:", exc_info=True) + + return ERROR + except BaseException: + logger.critical("Exception:", exc_info=True) + + return UNKNOWN_ERROR + + return exc_logging_wrapper + + try: + if not options.debug_mode: + run = intercepts_unhandled_exc(self.run) + else: + run = self.run + return run(options, args) + finally: + self.handle_pip_version_check(options) diff --git a/venv/lib/python3.10/site-packages/pip/_internal/cli/cmdoptions.py b/venv/lib/python3.10/site-packages/pip/_internal/cli/cmdoptions.py new file mode 100644 index 00000000..b7e54f7c --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip/_internal/cli/cmdoptions.py @@ -0,0 +1,1018 @@ +""" +shared options and groups + +The principle here is to define options once, but *not* instantiate them +globally. One reason being that options with action='append' can carry state +between parses. pip parses general options twice internally, and shouldn't +pass on state. To be consistent, all options will follow this design. +""" + +# The following comment should be removed at some point in the future. +# mypy: strict-optional=False + +import logging +import os +import textwrap +from functools import partial +from optparse import SUPPRESS_HELP, Option, OptionGroup, OptionParser, Values +from textwrap import dedent +from typing import Any, Callable, Dict, Optional, Tuple + +from pip._vendor.packaging.utils import canonicalize_name + +from pip._internal.cli.parser import ConfigOptionParser +from pip._internal.cli.progress_bars import BAR_TYPES +from pip._internal.exceptions import CommandError +from pip._internal.locations import USER_CACHE_DIR, get_src_prefix +from pip._internal.models.format_control import FormatControl +from pip._internal.models.index import PyPI +from pip._internal.models.target_python import TargetPython +from pip._internal.utils.hashes import STRONG_HASHES +from pip._internal.utils.misc import strtobool + +logger = logging.getLogger(__name__) + + +def raise_option_error(parser: OptionParser, option: Option, msg: str) -> None: + """ + Raise an option parsing error using parser.error(). + + Args: + parser: an OptionParser instance. + option: an Option instance. + msg: the error text. + """ + msg = f"{option} error: {msg}" + msg = textwrap.fill(" ".join(msg.split())) + parser.error(msg) + + +def make_option_group(group: Dict[str, Any], parser: ConfigOptionParser) -> OptionGroup: + """ + Return an OptionGroup object + group -- assumed to be dict with 'name' and 'options' keys + parser -- an optparse Parser + """ + option_group = OptionGroup(parser, group["name"]) + for option in group["options"]: + option_group.add_option(option()) + return option_group + + +def check_install_build_global( + options: Values, check_options: Optional[Values] = None +) -> None: + """Disable wheels if per-setup.py call options are set. + + :param options: The OptionParser options to update. + :param check_options: The options to check, if not supplied defaults to + options. + """ + if check_options is None: + check_options = options + + def getname(n: str) -> Optional[Any]: + return getattr(check_options, n, None) + + names = ["build_options", "global_options", "install_options"] + if any(map(getname, names)): + control = options.format_control + control.disallow_binaries() + logger.warning( + "Disabling all use of wheels due to the use of --build-option " + "/ --global-option / --install-option.", + ) + + +def check_dist_restriction(options: Values, check_target: bool = False) -> None: + """Function for determining if custom platform options are allowed. + + :param options: The OptionParser options. + :param check_target: Whether or not to check if --target is being used. + """ + dist_restriction_set = any( + [ + options.python_version, + options.platforms, + options.abis, + options.implementation, + ] + ) + + binary_only = FormatControl(set(), {":all:"}) + sdist_dependencies_allowed = ( + options.format_control != binary_only and not options.ignore_dependencies + ) + + # Installations or downloads using dist restrictions must not combine + # source distributions and dist-specific wheels, as they are not + # guaranteed to be locally compatible. + if dist_restriction_set and sdist_dependencies_allowed: + raise CommandError( + "When restricting platform and interpreter constraints using " + "--python-version, --platform, --abi, or --implementation, " + "either --no-deps must be set, or --only-binary=:all: must be " + "set and --no-binary must not be set (or must be set to " + ":none:)." + ) + + if check_target: + if dist_restriction_set and not options.target_dir: + raise CommandError( + "Can not use any platform or abi specific options unless " + "installing via '--target'" + ) + + +def _path_option_check(option: Option, opt: str, value: str) -> str: + return os.path.expanduser(value) + + +def _package_name_option_check(option: Option, opt: str, value: str) -> str: + return canonicalize_name(value) + + +class PipOption(Option): + TYPES = Option.TYPES + ("path", "package_name") + TYPE_CHECKER = Option.TYPE_CHECKER.copy() + TYPE_CHECKER["package_name"] = _package_name_option_check + TYPE_CHECKER["path"] = _path_option_check + + +########### +# options # +########### + +help_: Callable[..., Option] = partial( + Option, + "-h", + "--help", + dest="help", + action="help", + help="Show help.", +) + +debug_mode: Callable[..., Option] = partial( + Option, + "--debug", + dest="debug_mode", + action="store_true", + default=False, + help=( + "Let unhandled exceptions propagate outside the main subroutine, " + "instead of logging them to stderr." + ), +) + +isolated_mode: Callable[..., Option] = partial( + Option, + "--isolated", + dest="isolated_mode", + action="store_true", + default=False, + help=( + "Run pip in an isolated mode, ignoring environment variables and user " + "configuration." + ), +) + +require_virtualenv: Callable[..., Option] = partial( + Option, + "--require-virtualenv", + "--require-venv", + dest="require_venv", + action="store_true", + default=False, + help=( + "Allow pip to only run in a virtual environment; " + "exit with an error otherwise." + ), +) + +verbose: Callable[..., Option] = partial( + Option, + "-v", + "--verbose", + dest="verbose", + action="count", + default=0, + help="Give more output. Option is additive, and can be used up to 3 times.", +) + +no_color: Callable[..., Option] = partial( + Option, + "--no-color", + dest="no_color", + action="store_true", + default=False, + help="Suppress colored output.", +) + +version: Callable[..., Option] = partial( + Option, + "-V", + "--version", + dest="version", + action="store_true", + help="Show version and exit.", +) + +quiet: Callable[..., Option] = partial( + Option, + "-q", + "--quiet", + dest="quiet", + action="count", + default=0, + help=( + "Give less output. Option is additive, and can be used up to 3" + " times (corresponding to WARNING, ERROR, and CRITICAL logging" + " levels)." + ), +) + +progress_bar: Callable[..., Option] = partial( + Option, + "--progress-bar", + dest="progress_bar", + type="choice", + choices=list(BAR_TYPES.keys()), + default="on", + help=( + "Specify type of progress to be displayed [" + + "|".join(BAR_TYPES.keys()) + + "] (default: %default)" + ), +) + +log: Callable[..., Option] = partial( + PipOption, + "--log", + "--log-file", + "--local-log", + dest="log", + metavar="path", + type="path", + help="Path to a verbose appending log.", +) + +no_input: Callable[..., Option] = partial( + Option, + # Don't ask for input + "--no-input", + dest="no_input", + action="store_true", + default=False, + help="Disable prompting for input.", +) + +proxy: Callable[..., Option] = partial( + Option, + "--proxy", + dest="proxy", + type="str", + default="", + help="Specify a proxy in the form [user:passwd@]proxy.server:port.", +) + +retries: Callable[..., Option] = partial( + Option, + "--retries", + dest="retries", + type="int", + default=5, + help="Maximum number of retries each connection should attempt " + "(default %default times).", +) + +timeout: Callable[..., Option] = partial( + Option, + "--timeout", + "--default-timeout", + metavar="sec", + dest="timeout", + type="float", + default=15, + help="Set the socket timeout (default %default seconds).", +) + + +def exists_action() -> Option: + return Option( + # Option when path already exist + "--exists-action", + dest="exists_action", + type="choice", + choices=["s", "i", "w", "b", "a"], + default=[], + action="append", + metavar="action", + help="Default action when a path already exists: " + "(s)witch, (i)gnore, (w)ipe, (b)ackup, (a)bort.", + ) + + +cert: Callable[..., Option] = partial( + PipOption, + "--cert", + dest="cert", + type="path", + metavar="path", + help=( + "Path to PEM-encoded CA certificate bundle. " + "If provided, overrides the default. " + "See 'SSL Certificate Verification' in pip documentation " + "for more information." + ), +) + +client_cert: Callable[..., Option] = partial( + PipOption, + "--client-cert", + dest="client_cert", + type="path", + default=None, + metavar="path", + help="Path to SSL client certificate, a single file containing the " + "private key and the certificate in PEM format.", +) + +index_url: Callable[..., Option] = partial( + Option, + "-i", + "--index-url", + "--pypi-url", + dest="index_url", + metavar="URL", + default=PyPI.simple_url, + help="Base URL of the Python Package Index (default %default). " + "This should point to a repository compliant with PEP 503 " + "(the simple repository API) or a local directory laid out " + "in the same format.", +) + + +def extra_index_url() -> Option: + return Option( + "--extra-index-url", + dest="extra_index_urls", + metavar="URL", + action="append", + default=[], + help="Extra URLs of package indexes to use in addition to " + "--index-url. Should follow the same rules as " + "--index-url.", + ) + + +no_index: Callable[..., Option] = partial( + Option, + "--no-index", + dest="no_index", + action="store_true", + default=False, + help="Ignore package index (only looking at --find-links URLs instead).", +) + + +def find_links() -> Option: + return Option( + "-f", + "--find-links", + dest="find_links", + action="append", + default=[], + metavar="url", + help="If a URL or path to an html file, then parse for links to " + "archives such as sdist (.tar.gz) or wheel (.whl) files. " + "If a local path or file:// URL that's a directory, " + "then look for archives in the directory listing. " + "Links to VCS project URLs are not supported.", + ) + + +def trusted_host() -> Option: + return Option( + "--trusted-host", + dest="trusted_hosts", + action="append", + metavar="HOSTNAME", + default=[], + help="Mark this host or host:port pair as trusted, even though it " + "does not have valid or any HTTPS.", + ) + + +def constraints() -> Option: + return Option( + "-c", + "--constraint", + dest="constraints", + action="append", + default=[], + metavar="file", + help="Constrain versions using the given constraints file. " + "This option can be used multiple times.", + ) + + +def requirements() -> Option: + return Option( + "-r", + "--requirement", + dest="requirements", + action="append", + default=[], + metavar="file", + help="Install from the given requirements file. " + "This option can be used multiple times.", + ) + + +def editable() -> Option: + return Option( + "-e", + "--editable", + dest="editables", + action="append", + default=[], + metavar="path/url", + help=( + "Install a project in editable mode (i.e. setuptools " + '"develop mode") from a local project path or a VCS url.' + ), + ) + + +def _handle_src(option: Option, opt_str: str, value: str, parser: OptionParser) -> None: + value = os.path.abspath(value) + setattr(parser.values, option.dest, value) + + +src: Callable[..., Option] = partial( + PipOption, + "--src", + "--source", + "--source-dir", + "--source-directory", + dest="src_dir", + type="path", + metavar="dir", + default=get_src_prefix(), + action="callback", + callback=_handle_src, + help="Directory to check out editable projects into. " + 'The default in a virtualenv is "/src". ' + 'The default for global installs is "/src".', +) + + +def _get_format_control(values: Values, option: Option) -> Any: + """Get a format_control object.""" + return getattr(values, option.dest) + + +def _handle_no_binary( + option: Option, opt_str: str, value: str, parser: OptionParser +) -> None: + existing = _get_format_control(parser.values, option) + FormatControl.handle_mutual_excludes( + value, + existing.no_binary, + existing.only_binary, + ) + + +def _handle_only_binary( + option: Option, opt_str: str, value: str, parser: OptionParser +) -> None: + existing = _get_format_control(parser.values, option) + FormatControl.handle_mutual_excludes( + value, + existing.only_binary, + existing.no_binary, + ) + + +def no_binary() -> Option: + format_control = FormatControl(set(), set()) + return Option( + "--no-binary", + dest="format_control", + action="callback", + callback=_handle_no_binary, + type="str", + default=format_control, + help="Do not use binary packages. Can be supplied multiple times, and " + 'each time adds to the existing value. Accepts either ":all:" to ' + 'disable all binary packages, ":none:" to empty the set (notice ' + "the colons), or one or more package names with commas between " + "them (no colons). Note that some packages are tricky to compile " + "and may fail to install when this option is used on them.", + ) + + +def only_binary() -> Option: + format_control = FormatControl(set(), set()) + return Option( + "--only-binary", + dest="format_control", + action="callback", + callback=_handle_only_binary, + type="str", + default=format_control, + help="Do not use source packages. Can be supplied multiple times, and " + 'each time adds to the existing value. Accepts either ":all:" to ' + 'disable all source packages, ":none:" to empty the set, or one ' + "or more package names with commas between them. Packages " + "without binary distributions will fail to install when this " + "option is used on them.", + ) + + +platforms: Callable[..., Option] = partial( + Option, + "--platform", + dest="platforms", + metavar="platform", + action="append", + default=None, + help=( + "Only use wheels compatible with . Defaults to the " + "platform of the running system. Use this option multiple times to " + "specify multiple platforms supported by the target interpreter." + ), +) + + +# This was made a separate function for unit-testing purposes. +def _convert_python_version(value: str) -> Tuple[Tuple[int, ...], Optional[str]]: + """ + Convert a version string like "3", "37", or "3.7.3" into a tuple of ints. + + :return: A 2-tuple (version_info, error_msg), where `error_msg` is + non-None if and only if there was a parsing error. + """ + if not value: + # The empty string is the same as not providing a value. + return (None, None) + + parts = value.split(".") + if len(parts) > 3: + return ((), "at most three version parts are allowed") + + if len(parts) == 1: + # Then we are in the case of "3" or "37". + value = parts[0] + if len(value) > 1: + parts = [value[0], value[1:]] + + try: + version_info = tuple(int(part) for part in parts) + except ValueError: + return ((), "each version part must be an integer") + + return (version_info, None) + + +def _handle_python_version( + option: Option, opt_str: str, value: str, parser: OptionParser +) -> None: + """ + Handle a provided --python-version value. + """ + version_info, error_msg = _convert_python_version(value) + if error_msg is not None: + msg = "invalid --python-version value: {!r}: {}".format( + value, + error_msg, + ) + raise_option_error(parser, option=option, msg=msg) + + parser.values.python_version = version_info + + +python_version: Callable[..., Option] = partial( + Option, + "--python-version", + dest="python_version", + metavar="python_version", + action="callback", + callback=_handle_python_version, + type="str", + default=None, + help=dedent( + """\ + The Python interpreter version to use for wheel and "Requires-Python" + compatibility checks. Defaults to a version derived from the running + interpreter. The version can be specified using up to three dot-separated + integers (e.g. "3" for 3.0.0, "3.7" for 3.7.0, or "3.7.3"). A major-minor + version can also be given as a string without dots (e.g. "37" for 3.7.0). + """ + ), +) + + +implementation: Callable[..., Option] = partial( + Option, + "--implementation", + dest="implementation", + metavar="implementation", + default=None, + help=( + "Only use wheels compatible with Python " + "implementation , e.g. 'pp', 'jy', 'cp', " + " or 'ip'. If not specified, then the current " + "interpreter implementation is used. Use 'py' to force " + "implementation-agnostic wheels." + ), +) + + +abis: Callable[..., Option] = partial( + Option, + "--abi", + dest="abis", + metavar="abi", + action="append", + default=None, + help=( + "Only use wheels compatible with Python abi , e.g. 'pypy_41'. " + "If not specified, then the current interpreter abi tag is used. " + "Use this option multiple times to specify multiple abis supported " + "by the target interpreter. Generally you will need to specify " + "--implementation, --platform, and --python-version when using this " + "option." + ), +) + + +def add_target_python_options(cmd_opts: OptionGroup) -> None: + cmd_opts.add_option(platforms()) + cmd_opts.add_option(python_version()) + cmd_opts.add_option(implementation()) + cmd_opts.add_option(abis()) + + +def make_target_python(options: Values) -> TargetPython: + target_python = TargetPython( + platforms=options.platforms, + py_version_info=options.python_version, + abis=options.abis, + implementation=options.implementation, + ) + + return target_python + + +def prefer_binary() -> Option: + return Option( + "--prefer-binary", + dest="prefer_binary", + action="store_true", + default=False, + help="Prefer older binary packages over newer source packages.", + ) + + +cache_dir: Callable[..., Option] = partial( + PipOption, + "--cache-dir", + dest="cache_dir", + default=USER_CACHE_DIR, + metavar="dir", + type="path", + help="Store the cache data in .", +) + + +def _handle_no_cache_dir( + option: Option, opt: str, value: str, parser: OptionParser +) -> None: + """ + Process a value provided for the --no-cache-dir option. + + This is an optparse.Option callback for the --no-cache-dir option. + """ + # The value argument will be None if --no-cache-dir is passed via the + # command-line, since the option doesn't accept arguments. However, + # the value can be non-None if the option is triggered e.g. by an + # environment variable, like PIP_NO_CACHE_DIR=true. + if value is not None: + # Then parse the string value to get argument error-checking. + try: + strtobool(value) + except ValueError as exc: + raise_option_error(parser, option=option, msg=str(exc)) + + # Originally, setting PIP_NO_CACHE_DIR to a value that strtobool() + # converted to 0 (like "false" or "no") caused cache_dir to be disabled + # rather than enabled (logic would say the latter). Thus, we disable + # the cache directory not just on values that parse to True, but (for + # backwards compatibility reasons) also on values that parse to False. + # In other words, always set it to False if the option is provided in + # some (valid) form. + parser.values.cache_dir = False + + +no_cache: Callable[..., Option] = partial( + Option, + "--no-cache-dir", + dest="cache_dir", + action="callback", + callback=_handle_no_cache_dir, + help="Disable the cache.", +) + +no_deps: Callable[..., Option] = partial( + Option, + "--no-deps", + "--no-dependencies", + dest="ignore_dependencies", + action="store_true", + default=False, + help="Don't install package dependencies.", +) + +ignore_requires_python: Callable[..., Option] = partial( + Option, + "--ignore-requires-python", + dest="ignore_requires_python", + action="store_true", + help="Ignore the Requires-Python information.", +) + +no_build_isolation: Callable[..., Option] = partial( + Option, + "--no-build-isolation", + dest="build_isolation", + action="store_false", + default=True, + help="Disable isolation when building a modern source distribution. " + "Build dependencies specified by PEP 518 must be already installed " + "if this option is used.", +) + + +def _handle_no_use_pep517( + option: Option, opt: str, value: str, parser: OptionParser +) -> None: + """ + Process a value provided for the --no-use-pep517 option. + + This is an optparse.Option callback for the no_use_pep517 option. + """ + # Since --no-use-pep517 doesn't accept arguments, the value argument + # will be None if --no-use-pep517 is passed via the command-line. + # However, the value can be non-None if the option is triggered e.g. + # by an environment variable, for example "PIP_NO_USE_PEP517=true". + if value is not None: + msg = """A value was passed for --no-use-pep517, + probably using either the PIP_NO_USE_PEP517 environment variable + or the "no-use-pep517" config file option. Use an appropriate value + of the PIP_USE_PEP517 environment variable or the "use-pep517" + config file option instead. + """ + raise_option_error(parser, option=option, msg=msg) + + # Otherwise, --no-use-pep517 was passed via the command-line. + parser.values.use_pep517 = False + + +use_pep517: Any = partial( + Option, + "--use-pep517", + dest="use_pep517", + action="store_true", + default=None, + help="Use PEP 517 for building source distributions " + "(use --no-use-pep517 to force legacy behaviour).", +) + +no_use_pep517: Any = partial( + Option, + "--no-use-pep517", + dest="use_pep517", + action="callback", + callback=_handle_no_use_pep517, + default=None, + help=SUPPRESS_HELP, +) + +install_options: Callable[..., Option] = partial( + Option, + "--install-option", + dest="install_options", + action="append", + metavar="options", + help="Extra arguments to be supplied to the setup.py install " + 'command (use like --install-option="--install-scripts=/usr/local/' + 'bin"). Use multiple --install-option options to pass multiple ' + "options to setup.py install. If you are using an option with a " + "directory path, be sure to use absolute path.", +) + +build_options: Callable[..., Option] = partial( + Option, + "--build-option", + dest="build_options", + metavar="options", + action="append", + help="Extra arguments to be supplied to 'setup.py bdist_wheel'.", +) + +global_options: Callable[..., Option] = partial( + Option, + "--global-option", + dest="global_options", + action="append", + metavar="options", + help="Extra global options to be supplied to the setup.py " + "call before the install or bdist_wheel command.", +) + +no_clean: Callable[..., Option] = partial( + Option, + "--no-clean", + action="store_true", + default=False, + help="Don't clean up build directories.", +) + +pre: Callable[..., Option] = partial( + Option, + "--pre", + action="store_true", + default=False, + help="Include pre-release and development versions. By default, " + "pip only finds stable versions.", +) + +disable_pip_version_check: Callable[..., Option] = partial( + Option, + "--disable-pip-version-check", + dest="disable_pip_version_check", + action="store_true", + default=True, + help="Don't periodically check PyPI to determine whether a new version " + "of pip is available for download. Implied with --no-index.", +) + + +def _handle_merge_hash( + option: Option, opt_str: str, value: str, parser: OptionParser +) -> None: + """Given a value spelled "algo:digest", append the digest to a list + pointed to in a dict by the algo name.""" + if not parser.values.hashes: + parser.values.hashes = {} + try: + algo, digest = value.split(":", 1) + except ValueError: + parser.error( + "Arguments to {} must be a hash name " # noqa + "followed by a value, like --hash=sha256:" + "abcde...".format(opt_str) + ) + if algo not in STRONG_HASHES: + parser.error( + "Allowed hash algorithms for {} are {}.".format( # noqa + opt_str, ", ".join(STRONG_HASHES) + ) + ) + parser.values.hashes.setdefault(algo, []).append(digest) + + +hash: Callable[..., Option] = partial( + Option, + "--hash", + # Hash values eventually end up in InstallRequirement.hashes due to + # __dict__ copying in process_line(). + dest="hashes", + action="callback", + callback=_handle_merge_hash, + type="string", + help="Verify that the package's archive matches this " + "hash before installing. Example: --hash=sha256:abcdef...", +) + + +require_hashes: Callable[..., Option] = partial( + Option, + "--require-hashes", + dest="require_hashes", + action="store_true", + default=False, + help="Require a hash to check each requirement against, for " + "repeatable installs. This option is implied when any package in a " + "requirements file has a --hash option.", +) + + +list_path: Callable[..., Option] = partial( + PipOption, + "--path", + dest="path", + type="path", + action="append", + help="Restrict to the specified installation path for listing " + "packages (can be used multiple times).", +) + + +def check_list_path_option(options: Values) -> None: + if options.path and (options.user or options.local): + raise CommandError("Cannot combine '--path' with '--user' or '--local'") + + +list_exclude: Callable[..., Option] = partial( + PipOption, + "--exclude", + dest="excludes", + action="append", + metavar="package", + type="package_name", + help="Exclude specified package from the output", +) + + +no_python_version_warning: Callable[..., Option] = partial( + Option, + "--no-python-version-warning", + dest="no_python_version_warning", + action="store_true", + default=False, + help="Silence deprecation warnings for upcoming unsupported Pythons.", +) + + +use_new_feature: Callable[..., Option] = partial( + Option, + "--use-feature", + dest="features_enabled", + metavar="feature", + action="append", + default=[], + choices=["2020-resolver", "fast-deps", "in-tree-build"], + help="Enable new functionality, that may be backward incompatible.", +) + +use_deprecated_feature: Callable[..., Option] = partial( + Option, + "--use-deprecated", + dest="deprecated_features_enabled", + metavar="feature", + action="append", + default=[], + choices=[ + "legacy-resolver", + "out-of-tree-build", + "backtrack-on-build-failures", + "html5lib", + ], + help=("Enable deprecated functionality, that will be removed in the future."), +) + + +########## +# groups # +########## + +general_group: Dict[str, Any] = { + "name": "General Options", + "options": [ + help_, + debug_mode, + isolated_mode, + require_virtualenv, + verbose, + version, + quiet, + log, + no_input, + proxy, + retries, + timeout, + exists_action, + trusted_host, + cert, + client_cert, + cache_dir, + no_cache, + disable_pip_version_check, + no_color, + no_python_version_warning, + use_new_feature, + use_deprecated_feature, + ], +} + +index_group: Dict[str, Any] = { + "name": "Package Index Options", + "options": [ + index_url, + extra_index_url, + no_index, + find_links, + ], +} diff --git a/venv/lib/python3.10/site-packages/pip/_internal/cli/command_context.py b/venv/lib/python3.10/site-packages/pip/_internal/cli/command_context.py new file mode 100644 index 00000000..ed683223 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip/_internal/cli/command_context.py @@ -0,0 +1,27 @@ +from contextlib import ExitStack, contextmanager +from typing import ContextManager, Iterator, TypeVar + +_T = TypeVar("_T", covariant=True) + + +class CommandContextMixIn: + def __init__(self) -> None: + super().__init__() + self._in_main_context = False + self._main_context = ExitStack() + + @contextmanager + def main_context(self) -> Iterator[None]: + assert not self._in_main_context + + self._in_main_context = True + try: + with self._main_context: + yield + finally: + self._in_main_context = False + + def enter_context(self, context_provider: ContextManager[_T]) -> _T: + assert self._in_main_context + + return self._main_context.enter_context(context_provider) diff --git a/venv/lib/python3.10/site-packages/pip/_internal/cli/main.py b/venv/lib/python3.10/site-packages/pip/_internal/cli/main.py new file mode 100644 index 00000000..0e312215 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip/_internal/cli/main.py @@ -0,0 +1,70 @@ +"""Primary application entrypoint. +""" +import locale +import logging +import os +import sys +from typing import List, Optional + +from pip._internal.cli.autocompletion import autocomplete +from pip._internal.cli.main_parser import parse_command +from pip._internal.commands import create_command +from pip._internal.exceptions import PipError +from pip._internal.utils import deprecation + +logger = logging.getLogger(__name__) + + +# Do not import and use main() directly! Using it directly is actively +# discouraged by pip's maintainers. The name, location and behavior of +# this function is subject to change, so calling it directly is not +# portable across different pip versions. + +# In addition, running pip in-process is unsupported and unsafe. This is +# elaborated in detail at +# https://pip.pypa.io/en/stable/user_guide/#using-pip-from-your-program. +# That document also provides suggestions that should work for nearly +# all users that are considering importing and using main() directly. + +# However, we know that certain users will still want to invoke pip +# in-process. If you understand and accept the implications of using pip +# in an unsupported manner, the best approach is to use runpy to avoid +# depending on the exact location of this entry point. + +# The following example shows how to use runpy to invoke pip in that +# case: +# +# sys.argv = ["pip", your, args, here] +# runpy.run_module("pip", run_name="__main__") +# +# Note that this will exit the process after running, unlike a direct +# call to main. As it is not safe to do any processing after calling +# main, this should not be an issue in practice. + + +def main(args: Optional[List[str]] = None) -> int: + if args is None: + args = sys.argv[1:] + + # Configure our deprecation warnings to be sent through loggers + deprecation.install_warning_logger() + + autocomplete() + + try: + cmd_name, cmd_args = parse_command(args) + except PipError as exc: + sys.stderr.write(f"ERROR: {exc}") + sys.stderr.write(os.linesep) + sys.exit(1) + + # Needed for locale.getpreferredencoding(False) to work + # in pip._internal.utils.encoding.auto_decode + try: + locale.setlocale(locale.LC_ALL, "") + except locale.Error as e: + # setlocale can apparently crash if locale are uninitialized + logger.debug("Ignoring error %s when setting locale", e) + command = create_command(cmd_name, isolated=("--isolated" in cmd_args)) + + return command.main(cmd_args) diff --git a/venv/lib/python3.10/site-packages/pip/_internal/cli/main_parser.py b/venv/lib/python3.10/site-packages/pip/_internal/cli/main_parser.py new file mode 100644 index 00000000..3666ab04 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip/_internal/cli/main_parser.py @@ -0,0 +1,87 @@ +"""A single place for constructing and exposing the main parser +""" + +import os +import sys +from typing import List, Tuple + +from pip._internal.cli import cmdoptions +from pip._internal.cli.parser import ConfigOptionParser, UpdatingDefaultsHelpFormatter +from pip._internal.commands import commands_dict, get_similar_commands +from pip._internal.exceptions import CommandError +from pip._internal.utils.misc import get_pip_version, get_prog + +__all__ = ["create_main_parser", "parse_command"] + + +def create_main_parser() -> ConfigOptionParser: + """Creates and returns the main parser for pip's CLI""" + + parser = ConfigOptionParser( + usage="\n%prog [options]", + add_help_option=False, + formatter=UpdatingDefaultsHelpFormatter(), + name="global", + prog=get_prog(), + ) + parser.disable_interspersed_args() + + parser.version = get_pip_version() + + # add the general options + gen_opts = cmdoptions.make_option_group(cmdoptions.general_group, parser) + parser.add_option_group(gen_opts) + + # so the help formatter knows + parser.main = True # type: ignore + + # create command listing for description + description = [""] + [ + f"{name:27} {command_info.summary}" + for name, command_info in commands_dict.items() + ] + parser.description = "\n".join(description) + + return parser + + +def parse_command(args: List[str]) -> Tuple[str, List[str]]: + parser = create_main_parser() + + # Note: parser calls disable_interspersed_args(), so the result of this + # call is to split the initial args into the general options before the + # subcommand and everything else. + # For example: + # args: ['--timeout=5', 'install', '--user', 'INITools'] + # general_options: ['--timeout==5'] + # args_else: ['install', '--user', 'INITools'] + general_options, args_else = parser.parse_args(args) + + # --version + if general_options.version: + sys.stdout.write(parser.version) + sys.stdout.write(os.linesep) + sys.exit() + + # pip || pip help -> print_help() + if not args_else or (args_else[0] == "help" and len(args_else) == 1): + parser.print_help() + sys.exit() + + # the subcommand name + cmd_name = args_else[0] + + if cmd_name not in commands_dict: + guess = get_similar_commands(cmd_name) + + msg = [f'unknown command "{cmd_name}"'] + if guess: + msg.append(f'maybe you meant "{guess}"') + + raise CommandError(" - ".join(msg)) + + # all the args without the subcommand + cmd_args = args[:] + cmd_args.remove(cmd_name) + + return cmd_name, cmd_args diff --git a/venv/lib/python3.10/site-packages/pip/_internal/cli/parser.py b/venv/lib/python3.10/site-packages/pip/_internal/cli/parser.py new file mode 100644 index 00000000..a1c99a8c --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip/_internal/cli/parser.py @@ -0,0 +1,292 @@ +"""Base option parser setup""" + +import logging +import optparse +import shutil +import sys +import textwrap +from contextlib import suppress +from typing import Any, Dict, Iterator, List, Tuple + +from pip._internal.cli.status_codes import UNKNOWN_ERROR +from pip._internal.configuration import Configuration, ConfigurationError +from pip._internal.utils.misc import redact_auth_from_url, strtobool + +logger = logging.getLogger(__name__) + + +class PrettyHelpFormatter(optparse.IndentedHelpFormatter): + """A prettier/less verbose help formatter for optparse.""" + + def __init__(self, *args: Any, **kwargs: Any) -> None: + # help position must be aligned with __init__.parseopts.description + kwargs["max_help_position"] = 30 + kwargs["indent_increment"] = 1 + kwargs["width"] = shutil.get_terminal_size()[0] - 2 + super().__init__(*args, **kwargs) + + def format_option_strings(self, option: optparse.Option) -> str: + return self._format_option_strings(option) + + def _format_option_strings( + self, option: optparse.Option, mvarfmt: str = " <{}>", optsep: str = ", " + ) -> str: + """ + Return a comma-separated list of option strings and metavars. + + :param option: tuple of (short opt, long opt), e.g: ('-f', '--format') + :param mvarfmt: metavar format string + :param optsep: separator + """ + opts = [] + + if option._short_opts: + opts.append(option._short_opts[0]) + if option._long_opts: + opts.append(option._long_opts[0]) + if len(opts) > 1: + opts.insert(1, optsep) + + if option.takes_value(): + assert option.dest is not None + metavar = option.metavar or option.dest.lower() + opts.append(mvarfmt.format(metavar.lower())) + + return "".join(opts) + + def format_heading(self, heading: str) -> str: + if heading == "Options": + return "" + return heading + ":\n" + + def format_usage(self, usage: str) -> str: + """ + Ensure there is only one newline between usage and the first heading + if there is no description. + """ + msg = "\nUsage: {}\n".format(self.indent_lines(textwrap.dedent(usage), " ")) + return msg + + def format_description(self, description: str) -> str: + # leave full control over description to us + if description: + if hasattr(self.parser, "main"): + label = "Commands" + else: + label = "Description" + # some doc strings have initial newlines, some don't + description = description.lstrip("\n") + # some doc strings have final newlines and spaces, some don't + description = description.rstrip() + # dedent, then reindent + description = self.indent_lines(textwrap.dedent(description), " ") + description = f"{label}:\n{description}\n" + return description + else: + return "" + + def format_epilog(self, epilog: str) -> str: + # leave full control over epilog to us + if epilog: + return epilog + else: + return "" + + def indent_lines(self, text: str, indent: str) -> str: + new_lines = [indent + line for line in text.split("\n")] + return "\n".join(new_lines) + + +class UpdatingDefaultsHelpFormatter(PrettyHelpFormatter): + """Custom help formatter for use in ConfigOptionParser. + + This is updates the defaults before expanding them, allowing + them to show up correctly in the help listing. + + Also redact auth from url type options + """ + + def expand_default(self, option: optparse.Option) -> str: + default_values = None + if self.parser is not None: + assert isinstance(self.parser, ConfigOptionParser) + self.parser._update_defaults(self.parser.defaults) + assert option.dest is not None + default_values = self.parser.defaults.get(option.dest) + help_text = super().expand_default(option) + + if default_values and option.metavar == "URL": + if isinstance(default_values, str): + default_values = [default_values] + + # If its not a list, we should abort and just return the help text + if not isinstance(default_values, list): + default_values = [] + + for val in default_values: + help_text = help_text.replace(val, redact_auth_from_url(val)) + + return help_text + + +class CustomOptionParser(optparse.OptionParser): + def insert_option_group( + self, idx: int, *args: Any, **kwargs: Any + ) -> optparse.OptionGroup: + """Insert an OptionGroup at a given position.""" + group = self.add_option_group(*args, **kwargs) + + self.option_groups.pop() + self.option_groups.insert(idx, group) + + return group + + @property + def option_list_all(self) -> List[optparse.Option]: + """Get a list of all options, including those in option groups.""" + res = self.option_list[:] + for i in self.option_groups: + res.extend(i.option_list) + + return res + + +class ConfigOptionParser(CustomOptionParser): + """Custom option parser which updates its defaults by checking the + configuration files and environmental variables""" + + def __init__( + self, + *args: Any, + name: str, + isolated: bool = False, + **kwargs: Any, + ) -> None: + self.name = name + self.config = Configuration(isolated) + + assert self.name + super().__init__(*args, **kwargs) + + def check_default(self, option: optparse.Option, key: str, val: Any) -> Any: + try: + return option.check_value(key, val) + except optparse.OptionValueError as exc: + print(f"An error occurred during configuration: {exc}") + sys.exit(3) + + def _get_ordered_configuration_items(self) -> Iterator[Tuple[str, Any]]: + # Configuration gives keys in an unordered manner. Order them. + override_order = ["global", self.name, ":env:"] + + # Pool the options into different groups + section_items: Dict[str, List[Tuple[str, Any]]] = { + name: [] for name in override_order + } + for section_key, val in self.config.items(): + # ignore empty values + if not val: + logger.debug( + "Ignoring configuration key '%s' as it's value is empty.", + section_key, + ) + continue + + section, key = section_key.split(".", 1) + if section in override_order: + section_items[section].append((key, val)) + + # Yield each group in their override order + for section in override_order: + for key, val in section_items[section]: + yield key, val + + def _update_defaults(self, defaults: Dict[str, Any]) -> Dict[str, Any]: + """Updates the given defaults with values from the config files and + the environ. Does a little special handling for certain types of + options (lists).""" + + # Accumulate complex default state. + self.values = optparse.Values(self.defaults) + late_eval = set() + # Then set the options with those values + for key, val in self._get_ordered_configuration_items(): + # '--' because configuration supports only long names + option = self.get_option("--" + key) + + # Ignore options not present in this parser. E.g. non-globals put + # in [global] by users that want them to apply to all applicable + # commands. + if option is None: + continue + + assert option.dest is not None + + if option.action in ("store_true", "store_false"): + try: + val = strtobool(val) + except ValueError: + self.error( + "{} is not a valid value for {} option, " # noqa + "please specify a boolean value like yes/no, " + "true/false or 1/0 instead.".format(val, key) + ) + elif option.action == "count": + with suppress(ValueError): + val = strtobool(val) + with suppress(ValueError): + val = int(val) + if not isinstance(val, int) or val < 0: + self.error( + "{} is not a valid value for {} option, " # noqa + "please instead specify either a non-negative integer " + "or a boolean value like yes/no or false/true " + "which is equivalent to 1/0.".format(val, key) + ) + elif option.action == "append": + val = val.split() + val = [self.check_default(option, key, v) for v in val] + elif option.action == "callback": + assert option.callback is not None + late_eval.add(option.dest) + opt_str = option.get_opt_string() + val = option.convert_value(opt_str, val) + # From take_action + args = option.callback_args or () + kwargs = option.callback_kwargs or {} + option.callback(option, opt_str, val, self, *args, **kwargs) + else: + val = self.check_default(option, key, val) + + defaults[option.dest] = val + + for key in late_eval: + defaults[key] = getattr(self.values, key) + self.values = None + return defaults + + def get_default_values(self) -> optparse.Values: + """Overriding to make updating the defaults after instantiation of + the option parser possible, _update_defaults() does the dirty work.""" + if not self.process_default_values: + # Old, pre-Optik 1.5 behaviour. + return optparse.Values(self.defaults) + + # Load the configuration, or error out in case of an error + try: + self.config.load() + except ConfigurationError as err: + self.exit(UNKNOWN_ERROR, str(err)) + + defaults = self._update_defaults(self.defaults.copy()) # ours + for option in self._get_all_options(): + assert option.dest is not None + default = defaults.get(option.dest) + if isinstance(default, str): + opt_str = option.get_opt_string() + defaults[option.dest] = option.check_value(opt_str, default) + return optparse.Values(defaults) + + def error(self, msg: str) -> None: + self.print_usage(sys.stderr) + self.exit(UNKNOWN_ERROR, f"{msg}\n") diff --git a/venv/lib/python3.10/site-packages/pip/_internal/cli/progress_bars.py b/venv/lib/python3.10/site-packages/pip/_internal/cli/progress_bars.py new file mode 100644 index 00000000..ffa1964f --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip/_internal/cli/progress_bars.py @@ -0,0 +1,321 @@ +import functools +import itertools +import sys +from signal import SIGINT, default_int_handler, signal +from typing import Any, Callable, Iterator, Optional, Tuple + +from pip._vendor.progress.bar import Bar, FillingCirclesBar, IncrementalBar +from pip._vendor.progress.spinner import Spinner +from pip._vendor.rich.progress import ( + BarColumn, + DownloadColumn, + FileSizeColumn, + Progress, + ProgressColumn, + SpinnerColumn, + TextColumn, + TimeElapsedColumn, + TimeRemainingColumn, + TransferSpeedColumn, +) + +from pip._internal.utils.compat import WINDOWS +from pip._internal.utils.logging import get_indentation +from pip._internal.utils.misc import format_size + +try: + from pip._vendor import colorama +# Lots of different errors can come from this, including SystemError and +# ImportError. +except Exception: + colorama = None + +DownloadProgressRenderer = Callable[[Iterator[bytes]], Iterator[bytes]] + + +def _select_progress_class(preferred: Bar, fallback: Bar) -> Bar: + encoding = getattr(preferred.file, "encoding", None) + + # If we don't know what encoding this file is in, then we'll just assume + # that it doesn't support unicode and use the ASCII bar. + if not encoding: + return fallback + + # Collect all of the possible characters we want to use with the preferred + # bar. + characters = [ + getattr(preferred, "empty_fill", ""), + getattr(preferred, "fill", ""), + ] + characters += list(getattr(preferred, "phases", [])) + + # Try to decode the characters we're using for the bar using the encoding + # of the given file, if this works then we'll assume that we can use the + # fancier bar and if not we'll fall back to the plaintext bar. + try: + "".join(characters).encode(encoding) + except UnicodeEncodeError: + return fallback + else: + return preferred + + +_BaseBar: Any = _select_progress_class(IncrementalBar, Bar) + + +class InterruptibleMixin: + """ + Helper to ensure that self.finish() gets called on keyboard interrupt. + + This allows downloads to be interrupted without leaving temporary state + (like hidden cursors) behind. + + This class is similar to the progress library's existing SigIntMixin + helper, but as of version 1.2, that helper has the following problems: + + 1. It calls sys.exit(). + 2. It discards the existing SIGINT handler completely. + 3. It leaves its own handler in place even after an uninterrupted finish, + which will have unexpected delayed effects if the user triggers an + unrelated keyboard interrupt some time after a progress-displaying + download has already completed, for example. + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + """ + Save the original SIGINT handler for later. + """ + # https://github.com/python/mypy/issues/5887 + super().__init__(*args, **kwargs) # type: ignore + + self.original_handler = signal(SIGINT, self.handle_sigint) + + # If signal() returns None, the previous handler was not installed from + # Python, and we cannot restore it. This probably should not happen, + # but if it does, we must restore something sensible instead, at least. + # The least bad option should be Python's default SIGINT handler, which + # just raises KeyboardInterrupt. + if self.original_handler is None: + self.original_handler = default_int_handler + + def finish(self) -> None: + """ + Restore the original SIGINT handler after finishing. + + This should happen regardless of whether the progress display finishes + normally, or gets interrupted. + """ + super().finish() # type: ignore + signal(SIGINT, self.original_handler) + + def handle_sigint(self, signum, frame): # type: ignore + """ + Call self.finish() before delegating to the original SIGINT handler. + + This handler should only be in place while the progress display is + active. + """ + self.finish() + self.original_handler(signum, frame) + + +class SilentBar(Bar): + def update(self) -> None: + pass + + +class BlueEmojiBar(IncrementalBar): + + suffix = "%(percent)d%%" + bar_prefix = " " + bar_suffix = " " + phases = ("\U0001F539", "\U0001F537", "\U0001F535") + + +class DownloadProgressMixin: + def __init__(self, *args: Any, **kwargs: Any) -> None: + # https://github.com/python/mypy/issues/5887 + super().__init__(*args, **kwargs) # type: ignore + self.message: str = (" " * (get_indentation() + 2)) + self.message + + @property + def downloaded(self) -> str: + return format_size(self.index) # type: ignore + + @property + def download_speed(self) -> str: + # Avoid zero division errors... + if self.avg == 0.0: # type: ignore + return "..." + return format_size(1 / self.avg) + "/s" # type: ignore + + @property + def pretty_eta(self) -> str: + if self.eta: # type: ignore + return f"eta {self.eta_td}" # type: ignore + return "" + + def iter(self, it): # type: ignore + for x in it: + yield x + # B305 is incorrectly raised here + # https://github.com/PyCQA/flake8-bugbear/issues/59 + self.next(len(x)) # noqa: B305 + self.finish() + + +class WindowsMixin: + def __init__(self, *args: Any, **kwargs: Any) -> None: + # The Windows terminal does not support the hide/show cursor ANSI codes + # even with colorama. So we'll ensure that hide_cursor is False on + # Windows. + # This call needs to go before the super() call, so that hide_cursor + # is set in time. The base progress bar class writes the "hide cursor" + # code to the terminal in its init, so if we don't set this soon + # enough, we get a "hide" with no corresponding "show"... + if WINDOWS and self.hide_cursor: # type: ignore + self.hide_cursor = False + + # https://github.com/python/mypy/issues/5887 + super().__init__(*args, **kwargs) # type: ignore + + # Check if we are running on Windows and we have the colorama module, + # if we do then wrap our file with it. + if WINDOWS and colorama: + self.file = colorama.AnsiToWin32(self.file) # type: ignore + # The progress code expects to be able to call self.file.isatty() + # but the colorama.AnsiToWin32() object doesn't have that, so we'll + # add it. + self.file.isatty = lambda: self.file.wrapped.isatty() + # The progress code expects to be able to call self.file.flush() + # but the colorama.AnsiToWin32() object doesn't have that, so we'll + # add it. + self.file.flush = lambda: self.file.wrapped.flush() + + +class BaseDownloadProgressBar(WindowsMixin, InterruptibleMixin, DownloadProgressMixin): + + file = sys.stdout + message = "%(percent)d%%" + suffix = "%(downloaded)s %(download_speed)s %(pretty_eta)s" + + +class DefaultDownloadProgressBar(BaseDownloadProgressBar, _BaseBar): + pass + + +class DownloadSilentBar(BaseDownloadProgressBar, SilentBar): + pass + + +class DownloadBar(BaseDownloadProgressBar, Bar): + pass + + +class DownloadFillingCirclesBar(BaseDownloadProgressBar, FillingCirclesBar): + pass + + +class DownloadBlueEmojiProgressBar(BaseDownloadProgressBar, BlueEmojiBar): + pass + + +class DownloadProgressSpinner( + WindowsMixin, InterruptibleMixin, DownloadProgressMixin, Spinner +): + + file = sys.stdout + suffix = "%(downloaded)s %(download_speed)s" + + def next_phase(self) -> str: + if not hasattr(self, "_phaser"): + self._phaser = itertools.cycle(self.phases) + return next(self._phaser) + + def update(self) -> None: + message = self.message % self + phase = self.next_phase() + suffix = self.suffix % self + line = "".join( + [ + message, + " " if message else "", + phase, + " " if suffix else "", + suffix, + ] + ) + + self.writeln(line) + + +BAR_TYPES = { + "off": (DownloadSilentBar, DownloadSilentBar), + "on": (DefaultDownloadProgressBar, DownloadProgressSpinner), + "ascii": (DownloadBar, DownloadProgressSpinner), + "pretty": (DownloadFillingCirclesBar, DownloadProgressSpinner), + "emoji": (DownloadBlueEmojiProgressBar, DownloadProgressSpinner), +} + + +def _legacy_progress_bar( + progress_bar: str, max: Optional[int] +) -> DownloadProgressRenderer: + if max is None or max == 0: + return BAR_TYPES[progress_bar][1]().iter # type: ignore + else: + return BAR_TYPES[progress_bar][0](max=max).iter + + +# +# Modern replacement, for our legacy progress bars. +# +def _rich_progress_bar( + iterable: Iterator[bytes], + *, + bar_type: str, + size: int, +) -> Iterator[bytes]: + assert bar_type == "on", "This should only be used in the default mode." + + if not size: + total = float("inf") + columns: Tuple[ProgressColumn, ...] = ( + TextColumn("[progress.description]{task.description}"), + SpinnerColumn("line", speed=1.5), + FileSizeColumn(), + TransferSpeedColumn(), + TimeElapsedColumn(), + ) + else: + total = size + columns = ( + TextColumn("[progress.description]{task.description}"), + BarColumn(), + DownloadColumn(), + TransferSpeedColumn(), + TextColumn("eta"), + TimeRemainingColumn(), + ) + + progress = Progress(*columns, refresh_per_second=30) + task_id = progress.add_task(" " * (get_indentation() + 2), total=total) + with progress: + for chunk in iterable: + yield chunk + progress.update(task_id, advance=len(chunk)) + + +def get_download_progress_renderer( + *, bar_type: str, size: Optional[int] = None +) -> DownloadProgressRenderer: + """Get an object that can be used to render the download progress. + + Returns a callable, that takes an iterable to "wrap". + """ + if bar_type == "on": + return functools.partial(_rich_progress_bar, bar_type=bar_type, size=size) + elif bar_type == "off": + return iter # no-op, when passed an iterator + else: + return _legacy_progress_bar(bar_type, size) diff --git a/venv/lib/python3.10/site-packages/pip/_internal/cli/req_command.py b/venv/lib/python3.10/site-packages/pip/_internal/cli/req_command.py new file mode 100644 index 00000000..5d4d1f0f --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip/_internal/cli/req_command.py @@ -0,0 +1,506 @@ +"""Contains the Command base classes that depend on PipSession. + +The classes in this module are in a separate module so the commands not +needing download / PackageFinder capability don't unnecessarily import the +PackageFinder machinery and all its vendored dependencies, etc. +""" + +import logging +import os +import sys +from functools import partial +from optparse import Values +from typing import Any, List, Optional, Tuple + +from pip._internal.cache import WheelCache +from pip._internal.cli import cmdoptions +from pip._internal.cli.base_command import Command +from pip._internal.cli.command_context import CommandContextMixIn +from pip._internal.exceptions import CommandError, PreviousBuildDirError +from pip._internal.index.collector import LinkCollector +from pip._internal.index.package_finder import PackageFinder +from pip._internal.models.selection_prefs import SelectionPreferences +from pip._internal.models.target_python import TargetPython +from pip._internal.network.session import PipSession +from pip._internal.operations.prepare import RequirementPreparer +from pip._internal.req.constructors import ( + install_req_from_editable, + install_req_from_line, + install_req_from_parsed_requirement, + install_req_from_req_string, +) +from pip._internal.req.req_file import parse_requirements +from pip._internal.req.req_install import InstallRequirement +from pip._internal.req.req_tracker import RequirementTracker +from pip._internal.resolution.base import BaseResolver +from pip._internal.self_outdated_check import pip_self_version_check +from pip._internal.utils.deprecation import deprecated +from pip._internal.utils.temp_dir import ( + TempDirectory, + TempDirectoryTypeRegistry, + tempdir_kinds, +) +from pip._internal.utils.virtualenv import running_under_virtualenv + +logger = logging.getLogger(__name__) + + +class SessionCommandMixin(CommandContextMixIn): + + """ + A class mixin for command classes needing _build_session(). + """ + + def __init__(self) -> None: + super().__init__() + self._session: Optional[PipSession] = None + + @classmethod + def _get_index_urls(cls, options: Values) -> Optional[List[str]]: + """Return a list of index urls from user-provided options.""" + index_urls = [] + if not getattr(options, "no_index", False): + url = getattr(options, "index_url", None) + if url: + index_urls.append(url) + urls = getattr(options, "extra_index_urls", None) + if urls: + index_urls.extend(urls) + # Return None rather than an empty list + return index_urls or None + + def get_default_session(self, options: Values) -> PipSession: + """Get a default-managed session.""" + if self._session is None: + self._session = self.enter_context(self._build_session(options)) + # there's no type annotation on requests.Session, so it's + # automatically ContextManager[Any] and self._session becomes Any, + # then https://github.com/python/mypy/issues/7696 kicks in + assert self._session is not None + return self._session + + def _build_session( + self, + options: Values, + retries: Optional[int] = None, + timeout: Optional[int] = None, + ) -> PipSession: + assert not options.cache_dir or os.path.isabs(options.cache_dir) + session = PipSession( + cache=( + os.path.join(options.cache_dir, "http") if options.cache_dir else None + ), + retries=retries if retries is not None else options.retries, + trusted_hosts=options.trusted_hosts, + index_urls=self._get_index_urls(options), + ) + + # Handle custom ca-bundles from the user + if options.cert: + session.verify = options.cert + + # Handle SSL client certificate + if options.client_cert: + session.cert = options.client_cert + + # Handle timeouts + if options.timeout or timeout: + session.timeout = timeout if timeout is not None else options.timeout + + # Handle configured proxies + if options.proxy: + session.proxies = { + "http": options.proxy, + "https": options.proxy, + } + + # Determine if we can prompt the user for authentication or not + session.auth.prompting = not options.no_input + + return session + + +class IndexGroupCommand(Command, SessionCommandMixin): + + """ + Abstract base class for commands with the index_group options. + + This also corresponds to the commands that permit the pip version check. + """ + + def handle_pip_version_check(self, options: Values) -> None: + """ + Do the pip version check if not disabled. + + This overrides the default behavior of not doing the check. + """ + # Make sure the index_group options are present. + assert hasattr(options, "no_index") + + if options.disable_pip_version_check or options.no_index: + return + + # Otherwise, check if we're using the latest version of pip available. + session = self._build_session( + options, retries=0, timeout=min(5, options.timeout) + ) + with session: + pip_self_version_check(session, options) + + +KEEPABLE_TEMPDIR_TYPES = [ + tempdir_kinds.BUILD_ENV, + tempdir_kinds.EPHEM_WHEEL_CACHE, + tempdir_kinds.REQ_BUILD, +] + + +def warn_if_run_as_root() -> None: + """Output a warning for sudo users on Unix. + + In a virtual environment, sudo pip still writes to virtualenv. + On Windows, users may run pip as Administrator without issues. + This warning only applies to Unix root users outside of virtualenv. + """ + if running_under_virtualenv(): + return + if not hasattr(os, "getuid"): + return + # On Windows, there are no "system managed" Python packages. Installing as + # Administrator via pip is the correct way of updating system environments. + # + # We choose sys.platform over utils.compat.WINDOWS here to enable Mypy platform + # checks: https://mypy.readthedocs.io/en/stable/common_issues.html + if sys.platform == "win32" or sys.platform == "cygwin": + return + + if os.getuid() != 0: + return + + logger.warning( + "Running pip as the 'root' user can result in broken permissions and " + "conflicting behaviour with the system package manager. " + "It is recommended to use a virtual environment instead: " + "https://pip.pypa.io/warnings/venv" + ) + + +def with_cleanup(func: Any) -> Any: + """Decorator for common logic related to managing temporary + directories. + """ + + def configure_tempdir_registry(registry: TempDirectoryTypeRegistry) -> None: + for t in KEEPABLE_TEMPDIR_TYPES: + registry.set_delete(t, False) + + def wrapper( + self: RequirementCommand, options: Values, args: List[Any] + ) -> Optional[int]: + assert self.tempdir_registry is not None + if options.no_clean: + configure_tempdir_registry(self.tempdir_registry) + + try: + return func(self, options, args) + except PreviousBuildDirError: + # This kind of conflict can occur when the user passes an explicit + # build directory with a pre-existing folder. In that case we do + # not want to accidentally remove it. + configure_tempdir_registry(self.tempdir_registry) + raise + + return wrapper + + +class RequirementCommand(IndexGroupCommand): + def __init__(self, *args: Any, **kw: Any) -> None: + super().__init__(*args, **kw) + + self.cmd_opts.add_option(cmdoptions.no_clean()) + + @staticmethod + def determine_resolver_variant(options: Values) -> str: + """Determines which resolver should be used, based on the given options.""" + if "legacy-resolver" in options.deprecated_features_enabled: + return "legacy" + + return "2020-resolver" + + @staticmethod + def determine_build_failure_suppression(options: Values) -> bool: + """Determines whether build failures should be suppressed and backtracked on.""" + if "backtrack-on-build-failures" not in options.deprecated_features_enabled: + return False + + if "legacy-resolver" in options.deprecated_features_enabled: + raise CommandError("Cannot backtrack with legacy resolver.") + + deprecated( + reason=( + "Backtracking on build failures can mask issues related to how " + "a package generates metadata or builds a wheel. This flag will " + "be removed in pip 22.2." + ), + gone_in=None, + replacement=( + "avoiding known-bad versions by explicitly telling pip to ignore them " + "(either directly as requirements, or via a constraints file)" + ), + feature_flag=None, + issue=10655, + ) + return True + + @classmethod + def make_requirement_preparer( + cls, + temp_build_dir: TempDirectory, + options: Values, + req_tracker: RequirementTracker, + session: PipSession, + finder: PackageFinder, + use_user_site: bool, + download_dir: Optional[str] = None, + verbosity: int = 0, + ) -> RequirementPreparer: + """ + Create a RequirementPreparer instance for the given parameters. + """ + temp_build_dir_path = temp_build_dir.path + assert temp_build_dir_path is not None + + resolver_variant = cls.determine_resolver_variant(options) + if resolver_variant == "2020-resolver": + lazy_wheel = "fast-deps" in options.features_enabled + if lazy_wheel: + logger.warning( + "pip is using lazily downloaded wheels using HTTP " + "range requests to obtain dependency information. " + "This experimental feature is enabled through " + "--use-feature=fast-deps and it is not ready for " + "production." + ) + else: + lazy_wheel = False + if "fast-deps" in options.features_enabled: + logger.warning( + "fast-deps has no effect when used with the legacy resolver." + ) + + in_tree_build = "out-of-tree-build" not in options.deprecated_features_enabled + if "in-tree-build" in options.features_enabled: + deprecated( + reason="In-tree builds are now the default.", + replacement="to remove the --use-feature=in-tree-build flag", + gone_in="22.1", + ) + if "out-of-tree-build" in options.deprecated_features_enabled: + deprecated( + reason="Out-of-tree builds are deprecated.", + replacement=None, + gone_in="22.1", + ) + + if options.progress_bar not in {"on", "off"}: + deprecated( + reason="Custom progress bar styles are deprecated", + replacement="to use the default progress bar style.", + gone_in="22.1", + ) + + return RequirementPreparer( + build_dir=temp_build_dir_path, + src_dir=options.src_dir, + download_dir=download_dir, + build_isolation=options.build_isolation, + req_tracker=req_tracker, + session=session, + progress_bar=options.progress_bar, + finder=finder, + require_hashes=options.require_hashes, + use_user_site=use_user_site, + lazy_wheel=lazy_wheel, + verbosity=verbosity, + in_tree_build=in_tree_build, + ) + + @classmethod + def make_resolver( + cls, + preparer: RequirementPreparer, + finder: PackageFinder, + options: Values, + wheel_cache: Optional[WheelCache] = None, + use_user_site: bool = False, + ignore_installed: bool = True, + ignore_requires_python: bool = False, + force_reinstall: bool = False, + upgrade_strategy: str = "to-satisfy-only", + use_pep517: Optional[bool] = None, + py_version_info: Optional[Tuple[int, ...]] = None, + ) -> BaseResolver: + """ + Create a Resolver instance for the given parameters. + """ + make_install_req = partial( + install_req_from_req_string, + isolated=options.isolated_mode, + use_pep517=use_pep517, + ) + suppress_build_failures = cls.determine_build_failure_suppression(options) + resolver_variant = cls.determine_resolver_variant(options) + # The long import name and duplicated invocation is needed to convince + # Mypy into correctly typechecking. Otherwise it would complain the + # "Resolver" class being redefined. + if resolver_variant == "2020-resolver": + import pip._internal.resolution.resolvelib.resolver + + return pip._internal.resolution.resolvelib.resolver.Resolver( + preparer=preparer, + finder=finder, + wheel_cache=wheel_cache, + make_install_req=make_install_req, + use_user_site=use_user_site, + ignore_dependencies=options.ignore_dependencies, + ignore_installed=ignore_installed, + ignore_requires_python=ignore_requires_python, + force_reinstall=force_reinstall, + upgrade_strategy=upgrade_strategy, + py_version_info=py_version_info, + suppress_build_failures=suppress_build_failures, + ) + import pip._internal.resolution.legacy.resolver + + return pip._internal.resolution.legacy.resolver.Resolver( + preparer=preparer, + finder=finder, + wheel_cache=wheel_cache, + make_install_req=make_install_req, + use_user_site=use_user_site, + ignore_dependencies=options.ignore_dependencies, + ignore_installed=ignore_installed, + ignore_requires_python=ignore_requires_python, + force_reinstall=force_reinstall, + upgrade_strategy=upgrade_strategy, + py_version_info=py_version_info, + ) + + def get_requirements( + self, + args: List[str], + options: Values, + finder: PackageFinder, + session: PipSession, + ) -> List[InstallRequirement]: + """ + Parse command-line arguments into the corresponding requirements. + """ + requirements: List[InstallRequirement] = [] + for filename in options.constraints: + for parsed_req in parse_requirements( + filename, + constraint=True, + finder=finder, + options=options, + session=session, + ): + req_to_add = install_req_from_parsed_requirement( + parsed_req, + isolated=options.isolated_mode, + user_supplied=False, + ) + requirements.append(req_to_add) + + for req in args: + req_to_add = install_req_from_line( + req, + None, + isolated=options.isolated_mode, + use_pep517=options.use_pep517, + user_supplied=True, + ) + requirements.append(req_to_add) + + for req in options.editables: + req_to_add = install_req_from_editable( + req, + user_supplied=True, + isolated=options.isolated_mode, + use_pep517=options.use_pep517, + ) + requirements.append(req_to_add) + + # NOTE: options.require_hashes may be set if --require-hashes is True + for filename in options.requirements: + for parsed_req in parse_requirements( + filename, finder=finder, options=options, session=session + ): + req_to_add = install_req_from_parsed_requirement( + parsed_req, + isolated=options.isolated_mode, + use_pep517=options.use_pep517, + user_supplied=True, + ) + requirements.append(req_to_add) + + # If any requirement has hash options, enable hash checking. + if any(req.has_hash_options for req in requirements): + options.require_hashes = True + + if not (args or options.editables or options.requirements): + opts = {"name": self.name} + if options.find_links: + raise CommandError( + "You must give at least one requirement to {name} " + '(maybe you meant "pip {name} {links}"?)'.format( + **dict(opts, links=" ".join(options.find_links)) + ) + ) + else: + raise CommandError( + "You must give at least one requirement to {name} " + '(see "pip help {name}")'.format(**opts) + ) + + return requirements + + @staticmethod + def trace_basic_info(finder: PackageFinder) -> None: + """ + Trace basic information about the provided objects. + """ + # Display where finder is looking for packages + search_scope = finder.search_scope + locations = search_scope.get_formatted_locations() + if locations: + logger.info(locations) + + def _build_package_finder( + self, + options: Values, + session: PipSession, + target_python: Optional[TargetPython] = None, + ignore_requires_python: Optional[bool] = None, + ) -> PackageFinder: + """ + Create a package finder appropriate to this requirement command. + + :param ignore_requires_python: Whether to ignore incompatible + "Requires-Python" values in links. Defaults to False. + """ + link_collector = LinkCollector.create(session, options=options) + selection_prefs = SelectionPreferences( + allow_yanked=True, + format_control=options.format_control, + allow_all_prereleases=options.pre, + prefer_binary=options.prefer_binary, + ignore_requires_python=ignore_requires_python, + ) + + return PackageFinder.create( + link_collector=link_collector, + selection_prefs=selection_prefs, + target_python=target_python, + use_deprecated_html5lib="html5lib" in options.deprecated_features_enabled, + ) diff --git a/venv/lib/python3.10/site-packages/pip/_internal/cli/spinners.py b/venv/lib/python3.10/site-packages/pip/_internal/cli/spinners.py new file mode 100644 index 00000000..1e313e10 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip/_internal/cli/spinners.py @@ -0,0 +1,157 @@ +import contextlib +import itertools +import logging +import sys +import time +from typing import IO, Iterator + +from pip._vendor.progress import HIDE_CURSOR, SHOW_CURSOR + +from pip._internal.utils.compat import WINDOWS +from pip._internal.utils.logging import get_indentation + +logger = logging.getLogger(__name__) + + +class SpinnerInterface: + def spin(self) -> None: + raise NotImplementedError() + + def finish(self, final_status: str) -> None: + raise NotImplementedError() + + +class InteractiveSpinner(SpinnerInterface): + def __init__( + self, + message: str, + file: IO[str] = None, + spin_chars: str = "-\\|/", + # Empirically, 8 updates/second looks nice + min_update_interval_seconds: float = 0.125, + ): + self._message = message + if file is None: + file = sys.stdout + self._file = file + self._rate_limiter = RateLimiter(min_update_interval_seconds) + self._finished = False + + self._spin_cycle = itertools.cycle(spin_chars) + + self._file.write(" " * get_indentation() + self._message + " ... ") + self._width = 0 + + def _write(self, status: str) -> None: + assert not self._finished + # Erase what we wrote before by backspacing to the beginning, writing + # spaces to overwrite the old text, and then backspacing again + backup = "\b" * self._width + self._file.write(backup + " " * self._width + backup) + # Now we have a blank slate to add our status + self._file.write(status) + self._width = len(status) + self._file.flush() + self._rate_limiter.reset() + + def spin(self) -> None: + if self._finished: + return + if not self._rate_limiter.ready(): + return + self._write(next(self._spin_cycle)) + + def finish(self, final_status: str) -> None: + if self._finished: + return + self._write(final_status) + self._file.write("\n") + self._file.flush() + self._finished = True + + +# Used for dumb terminals, non-interactive installs (no tty), etc. +# We still print updates occasionally (once every 60 seconds by default) to +# act as a keep-alive for systems like Travis-CI that take lack-of-output as +# an indication that a task has frozen. +class NonInteractiveSpinner(SpinnerInterface): + def __init__(self, message: str, min_update_interval_seconds: float = 60.0) -> None: + self._message = message + self._finished = False + self._rate_limiter = RateLimiter(min_update_interval_seconds) + self._update("started") + + def _update(self, status: str) -> None: + assert not self._finished + self._rate_limiter.reset() + logger.info("%s: %s", self._message, status) + + def spin(self) -> None: + if self._finished: + return + if not self._rate_limiter.ready(): + return + self._update("still running...") + + def finish(self, final_status: str) -> None: + if self._finished: + return + self._update(f"finished with status '{final_status}'") + self._finished = True + + +class RateLimiter: + def __init__(self, min_update_interval_seconds: float) -> None: + self._min_update_interval_seconds = min_update_interval_seconds + self._last_update: float = 0 + + def ready(self) -> bool: + now = time.time() + delta = now - self._last_update + return delta >= self._min_update_interval_seconds + + def reset(self) -> None: + self._last_update = time.time() + + +@contextlib.contextmanager +def open_spinner(message: str) -> Iterator[SpinnerInterface]: + # Interactive spinner goes directly to sys.stdout rather than being routed + # through the logging system, but it acts like it has level INFO, + # i.e. it's only displayed if we're at level INFO or better. + # Non-interactive spinner goes through the logging system, so it is always + # in sync with logging configuration. + if sys.stdout.isatty() and logger.getEffectiveLevel() <= logging.INFO: + spinner: SpinnerInterface = InteractiveSpinner(message) + else: + spinner = NonInteractiveSpinner(message) + try: + with hidden_cursor(sys.stdout): + yield spinner + except KeyboardInterrupt: + spinner.finish("canceled") + raise + except Exception: + spinner.finish("error") + raise + else: + spinner.finish("done") + + +@contextlib.contextmanager +def hidden_cursor(file: IO[str]) -> Iterator[None]: + # The Windows terminal does not support the hide/show cursor ANSI codes, + # even via colorama. So don't even try. + if WINDOWS: + yield + # We don't want to clutter the output with control characters if we're + # writing to a file, or if the user is running with --quiet. + # See https://github.com/pypa/pip/issues/3418 + elif not file.isatty() or logger.getEffectiveLevel() > logging.INFO: + yield + else: + file.write(HIDE_CURSOR) + try: + yield + finally: + file.write(SHOW_CURSOR) diff --git a/venv/lib/python3.10/site-packages/pip/_internal/cli/status_codes.py b/venv/lib/python3.10/site-packages/pip/_internal/cli/status_codes.py new file mode 100644 index 00000000..5e29502c --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip/_internal/cli/status_codes.py @@ -0,0 +1,6 @@ +SUCCESS = 0 +ERROR = 1 +UNKNOWN_ERROR = 2 +VIRTUALENV_NOT_FOUND = 3 +PREVIOUS_BUILD_DIR_ERROR = 4 +NO_MATCHES_FOUND = 23 diff --git a/venv/lib/python3.10/site-packages/pip/_internal/commands/__init__.py b/venv/lib/python3.10/site-packages/pip/_internal/commands/__init__.py new file mode 100644 index 00000000..c72f24f3 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip/_internal/commands/__init__.py @@ -0,0 +1,127 @@ +""" +Package containing all pip commands +""" + +import importlib +from collections import namedtuple +from typing import Any, Dict, Optional + +from pip._internal.cli.base_command import Command + +CommandInfo = namedtuple("CommandInfo", "module_path, class_name, summary") + +# This dictionary does a bunch of heavy lifting for help output: +# - Enables avoiding additional (costly) imports for presenting `--help`. +# - The ordering matters for help display. +# +# Even though the module path starts with the same "pip._internal.commands" +# prefix, the full path makes testing easier (specifically when modifying +# `commands_dict` in test setup / teardown). +commands_dict: Dict[str, CommandInfo] = { + "install": CommandInfo( + "pip._internal.commands.install", + "InstallCommand", + "Install packages.", + ), + "download": CommandInfo( + "pip._internal.commands.download", + "DownloadCommand", + "Download packages.", + ), + "uninstall": CommandInfo( + "pip._internal.commands.uninstall", + "UninstallCommand", + "Uninstall packages.", + ), + "freeze": CommandInfo( + "pip._internal.commands.freeze", + "FreezeCommand", + "Output installed packages in requirements format.", + ), + "list": CommandInfo( + "pip._internal.commands.list", + "ListCommand", + "List installed packages.", + ), + "show": CommandInfo( + "pip._internal.commands.show", + "ShowCommand", + "Show information about installed packages.", + ), + "check": CommandInfo( + "pip._internal.commands.check", + "CheckCommand", + "Verify installed packages have compatible dependencies.", + ), + "config": CommandInfo( + "pip._internal.commands.configuration", + "ConfigurationCommand", + "Manage local and global configuration.", + ), + "search": CommandInfo( + "pip._internal.commands.search", + "SearchCommand", + "Search PyPI for packages.", + ), + "cache": CommandInfo( + "pip._internal.commands.cache", + "CacheCommand", + "Inspect and manage pip's wheel cache.", + ), + "index": CommandInfo( + "pip._internal.commands.index", + "IndexCommand", + "Inspect information available from package indexes.", + ), + "wheel": CommandInfo( + "pip._internal.commands.wheel", + "WheelCommand", + "Build wheels from your requirements.", + ), + "hash": CommandInfo( + "pip._internal.commands.hash", + "HashCommand", + "Compute hashes of package archives.", + ), + "completion": CommandInfo( + "pip._internal.commands.completion", + "CompletionCommand", + "A helper command used for command completion.", + ), + "debug": CommandInfo( + "pip._internal.commands.debug", + "DebugCommand", + "Show information useful for debugging.", + ), + "help": CommandInfo( + "pip._internal.commands.help", + "HelpCommand", + "Show help for commands.", + ), +} + + +def create_command(name: str, **kwargs: Any) -> Command: + """ + Create an instance of the Command class with the given name. + """ + module_path, class_name, summary = commands_dict[name] + module = importlib.import_module(module_path) + command_class = getattr(module, class_name) + command = command_class(name=name, summary=summary, **kwargs) + + return command + + +def get_similar_commands(name: str) -> Optional[str]: + """Command name auto-correct.""" + from difflib import get_close_matches + + name = name.lower() + + close_commands = get_close_matches(name, commands_dict.keys()) + + if close_commands: + return close_commands[0] + else: + return None diff --git a/venv/lib/python3.10/site-packages/pip/_internal/commands/cache.py b/venv/lib/python3.10/site-packages/pip/_internal/commands/cache.py new file mode 100644 index 00000000..f1a489d3 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip/_internal/commands/cache.py @@ -0,0 +1,223 @@ +import os +import textwrap +from optparse import Values +from typing import Any, List + +import pip._internal.utils.filesystem as filesystem +from pip._internal.cli.base_command import Command +from pip._internal.cli.status_codes import ERROR, SUCCESS +from pip._internal.exceptions import CommandError, PipError +from pip._internal.utils.logging import getLogger + +logger = getLogger(__name__) + + +class CacheCommand(Command): + """ + Inspect and manage pip's wheel cache. + + Subcommands: + + - dir: Show the cache directory. + - info: Show information about the cache. + - list: List filenames of packages stored in the cache. + - remove: Remove one or more package from the cache. + - purge: Remove all items from the cache. + + ```` can be a glob expression or a package name. + """ + + ignore_require_venv = True + usage = """ + %prog dir + %prog info + %prog list [] [--format=[human, abspath]] + %prog remove + %prog purge + """ + + def add_options(self) -> None: + + self.cmd_opts.add_option( + "--format", + action="store", + dest="list_format", + default="human", + choices=("human", "abspath"), + help="Select the output format among: human (default) or abspath", + ) + + self.parser.insert_option_group(0, self.cmd_opts) + + def run(self, options: Values, args: List[str]) -> int: + handlers = { + "dir": self.get_cache_dir, + "info": self.get_cache_info, + "list": self.list_cache_items, + "remove": self.remove_cache_items, + "purge": self.purge_cache, + } + + if not options.cache_dir: + logger.error("pip cache commands can not function since cache is disabled.") + return ERROR + + # Determine action + if not args or args[0] not in handlers: + logger.error( + "Need an action (%s) to perform.", + ", ".join(sorted(handlers)), + ) + return ERROR + + action = args[0] + + # Error handling happens here, not in the action-handlers. + try: + handlers[action](options, args[1:]) + except PipError as e: + logger.error(e.args[0]) + return ERROR + + return SUCCESS + + def get_cache_dir(self, options: Values, args: List[Any]) -> None: + if args: + raise CommandError("Too many arguments") + + logger.info(options.cache_dir) + + def get_cache_info(self, options: Values, args: List[Any]) -> None: + if args: + raise CommandError("Too many arguments") + + num_http_files = len(self._find_http_files(options)) + num_packages = len(self._find_wheels(options, "*")) + + http_cache_location = self._cache_dir(options, "http") + wheels_cache_location = self._cache_dir(options, "wheels") + http_cache_size = filesystem.format_directory_size(http_cache_location) + wheels_cache_size = filesystem.format_directory_size(wheels_cache_location) + + message = ( + textwrap.dedent( + """ + Package index page cache location: {http_cache_location} + Package index page cache size: {http_cache_size} + Number of HTTP files: {num_http_files} + Wheels location: {wheels_cache_location} + Wheels size: {wheels_cache_size} + Number of wheels: {package_count} + """ + ) + .format( + http_cache_location=http_cache_location, + http_cache_size=http_cache_size, + num_http_files=num_http_files, + wheels_cache_location=wheels_cache_location, + package_count=num_packages, + wheels_cache_size=wheels_cache_size, + ) + .strip() + ) + + logger.info(message) + + def list_cache_items(self, options: Values, args: List[Any]) -> None: + if len(args) > 1: + raise CommandError("Too many arguments") + + if args: + pattern = args[0] + else: + pattern = "*" + + files = self._find_wheels(options, pattern) + if options.list_format == "human": + self.format_for_human(files) + else: + self.format_for_abspath(files) + + def format_for_human(self, files: List[str]) -> None: + if not files: + logger.info("Nothing cached.") + return + + results = [] + for filename in files: + wheel = os.path.basename(filename) + size = filesystem.format_file_size(filename) + results.append(f" - {wheel} ({size})") + logger.info("Cache contents:\n") + logger.info("\n".join(sorted(results))) + + def format_for_abspath(self, files: List[str]) -> None: + if not files: + return + + results = [] + for filename in files: + results.append(filename) + + logger.info("\n".join(sorted(results))) + + def remove_cache_items(self, options: Values, args: List[Any]) -> None: + if len(args) > 1: + raise CommandError("Too many arguments") + + if not args: + raise CommandError("Please provide a pattern") + + files = self._find_wheels(options, args[0]) + + no_matching_msg = "No matching packages" + if args[0] == "*": + # Only fetch http files if no specific pattern given + files += self._find_http_files(options) + else: + # Add the pattern to the log message + no_matching_msg += ' for pattern "{}"'.format(args[0]) + + if not files: + logger.warning(no_matching_msg) + + for filename in files: + os.unlink(filename) + logger.verbose("Removed %s", filename) + logger.info("Files removed: %s", len(files)) + + def purge_cache(self, options: Values, args: List[Any]) -> None: + if args: + raise CommandError("Too many arguments") + + return self.remove_cache_items(options, ["*"]) + + def _cache_dir(self, options: Values, subdir: str) -> str: + return os.path.join(options.cache_dir, subdir) + + def _find_http_files(self, options: Values) -> List[str]: + http_dir = self._cache_dir(options, "http") + return filesystem.find_files(http_dir, "*") + + def _find_wheels(self, options: Values, pattern: str) -> List[str]: + wheel_dir = self._cache_dir(options, "wheels") + + # The wheel filename format, as specified in PEP 427, is: + # {distribution}-{version}(-{build})?-{python}-{abi}-{platform}.whl + # + # Additionally, non-alphanumeric values in the distribution are + # normalized to underscores (_), meaning hyphens can never occur + # before `-{version}`. + # + # Given that information: + # - If the pattern we're given contains a hyphen (-), the user is + # providing at least the version. Thus, we can just append `*.whl` + # to match the rest of it. + # - If the pattern we're given doesn't contain a hyphen (-), the + # user is only providing the name. Thus, we append `-*.whl` to + # match the hyphen before the version, followed by anything else. + # + # PEP 427: https://www.python.org/dev/peps/pep-0427/ + pattern = pattern + ("*.whl" if "-" in pattern else "-*.whl") + + return filesystem.find_files(wheel_dir, pattern) diff --git a/venv/lib/python3.10/site-packages/pip/_internal/commands/check.py b/venv/lib/python3.10/site-packages/pip/_internal/commands/check.py new file mode 100644 index 00000000..3864220b --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip/_internal/commands/check.py @@ -0,0 +1,53 @@ +import logging +from optparse import Values +from typing import List + +from pip._internal.cli.base_command import Command +from pip._internal.cli.status_codes import ERROR, SUCCESS +from pip._internal.operations.check import ( + check_package_set, + create_package_set_from_installed, +) +from pip._internal.utils.misc import write_output + +logger = logging.getLogger(__name__) + + +class CheckCommand(Command): + """Verify installed packages have compatible dependencies.""" + + usage = """ + %prog [options]""" + + def run(self, options: Values, args: List[str]) -> int: + + package_set, parsing_probs = create_package_set_from_installed() + missing, conflicting = check_package_set(package_set) + + for project_name in missing: + version = package_set[project_name].version + for dependency in missing[project_name]: + write_output( + "%s %s requires %s, which is not installed.", + project_name, + version, + dependency[0], + ) + + for project_name in conflicting: + version = package_set[project_name].version + for dep_name, dep_version, req in conflicting[project_name]: + write_output( + "%s %s has requirement %s, but you have %s %s.", + project_name, + version, + req, + dep_name, + dep_version, + ) + + if missing or conflicting or parsing_probs: + return ERROR + else: + write_output("No broken requirements found.") + return SUCCESS diff --git a/venv/lib/python3.10/site-packages/pip/_internal/commands/completion.py b/venv/lib/python3.10/site-packages/pip/_internal/commands/completion.py new file mode 100644 index 00000000..c0fb4caf --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip/_internal/commands/completion.py @@ -0,0 +1,96 @@ +import sys +import textwrap +from optparse import Values +from typing import List + +from pip._internal.cli.base_command import Command +from pip._internal.cli.status_codes import SUCCESS +from pip._internal.utils.misc import get_prog + +BASE_COMPLETION = """ +# pip {shell} completion start{script}# pip {shell} completion end +""" + +COMPLETION_SCRIPTS = { + "bash": """ + _pip_completion() + {{ + COMPREPLY=( $( COMP_WORDS="${{COMP_WORDS[*]}}" \\ + COMP_CWORD=$COMP_CWORD \\ + PIP_AUTO_COMPLETE=1 $1 2>/dev/null ) ) + }} + complete -o default -F _pip_completion {prog} + """, + "zsh": """ + function _pip_completion {{ + local words cword + read -Ac words + read -cn cword + reply=( $( COMP_WORDS="$words[*]" \\ + COMP_CWORD=$(( cword-1 )) \\ + PIP_AUTO_COMPLETE=1 $words[1] 2>/dev/null )) + }} + compctl -K _pip_completion {prog} + """, + "fish": """ + function __fish_complete_pip + set -lx COMP_WORDS (commandline -o) "" + set -lx COMP_CWORD ( \\ + math (contains -i -- (commandline -t) $COMP_WORDS)-1 \\ + ) + set -lx PIP_AUTO_COMPLETE 1 + string split \\ -- (eval $COMP_WORDS[1]) + end + complete -fa "(__fish_complete_pip)" -c {prog} + """, +} + + +class CompletionCommand(Command): + """A helper command to be used for command completion.""" + + ignore_require_venv = True + + def add_options(self) -> None: + self.cmd_opts.add_option( + "--bash", + "-b", + action="store_const", + const="bash", + dest="shell", + help="Emit completion code for bash", + ) + self.cmd_opts.add_option( + "--zsh", + "-z", + action="store_const", + const="zsh", + dest="shell", + help="Emit completion code for zsh", + ) + self.cmd_opts.add_option( + "--fish", + "-f", + action="store_const", + const="fish", + dest="shell", + help="Emit completion code for fish", + ) + + self.parser.insert_option_group(0, self.cmd_opts) + + def run(self, options: Values, args: List[str]) -> int: + """Prints the completion code of the given shell""" + shells = COMPLETION_SCRIPTS.keys() + shell_options = ["--" + shell for shell in sorted(shells)] + if options.shell in shells: + script = textwrap.dedent( + COMPLETION_SCRIPTS.get(options.shell, "").format(prog=get_prog()) + ) + print(BASE_COMPLETION.format(script=script, shell=options.shell)) + return SUCCESS + else: + sys.stderr.write( + "ERROR: You must pass {}\n".format(" or ".join(shell_options)) + ) + return SUCCESS diff --git a/venv/lib/python3.10/site-packages/pip/_internal/commands/configuration.py b/venv/lib/python3.10/site-packages/pip/_internal/commands/configuration.py new file mode 100644 index 00000000..c6c74ed5 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip/_internal/commands/configuration.py @@ -0,0 +1,266 @@ +import logging +import os +import subprocess +from optparse import Values +from typing import Any, List, Optional + +from pip._internal.cli.base_command import Command +from pip._internal.cli.status_codes import ERROR, SUCCESS +from pip._internal.configuration import ( + Configuration, + Kind, + get_configuration_files, + kinds, +) +from pip._internal.exceptions import PipError +from pip._internal.utils.logging import indent_log +from pip._internal.utils.misc import get_prog, write_output + +logger = logging.getLogger(__name__) + + +class ConfigurationCommand(Command): + """ + Manage local and global configuration. + + Subcommands: + + - list: List the active configuration (or from the file specified) + - edit: Edit the configuration file in an editor + - get: Get the value associated with name + - set: Set the name=value + - unset: Unset the value associated with name + - debug: List the configuration files and values defined under them + + If none of --user, --global and --site are passed, a virtual + environment configuration file is used if one is active and the file + exists. Otherwise, all modifications happen to the user file by + default. + """ + + ignore_require_venv = True + usage = """ + %prog [] list + %prog [] [--editor ] edit + + %prog [] get name + %prog [] set name value + %prog [] unset name + %prog [] debug + """ + + def add_options(self) -> None: + self.cmd_opts.add_option( + "--editor", + dest="editor", + action="store", + default=None, + help=( + "Editor to use to edit the file. Uses VISUAL or EDITOR " + "environment variables if not provided." + ), + ) + + self.cmd_opts.add_option( + "--global", + dest="global_file", + action="store_true", + default=False, + help="Use the system-wide configuration file only", + ) + + self.cmd_opts.add_option( + "--user", + dest="user_file", + action="store_true", + default=False, + help="Use the user configuration file only", + ) + + self.cmd_opts.add_option( + "--site", + dest="site_file", + action="store_true", + default=False, + help="Use the current environment configuration file only", + ) + + self.parser.insert_option_group(0, self.cmd_opts) + + def run(self, options: Values, args: List[str]) -> int: + handlers = { + "list": self.list_values, + "edit": self.open_in_editor, + "get": self.get_name, + "set": self.set_name_value, + "unset": self.unset_name, + "debug": self.list_config_values, + } + + # Determine action + if not args or args[0] not in handlers: + logger.error( + "Need an action (%s) to perform.", + ", ".join(sorted(handlers)), + ) + return ERROR + + action = args[0] + + # Determine which configuration files are to be loaded + # Depends on whether the command is modifying. + try: + load_only = self._determine_file( + options, need_value=(action in ["get", "set", "unset", "edit"]) + ) + except PipError as e: + logger.error(e.args[0]) + return ERROR + + # Load a new configuration + self.configuration = Configuration( + isolated=options.isolated_mode, load_only=load_only + ) + self.configuration.load() + + # Error handling happens here, not in the action-handlers. + try: + handlers[action](options, args[1:]) + except PipError as e: + logger.error(e.args[0]) + return ERROR + + return SUCCESS + + def _determine_file(self, options: Values, need_value: bool) -> Optional[Kind]: + file_options = [ + key + for key, value in ( + (kinds.USER, options.user_file), + (kinds.GLOBAL, options.global_file), + (kinds.SITE, options.site_file), + ) + if value + ] + + if not file_options: + if not need_value: + return None + # Default to user, unless there's a site file. + elif any( + os.path.exists(site_config_file) + for site_config_file in get_configuration_files()[kinds.SITE] + ): + return kinds.SITE + else: + return kinds.USER + elif len(file_options) == 1: + return file_options[0] + + raise PipError( + "Need exactly one file to operate upon " + "(--user, --site, --global) to perform." + ) + + def list_values(self, options: Values, args: List[str]) -> None: + self._get_n_args(args, "list", n=0) + + for key, value in sorted(self.configuration.items()): + write_output("%s=%r", key, value) + + def get_name(self, options: Values, args: List[str]) -> None: + key = self._get_n_args(args, "get [name]", n=1) + value = self.configuration.get_value(key) + + write_output("%s", value) + + def set_name_value(self, options: Values, args: List[str]) -> None: + key, value = self._get_n_args(args, "set [name] [value]", n=2) + self.configuration.set_value(key, value) + + self._save_configuration() + + def unset_name(self, options: Values, args: List[str]) -> None: + key = self._get_n_args(args, "unset [name]", n=1) + self.configuration.unset_value(key) + + self._save_configuration() + + def list_config_values(self, options: Values, args: List[str]) -> None: + """List config key-value pairs across different config files""" + self._get_n_args(args, "debug", n=0) + + self.print_env_var_values() + # Iterate over config files and print if they exist, and the + # key-value pairs present in them if they do + for variant, files in sorted(self.configuration.iter_config_files()): + write_output("%s:", variant) + for fname in files: + with indent_log(): + file_exists = os.path.exists(fname) + write_output("%s, exists: %r", fname, file_exists) + if file_exists: + self.print_config_file_values(variant) + + def print_config_file_values(self, variant: Kind) -> None: + """Get key-value pairs from the file of a variant""" + for name, value in self.configuration.get_values_in_config(variant).items(): + with indent_log(): + write_output("%s: %s", name, value) + + def print_env_var_values(self) -> None: + """Get key-values pairs present as environment variables""" + write_output("%s:", "env_var") + with indent_log(): + for key, value in sorted(self.configuration.get_environ_vars()): + env_var = f"PIP_{key.upper()}" + write_output("%s=%r", env_var, value) + + def open_in_editor(self, options: Values, args: List[str]) -> None: + editor = self._determine_editor(options) + + fname = self.configuration.get_file_to_edit() + if fname is None: + raise PipError("Could not determine appropriate file.") + + try: + subprocess.check_call([editor, fname]) + except subprocess.CalledProcessError as e: + raise PipError( + "Editor Subprocess exited with exit code {}".format(e.returncode) + ) + + def _get_n_args(self, args: List[str], example: str, n: int) -> Any: + """Helper to make sure the command got the right number of arguments""" + if len(args) != n: + msg = ( + "Got unexpected number of arguments, expected {}. " + '(example: "{} config {}")' + ).format(n, get_prog(), example) + raise PipError(msg) + + if n == 1: + return args[0] + else: + return args + + def _save_configuration(self) -> None: + # We successfully ran a modifying command. Need to save the + # configuration. + try: + self.configuration.save() + except Exception: + logger.exception( + "Unable to save configuration. Please report this as a bug." + ) + raise PipError("Internal Error.") + + def _determine_editor(self, options: Values) -> str: + if options.editor is not None: + return options.editor + elif "VISUAL" in os.environ: + return os.environ["VISUAL"] + elif "EDITOR" in os.environ: + return os.environ["EDITOR"] + else: + raise PipError("Could not determine editor to use.") diff --git a/venv/lib/python3.10/site-packages/pip/_internal/commands/debug.py b/venv/lib/python3.10/site-packages/pip/_internal/commands/debug.py new file mode 100644 index 00000000..d3f1f28d --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip/_internal/commands/debug.py @@ -0,0 +1,202 @@ +import locale +import logging +import os +import sys +from optparse import Values +from types import ModuleType +from typing import Any, Dict, List, Optional + +import pip._vendor +from pip._vendor.certifi import where +from pip._vendor.packaging.version import parse as parse_version + +from pip import __file__ as pip_location +from pip._internal.cli import cmdoptions +from pip._internal.cli.base_command import Command +from pip._internal.cli.cmdoptions import make_target_python +from pip._internal.cli.status_codes import SUCCESS +from pip._internal.configuration import Configuration +from pip._internal.metadata import get_environment +from pip._internal.utils.logging import indent_log +from pip._internal.utils.misc import get_pip_version + +logger = logging.getLogger(__name__) + + +def show_value(name: str, value: Any) -> None: + logger.info("%s: %s", name, value) + + +def show_sys_implementation() -> None: + logger.info("sys.implementation:") + implementation_name = sys.implementation.name + with indent_log(): + show_value("name", implementation_name) + + +def create_vendor_txt_map() -> Dict[str, str]: + vendor_txt_path = os.path.join( + os.path.dirname(pip_location), "_vendor", "vendor.txt" + ) + + with open(vendor_txt_path) as f: + # Purge non version specifying lines. + # Also, remove any space prefix or suffixes (including comments). + lines = [ + line.strip().split(" ", 1)[0] for line in f.readlines() if "==" in line + ] + + # Transform into "module" -> version dict. + return dict(line.split("==", 1) for line in lines) # type: ignore + + +def get_module_from_module_name(module_name: str) -> ModuleType: + # Module name can be uppercase in vendor.txt for some reason... + module_name = module_name.lower() + # PATCH: setuptools is actually only pkg_resources. + if module_name == "setuptools": + module_name = "pkg_resources" + + __import__(f"pip._vendor.{module_name}", globals(), locals(), level=0) + return getattr(pip._vendor, module_name) + + +def get_vendor_version_from_module(module_name: str) -> Optional[str]: + module = get_module_from_module_name(module_name) + version = getattr(module, "__version__", None) + + if not version: + # Try to find version in debundled module info. + env = get_environment([os.path.dirname(module.__file__)]) + dist = env.get_distribution(module_name) + if dist: + version = str(dist.version) + + return version + + +def show_actual_vendor_versions(vendor_txt_versions: Dict[str, str]) -> None: + """Log the actual version and print extra info if there is + a conflict or if the actual version could not be imported. + """ + for module_name, expected_version in vendor_txt_versions.items(): + extra_message = "" + actual_version = get_vendor_version_from_module(module_name) + if not actual_version: + extra_message = ( + " (Unable to locate actual module version, using" + " vendor.txt specified version)" + ) + actual_version = expected_version + elif parse_version(actual_version) != parse_version(expected_version): + extra_message = ( + " (CONFLICT: vendor.txt suggests version should" + " be {})".format(expected_version) + ) + logger.info("%s==%s%s", module_name, actual_version, extra_message) + + +def show_vendor_versions() -> None: + logger.info("vendored library versions:") + + vendor_txt_versions = create_vendor_txt_map() + with indent_log(): + show_actual_vendor_versions(vendor_txt_versions) + + +def show_tags(options: Values) -> None: + tag_limit = 10 + + target_python = make_target_python(options) + tags = target_python.get_tags() + + # Display the target options that were explicitly provided. + formatted_target = target_python.format_given() + suffix = "" + if formatted_target: + suffix = f" (target: {formatted_target})" + + msg = "Compatible tags: {}{}".format(len(tags), suffix) + logger.info(msg) + + if options.verbose < 1 and len(tags) > tag_limit: + tags_limited = True + tags = tags[:tag_limit] + else: + tags_limited = False + + with indent_log(): + for tag in tags: + logger.info(str(tag)) + + if tags_limited: + msg = ( + "...\n[First {tag_limit} tags shown. Pass --verbose to show all.]" + ).format(tag_limit=tag_limit) + logger.info(msg) + + +def ca_bundle_info(config: Configuration) -> str: + levels = set() + for key, _ in config.items(): + levels.add(key.split(".")[0]) + + if not levels: + return "Not specified" + + levels_that_override_global = ["install", "wheel", "download"] + global_overriding_level = [ + level for level in levels if level in levels_that_override_global + ] + if not global_overriding_level: + return "global" + + if "global" in levels: + levels.remove("global") + return ", ".join(levels) + + +class DebugCommand(Command): + """ + Display debug information. + """ + + usage = """ + %prog """ + ignore_require_venv = True + + def add_options(self) -> None: + cmdoptions.add_target_python_options(self.cmd_opts) + self.parser.insert_option_group(0, self.cmd_opts) + self.parser.config.load() + + def run(self, options: Values, args: List[str]) -> int: + logger.warning( + "This command is only meant for debugging. " + "Do not use this with automation for parsing and getting these " + "details, since the output and options of this command may " + "change without notice." + ) + show_value("pip version", get_pip_version()) + show_value("sys.version", sys.version) + show_value("sys.executable", sys.executable) + show_value("sys.getdefaultencoding", sys.getdefaultencoding()) + show_value("sys.getfilesystemencoding", sys.getfilesystemencoding()) + show_value( + "locale.getpreferredencoding", + locale.getpreferredencoding(), + ) + show_value("sys.platform", sys.platform) + show_sys_implementation() + + show_value("'cert' config value", ca_bundle_info(self.parser.config)) + show_value("REQUESTS_CA_BUNDLE", os.environ.get("REQUESTS_CA_BUNDLE")) + show_value("CURL_CA_BUNDLE", os.environ.get("CURL_CA_BUNDLE")) + show_value("pip._vendor.certifi.where()", where()) + show_value("pip._vendor.DEBUNDLED", pip._vendor.DEBUNDLED) + + show_vendor_versions() + + show_tags(options) + + return SUCCESS diff --git a/venv/lib/python3.10/site-packages/pip/_internal/commands/download.py b/venv/lib/python3.10/site-packages/pip/_internal/commands/download.py new file mode 100644 index 00000000..233b7e98 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip/_internal/commands/download.py @@ -0,0 +1,140 @@ +import logging +import os +from optparse import Values +from typing import List + +from pip._internal.cli import cmdoptions +from pip._internal.cli.cmdoptions import make_target_python +from pip._internal.cli.req_command import RequirementCommand, with_cleanup +from pip._internal.cli.status_codes import SUCCESS +from pip._internal.req.req_tracker import get_requirement_tracker +from pip._internal.utils.misc import ensure_dir, normalize_path, write_output +from pip._internal.utils.temp_dir import TempDirectory + +logger = logging.getLogger(__name__) + + +class DownloadCommand(RequirementCommand): + """ + Download packages from: + + - PyPI (and other indexes) using requirement specifiers. + - VCS project urls. + - Local project directories. + - Local or remote source archives. + + pip also supports downloading from "requirements files", which provide + an easy way to specify a whole environment to be downloaded. + """ + + usage = """ + %prog [options] [package-index-options] ... + %prog [options] -r [package-index-options] ... + %prog [options] ... + %prog [options] ... + %prog [options] ...""" + + def add_options(self) -> None: + self.cmd_opts.add_option(cmdoptions.constraints()) + self.cmd_opts.add_option(cmdoptions.requirements()) + self.cmd_opts.add_option(cmdoptions.no_deps()) + self.cmd_opts.add_option(cmdoptions.global_options()) + self.cmd_opts.add_option(cmdoptions.no_binary()) + self.cmd_opts.add_option(cmdoptions.only_binary()) + self.cmd_opts.add_option(cmdoptions.prefer_binary()) + self.cmd_opts.add_option(cmdoptions.src()) + self.cmd_opts.add_option(cmdoptions.pre()) + self.cmd_opts.add_option(cmdoptions.require_hashes()) + self.cmd_opts.add_option(cmdoptions.progress_bar()) + self.cmd_opts.add_option(cmdoptions.no_build_isolation()) + self.cmd_opts.add_option(cmdoptions.use_pep517()) + self.cmd_opts.add_option(cmdoptions.no_use_pep517()) + self.cmd_opts.add_option(cmdoptions.ignore_requires_python()) + + self.cmd_opts.add_option( + "-d", + "--dest", + "--destination-dir", + "--destination-directory", + dest="download_dir", + metavar="dir", + default=os.curdir, + help="Download packages into .", + ) + + cmdoptions.add_target_python_options(self.cmd_opts) + + index_opts = cmdoptions.make_option_group( + cmdoptions.index_group, + self.parser, + ) + + self.parser.insert_option_group(0, index_opts) + self.parser.insert_option_group(0, self.cmd_opts) + + @with_cleanup + def run(self, options: Values, args: List[str]) -> int: + + options.ignore_installed = True + # editable doesn't really make sense for `pip download`, but the bowels + # of the RequirementSet code require that property. + options.editables = [] + + cmdoptions.check_dist_restriction(options) + + options.download_dir = normalize_path(options.download_dir) + ensure_dir(options.download_dir) + + session = self.get_default_session(options) + + target_python = make_target_python(options) + finder = self._build_package_finder( + options=options, + session=session, + target_python=target_python, + ignore_requires_python=options.ignore_requires_python, + ) + + req_tracker = self.enter_context(get_requirement_tracker()) + + directory = TempDirectory( + delete=not options.no_clean, + kind="download", + globally_managed=True, + ) + + reqs = self.get_requirements(args, options, finder, session) + + preparer = self.make_requirement_preparer( + temp_build_dir=directory, + options=options, + req_tracker=req_tracker, + session=session, + finder=finder, + download_dir=options.download_dir, + use_user_site=False, + verbosity=self.verbosity, + ) + + resolver = self.make_resolver( + preparer=preparer, + finder=finder, + options=options, + ignore_requires_python=options.ignore_requires_python, + py_version_info=options.python_version, + ) + + self.trace_basic_info(finder) + + requirement_set = resolver.resolve(reqs, check_supported_wheels=True) + + downloaded: List[str] = [] + for req in requirement_set.requirements.values(): + if req.satisfied_by is None: + assert req.name is not None + preparer.save_linked_requirement(req) + downloaded.append(req.name) + if downloaded: + write_output("Successfully downloaded %s", " ".join(downloaded)) + + return SUCCESS diff --git a/venv/lib/python3.10/site-packages/pip/_internal/commands/freeze.py b/venv/lib/python3.10/site-packages/pip/_internal/commands/freeze.py new file mode 100644 index 00000000..6e9cc766 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip/_internal/commands/freeze.py @@ -0,0 +1,97 @@ +import sys +from optparse import Values +from typing import List + +from pip._internal.cli import cmdoptions +from pip._internal.cli.base_command import Command +from pip._internal.cli.status_codes import SUCCESS +from pip._internal.operations.freeze import freeze +from pip._internal.utils.compat import stdlib_pkgs + +DEV_PKGS = {"pip", "setuptools", "distribute", "wheel", "pkg-resources"} + + +class FreezeCommand(Command): + """ + Output installed packages in requirements format. + + packages are listed in a case-insensitive sorted order. + """ + + usage = """ + %prog [options]""" + log_streams = ("ext://sys.stderr", "ext://sys.stderr") + + def add_options(self) -> None: + self.cmd_opts.add_option( + "-r", + "--requirement", + dest="requirements", + action="append", + default=[], + metavar="file", + help=( + "Use the order in the given requirements file and its " + "comments when generating output. This option can be " + "used multiple times." + ), + ) + self.cmd_opts.add_option( + "-l", + "--local", + dest="local", + action="store_true", + default=False, + help=( + "If in a virtualenv that has global access, do not output " + "globally-installed packages." + ), + ) + self.cmd_opts.add_option( + "--user", + dest="user", + action="store_true", + default=False, + help="Only output packages installed in user-site.", + ) + self.cmd_opts.add_option(cmdoptions.list_path()) + self.cmd_opts.add_option( + "--all", + dest="freeze_all", + action="store_true", + help=( + "Do not skip these packages in the output:" + " {}".format(", ".join(DEV_PKGS)) + ), + ) + self.cmd_opts.add_option( + "--exclude-editable", + dest="exclude_editable", + action="store_true", + help="Exclude editable package from output.", + ) + self.cmd_opts.add_option(cmdoptions.list_exclude()) + + self.parser.insert_option_group(0, self.cmd_opts) + + def run(self, options: Values, args: List[str]) -> int: + skip = set(stdlib_pkgs) + if not options.freeze_all: + skip.update(DEV_PKGS) + + if options.excludes: + skip.update(options.excludes) + + cmdoptions.check_list_path_option(options) + + for line in freeze( + requirement=options.requirements, + local_only=options.local, + user_only=options.user, + paths=options.path, + isolated=options.isolated_mode, + skip=skip, + exclude_editable=options.exclude_editable, + ): + sys.stdout.write(line + "\n") + return SUCCESS diff --git a/venv/lib/python3.10/site-packages/pip/_internal/commands/hash.py b/venv/lib/python3.10/site-packages/pip/_internal/commands/hash.py new file mode 100644 index 00000000..042dac81 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip/_internal/commands/hash.py @@ -0,0 +1,59 @@ +import hashlib +import logging +import sys +from optparse import Values +from typing import List + +from pip._internal.cli.base_command import Command +from pip._internal.cli.status_codes import ERROR, SUCCESS +from pip._internal.utils.hashes import FAVORITE_HASH, STRONG_HASHES +from pip._internal.utils.misc import read_chunks, write_output + +logger = logging.getLogger(__name__) + + +class HashCommand(Command): + """ + Compute a hash of a local package archive. + + These can be used with --hash in a requirements file to do repeatable + installs. + """ + + usage = "%prog [options] ..." + ignore_require_venv = True + + def add_options(self) -> None: + self.cmd_opts.add_option( + "-a", + "--algorithm", + dest="algorithm", + choices=STRONG_HASHES, + action="store", + default=FAVORITE_HASH, + help="The hash algorithm to use: one of {}".format( + ", ".join(STRONG_HASHES) + ), + ) + self.parser.insert_option_group(0, self.cmd_opts) + + def run(self, options: Values, args: List[str]) -> int: + if not args: + self.parser.print_usage(sys.stderr) + return ERROR + + algorithm = options.algorithm + for path in args: + write_output( + "%s:\n--hash=%s:%s", path, algorithm, _hash_of_file(path, algorithm) + ) + return SUCCESS + + +def _hash_of_file(path: str, algorithm: str) -> str: + """Return the hash digest of a file.""" + with open(path, "rb") as archive: + hash = hashlib.new(algorithm) + for chunk in read_chunks(archive): + hash.update(chunk) + return hash.hexdigest() diff --git a/venv/lib/python3.10/site-packages/pip/_internal/commands/help.py b/venv/lib/python3.10/site-packages/pip/_internal/commands/help.py new file mode 100644 index 00000000..62066318 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pip/_internal/commands/help.py @@ -0,0 +1,41 @@ +from optparse import Values +from typing import List + +from pip._internal.cli.base_command import Command +from pip._internal.cli.status_codes import SUCCESS +from pip._internal.exceptions import CommandError + + +class HelpCommand(Command): + """Show help for commands""" + + usage = """ + %prog """ + ignore_require_venv = True + + def run(self, options: Values, args: List[str]) -> int: + from pip._internal.commands import ( + commands_dict, + create_command, + get_similar_commands, + ) + + try: + # 'pip help' with no args is handled by pip.__init__.parseopt() + cmd_name = args[0] # the command we need help for + except IndexError: + return SUCCESS + + if cmd_name not in commands_dict: + guess = get_similar_commands(cmd_name) + + msg = [f'unknown command "{cmd_name}"'] + if guess: + msg.append(f'maybe you meant "{guess}"') + + raise CommandError(" - ".join(msg)) + + command = create_command(cmd_name) + command.parser.print_help() + + return SUCCESS diff --git a/venv/lib/python3.10/site-packages/pip/_internal/commands/index.py b/venv/lib/python3.10/site-packages/pip/_internal/commands/index.py new file mode 100644 index 00000000..e69de29b diff --git a/venv/lib/python3.10/site-packages/pkg_resources/__init__.py b/venv/lib/python3.10/site-packages/pkg_resources/__init__.py new file mode 100644 index 00000000..955fdc48 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pkg_resources/__init__.py @@ -0,0 +1,3303 @@ +""" +Package resource API +-------------------- + +A resource is a logical file contained within a package, or a logical +subdirectory thereof. The package resource API expects resource names +to have their path parts separated with ``/``, *not* whatever the local +path separator is. Do not use os.path operations to manipulate resource +names being passed into the API. + +The package resource API is designed to work with normal filesystem packages, +.egg files, and unpacked .egg files. It can also work in a limited way with +.zip files and with custom PEP 302 loaders that support the ``get_data()`` +method. +""" + +import sys +import os +import io +import time +import re +import types +import zipfile +import zipimport +import warnings +import stat +import functools +import pkgutil +import operator +import platform +import collections +import plistlib +import email.parser +import errno +import tempfile +import textwrap +import itertools +import inspect +import ntpath +import posixpath +import importlib +from pkgutil import get_importer + +try: + import _imp +except ImportError: + # Python 3.2 compatibility + import imp as _imp + +try: + FileExistsError +except NameError: + FileExistsError = OSError + +# capture these to bypass sandboxing +from os import utime +try: + from os import mkdir, rename, unlink + WRITE_SUPPORT = True +except ImportError: + # no write support, probably under GAE + WRITE_SUPPORT = False + +from os import open as os_open +from os.path import isdir, split + +try: + import importlib.machinery as importlib_machinery + # access attribute to force import under delayed import mechanisms. + importlib_machinery.__name__ +except ImportError: + importlib_machinery = None + +from pkg_resources.extern import appdirs +from pkg_resources.extern import packaging +__import__('pkg_resources.extern.packaging.version') +__import__('pkg_resources.extern.packaging.specifiers') +__import__('pkg_resources.extern.packaging.requirements') +__import__('pkg_resources.extern.packaging.markers') + +if sys.version_info < (3, 5): + raise RuntimeError("Python 3.5 or later is required") + +# declare some globals that will be defined later to +# satisfy the linters. +require = None +working_set = None +add_activation_listener = None +resources_stream = None +cleanup_resources = None +resource_dir = None +resource_stream = None +set_extraction_path = None +resource_isdir = None +resource_string = None +iter_entry_points = None +resource_listdir = None +resource_filename = None +resource_exists = None +_distribution_finders = None +_namespace_handlers = None +_namespace_packages = None + + +class PEP440Warning(RuntimeWarning): + """ + Used when there is an issue with a version or specifier not complying with + PEP 440. + """ + + +def parse_version(v): + try: + return packaging.version.Version(v) + except packaging.version.InvalidVersion: + warnings.warn( + f"{v} is an invalid version and will not be supported in " + "a future release", + PkgResourcesDeprecationWarning, + ) + return packaging.version.LegacyVersion(v) + + +_state_vars = {} + + +def _declare_state(vartype, **kw): + globals().update(kw) + _state_vars.update(dict.fromkeys(kw, vartype)) + + +def __getstate__(): + state = {} + g = globals() + for k, v in _state_vars.items(): + state[k] = g['_sget_' + v](g[k]) + return state + + +def __setstate__(state): + g = globals() + for k, v in state.items(): + g['_sset_' + _state_vars[k]](k, g[k], v) + return state + + +def _sget_dict(val): + return val.copy() + + +def _sset_dict(key, ob, state): + ob.clear() + ob.update(state) + + +def _sget_object(val): + return val.__getstate__() + + +def _sset_object(key, ob, state): + ob.__setstate__(state) + + +_sget_none = _sset_none = lambda *args: None + + +def get_supported_platform(): + """Return this platform's maximum compatible version. + + distutils.util.get_platform() normally reports the minimum version + of macOS that would be required to *use* extensions produced by + distutils. But what we want when checking compatibility is to know the + version of macOS that we are *running*. To allow usage of packages that + explicitly require a newer version of macOS, we must also know the + current version of the OS. + + If this condition occurs for any other platform with a version in its + platform strings, this function should be extended accordingly. + """ + plat = get_build_platform() + m = macosVersionString.match(plat) + if m is not None and sys.platform == "darwin": + try: + plat = 'macosx-%s-%s' % ('.'.join(_macos_vers()[:2]), m.group(3)) + except ValueError: + # not macOS + pass + return plat + + +__all__ = [ + # Basic resource access and distribution/entry point discovery + 'require', 'run_script', 'get_provider', 'get_distribution', + 'load_entry_point', 'get_entry_map', 'get_entry_info', + 'iter_entry_points', + 'resource_string', 'resource_stream', 'resource_filename', + 'resource_listdir', 'resource_exists', 'resource_isdir', + + # Environmental control + 'declare_namespace', 'working_set', 'add_activation_listener', + 'find_distributions', 'set_extraction_path', 'cleanup_resources', + 'get_default_cache', + + # Primary implementation classes + 'Environment', 'WorkingSet', 'ResourceManager', + 'Distribution', 'Requirement', 'EntryPoint', + + # Exceptions + 'ResolutionError', 'VersionConflict', 'DistributionNotFound', + 'UnknownExtra', 'ExtractionError', + + # Warnings + 'PEP440Warning', + + # Parsing functions and string utilities + 'parse_requirements', 'parse_version', 'safe_name', 'safe_version', + 'get_platform', 'compatible_platforms', 'yield_lines', 'split_sections', + 'safe_extra', 'to_filename', 'invalid_marker', 'evaluate_marker', + + # filesystem utilities + 'ensure_directory', 'normalize_path', + + # Distribution "precedence" constants + 'EGG_DIST', 'BINARY_DIST', 'SOURCE_DIST', 'CHECKOUT_DIST', 'DEVELOP_DIST', + + # "Provider" interfaces, implementations, and registration/lookup APIs + 'IMetadataProvider', 'IResourceProvider', 'FileMetadata', + 'PathMetadata', 'EggMetadata', 'EmptyProvider', 'empty_provider', + 'NullProvider', 'EggProvider', 'DefaultProvider', 'ZipProvider', + 'register_finder', 'register_namespace_handler', 'register_loader_type', + 'fixup_namespace_packages', 'get_importer', + + # Warnings + 'PkgResourcesDeprecationWarning', + + # Deprecated/backward compatibility only + 'run_main', 'AvailableDistributions', +] + + +class ResolutionError(Exception): + """Abstract base for dependency resolution errors""" + + def __repr__(self): + return self.__class__.__name__ + repr(self.args) + + +class VersionConflict(ResolutionError): + """ + An already-installed version conflicts with the requested version. + + Should be initialized with the installed Distribution and the requested + Requirement. + """ + + _template = "{self.dist} is installed but {self.req} is required" + + @property + def dist(self): + return self.args[0] + + @property + def req(self): + return self.args[1] + + def report(self): + return self._template.format(**locals()) + + def with_context(self, required_by): + """ + If required_by is non-empty, return a version of self that is a + ContextualVersionConflict. + """ + if not required_by: + return self + args = self.args + (required_by,) + return ContextualVersionConflict(*args) + + +class ContextualVersionConflict(VersionConflict): + """ + A VersionConflict that accepts a third parameter, the set of the + requirements that required the installed Distribution. + """ + + _template = VersionConflict._template + ' by {self.required_by}' + + @property + def required_by(self): + return self.args[2] + + +class DistributionNotFound(ResolutionError): + """A requested distribution was not found""" + + _template = ("The '{self.req}' distribution was not found " + "and is required by {self.requirers_str}") + + @property + def req(self): + return self.args[0] + + @property + def requirers(self): + return self.args[1] + + @property + def requirers_str(self): + if not self.requirers: + return 'the application' + return ', '.join(self.requirers) + + def report(self): + return self._template.format(**locals()) + + def __str__(self): + return self.report() + + +class UnknownExtra(ResolutionError): + """Distribution doesn't have an "extra feature" of the given name""" + + +_provider_factories = {} + +PY_MAJOR = '{}.{}'.format(*sys.version_info) +EGG_DIST = 3 +BINARY_DIST = 2 +SOURCE_DIST = 1 +CHECKOUT_DIST = 0 +DEVELOP_DIST = -1 + + +def register_loader_type(loader_type, provider_factory): + """Register `provider_factory` to make providers for `loader_type` + + `loader_type` is the type or class of a PEP 302 ``module.__loader__``, + and `provider_factory` is a function that, passed a *module* object, + returns an ``IResourceProvider`` for that module. + """ + _provider_factories[loader_type] = provider_factory + + +def get_provider(moduleOrReq): + """Return an IResourceProvider for the named module or requirement""" + if isinstance(moduleOrReq, Requirement): + return working_set.find(moduleOrReq) or require(str(moduleOrReq))[0] + try: + module = sys.modules[moduleOrReq] + except KeyError: + __import__(moduleOrReq) + module = sys.modules[moduleOrReq] + loader = getattr(module, '__loader__', None) + return _find_adapter(_provider_factories, loader)(module) + + +def _macos_vers(_cache=[]): + if not _cache: + version = platform.mac_ver()[0] + # fallback for MacPorts + if version == '': + plist = '/System/Library/CoreServices/SystemVersion.plist' + if os.path.exists(plist): + if hasattr(plistlib, 'readPlist'): + plist_content = plistlib.readPlist(plist) + if 'ProductVersion' in plist_content: + version = plist_content['ProductVersion'] + + _cache.append(version.split('.')) + return _cache[0] + + +def _macos_arch(machine): + return {'PowerPC': 'ppc', 'Power_Macintosh': 'ppc'}.get(machine, machine) + + +def get_build_platform(): + """Return this platform's string for platform-specific distributions + + XXX Currently this is the same as ``distutils.util.get_platform()``, but it + needs some hacks for Linux and macOS. + """ + from sysconfig import get_platform + + plat = get_platform() + if sys.platform == "darwin" and not plat.startswith('macosx-'): + try: + version = _macos_vers() + machine = os.uname()[4].replace(" ", "_") + return "macosx-%d.%d-%s" % ( + int(version[0]), int(version[1]), + _macos_arch(machine), + ) + except ValueError: + # if someone is running a non-Mac darwin system, this will fall + # through to the default implementation + pass + return plat + + +macosVersionString = re.compile(r"macosx-(\d+)\.(\d+)-(.*)") +darwinVersionString = re.compile(r"darwin-(\d+)\.(\d+)\.(\d+)-(.*)") +# XXX backward compat +get_platform = get_build_platform + + +def compatible_platforms(provided, required): + """Can code for the `provided` platform run on the `required` platform? + + Returns true if either platform is ``None``, or the platforms are equal. + + XXX Needs compatibility checks for Linux and other unixy OSes. + """ + if provided is None or required is None or provided == required: + # easy case + return True + + # macOS special cases + reqMac = macosVersionString.match(required) + if reqMac: + provMac = macosVersionString.match(provided) + + # is this a Mac package? + if not provMac: + # this is backwards compatibility for packages built before + # setuptools 0.6. All packages built after this point will + # use the new macOS designation. + provDarwin = darwinVersionString.match(provided) + if provDarwin: + dversion = int(provDarwin.group(1)) + macosversion = "%s.%s" % (reqMac.group(1), reqMac.group(2)) + if dversion == 7 and macosversion >= "10.3" or \ + dversion == 8 and macosversion >= "10.4": + return True + # egg isn't macOS or legacy darwin + return False + + # are they the same major version and machine type? + if provMac.group(1) != reqMac.group(1) or \ + provMac.group(3) != reqMac.group(3): + return False + + # is the required OS major update >= the provided one? + if int(provMac.group(2)) > int(reqMac.group(2)): + return False + + return True + + # XXX Linux and other platforms' special cases should go here + return False + + +def run_script(dist_spec, script_name): + """Locate distribution `dist_spec` and run its `script_name` script""" + ns = sys._getframe(1).f_globals + name = ns['__name__'] + ns.clear() + ns['__name__'] = name + require(dist_spec)[0].run_script(script_name, ns) + + +# backward compatibility +run_main = run_script + + +def get_distribution(dist): + """Return a current distribution object for a Requirement or string""" + if isinstance(dist, str): + dist = Requirement.parse(dist) + if isinstance(dist, Requirement): + dist = get_provider(dist) + if not isinstance(dist, Distribution): + raise TypeError("Expected string, Requirement, or Distribution", dist) + return dist + + +def load_entry_point(dist, group, name): + """Return `name` entry point of `group` for `dist` or raise ImportError""" + return get_distribution(dist).load_entry_point(group, name) + + +def get_entry_map(dist, group=None): + """Return the entry point map for `group`, or the full entry map""" + return get_distribution(dist).get_entry_map(group) + + +def get_entry_info(dist, group, name): + """Return the EntryPoint object for `group`+`name`, or ``None``""" + return get_distribution(dist).get_entry_info(group, name) + + +class IMetadataProvider: + def has_metadata(name): + """Does the package's distribution contain the named metadata?""" + + def get_metadata(name): + """The named metadata resource as a string""" + + def get_metadata_lines(name): + """Yield named metadata resource as list of non-blank non-comment lines + + Leading and trailing whitespace is stripped from each line, and lines + with ``#`` as the first non-blank character are omitted.""" + + def metadata_isdir(name): + """Is the named metadata a directory? (like ``os.path.isdir()``)""" + + def metadata_listdir(name): + """List of metadata names in the directory (like ``os.listdir()``)""" + + def run_script(script_name, namespace): + """Execute the named script in the supplied namespace dictionary""" + + +class IResourceProvider(IMetadataProvider): + """An object that provides access to package resources""" + + def get_resource_filename(manager, resource_name): + """Return a true filesystem path for `resource_name` + + `manager` must be an ``IResourceManager``""" + + def get_resource_stream(manager, resource_name): + """Return a readable file-like object for `resource_name` + + `manager` must be an ``IResourceManager``""" + + def get_resource_string(manager, resource_name): + """Return a string containing the contents of `resource_name` + + `manager` must be an ``IResourceManager``""" + + def has_resource(resource_name): + """Does the package contain the named resource?""" + + def resource_isdir(resource_name): + """Is the named resource a directory? (like ``os.path.isdir()``)""" + + def resource_listdir(resource_name): + """List of resource names in the directory (like ``os.listdir()``)""" + + +class WorkingSet: + """A collection of active distributions on sys.path (or a similar list)""" + + def __init__(self, entries=None): + """Create working set from list of path entries (default=sys.path)""" + self.entries = [] + self.entry_keys = {} + self.by_key = {} + self.callbacks = [] + + if entries is None: + entries = sys.path + + for entry in entries: + self.add_entry(entry) + + @classmethod + def _build_master(cls): + """ + Prepare the master working set. + """ + ws = cls() + try: + from __main__ import __requires__ + except ImportError: + # The main program does not list any requirements + return ws + + # ensure the requirements are met + try: + ws.require(__requires__) + except VersionConflict: + return cls._build_from_requirements(__requires__) + + return ws + + @classmethod + def _build_from_requirements(cls, req_spec): + """ + Build a working set from a requirement spec. Rewrites sys.path. + """ + # try it without defaults already on sys.path + # by starting with an empty path + ws = cls([]) + reqs = parse_requirements(req_spec) + dists = ws.resolve(reqs, Environment()) + for dist in dists: + ws.add(dist) + + # add any missing entries from sys.path + for entry in sys.path: + if entry not in ws.entries: + ws.add_entry(entry) + + # then copy back to sys.path + sys.path[:] = ws.entries + return ws + + def add_entry(self, entry): + """Add a path item to ``.entries``, finding any distributions on it + + ``find_distributions(entry, True)`` is used to find distributions + corresponding to the path entry, and they are added. `entry` is + always appended to ``.entries``, even if it is already present. + (This is because ``sys.path`` can contain the same value more than + once, and the ``.entries`` of the ``sys.path`` WorkingSet should always + equal ``sys.path``.) + """ + self.entry_keys.setdefault(entry, []) + self.entries.append(entry) + for dist in find_distributions(entry, True): + self.add(dist, entry, False) + + def __contains__(self, dist): + """True if `dist` is the active distribution for its project""" + return self.by_key.get(dist.key) == dist + + def find(self, req): + """Find a distribution matching requirement `req` + + If there is an active distribution for the requested project, this + returns it as long as it meets the version requirement specified by + `req`. But, if there is an active distribution for the project and it + does *not* meet the `req` requirement, ``VersionConflict`` is raised. + If there is no active distribution for the requested project, ``None`` + is returned. + """ + dist = self.by_key.get(req.key) + if dist is not None and dist not in req: + # XXX add more info + raise VersionConflict(dist, req) + return dist + + def iter_entry_points(self, group, name=None): + """Yield entry point objects from `group` matching `name` + + If `name` is None, yields all entry points in `group` from all + distributions in the working set, otherwise only ones matching + both `group` and `name` are yielded (in distribution order). + """ + return ( + entry + for dist in self + for entry in dist.get_entry_map(group).values() + if name is None or name == entry.name + ) + + def run_script(self, requires, script_name): + """Locate distribution for `requires` and run `script_name` script""" + ns = sys._getframe(1).f_globals + name = ns['__name__'] + ns.clear() + ns['__name__'] = name + self.require(requires)[0].run_script(script_name, ns) + + def __iter__(self): + """Yield distributions for non-duplicate projects in the working set + + The yield order is the order in which the items' path entries were + added to the working set. + """ + seen = {} + for item in self.entries: + if item not in self.entry_keys: + # workaround a cache issue + continue + + for key in self.entry_keys[item]: + if key not in seen: + seen[key] = 1 + yield self.by_key[key] + + def add(self, dist, entry=None, insert=True, replace=False): + """Add `dist` to working set, associated with `entry` + + If `entry` is unspecified, it defaults to the ``.location`` of `dist`. + On exit from this routine, `entry` is added to the end of the working + set's ``.entries`` (if it wasn't already present). + + `dist` is only added to the working set if it's for a project that + doesn't already have a distribution in the set, unless `replace=True`. + If it's added, any callbacks registered with the ``subscribe()`` method + will be called. + """ + if insert: + dist.insert_on(self.entries, entry, replace=replace) + + if entry is None: + entry = dist.location + keys = self.entry_keys.setdefault(entry, []) + keys2 = self.entry_keys.setdefault(dist.location, []) + if not replace and dist.key in self.by_key: + # ignore hidden distros + return + + self.by_key[dist.key] = dist + if dist.key not in keys: + keys.append(dist.key) + if dist.key not in keys2: + keys2.append(dist.key) + self._added_new(dist) + + # FIXME: 'WorkingSet.resolve' is too complex (11) + def resolve(self, requirements, env=None, installer=None, # noqa: C901 + replace_conflicting=False, extras=None): + """List all distributions needed to (recursively) meet `requirements` + + `requirements` must be a sequence of ``Requirement`` objects. `env`, + if supplied, should be an ``Environment`` instance. If + not supplied, it defaults to all distributions available within any + entry or distribution in the working set. `installer`, if supplied, + will be invoked with each requirement that cannot be met by an + already-installed distribution; it should return a ``Distribution`` or + ``None``. + + Unless `replace_conflicting=True`, raises a VersionConflict exception + if + any requirements are found on the path that have the correct name but + the wrong version. Otherwise, if an `installer` is supplied it will be + invoked to obtain the correct version of the requirement and activate + it. + + `extras` is a list of the extras to be used with these requirements. + This is important because extra requirements may look like `my_req; + extra = "my_extra"`, which would otherwise be interpreted as a purely + optional requirement. Instead, we want to be able to assert that these + requirements are truly required. + """ + + # set up the stack + requirements = list(requirements)[::-1] + # set of processed requirements + processed = {} + # key -> dist + best = {} + to_activate = [] + + req_extras = _ReqExtras() + + # Mapping of requirement to set of distributions that required it; + # useful for reporting info about conflicts. + required_by = collections.defaultdict(set) + + while requirements: + # process dependencies breadth-first + req = requirements.pop(0) + if req in processed: + # Ignore cyclic or redundant dependencies + continue + + if not req_extras.markers_pass(req, extras): + continue + + dist = best.get(req.key) + if dist is None: + # Find the best distribution and add it to the map + dist = self.by_key.get(req.key) + if dist is None or (dist not in req and replace_conflicting): + ws = self + if env is None: + if dist is None: + env = Environment(self.entries) + else: + # Use an empty environment and workingset to avoid + # any further conflicts with the conflicting + # distribution + env = Environment([]) + ws = WorkingSet([]) + dist = best[req.key] = env.best_match( + req, ws, installer, + replace_conflicting=replace_conflicting + ) + if dist is None: + requirers = required_by.get(req, None) + raise DistributionNotFound(req, requirers) + to_activate.append(dist) + if dist not in req: + # Oops, the "best" so far conflicts with a dependency + dependent_req = required_by[req] + raise VersionConflict(dist, req).with_context(dependent_req) + + # push the new requirements onto the stack + new_requirements = dist.requires(req.extras)[::-1] + requirements.extend(new_requirements) + + # Register the new requirements needed by req + for new_requirement in new_requirements: + required_by[new_requirement].add(req.project_name) + req_extras[new_requirement] = req.extras + + processed[req] = True + + # return list of distros to activate + return to_activate + + def find_plugins( + self, plugin_env, full_env=None, installer=None, fallback=True): + """Find all activatable distributions in `plugin_env` + + Example usage:: + + distributions, errors = working_set.find_plugins( + Environment(plugin_dirlist) + ) + # add plugins+libs to sys.path + map(working_set.add, distributions) + # display errors + print('Could not load', errors) + + The `plugin_env` should be an ``Environment`` instance that contains + only distributions that are in the project's "plugin directory" or + directories. The `full_env`, if supplied, should be an ``Environment`` + contains all currently-available distributions. If `full_env` is not + supplied, one is created automatically from the ``WorkingSet`` this + method is called on, which will typically mean that every directory on + ``sys.path`` will be scanned for distributions. + + `installer` is a standard installer callback as used by the + ``resolve()`` method. The `fallback` flag indicates whether we should + attempt to resolve older versions of a plugin if the newest version + cannot be resolved. + + This method returns a 2-tuple: (`distributions`, `error_info`), where + `distributions` is a list of the distributions found in `plugin_env` + that were loadable, along with any other distributions that are needed + to resolve their dependencies. `error_info` is a dictionary mapping + unloadable plugin distributions to an exception instance describing the + error that occurred. Usually this will be a ``DistributionNotFound`` or + ``VersionConflict`` instance. + """ + + plugin_projects = list(plugin_env) + # scan project names in alphabetic order + plugin_projects.sort() + + error_info = {} + distributions = {} + + if full_env is None: + env = Environment(self.entries) + env += plugin_env + else: + env = full_env + plugin_env + + shadow_set = self.__class__([]) + # put all our entries in shadow_set + list(map(shadow_set.add, self)) + + for project_name in plugin_projects: + + for dist in plugin_env[project_name]: + + req = [dist.as_requirement()] + + try: + resolvees = shadow_set.resolve(req, env, installer) + + except ResolutionError as v: + # save error info + error_info[dist] = v + if fallback: + # try the next older version of project + continue + else: + # give up on this project, keep going + break + + else: + list(map(shadow_set.add, resolvees)) + distributions.update(dict.fromkeys(resolvees)) + + # success, no need to try any more versions of this project + break + + distributions = list(distributions) + distributions.sort() + + return distributions, error_info + + def require(self, *requirements): + """Ensure that distributions matching `requirements` are activated + + `requirements` must be a string or a (possibly-nested) sequence + thereof, specifying the distributions and versions required. The + return value is a sequence of the distributions that needed to be + activated to fulfill the requirements; all relevant distributions are + included, even if they were already activated in this working set. + """ + needed = self.resolve(parse_requirements(requirements)) + + for dist in needed: + self.add(dist) + + return needed + + def subscribe(self, callback, existing=True): + """Invoke `callback` for all distributions + + If `existing=True` (default), + call on all existing ones, as well. + """ + if callback in self.callbacks: + return + self.callbacks.append(callback) + if not existing: + return + for dist in self: + callback(dist) + + def _added_new(self, dist): + for callback in self.callbacks: + callback(dist) + + def __getstate__(self): + return ( + self.entries[:], self.entry_keys.copy(), self.by_key.copy(), + self.callbacks[:] + ) + + def __setstate__(self, e_k_b_c): + entries, keys, by_key, callbacks = e_k_b_c + self.entries = entries[:] + self.entry_keys = keys.copy() + self.by_key = by_key.copy() + self.callbacks = callbacks[:] + + +class _ReqExtras(dict): + """ + Map each requirement to the extras that demanded it. + """ + + def markers_pass(self, req, extras=None): + """ + Evaluate markers for req against each extra that + demanded it. + + Return False if the req has a marker and fails + evaluation. Otherwise, return True. + """ + extra_evals = ( + req.marker.evaluate({'extra': extra}) + for extra in self.get(req, ()) + (extras or (None,)) + ) + return not req.marker or any(extra_evals) + + +class Environment: + """Searchable snapshot of distributions on a search path""" + + def __init__( + self, search_path=None, platform=get_supported_platform(), + python=PY_MAJOR): + """Snapshot distributions available on a search path + + Any distributions found on `search_path` are added to the environment. + `search_path` should be a sequence of ``sys.path`` items. If not + supplied, ``sys.path`` is used. + + `platform` is an optional string specifying the name of the platform + that platform-specific distributions must be compatible with. If + unspecified, it defaults to the current platform. `python` is an + optional string naming the desired version of Python (e.g. ``'3.6'``); + it defaults to the current version. + + You may explicitly set `platform` (and/or `python`) to ``None`` if you + wish to map *all* distributions, not just those compatible with the + running platform or Python version. + """ + self._distmap = {} + self.platform = platform + self.python = python + self.scan(search_path) + + def can_add(self, dist): + """Is distribution `dist` acceptable for this environment? + + The distribution must match the platform and python version + requirements specified when this environment was created, or False + is returned. + """ + py_compat = ( + self.python is None + or dist.py_version is None + or dist.py_version == self.python + ) + return py_compat and compatible_platforms(dist.platform, self.platform) + + def remove(self, dist): + """Remove `dist` from the environment""" + self._distmap[dist.key].remove(dist) + + def scan(self, search_path=None): + """Scan `search_path` for distributions usable in this environment + + Any distributions found are added to the environment. + `search_path` should be a sequence of ``sys.path`` items. If not + supplied, ``sys.path`` is used. Only distributions conforming to + the platform/python version defined at initialization are added. + """ + if search_path is None: + search_path = sys.path + + for item in search_path: + for dist in find_distributions(item): + self.add(dist) + + def __getitem__(self, project_name): + """Return a newest-to-oldest list of distributions for `project_name` + + Uses case-insensitive `project_name` comparison, assuming all the + project's distributions use their project's name converted to all + lowercase as their key. + + """ + distribution_key = project_name.lower() + return self._distmap.get(distribution_key, []) + + def add(self, dist): + """Add `dist` if we ``can_add()`` it and it has not already been added + """ + if self.can_add(dist) and dist.has_version(): + dists = self._distmap.setdefault(dist.key, []) + if dist not in dists: + dists.append(dist) + dists.sort(key=operator.attrgetter('hashcmp'), reverse=True) + + def best_match( + self, req, working_set, installer=None, replace_conflicting=False): + """Find distribution best matching `req` and usable on `working_set` + + This calls the ``find(req)`` method of the `working_set` to see if a + suitable distribution is already active. (This may raise + ``VersionConflict`` if an unsuitable version of the project is already + active in the specified `working_set`.) If a suitable distribution + isn't active, this method returns the newest distribution in the + environment that meets the ``Requirement`` in `req`. If no suitable + distribution is found, and `installer` is supplied, then the result of + calling the environment's ``obtain(req, installer)`` method will be + returned. + """ + try: + dist = working_set.find(req) + except VersionConflict: + if not replace_conflicting: + raise + dist = None + if dist is not None: + return dist + for dist in self[req.key]: + if dist in req: + return dist + # try to download/install + return self.obtain(req, installer) + + def obtain(self, requirement, installer=None): + """Obtain a distribution matching `requirement` (e.g. via download) + + Obtain a distro that matches requirement (e.g. via download). In the + base ``Environment`` class, this routine just returns + ``installer(requirement)``, unless `installer` is None, in which case + None is returned instead. This method is a hook that allows subclasses + to attempt other ways of obtaining a distribution before falling back + to the `installer` argument.""" + if installer is not None: + return installer(requirement) + + def __iter__(self): + """Yield the unique project names of the available distributions""" + for key in self._distmap.keys(): + if self[key]: + yield key + + def __iadd__(self, other): + """In-place addition of a distribution or environment""" + if isinstance(other, Distribution): + self.add(other) + elif isinstance(other, Environment): + for project in other: + for dist in other[project]: + self.add(dist) + else: + raise TypeError("Can't add %r to environment" % (other,)) + return self + + def __add__(self, other): + """Add an environment or distribution to an environment""" + new = self.__class__([], platform=None, python=None) + for env in self, other: + new += env + return new + + +# XXX backward compatibility +AvailableDistributions = Environment + + +class ExtractionError(RuntimeError): + """An error occurred extracting a resource + + The following attributes are available from instances of this exception: + + manager + The resource manager that raised this exception + + cache_path + The base directory for resource extraction + + original_error + The exception instance that caused extraction to fail + """ + + +class ResourceManager: + """Manage resource extraction and packages""" + extraction_path = None + + def __init__(self): + self.cached_files = {} + + def resource_exists(self, package_or_requirement, resource_name): + """Does the named resource exist?""" + return get_provider(package_or_requirement).has_resource(resource_name) + + def resource_isdir(self, package_or_requirement, resource_name): + """Is the named resource an existing directory?""" + return get_provider(package_or_requirement).resource_isdir( + resource_name + ) + + def resource_filename(self, package_or_requirement, resource_name): + """Return a true filesystem path for specified resource""" + return get_provider(package_or_requirement).get_resource_filename( + self, resource_name + ) + + def resource_stream(self, package_or_requirement, resource_name): + """Return a readable file-like object for specified resource""" + return get_provider(package_or_requirement).get_resource_stream( + self, resource_name + ) + + def resource_string(self, package_or_requirement, resource_name): + """Return specified resource as a string""" + return get_provider(package_or_requirement).get_resource_string( + self, resource_name + ) + + def resource_listdir(self, package_or_requirement, resource_name): + """List the contents of the named resource directory""" + return get_provider(package_or_requirement).resource_listdir( + resource_name + ) + + def extraction_error(self): + """Give an error message for problems extracting file(s)""" + + old_exc = sys.exc_info()[1] + cache_path = self.extraction_path or get_default_cache() + + tmpl = textwrap.dedent(""" + Can't extract file(s) to egg cache + + The following error occurred while trying to extract file(s) + to the Python egg cache: + + {old_exc} + + The Python egg cache directory is currently set to: + + {cache_path} + + Perhaps your account does not have write access to this directory? + You can change the cache directory by setting the PYTHON_EGG_CACHE + environment variable to point to an accessible directory. + """).lstrip() + err = ExtractionError(tmpl.format(**locals())) + err.manager = self + err.cache_path = cache_path + err.original_error = old_exc + raise err + + def get_cache_path(self, archive_name, names=()): + """Return absolute location in cache for `archive_name` and `names` + + The parent directory of the resulting path will be created if it does + not already exist. `archive_name` should be the base filename of the + enclosing egg (which may not be the name of the enclosing zipfile!), + including its ".egg" extension. `names`, if provided, should be a + sequence of path name parts "under" the egg's extraction location. + + This method should only be called by resource providers that need to + obtain an extraction location, and only for names they intend to + extract, as it tracks the generated names for possible cleanup later. + """ + extract_path = self.extraction_path or get_default_cache() + target_path = os.path.join(extract_path, archive_name + '-tmp', *names) + try: + _bypass_ensure_directory(target_path) + except Exception: + self.extraction_error() + + self._warn_unsafe_extraction_path(extract_path) + + self.cached_files[target_path] = 1 + return target_path + + @staticmethod + def _warn_unsafe_extraction_path(path): + """ + If the default extraction path is overridden and set to an insecure + location, such as /tmp, it opens up an opportunity for an attacker to + replace an extracted file with an unauthorized payload. Warn the user + if a known insecure location is used. + + See Distribute #375 for more details. + """ + if os.name == 'nt' and not path.startswith(os.environ['windir']): + # On Windows, permissions are generally restrictive by default + # and temp directories are not writable by other users, so + # bypass the warning. + return + mode = os.stat(path).st_mode + if mode & stat.S_IWOTH or mode & stat.S_IWGRP: + msg = ( + "Extraction path is writable by group/others " + "and vulnerable to attack when " + "used with get_resource_filename ({path}). " + "Consider a more secure " + "location (set with .set_extraction_path or the " + "PYTHON_EGG_CACHE environment variable)." + ).format(**locals()) + warnings.warn(msg, UserWarning) + + def postprocess(self, tempname, filename): + """Perform any platform-specific postprocessing of `tempname` + + This is where Mac header rewrites should be done; other platforms don't + have anything special they should do. + + Resource providers should call this method ONLY after successfully + extracting a compressed resource. They must NOT call it on resources + that are already in the filesystem. + + `tempname` is the current (temporary) name of the file, and `filename` + is the name it will be renamed to by the caller after this routine + returns. + """ + + if os.name == 'posix': + # Make the resource executable + mode = ((os.stat(tempname).st_mode) | 0o555) & 0o7777 + os.chmod(tempname, mode) + + def set_extraction_path(self, path): + """Set the base path where resources will be extracted to, if needed. + + If you do not call this routine before any extractions take place, the + path defaults to the return value of ``get_default_cache()``. (Which + is based on the ``PYTHON_EGG_CACHE`` environment variable, with various + platform-specific fallbacks. See that routine's documentation for more + details.) + + Resources are extracted to subdirectories of this path based upon + information given by the ``IResourceProvider``. You may set this to a + temporary directory, but then you must call ``cleanup_resources()`` to + delete the extracted files when done. There is no guarantee that + ``cleanup_resources()`` will be able to remove all extracted files. + + (Note: you may not change the extraction path for a given resource + manager once resources have been extracted, unless you first call + ``cleanup_resources()``.) + """ + if self.cached_files: + raise ValueError( + "Can't change extraction path, files already extracted" + ) + + self.extraction_path = path + + def cleanup_resources(self, force=False): + """ + Delete all extracted resource files and directories, returning a list + of the file and directory names that could not be successfully removed. + This function does not have any concurrency protection, so it should + generally only be called when the extraction path is a temporary + directory exclusive to a single process. This method is not + automatically called; you must call it explicitly or register it as an + ``atexit`` function if you wish to ensure cleanup of a temporary + directory used for extractions. + """ + # XXX + + +def get_default_cache(): + """ + Return the ``PYTHON_EGG_CACHE`` environment variable + or a platform-relevant user cache dir for an app + named "Python-Eggs". + """ + return ( + os.environ.get('PYTHON_EGG_CACHE') + or appdirs.user_cache_dir(appname='Python-Eggs') + ) + + +def safe_name(name): + """Convert an arbitrary string to a standard distribution name + + Any runs of non-alphanumeric/. characters are replaced with a single '-'. + """ + return re.sub('[^A-Za-z0-9.]+', '-', name) + + +def safe_version(version): + """ + Convert an arbitrary string to a standard version string + """ + try: + # normalize the version + return str(packaging.version.Version(version)) + except packaging.version.InvalidVersion: + version = version.replace(' ', '.') + return re.sub('[^A-Za-z0-9.]+', '-', version) + + +def safe_extra(extra): + """Convert an arbitrary string to a standard 'extra' name + + Any runs of non-alphanumeric characters are replaced with a single '_', + and the result is always lowercased. + """ + return re.sub('[^A-Za-z0-9.-]+', '_', extra).lower() + + +def to_filename(name): + """Convert a project or version name to its filename-escaped form + + Any '-' characters are currently replaced with '_'. + """ + return name.replace('-', '_') + + +def invalid_marker(text): + """ + Validate text as a PEP 508 environment marker; return an exception + if invalid or False otherwise. + """ + try: + evaluate_marker(text) + except SyntaxError as e: + e.filename = None + e.lineno = None + return e + return False + + +def evaluate_marker(text, extra=None): + """ + Evaluate a PEP 508 environment marker. + Return a boolean indicating the marker result in this environment. + Raise SyntaxError if marker is invalid. + + This implementation uses the 'pyparsing' module. + """ + try: + marker = packaging.markers.Marker(text) + return marker.evaluate() + except packaging.markers.InvalidMarker as e: + raise SyntaxError(e) from e + + +class NullProvider: + """Try to implement resources and metadata for arbitrary PEP 302 loaders""" + + egg_name = None + egg_info = None + loader = None + + def __init__(self, module): + self.loader = getattr(module, '__loader__', None) + self.module_path = os.path.dirname(getattr(module, '__file__', '')) + + def get_resource_filename(self, manager, resource_name): + return self._fn(self.module_path, resource_name) + + def get_resource_stream(self, manager, resource_name): + return io.BytesIO(self.get_resource_string(manager, resource_name)) + + def get_resource_string(self, manager, resource_name): + return self._get(self._fn(self.module_path, resource_name)) + + def has_resource(self, resource_name): + return self._has(self._fn(self.module_path, resource_name)) + + def _get_metadata_path(self, name): + return self._fn(self.egg_info, name) + + def has_metadata(self, name): + if not self.egg_info: + return self.egg_info + + path = self._get_metadata_path(name) + return self._has(path) + + def get_metadata(self, name): + if not self.egg_info: + return "" + path = self._get_metadata_path(name) + value = self._get(path) + try: + return value.decode('utf-8') + except UnicodeDecodeError as exc: + # Include the path in the error message to simplify + # troubleshooting, and without changing the exception type. + exc.reason += ' in {} file at path: {}'.format(name, path) + raise + + def get_metadata_lines(self, name): + return yield_lines(self.get_metadata(name)) + + def resource_isdir(self, resource_name): + return self._isdir(self._fn(self.module_path, resource_name)) + + def metadata_isdir(self, name): + return self.egg_info and self._isdir(self._fn(self.egg_info, name)) + + def resource_listdir(self, resource_name): + return self._listdir(self._fn(self.module_path, resource_name)) + + def metadata_listdir(self, name): + if self.egg_info: + return self._listdir(self._fn(self.egg_info, name)) + return [] + + def run_script(self, script_name, namespace): + script = 'scripts/' + script_name + if not self.has_metadata(script): + raise ResolutionError( + "Script {script!r} not found in metadata at {self.egg_info!r}" + .format(**locals()), + ) + script_text = self.get_metadata(script).replace('\r\n', '\n') + script_text = script_text.replace('\r', '\n') + script_filename = self._fn(self.egg_info, script) + namespace['__file__'] = script_filename + if os.path.exists(script_filename): + with open(script_filename) as fid: + source = fid.read() + code = compile(source, script_filename, 'exec') + exec(code, namespace, namespace) + else: + from linecache import cache + cache[script_filename] = ( + len(script_text), 0, script_text.split('\n'), script_filename + ) + script_code = compile(script_text, script_filename, 'exec') + exec(script_code, namespace, namespace) + + def _has(self, path): + raise NotImplementedError( + "Can't perform this operation for unregistered loader type" + ) + + def _isdir(self, path): + raise NotImplementedError( + "Can't perform this operation for unregistered loader type" + ) + + def _listdir(self, path): + raise NotImplementedError( + "Can't perform this operation for unregistered loader type" + ) + + def _fn(self, base, resource_name): + self._validate_resource_path(resource_name) + if resource_name: + return os.path.join(base, *resource_name.split('/')) + return base + + @staticmethod + def _validate_resource_path(path): + """ + Validate the resource paths according to the docs. + https://setuptools.pypa.io/en/latest/pkg_resources.html#basic-resource-access + + >>> warned = getfixture('recwarn') + >>> warnings.simplefilter('always') + >>> vrp = NullProvider._validate_resource_path + >>> vrp('foo/bar.txt') + >>> bool(warned) + False + >>> vrp('../foo/bar.txt') + >>> bool(warned) + True + >>> warned.clear() + >>> vrp('/foo/bar.txt') + >>> bool(warned) + True + >>> vrp('foo/../../bar.txt') + >>> bool(warned) + True + >>> warned.clear() + >>> vrp('foo/f../bar.txt') + >>> bool(warned) + False + + Windows path separators are straight-up disallowed. + >>> vrp(r'\\foo/bar.txt') + Traceback (most recent call last): + ... + ValueError: Use of .. or absolute path in a resource path \ +is not allowed. + + >>> vrp(r'C:\\foo/bar.txt') + Traceback (most recent call last): + ... + ValueError: Use of .. or absolute path in a resource path \ +is not allowed. + + Blank values are allowed + + >>> vrp('') + >>> bool(warned) + False + + Non-string values are not. + + >>> vrp(None) + Traceback (most recent call last): + ... + AttributeError: ... + """ + invalid = ( + os.path.pardir in path.split(posixpath.sep) or + posixpath.isabs(path) or + ntpath.isabs(path) + ) + if not invalid: + return + + msg = "Use of .. or absolute path in a resource path is not allowed." + + # Aggressively disallow Windows absolute paths + if ntpath.isabs(path) and not posixpath.isabs(path): + raise ValueError(msg) + + # for compatibility, warn; in future + # raise ValueError(msg) + warnings.warn( + msg[:-1] + " and will raise exceptions in a future release.", + DeprecationWarning, + stacklevel=4, + ) + + def _get(self, path): + if hasattr(self.loader, 'get_data'): + return self.loader.get_data(path) + raise NotImplementedError( + "Can't perform this operation for loaders without 'get_data()'" + ) + + +register_loader_type(object, NullProvider) + + +def _parents(path): + """ + yield all parents of path including path + """ + last = None + while path != last: + yield path + last = path + path, _ = os.path.split(path) + + +class EggProvider(NullProvider): + """Provider based on a virtual filesystem""" + + def __init__(self, module): + NullProvider.__init__(self, module) + self._setup_prefix() + + def _setup_prefix(self): + # Assume that metadata may be nested inside a "basket" + # of multiple eggs and use module_path instead of .archive. + eggs = filter(_is_egg_path, _parents(self.module_path)) + egg = next(eggs, None) + egg and self._set_egg(egg) + + def _set_egg(self, path): + self.egg_name = os.path.basename(path) + self.egg_info = os.path.join(path, 'EGG-INFO') + self.egg_root = path + + +class DefaultProvider(EggProvider): + """Provides access to package resources in the filesystem""" + + def _has(self, path): + return os.path.exists(path) + + def _isdir(self, path): + return os.path.isdir(path) + + def _listdir(self, path): + return os.listdir(path) + + def get_resource_stream(self, manager, resource_name): + return open(self._fn(self.module_path, resource_name), 'rb') + + def _get(self, path): + with open(path, 'rb') as stream: + return stream.read() + + @classmethod + def _register(cls): + loader_names = 'SourceFileLoader', 'SourcelessFileLoader', + for name in loader_names: + loader_cls = getattr(importlib_machinery, name, type(None)) + register_loader_type(loader_cls, cls) + + +DefaultProvider._register() + + +class EmptyProvider(NullProvider): + """Provider that returns nothing for all requests""" + + module_path = None + + _isdir = _has = lambda self, path: False + + def _get(self, path): + return '' + + def _listdir(self, path): + return [] + + def __init__(self): + pass + + +empty_provider = EmptyProvider() + + +class ZipManifests(dict): + """ + zip manifest builder + """ + + @classmethod + def build(cls, path): + """ + Build a dictionary similar to the zipimport directory + caches, except instead of tuples, store ZipInfo objects. + + Use a platform-specific path separator (os.sep) for the path keys + for compatibility with pypy on Windows. + """ + with zipfile.ZipFile(path) as zfile: + items = ( + ( + name.replace('/', os.sep), + zfile.getinfo(name), + ) + for name in zfile.namelist() + ) + return dict(items) + + load = build + + +class MemoizedZipManifests(ZipManifests): + """ + Memoized zipfile manifests. + """ + manifest_mod = collections.namedtuple('manifest_mod', 'manifest mtime') + + def load(self, path): + """ + Load a manifest at path or return a suitable manifest already loaded. + """ + path = os.path.normpath(path) + mtime = os.stat(path).st_mtime + + if path not in self or self[path].mtime != mtime: + manifest = self.build(path) + self[path] = self.manifest_mod(manifest, mtime) + + return self[path].manifest + + +class ZipProvider(EggProvider): + """Resource support for zips and eggs""" + + eagers = None + _zip_manifests = MemoizedZipManifests() + + def __init__(self, module): + EggProvider.__init__(self, module) + self.zip_pre = self.loader.archive + os.sep + + def _zipinfo_name(self, fspath): + # Convert a virtual filename (full path to file) into a zipfile subpath + # usable with the zipimport directory cache for our target archive + fspath = fspath.rstrip(os.sep) + if fspath == self.loader.archive: + return '' + if fspath.startswith(self.zip_pre): + return fspath[len(self.zip_pre):] + raise AssertionError( + "%s is not a subpath of %s" % (fspath, self.zip_pre) + ) + + def _parts(self, zip_path): + # Convert a zipfile subpath into an egg-relative path part list. + # pseudo-fs path + fspath = self.zip_pre + zip_path + if fspath.startswith(self.egg_root + os.sep): + return fspath[len(self.egg_root) + 1:].split(os.sep) + raise AssertionError( + "%s is not a subpath of %s" % (fspath, self.egg_root) + ) + + @property + def zipinfo(self): + return self._zip_manifests.load(self.loader.archive) + + def get_resource_filename(self, manager, resource_name): + if not self.egg_name: + raise NotImplementedError( + "resource_filename() only supported for .egg, not .zip" + ) + # no need to lock for extraction, since we use temp names + zip_path = self._resource_to_zip(resource_name) + eagers = self._get_eager_resources() + if '/'.join(self._parts(zip_path)) in eagers: + for name in eagers: + self._extract_resource(manager, self._eager_to_zip(name)) + return self._extract_resource(manager, zip_path) + + @staticmethod + def _get_date_and_size(zip_stat): + size = zip_stat.file_size + # ymdhms+wday, yday, dst + date_time = zip_stat.date_time + (0, 0, -1) + # 1980 offset already done + timestamp = time.mktime(date_time) + return timestamp, size + + # FIXME: 'ZipProvider._extract_resource' is too complex (12) + def _extract_resource(self, manager, zip_path): # noqa: C901 + + if zip_path in self._index(): + for name in self._index()[zip_path]: + last = self._extract_resource( + manager, os.path.join(zip_path, name) + ) + # return the extracted directory name + return os.path.dirname(last) + + timestamp, size = self._get_date_and_size(self.zipinfo[zip_path]) + + if not WRITE_SUPPORT: + raise IOError('"os.rename" and "os.unlink" are not supported ' + 'on this platform') + try: + + real_path = manager.get_cache_path( + self.egg_name, self._parts(zip_path) + ) + + if self._is_current(real_path, zip_path): + return real_path + + outf, tmpnam = _mkstemp( + ".$extract", + dir=os.path.dirname(real_path), + ) + os.write(outf, self.loader.get_data(zip_path)) + os.close(outf) + utime(tmpnam, (timestamp, timestamp)) + manager.postprocess(tmpnam, real_path) + + try: + rename(tmpnam, real_path) + + except os.error: + if os.path.isfile(real_path): + if self._is_current(real_path, zip_path): + # the file became current since it was checked above, + # so proceed. + return real_path + # Windows, del old file and retry + elif os.name == 'nt': + unlink(real_path) + rename(tmpnam, real_path) + return real_path + raise + + except os.error: + # report a user-friendly error + manager.extraction_error() + + return real_path + + def _is_current(self, file_path, zip_path): + """ + Return True if the file_path is current for this zip_path + """ + timestamp, size = self._get_date_and_size(self.zipinfo[zip_path]) + if not os.path.isfile(file_path): + return False + stat = os.stat(file_path) + if stat.st_size != size or stat.st_mtime != timestamp: + return False + # check that the contents match + zip_contents = self.loader.get_data(zip_path) + with open(file_path, 'rb') as f: + file_contents = f.read() + return zip_contents == file_contents + + def _get_eager_resources(self): + if self.eagers is None: + eagers = [] + for name in ('native_libs.txt', 'eager_resources.txt'): + if self.has_metadata(name): + eagers.extend(self.get_metadata_lines(name)) + self.eagers = eagers + return self.eagers + + def _index(self): + try: + return self._dirindex + except AttributeError: + ind = {} + for path in self.zipinfo: + parts = path.split(os.sep) + while parts: + parent = os.sep.join(parts[:-1]) + if parent in ind: + ind[parent].append(parts[-1]) + break + else: + ind[parent] = [parts.pop()] + self._dirindex = ind + return ind + + def _has(self, fspath): + zip_path = self._zipinfo_name(fspath) + return zip_path in self.zipinfo or zip_path in self._index() + + def _isdir(self, fspath): + return self._zipinfo_name(fspath) in self._index() + + def _listdir(self, fspath): + return list(self._index().get(self._zipinfo_name(fspath), ())) + + def _eager_to_zip(self, resource_name): + return self._zipinfo_name(self._fn(self.egg_root, resource_name)) + + def _resource_to_zip(self, resource_name): + return self._zipinfo_name(self._fn(self.module_path, resource_name)) + + +register_loader_type(zipimport.zipimporter, ZipProvider) + + +class FileMetadata(EmptyProvider): + """Metadata handler for standalone PKG-INFO files + + Usage:: + + metadata = FileMetadata("/path/to/PKG-INFO") + + This provider rejects all data and metadata requests except for PKG-INFO, + which is treated as existing, and will be the contents of the file at + the provided location. + """ + + def __init__(self, path): + self.path = path + + def _get_metadata_path(self, name): + return self.path + + def has_metadata(self, name): + return name == 'PKG-INFO' and os.path.isfile(self.path) + + def get_metadata(self, name): + if name != 'PKG-INFO': + raise KeyError("No metadata except PKG-INFO is available") + + with io.open(self.path, encoding='utf-8', errors="replace") as f: + metadata = f.read() + self._warn_on_replacement(metadata) + return metadata + + def _warn_on_replacement(self, metadata): + replacement_char = '�' + if replacement_char in metadata: + tmpl = "{self.path} could not be properly decoded in UTF-8" + msg = tmpl.format(**locals()) + warnings.warn(msg) + + def get_metadata_lines(self, name): + return yield_lines(self.get_metadata(name)) + + +class PathMetadata(DefaultProvider): + """Metadata provider for egg directories + + Usage:: + + # Development eggs: + + egg_info = "/path/to/PackageName.egg-info" + base_dir = os.path.dirname(egg_info) + metadata = PathMetadata(base_dir, egg_info) + dist_name = os.path.splitext(os.path.basename(egg_info))[0] + dist = Distribution(basedir, project_name=dist_name, metadata=metadata) + + # Unpacked egg directories: + + egg_path = "/path/to/PackageName-ver-pyver-etc.egg" + metadata = PathMetadata(egg_path, os.path.join(egg_path,'EGG-INFO')) + dist = Distribution.from_filename(egg_path, metadata=metadata) + """ + + def __init__(self, path, egg_info): + self.module_path = path + self.egg_info = egg_info + + +class EggMetadata(ZipProvider): + """Metadata provider for .egg files""" + + def __init__(self, importer): + """Create a metadata provider from a zipimporter""" + + self.zip_pre = importer.archive + os.sep + self.loader = importer + if importer.prefix: + self.module_path = os.path.join(importer.archive, importer.prefix) + else: + self.module_path = importer.archive + self._setup_prefix() + + +_declare_state('dict', _distribution_finders={}) + + +def register_finder(importer_type, distribution_finder): + """Register `distribution_finder` to find distributions in sys.path items + + `importer_type` is the type or class of a PEP 302 "Importer" (sys.path item + handler), and `distribution_finder` is a callable that, passed a path + item and the importer instance, yields ``Distribution`` instances found on + that path item. See ``pkg_resources.find_on_path`` for an example.""" + _distribution_finders[importer_type] = distribution_finder + + +def find_distributions(path_item, only=False): + """Yield distributions accessible via `path_item`""" + importer = get_importer(path_item) + finder = _find_adapter(_distribution_finders, importer) + return finder(importer, path_item, only) + + +def find_eggs_in_zip(importer, path_item, only=False): + """ + Find eggs in zip files; possibly multiple nested eggs. + """ + if importer.archive.endswith('.whl'): + # wheels are not supported with this finder + # they don't have PKG-INFO metadata, and won't ever contain eggs + return + metadata = EggMetadata(importer) + if metadata.has_metadata('PKG-INFO'): + yield Distribution.from_filename(path_item, metadata=metadata) + if only: + # don't yield nested distros + return + for subitem in metadata.resource_listdir(''): + if _is_egg_path(subitem): + subpath = os.path.join(path_item, subitem) + dists = find_eggs_in_zip(zipimport.zipimporter(subpath), subpath) + for dist in dists: + yield dist + elif subitem.lower().endswith(('.dist-info', '.egg-info')): + subpath = os.path.join(path_item, subitem) + submeta = EggMetadata(zipimport.zipimporter(subpath)) + submeta.egg_info = subpath + yield Distribution.from_location(path_item, subitem, submeta) + + +register_finder(zipimport.zipimporter, find_eggs_in_zip) + + +def find_nothing(importer, path_item, only=False): + return () + + +register_finder(object, find_nothing) + + +def _by_version_descending(names): + """ + Given a list of filenames, return them in descending order + by version number. + + >>> names = 'bar', 'foo', 'Python-2.7.10.egg', 'Python-2.7.2.egg' + >>> _by_version_descending(names) + ['Python-2.7.10.egg', 'Python-2.7.2.egg', 'bar', 'foo'] + >>> names = 'Setuptools-1.2.3b1.egg', 'Setuptools-1.2.3.egg' + >>> _by_version_descending(names) + ['Setuptools-1.2.3.egg', 'Setuptools-1.2.3b1.egg'] + >>> names = 'Setuptools-1.2.3b1.egg', 'Setuptools-1.2.3.post1.egg' + >>> _by_version_descending(names) + ['Setuptools-1.2.3.post1.egg', 'Setuptools-1.2.3b1.egg'] + """ + def try_parse(name): + """ + Attempt to parse as a version or return a null version. + """ + try: + return packaging.version.Version(name) + except Exception: + return packaging.version.Version('0') + + def _by_version(name): + """ + Parse each component of the filename + """ + name, ext = os.path.splitext(name) + parts = itertools.chain(name.split('-'), [ext]) + return [try_parse(part) for part in parts] + + return sorted(names, key=_by_version, reverse=True) + + +def find_on_path(importer, path_item, only=False): + """Yield distributions accessible on a sys.path directory""" + path_item = _normalize_cached(path_item) + + if _is_unpacked_egg(path_item): + yield Distribution.from_filename( + path_item, metadata=PathMetadata( + path_item, os.path.join(path_item, 'EGG-INFO') + ) + ) + return + + entries = ( + os.path.join(path_item, child) + for child in safe_listdir(path_item) + ) + + # for performance, before sorting by version, + # screen entries for only those that will yield + # distributions + filtered = ( + entry + for entry in entries + if dist_factory(path_item, entry, only) + ) + + # scan for .egg and .egg-info in directory + path_item_entries = _by_version_descending(filtered) + for entry in path_item_entries: + fullpath = os.path.join(path_item, entry) + factory = dist_factory(path_item, entry, only) + for dist in factory(fullpath): + yield dist + + +def dist_factory(path_item, entry, only): + """Return a dist_factory for the given entry.""" + lower = entry.lower() + is_egg_info = lower.endswith('.egg-info') + is_dist_info = ( + lower.endswith('.dist-info') and + os.path.isdir(os.path.join(path_item, entry)) + ) + is_meta = is_egg_info or is_dist_info + return ( + distributions_from_metadata + if is_meta else + find_distributions + if not only and _is_egg_path(entry) else + resolve_egg_link + if not only and lower.endswith('.egg-link') else + NoDists() + ) + + +class NoDists: + """ + >>> bool(NoDists()) + False + + >>> list(NoDists()('anything')) + [] + """ + def __bool__(self): + return False + + def __call__(self, fullpath): + return iter(()) + + +def safe_listdir(path): + """ + Attempt to list contents of path, but suppress some exceptions. + """ + try: + return os.listdir(path) + except (PermissionError, NotADirectoryError): + pass + except OSError as e: + # Ignore the directory if does not exist, not a directory or + # permission denied + if e.errno not in (errno.ENOTDIR, errno.EACCES, errno.ENOENT): + raise + return () + + +def distributions_from_metadata(path): + root = os.path.dirname(path) + if os.path.isdir(path): + if len(os.listdir(path)) == 0: + # empty metadata dir; skip + return + metadata = PathMetadata(root, path) + else: + metadata = FileMetadata(path) + entry = os.path.basename(path) + yield Distribution.from_location( + root, entry, metadata, precedence=DEVELOP_DIST, + ) + + +def non_empty_lines(path): + """ + Yield non-empty lines from file at path + """ + with open(path) as f: + for line in f: + line = line.strip() + if line: + yield line + + +def resolve_egg_link(path): + """ + Given a path to an .egg-link, resolve distributions + present in the referenced path. + """ + referenced_paths = non_empty_lines(path) + resolved_paths = ( + os.path.join(os.path.dirname(path), ref) + for ref in referenced_paths + ) + dist_groups = map(find_distributions, resolved_paths) + return next(dist_groups, ()) + + +register_finder(pkgutil.ImpImporter, find_on_path) + +if hasattr(importlib_machinery, 'FileFinder'): + register_finder(importlib_machinery.FileFinder, find_on_path) + +_declare_state('dict', _namespace_handlers={}) +_declare_state('dict', _namespace_packages={}) + + +def register_namespace_handler(importer_type, namespace_handler): + """Register `namespace_handler` to declare namespace packages + + `importer_type` is the type or class of a PEP 302 "Importer" (sys.path item + handler), and `namespace_handler` is a callable like this:: + + def namespace_handler(importer, path_entry, moduleName, module): + # return a path_entry to use for child packages + + Namespace handlers are only called if the importer object has already + agreed that it can handle the relevant path item, and they should only + return a subpath if the module __path__ does not already contain an + equivalent subpath. For an example namespace handler, see + ``pkg_resources.file_ns_handler``. + """ + _namespace_handlers[importer_type] = namespace_handler + + +def _handle_ns(packageName, path_item): + """Ensure that named package includes a subpath of path_item (if needed)""" + + importer = get_importer(path_item) + if importer is None: + return None + + # use find_spec (PEP 451) and fall-back to find_module (PEP 302) + try: + loader = importer.find_spec(packageName).loader + except AttributeError: + # capture warnings due to #1111 + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + loader = importer.find_module(packageName) + + if loader is None: + return None + module = sys.modules.get(packageName) + if module is None: + module = sys.modules[packageName] = types.ModuleType(packageName) + module.__path__ = [] + _set_parent_ns(packageName) + elif not hasattr(module, '__path__'): + raise TypeError("Not a package:", packageName) + handler = _find_adapter(_namespace_handlers, importer) + subpath = handler(importer, path_item, packageName, module) + if subpath is not None: + path = module.__path__ + path.append(subpath) + importlib.import_module(packageName) + _rebuild_mod_path(path, packageName, module) + return subpath + + +def _rebuild_mod_path(orig_path, package_name, module): + """ + Rebuild module.__path__ ensuring that all entries are ordered + corresponding to their sys.path order + """ + sys_path = [_normalize_cached(p) for p in sys.path] + + def safe_sys_path_index(entry): + """ + Workaround for #520 and #513. + """ + try: + return sys_path.index(entry) + except ValueError: + return float('inf') + + def position_in_sys_path(path): + """ + Return the ordinal of the path based on its position in sys.path + """ + path_parts = path.split(os.sep) + module_parts = package_name.count('.') + 1 + parts = path_parts[:-module_parts] + return safe_sys_path_index(_normalize_cached(os.sep.join(parts))) + + new_path = sorted(orig_path, key=position_in_sys_path) + new_path = [_normalize_cached(p) for p in new_path] + + if isinstance(module.__path__, list): + module.__path__[:] = new_path + else: + module.__path__ = new_path + + +def declare_namespace(packageName): + """Declare that package 'packageName' is a namespace package""" + + _imp.acquire_lock() + try: + if packageName in _namespace_packages: + return + + path = sys.path + parent, _, _ = packageName.rpartition('.') + + if parent: + declare_namespace(parent) + if parent not in _namespace_packages: + __import__(parent) + try: + path = sys.modules[parent].__path__ + except AttributeError as e: + raise TypeError("Not a package:", parent) from e + + # Track what packages are namespaces, so when new path items are added, + # they can be updated + _namespace_packages.setdefault(parent or None, []).append(packageName) + _namespace_packages.setdefault(packageName, []) + + for path_item in path: + # Ensure all the parent's path items are reflected in the child, + # if they apply + _handle_ns(packageName, path_item) + + finally: + _imp.release_lock() + + +def fixup_namespace_packages(path_item, parent=None): + """Ensure that previously-declared namespace packages include path_item""" + _imp.acquire_lock() + try: + for package in _namespace_packages.get(parent, ()): + subpath = _handle_ns(package, path_item) + if subpath: + fixup_namespace_packages(subpath, package) + finally: + _imp.release_lock() + + +def file_ns_handler(importer, path_item, packageName, module): + """Compute an ns-package subpath for a filesystem or zipfile importer""" + + subpath = os.path.join(path_item, packageName.split('.')[-1]) + normalized = _normalize_cached(subpath) + for item in module.__path__: + if _normalize_cached(item) == normalized: + break + else: + # Only return the path if it's not already there + return subpath + + +register_namespace_handler(pkgutil.ImpImporter, file_ns_handler) +register_namespace_handler(zipimport.zipimporter, file_ns_handler) + +if hasattr(importlib_machinery, 'FileFinder'): + register_namespace_handler(importlib_machinery.FileFinder, file_ns_handler) + + +def null_ns_handler(importer, path_item, packageName, module): + return None + + +register_namespace_handler(object, null_ns_handler) + + +def normalize_path(filename): + """Normalize a file/dir name for comparison purposes""" + return os.path.normcase(os.path.realpath(os.path.normpath( + _cygwin_patch(filename)))) + + +def _cygwin_patch(filename): # pragma: nocover + """ + Contrary to POSIX 2008, on Cygwin, getcwd (3) contains + symlink components. Using + os.path.abspath() works around this limitation. A fix in os.getcwd() + would probably better, in Cygwin even more so, except + that this seems to be by design... + """ + return os.path.abspath(filename) if sys.platform == 'cygwin' else filename + + +def _normalize_cached(filename, _cache={}): + try: + return _cache[filename] + except KeyError: + _cache[filename] = result = normalize_path(filename) + return result + + +def _is_egg_path(path): + """ + Determine if given path appears to be an egg. + """ + return _is_zip_egg(path) or _is_unpacked_egg(path) + + +def _is_zip_egg(path): + return ( + path.lower().endswith('.egg') and + os.path.isfile(path) and + zipfile.is_zipfile(path) + ) + + +def _is_unpacked_egg(path): + """ + Determine if given path appears to be an unpacked egg. + """ + return ( + path.lower().endswith('.egg') and + os.path.isfile(os.path.join(path, 'EGG-INFO', 'PKG-INFO')) + ) + + +def _set_parent_ns(packageName): + parts = packageName.split('.') + name = parts.pop() + if parts: + parent = '.'.join(parts) + setattr(sys.modules[parent], name, sys.modules[packageName]) + + +def _nonblank(str): + return str and not str.startswith('#') + + +@functools.singledispatch +def yield_lines(iterable): + """Yield valid lines of a string or iterable""" + return itertools.chain.from_iterable(map(yield_lines, iterable)) + + +@yield_lines.register(str) +def _(text): + return filter(_nonblank, map(str.strip, text.splitlines())) + + +MODULE = re.compile(r"\w+(\.\w+)*$").match +EGG_NAME = re.compile( + r""" + (?P[^-]+) ( + -(?P[^-]+) ( + -py(?P[^-]+) ( + -(?P.+) + )? + )? + )? + """, + re.VERBOSE | re.IGNORECASE, +).match + + +class EntryPoint: + """Object representing an advertised importable object""" + + def __init__(self, name, module_name, attrs=(), extras=(), dist=None): + if not MODULE(module_name): + raise ValueError("Invalid module name", module_name) + self.name = name + self.module_name = module_name + self.attrs = tuple(attrs) + self.extras = tuple(extras) + self.dist = dist + + def __str__(self): + s = "%s = %s" % (self.name, self.module_name) + if self.attrs: + s += ':' + '.'.join(self.attrs) + if self.extras: + s += ' [%s]' % ','.join(self.extras) + return s + + def __repr__(self): + return "EntryPoint.parse(%r)" % str(self) + + def load(self, require=True, *args, **kwargs): + """ + Require packages for this EntryPoint, then resolve it. + """ + if not require or args or kwargs: + warnings.warn( + "Parameters to load are deprecated. Call .resolve and " + ".require separately.", + PkgResourcesDeprecationWarning, + stacklevel=2, + ) + if require: + self.require(*args, **kwargs) + return self.resolve() + + def resolve(self): + """ + Resolve the entry point from its module and attrs. + """ + module = __import__(self.module_name, fromlist=['__name__'], level=0) + try: + return functools.reduce(getattr, self.attrs, module) + except AttributeError as exc: + raise ImportError(str(exc)) from exc + + def require(self, env=None, installer=None): + if self.extras and not self.dist: + raise UnknownExtra("Can't require() without a distribution", self) + + # Get the requirements for this entry point with all its extras and + # then resolve them. We have to pass `extras` along when resolving so + # that the working set knows what extras we want. Otherwise, for + # dist-info distributions, the working set will assume that the + # requirements for that extra are purely optional and skip over them. + reqs = self.dist.requires(self.extras) + items = working_set.resolve(reqs, env, installer, extras=self.extras) + list(map(working_set.add, items)) + + pattern = re.compile( + r'\s*' + r'(?P.+?)\s*' + r'=\s*' + r'(?P[\w.]+)\s*' + r'(:\s*(?P[\w.]+))?\s*' + r'(?P\[.*\])?\s*$' + ) + + @classmethod + def parse(cls, src, dist=None): + """Parse a single entry point from string `src` + + Entry point syntax follows the form:: + + name = some.module:some.attr [extra1, extra2] + + The entry name and module name are required, but the ``:attrs`` and + ``[extras]`` parts are optional + """ + m = cls.pattern.match(src) + if not m: + msg = "EntryPoint must be in 'name=module:attrs [extras]' format" + raise ValueError(msg, src) + res = m.groupdict() + extras = cls._parse_extras(res['extras']) + attrs = res['attr'].split('.') if res['attr'] else () + return cls(res['name'], res['module'], attrs, extras, dist) + + @classmethod + def _parse_extras(cls, extras_spec): + if not extras_spec: + return () + req = Requirement.parse('x' + extras_spec) + if req.specs: + raise ValueError() + return req.extras + + @classmethod + def parse_group(cls, group, lines, dist=None): + """Parse an entry point group""" + if not MODULE(group): + raise ValueError("Invalid group name", group) + this = {} + for line in yield_lines(lines): + ep = cls.parse(line, dist) + if ep.name in this: + raise ValueError("Duplicate entry point", group, ep.name) + this[ep.name] = ep + return this + + @classmethod + def parse_map(cls, data, dist=None): + """Parse a map of entry point groups""" + if isinstance(data, dict): + data = data.items() + else: + data = split_sections(data) + maps = {} + for group, lines in data: + if group is None: + if not lines: + continue + raise ValueError("Entry points must be listed in groups") + group = group.strip() + if group in maps: + raise ValueError("Duplicate group name", group) + maps[group] = cls.parse_group(group, lines, dist) + return maps + + +def _version_from_file(lines): + """ + Given an iterable of lines from a Metadata file, return + the value of the Version field, if present, or None otherwise. + """ + def is_version_line(line): + return line.lower().startswith('version:') + version_lines = filter(is_version_line, lines) + line = next(iter(version_lines), '') + _, _, value = line.partition(':') + return safe_version(value.strip()) or None + + +class Distribution: + """Wrap an actual or potential sys.path entry w/metadata""" + PKG_INFO = 'PKG-INFO' + + def __init__( + self, location=None, metadata=None, project_name=None, + version=None, py_version=PY_MAJOR, platform=None, + precedence=EGG_DIST): + self.project_name = safe_name(project_name or 'Unknown') + if version is not None: + self._version = safe_version(version) + self.py_version = py_version + self.platform = platform + self.location = location + self.precedence = precedence + self._provider = metadata or empty_provider + + @classmethod + def from_location(cls, location, basename, metadata=None, **kw): + project_name, version, py_version, platform = [None] * 4 + basename, ext = os.path.splitext(basename) + if ext.lower() in _distributionImpl: + cls = _distributionImpl[ext.lower()] + + match = EGG_NAME(basename) + if match: + project_name, version, py_version, platform = match.group( + 'name', 'ver', 'pyver', 'plat' + ) + return cls( + location, metadata, project_name=project_name, version=version, + py_version=py_version, platform=platform, **kw + )._reload_version() + + def _reload_version(self): + return self + + @property + def hashcmp(self): + return ( + self.parsed_version, + self.precedence, + self.key, + self.location, + self.py_version or '', + self.platform or '', + ) + + def __hash__(self): + return hash(self.hashcmp) + + def __lt__(self, other): + return self.hashcmp < other.hashcmp + + def __le__(self, other): + return self.hashcmp <= other.hashcmp + + def __gt__(self, other): + return self.hashcmp > other.hashcmp + + def __ge__(self, other): + return self.hashcmp >= other.hashcmp + + def __eq__(self, other): + if not isinstance(other, self.__class__): + # It's not a Distribution, so they are not equal + return False + return self.hashcmp == other.hashcmp + + def __ne__(self, other): + return not self == other + + # These properties have to be lazy so that we don't have to load any + # metadata until/unless it's actually needed. (i.e., some distributions + # may not know their name or version without loading PKG-INFO) + + @property + def key(self): + try: + return self._key + except AttributeError: + self._key = key = self.project_name.lower() + return key + + @property + def parsed_version(self): + if not hasattr(self, "_parsed_version"): + self._parsed_version = parse_version(self.version) + + return self._parsed_version + + def _warn_legacy_version(self): + LV = packaging.version.LegacyVersion + is_legacy = isinstance(self._parsed_version, LV) + if not is_legacy: + return + + # While an empty version is technically a legacy version and + # is not a valid PEP 440 version, it's also unlikely to + # actually come from someone and instead it is more likely that + # it comes from setuptools attempting to parse a filename and + # including it in the list. So for that we'll gate this warning + # on if the version is anything at all or not. + if not self.version: + return + + tmpl = textwrap.dedent(""" + '{project_name} ({version})' is being parsed as a legacy, + non PEP 440, + version. You may find odd behavior and sort order. + In particular it will be sorted as less than 0.0. It + is recommended to migrate to PEP 440 compatible + versions. + """).strip().replace('\n', ' ') + + warnings.warn(tmpl.format(**vars(self)), PEP440Warning) + + @property + def version(self): + try: + return self._version + except AttributeError as e: + version = self._get_version() + if version is None: + path = self._get_metadata_path_for_display(self.PKG_INFO) + msg = ( + "Missing 'Version:' header and/or {} file at path: {}" + ).format(self.PKG_INFO, path) + raise ValueError(msg, self) from e + + return version + + @property + def _dep_map(self): + """ + A map of extra to its list of (direct) requirements + for this distribution, including the null extra. + """ + try: + return self.__dep_map + except AttributeError: + self.__dep_map = self._filter_extras(self._build_dep_map()) + return self.__dep_map + + @staticmethod + def _filter_extras(dm): + """ + Given a mapping of extras to dependencies, strip off + environment markers and filter out any dependencies + not matching the markers. + """ + for extra in list(filter(None, dm)): + new_extra = extra + reqs = dm.pop(extra) + new_extra, _, marker = extra.partition(':') + fails_marker = marker and ( + invalid_marker(marker) + or not evaluate_marker(marker) + ) + if fails_marker: + reqs = [] + new_extra = safe_extra(new_extra) or None + + dm.setdefault(new_extra, []).extend(reqs) + return dm + + def _build_dep_map(self): + dm = {} + for name in 'requires.txt', 'depends.txt': + for extra, reqs in split_sections(self._get_metadata(name)): + dm.setdefault(extra, []).extend(parse_requirements(reqs)) + return dm + + def requires(self, extras=()): + """List of Requirements needed for this distro if `extras` are used""" + dm = self._dep_map + deps = [] + deps.extend(dm.get(None, ())) + for ext in extras: + try: + deps.extend(dm[safe_extra(ext)]) + except KeyError as e: + raise UnknownExtra( + "%s has no such extra feature %r" % (self, ext) + ) from e + return deps + + def _get_metadata_path_for_display(self, name): + """ + Return the path to the given metadata file, if available. + """ + try: + # We need to access _get_metadata_path() on the provider object + # directly rather than through this class's __getattr__() + # since _get_metadata_path() is marked private. + path = self._provider._get_metadata_path(name) + + # Handle exceptions e.g. in case the distribution's metadata + # provider doesn't support _get_metadata_path(). + except Exception: + return '[could not detect]' + + return path + + def _get_metadata(self, name): + if self.has_metadata(name): + for line in self.get_metadata_lines(name): + yield line + + def _get_version(self): + lines = self._get_metadata(self.PKG_INFO) + version = _version_from_file(lines) + + return version + + def activate(self, path=None, replace=False): + """Ensure distribution is importable on `path` (default=sys.path)""" + if path is None: + path = sys.path + self.insert_on(path, replace=replace) + if path is sys.path: + fixup_namespace_packages(self.location) + for pkg in self._get_metadata('namespace_packages.txt'): + if pkg in sys.modules: + declare_namespace(pkg) + + def egg_name(self): + """Return what this distribution's standard .egg filename should be""" + filename = "%s-%s-py%s" % ( + to_filename(self.project_name), to_filename(self.version), + self.py_version or PY_MAJOR + ) + + if self.platform: + filename += '-' + self.platform + return filename + + def __repr__(self): + if self.location: + return "%s (%s)" % (self, self.location) + else: + return str(self) + + def __str__(self): + try: + version = getattr(self, 'version', None) + except ValueError: + version = None + version = version or "[unknown version]" + return "%s %s" % (self.project_name, version) + + def __getattr__(self, attr): + """Delegate all unrecognized public attributes to .metadata provider""" + if attr.startswith('_'): + raise AttributeError(attr) + return getattr(self._provider, attr) + + def __dir__(self): + return list( + set(super(Distribution, self).__dir__()) + | set( + attr for attr in self._provider.__dir__() + if not attr.startswith('_') + ) + ) + + @classmethod + def from_filename(cls, filename, metadata=None, **kw): + return cls.from_location( + _normalize_cached(filename), os.path.basename(filename), metadata, + **kw + ) + + def as_requirement(self): + """Return a ``Requirement`` that matches this distribution exactly""" + if isinstance(self.parsed_version, packaging.version.Version): + spec = "%s==%s" % (self.project_name, self.parsed_version) + else: + spec = "%s===%s" % (self.project_name, self.parsed_version) + + return Requirement.parse(spec) + + def load_entry_point(self, group, name): + """Return the `name` entry point of `group` or raise ImportError""" + ep = self.get_entry_info(group, name) + if ep is None: + raise ImportError("Entry point %r not found" % ((group, name),)) + return ep.load() + + def get_entry_map(self, group=None): + """Return the entry point map for `group`, or the full entry map""" + try: + ep_map = self._ep_map + except AttributeError: + ep_map = self._ep_map = EntryPoint.parse_map( + self._get_metadata('entry_points.txt'), self + ) + if group is not None: + return ep_map.get(group, {}) + return ep_map + + def get_entry_info(self, group, name): + """Return the EntryPoint object for `group`+`name`, or ``None``""" + return self.get_entry_map(group).get(name) + + # FIXME: 'Distribution.insert_on' is too complex (13) + def insert_on(self, path, loc=None, replace=False): # noqa: C901 + """Ensure self.location is on path + + If replace=False (default): + - If location is already in path anywhere, do nothing. + - Else: + - If it's an egg and its parent directory is on path, + insert just ahead of the parent. + - Else: add to the end of path. + If replace=True: + - If location is already on path anywhere (not eggs) + or higher priority than its parent (eggs) + do nothing. + - Else: + - If it's an egg and its parent directory is on path, + insert just ahead of the parent, + removing any lower-priority entries. + - Else: add it to the front of path. + """ + + loc = loc or self.location + if not loc: + return + + nloc = _normalize_cached(loc) + bdir = os.path.dirname(nloc) + npath = [(p and _normalize_cached(p) or p) for p in path] + + for p, item in enumerate(npath): + if item == nloc: + if replace: + break + else: + # don't modify path (even removing duplicates) if + # found and not replace + return + elif item == bdir and self.precedence == EGG_DIST: + # if it's an .egg, give it precedence over its directory + # UNLESS it's already been added to sys.path and replace=False + if (not replace) and nloc in npath[p:]: + return + if path is sys.path: + self.check_version_conflict() + path.insert(p, loc) + npath.insert(p, nloc) + break + else: + if path is sys.path: + self.check_version_conflict() + if replace: + path.insert(0, loc) + else: + path.append(loc) + return + + # p is the spot where we found or inserted loc; now remove duplicates + while True: + try: + np = npath.index(nloc, p + 1) + except ValueError: + break + else: + del npath[np], path[np] + # ha! + p = np + + return + + def check_version_conflict(self): + if self.key == 'setuptools': + # ignore the inevitable setuptools self-conflicts :( + return + + nsp = dict.fromkeys(self._get_metadata('namespace_packages.txt')) + loc = normalize_path(self.location) + for modname in self._get_metadata('top_level.txt'): + if (modname not in sys.modules or modname in nsp + or modname in _namespace_packages): + continue + if modname in ('pkg_resources', 'setuptools', 'site'): + continue + fn = getattr(sys.modules[modname], '__file__', None) + if fn and (normalize_path(fn).startswith(loc) or + fn.startswith(self.location)): + continue + issue_warning( + "Module %s was already imported from %s, but %s is being added" + " to sys.path" % (modname, fn, self.location), + ) + + def has_version(self): + try: + self.version + except ValueError: + issue_warning("Unbuilt egg for " + repr(self)) + return False + return True + + def clone(self, **kw): + """Copy this distribution, substituting in any changed keyword args""" + names = 'project_name version py_version platform location precedence' + for attr in names.split(): + kw.setdefault(attr, getattr(self, attr, None)) + kw.setdefault('metadata', self._provider) + return self.__class__(**kw) + + @property + def extras(self): + return [dep for dep in self._dep_map if dep] + + +class EggInfoDistribution(Distribution): + def _reload_version(self): + """ + Packages installed by distutils (e.g. numpy or scipy), + which uses an old safe_version, and so + their version numbers can get mangled when + converted to filenames (e.g., 1.11.0.dev0+2329eae to + 1.11.0.dev0_2329eae). These distributions will not be + parsed properly + downstream by Distribution and safe_version, so + take an extra step and try to get the version number from + the metadata file itself instead of the filename. + """ + md_version = self._get_version() + if md_version: + self._version = md_version + return self + + +class DistInfoDistribution(Distribution): + """ + Wrap an actual or potential sys.path entry + w/metadata, .dist-info style. + """ + PKG_INFO = 'METADATA' + EQEQ = re.compile(r"([\(,])\s*(\d.*?)\s*([,\)])") + + @property + def _parsed_pkg_info(self): + """Parse and cache metadata""" + try: + return self._pkg_info + except AttributeError: + metadata = self.get_metadata(self.PKG_INFO) + self._pkg_info = email.parser.Parser().parsestr(metadata) + return self._pkg_info + + @property + def _dep_map(self): + try: + return self.__dep_map + except AttributeError: + self.__dep_map = self._compute_dependencies() + return self.__dep_map + + def _compute_dependencies(self): + """Recompute this distribution's dependencies.""" + dm = self.__dep_map = {None: []} + + reqs = [] + # Including any condition expressions + for req in self._parsed_pkg_info.get_all('Requires-Dist') or []: + reqs.extend(parse_requirements(req)) + + def reqs_for_extra(extra): + for req in reqs: + if not req.marker or req.marker.evaluate({'extra': extra}): + yield req + + common = frozenset(reqs_for_extra(None)) + dm[None].extend(common) + + for extra in self._parsed_pkg_info.get_all('Provides-Extra') or []: + s_extra = safe_extra(extra.strip()) + dm[s_extra] = list(frozenset(reqs_for_extra(extra)) - common) + + return dm + + +_distributionImpl = { + '.egg': Distribution, + '.egg-info': EggInfoDistribution, + '.dist-info': DistInfoDistribution, +} + + +def issue_warning(*args, **kw): + level = 1 + g = globals() + try: + # find the first stack frame that is *not* code in + # the pkg_resources module, to use for the warning + while sys._getframe(level).f_globals is g: + level += 1 + except ValueError: + pass + warnings.warn(stacklevel=level + 1, *args, **kw) + + +def parse_requirements(strs): + """Yield ``Requirement`` objects for each specification in `strs` + + `strs` must be a string, or a (possibly-nested) iterable thereof. + """ + # create a steppable iterator, so we can handle \-continuations + lines = iter(yield_lines(strs)) + + for line in lines: + # Drop comments -- a hash without a space may be in a URL. + if ' #' in line: + line = line[:line.find(' #')] + # If there is a line continuation, drop it, and append the next line. + if line.endswith('\\'): + line = line[:-2].strip() + try: + line += next(lines) + except StopIteration: + return + yield Requirement(line) + + +class RequirementParseError(packaging.requirements.InvalidRequirement): + "Compatibility wrapper for InvalidRequirement" + + +class Requirement(packaging.requirements.Requirement): + def __init__(self, requirement_string): + """DO NOT CALL THIS UNDOCUMENTED METHOD; use Requirement.parse()!""" + super(Requirement, self).__init__(requirement_string) + self.unsafe_name = self.name + project_name = safe_name(self.name) + self.project_name, self.key = project_name, project_name.lower() + self.specs = [ + (spec.operator, spec.version) for spec in self.specifier] + self.extras = tuple(map(safe_extra, self.extras)) + self.hashCmp = ( + self.key, + self.url, + self.specifier, + frozenset(self.extras), + str(self.marker) if self.marker else None, + ) + self.__hash = hash(self.hashCmp) + + def __eq__(self, other): + return ( + isinstance(other, Requirement) and + self.hashCmp == other.hashCmp + ) + + def __ne__(self, other): + return not self == other + + def __contains__(self, item): + if isinstance(item, Distribution): + if item.key != self.key: + return False + + item = item.version + + # Allow prereleases always in order to match the previous behavior of + # this method. In the future this should be smarter and follow PEP 440 + # more accurately. + return self.specifier.contains(item, prereleases=True) + + def __hash__(self): + return self.__hash + + def __repr__(self): + return "Requirement.parse(%r)" % str(self) + + @staticmethod + def parse(s): + req, = parse_requirements(s) + return req + + +def _always_object(classes): + """ + Ensure object appears in the mro even + for old-style classes. + """ + if object not in classes: + return classes + (object,) + return classes + + +def _find_adapter(registry, ob): + """Return an adapter factory for `ob` from `registry`""" + types = _always_object(inspect.getmro(getattr(ob, '__class__', type(ob)))) + for t in types: + if t in registry: + return registry[t] + + +def ensure_directory(path): + """Ensure that the parent directory of `path` exists""" + dirname = os.path.dirname(path) + os.makedirs(dirname, exist_ok=True) + + +def _bypass_ensure_directory(path): + """Sandbox-bypassing version of ensure_directory()""" + if not WRITE_SUPPORT: + raise IOError('"os.mkdir" not supported on this platform.') + dirname, filename = split(path) + if dirname and filename and not isdir(dirname): + _bypass_ensure_directory(dirname) + try: + mkdir(dirname, 0o755) + except FileExistsError: + pass + + +def split_sections(s): + """Split a string or iterable thereof into (section, content) pairs + + Each ``section`` is a stripped version of the section header ("[section]") + and each ``content`` is a list of stripped lines excluding blank lines and + comment-only lines. If there are any such lines before the first section + header, they're returned in a first ``section`` of ``None``. + """ + section = None + content = [] + for line in yield_lines(s): + if line.startswith("["): + if line.endswith("]"): + if section or content: + yield section, content + section = line[1:-1].strip() + content = [] + else: + raise ValueError("Invalid section heading", line) + else: + content.append(line) + + # wrap up last segment + yield section, content + + +def _mkstemp(*args, **kw): + old_open = os.open + try: + # temporarily bypass sandboxing + os.open = os_open + return tempfile.mkstemp(*args, **kw) + finally: + # and then put it back + os.open = old_open + + +# Silence the PEP440Warning by default, so that end users don't get hit by it +# randomly just because they use pkg_resources. We want to append the rule +# because we want earlier uses of filterwarnings to take precedence over this +# one. +warnings.filterwarnings("ignore", category=PEP440Warning, append=True) + + +# from jaraco.functools 1.3 +def _call_aside(f, *args, **kwargs): + f(*args, **kwargs) + return f + + +@_call_aside +def _initialize(g=globals()): + "Set up global resource manager (deliberately not state-saved)" + manager = ResourceManager() + g['_manager'] = manager + g.update( + (name, getattr(manager, name)) + for name in dir(manager) + if not name.startswith('_') + ) + + +class PkgResourcesDeprecationWarning(Warning): + """ + Base class for warning about deprecations in ``pkg_resources`` + + This class is not derived from ``DeprecationWarning``, and as such is + visible by default. + """ + + +@_call_aside +def _initialize_master_working_set(): + """ + Prepare the master working set and make the ``require()`` + API available. + + This function has explicit effects on the global state + of pkg_resources. It is intended to be invoked once at + the initialization of this module. + + Invocation by other packages is unsupported and done + at their own risk. + """ + working_set = WorkingSet._build_master() + _declare_state('object', working_set=working_set) + + require = working_set.require + iter_entry_points = working_set.iter_entry_points + add_activation_listener = working_set.subscribe + run_script = working_set.run_script + # backward compatibility + run_main = run_script + # Activate all distributions already on sys.path with replace=False and + # ensure that all distributions added to the working set in the future + # (e.g. by calling ``require()``) will get activated as well, + # with higher priority (replace=True). + tuple( + dist.activate(replace=False) + for dist in working_set + ) + add_activation_listener( + lambda dist: dist.activate(replace=True), + existing=False, + ) + working_set.entries = [] + # match order + list(map(working_set.add_entry, sys.path)) + globals().update(locals()) diff --git a/venv/lib/python3.10/site-packages/pkg_resources/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/pkg_resources/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1a943c59f2b1d9600f682a92b045ad89d41be80b GIT binary patch literal 100619 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/pkg_resources/_vendor/__pycache__/pyparsing.cpython-310.pyc b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/__pycache__/pyparsing.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..252561631bf653d53435d429f7c5bcf886d59e56 GIT binary patch literal 198764 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 MA14PZczN?)0KVvIHUIzs literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/pkg_resources/_vendor/appdirs.py b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/appdirs.py new file mode 100644 index 00000000..ae67001a --- /dev/null +++ b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/appdirs.py @@ -0,0 +1,608 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (c) 2005-2010 ActiveState Software Inc. +# Copyright (c) 2013 Eddy Petrișor + +"""Utilities for determining application-specific dirs. + +See for details and usage. +""" +# Dev Notes: +# - MSDN on where to store app data files: +# http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120 +# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html +# - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + +__version_info__ = (1, 4, 3) +__version__ = '.'.join(map(str, __version_info__)) + + +import sys +import os + +PY3 = sys.version_info[0] == 3 + +if PY3: + unicode = str + +if sys.platform.startswith('java'): + import platform + os_name = platform.java_ver()[3][0] + if os_name.startswith('Windows'): # "Windows XP", "Windows 7", etc. + system = 'win32' + elif os_name.startswith('Mac'): # "Mac OS X", etc. + system = 'darwin' + else: # "Linux", "SunOS", "FreeBSD", etc. + # Setting this to "linux2" is not ideal, but only Windows or Mac + # are actually checked for and the rest of the module expects + # *sys.platform* style strings. + system = 'linux2' +else: + system = sys.platform + + + +def user_data_dir(appname=None, appauthor=None, version=None, roaming=False): + r"""Return full path to the user-specific data dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "roaming" (boolean, default False) can be set True to use the Windows + roaming appdata directory. That means that for users on a Windows + network setup for roaming profiles, this user data will be + sync'd on login. See + + for a discussion of issues. + + Typical user data directories are: + Mac OS X: ~/Library/Application Support/ + Unix: ~/.local/share/ # or in $XDG_DATA_HOME, if defined + Win XP (not roaming): C:\Documents and Settings\\Application Data\\ + Win XP (roaming): C:\Documents and Settings\\Local Settings\Application Data\\ + Win 7 (not roaming): C:\Users\\AppData\Local\\ + Win 7 (roaming): C:\Users\\AppData\Roaming\\ + + For Unix, we follow the XDG spec and support $XDG_DATA_HOME. + That means, by default "~/.local/share/". + """ + if system == "win32": + if appauthor is None: + appauthor = appname + const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA" + path = os.path.normpath(_get_win_folder(const)) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) + else: + path = os.path.join(path, appname) + elif system == 'darwin': + path = os.path.expanduser('~/Library/Application Support/') + if appname: + path = os.path.join(path, appname) + else: + path = os.getenv('XDG_DATA_HOME', os.path.expanduser("~/.local/share")) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def site_data_dir(appname=None, appauthor=None, version=None, multipath=False): + r"""Return full path to the user-shared data dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "multipath" is an optional parameter only applicable to *nix + which indicates that the entire list of data dirs should be + returned. By default, the first item from XDG_DATA_DIRS is + returned, or '/usr/local/share/', + if XDG_DATA_DIRS is not set + + Typical site data directories are: + Mac OS X: /Library/Application Support/ + Unix: /usr/local/share/ or /usr/share/ + Win XP: C:\Documents and Settings\All Users\Application Data\\ + Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) + Win 7: C:\ProgramData\\ # Hidden, but writeable on Win 7. + + For Unix, this is using the $XDG_DATA_DIRS[0] default. + + WARNING: Do not use this on Windows. See the Vista-Fail note above for why. + """ + if system == "win32": + if appauthor is None: + appauthor = appname + path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA")) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) + else: + path = os.path.join(path, appname) + elif system == 'darwin': + path = os.path.expanduser('/Library/Application Support') + if appname: + path = os.path.join(path, appname) + else: + # XDG default for $XDG_DATA_DIRS + # only first, if multipath is False + path = os.getenv('XDG_DATA_DIRS', + os.pathsep.join(['/usr/local/share', '/usr/share'])) + pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] + if appname: + if version: + appname = os.path.join(appname, version) + pathlist = [os.sep.join([x, appname]) for x in pathlist] + + if multipath: + path = os.pathsep.join(pathlist) + else: + path = pathlist[0] + return path + + if appname and version: + path = os.path.join(path, version) + return path + + +def user_config_dir(appname=None, appauthor=None, version=None, roaming=False): + r"""Return full path to the user-specific config dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "roaming" (boolean, default False) can be set True to use the Windows + roaming appdata directory. That means that for users on a Windows + network setup for roaming profiles, this user data will be + sync'd on login. See + + for a discussion of issues. + + Typical user config directories are: + Mac OS X: same as user_data_dir + Unix: ~/.config/ # or in $XDG_CONFIG_HOME, if defined + Win *: same as user_data_dir + + For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME. + That means, by default "~/.config/". + """ + if system in ["win32", "darwin"]: + path = user_data_dir(appname, appauthor, None, roaming) + else: + path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config")) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def site_config_dir(appname=None, appauthor=None, version=None, multipath=False): + r"""Return full path to the user-shared data dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "multipath" is an optional parameter only applicable to *nix + which indicates that the entire list of config dirs should be + returned. By default, the first item from XDG_CONFIG_DIRS is + returned, or '/etc/xdg/', if XDG_CONFIG_DIRS is not set + + Typical site config directories are: + Mac OS X: same as site_data_dir + Unix: /etc/xdg/ or $XDG_CONFIG_DIRS[i]/ for each value in + $XDG_CONFIG_DIRS + Win *: same as site_data_dir + Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) + + For Unix, this is using the $XDG_CONFIG_DIRS[0] default, if multipath=False + + WARNING: Do not use this on Windows. See the Vista-Fail note above for why. + """ + if system in ["win32", "darwin"]: + path = site_data_dir(appname, appauthor) + if appname and version: + path = os.path.join(path, version) + else: + # XDG default for $XDG_CONFIG_DIRS + # only first, if multipath is False + path = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg') + pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] + if appname: + if version: + appname = os.path.join(appname, version) + pathlist = [os.sep.join([x, appname]) for x in pathlist] + + if multipath: + path = os.pathsep.join(pathlist) + else: + path = pathlist[0] + return path + + +def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True): + r"""Return full path to the user-specific cache dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "opinion" (boolean) can be False to disable the appending of + "Cache" to the base app data dir for Windows. See + discussion below. + + Typical user cache directories are: + Mac OS X: ~/Library/Caches/ + Unix: ~/.cache/ (XDG default) + Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Cache + Vista: C:\Users\\AppData\Local\\\Cache + + On Windows the only suggestion in the MSDN docs is that local settings go in + the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming + app data dir (the default returned by `user_data_dir` above). Apps typically + put cache data somewhere *under* the given dir here. Some examples: + ...\Mozilla\Firefox\Profiles\\Cache + ...\Acme\SuperApp\Cache\1.0 + OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value. + This can be disabled with the `opinion=False` option. + """ + if system == "win32": + if appauthor is None: + appauthor = appname + path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA")) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) + else: + path = os.path.join(path, appname) + if opinion: + path = os.path.join(path, "Cache") + elif system == 'darwin': + path = os.path.expanduser('~/Library/Caches') + if appname: + path = os.path.join(path, appname) + else: + path = os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache')) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def user_state_dir(appname=None, appauthor=None, version=None, roaming=False): + r"""Return full path to the user-specific state dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "roaming" (boolean, default False) can be set True to use the Windows + roaming appdata directory. That means that for users on a Windows + network setup for roaming profiles, this user data will be + sync'd on login. See + + for a discussion of issues. + + Typical user state directories are: + Mac OS X: same as user_data_dir + Unix: ~/.local/state/ # or in $XDG_STATE_HOME, if defined + Win *: same as user_data_dir + + For Unix, we follow this Debian proposal + to extend the XDG spec and support $XDG_STATE_HOME. + + That means, by default "~/.local/state/". + """ + if system in ["win32", "darwin"]: + path = user_data_dir(appname, appauthor, None, roaming) + else: + path = os.getenv('XDG_STATE_HOME', os.path.expanduser("~/.local/state")) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def user_log_dir(appname=None, appauthor=None, version=None, opinion=True): + r"""Return full path to the user-specific log dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "opinion" (boolean) can be False to disable the appending of + "Logs" to the base app data dir for Windows, and "log" to the + base cache dir for Unix. See discussion below. + + Typical user log directories are: + Mac OS X: ~/Library/Logs/ + Unix: ~/.cache//log # or under $XDG_CACHE_HOME if defined + Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Logs + Vista: C:\Users\\AppData\Local\\\Logs + + On Windows the only suggestion in the MSDN docs is that local settings + go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in + examples of what some windows apps use for a logs dir.) + + OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA` + value for Windows and appends "log" to the user cache dir for Unix. + This can be disabled with the `opinion=False` option. + """ + if system == "darwin": + path = os.path.join( + os.path.expanduser('~/Library/Logs'), + appname) + elif system == "win32": + path = user_data_dir(appname, appauthor, version) + version = False + if opinion: + path = os.path.join(path, "Logs") + else: + path = user_cache_dir(appname, appauthor, version) + version = False + if opinion: + path = os.path.join(path, "log") + if appname and version: + path = os.path.join(path, version) + return path + + +class AppDirs(object): + """Convenience wrapper for getting application dirs.""" + def __init__(self, appname=None, appauthor=None, version=None, + roaming=False, multipath=False): + self.appname = appname + self.appauthor = appauthor + self.version = version + self.roaming = roaming + self.multipath = multipath + + @property + def user_data_dir(self): + return user_data_dir(self.appname, self.appauthor, + version=self.version, roaming=self.roaming) + + @property + def site_data_dir(self): + return site_data_dir(self.appname, self.appauthor, + version=self.version, multipath=self.multipath) + + @property + def user_config_dir(self): + return user_config_dir(self.appname, self.appauthor, + version=self.version, roaming=self.roaming) + + @property + def site_config_dir(self): + return site_config_dir(self.appname, self.appauthor, + version=self.version, multipath=self.multipath) + + @property + def user_cache_dir(self): + return user_cache_dir(self.appname, self.appauthor, + version=self.version) + + @property + def user_state_dir(self): + return user_state_dir(self.appname, self.appauthor, + version=self.version) + + @property + def user_log_dir(self): + return user_log_dir(self.appname, self.appauthor, + version=self.version) + + +#---- internal support stuff + +def _get_win_folder_from_registry(csidl_name): + """This is a fallback technique at best. I'm not sure if using the + registry for this guarantees us the correct answer for all CSIDL_* + names. + """ + if PY3: + import winreg as _winreg + else: + import _winreg + + shell_folder_name = { + "CSIDL_APPDATA": "AppData", + "CSIDL_COMMON_APPDATA": "Common AppData", + "CSIDL_LOCAL_APPDATA": "Local AppData", + }[csidl_name] + + key = _winreg.OpenKey( + _winreg.HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" + ) + dir, type = _winreg.QueryValueEx(key, shell_folder_name) + return dir + + +def _get_win_folder_with_pywin32(csidl_name): + from win32com.shell import shellcon, shell + dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0) + # Try to make this a unicode path because SHGetFolderPath does + # not return unicode strings when there is unicode data in the + # path. + try: + dir = unicode(dir) + + # Downgrade to short path name if have highbit chars. See + # . + has_high_char = False + for c in dir: + if ord(c) > 255: + has_high_char = True + break + if has_high_char: + try: + import win32api + dir = win32api.GetShortPathName(dir) + except ImportError: + pass + except UnicodeError: + pass + return dir + + +def _get_win_folder_with_ctypes(csidl_name): + import ctypes + + csidl_const = { + "CSIDL_APPDATA": 26, + "CSIDL_COMMON_APPDATA": 35, + "CSIDL_LOCAL_APPDATA": 28, + }[csidl_name] + + buf = ctypes.create_unicode_buffer(1024) + ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) + + # Downgrade to short path name if have highbit chars. See + # . + has_high_char = False + for c in buf: + if ord(c) > 255: + has_high_char = True + break + if has_high_char: + buf2 = ctypes.create_unicode_buffer(1024) + if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): + buf = buf2 + + return buf.value + +def _get_win_folder_with_jna(csidl_name): + import array + from com.sun import jna + from com.sun.jna.platform import win32 + + buf_size = win32.WinDef.MAX_PATH * 2 + buf = array.zeros('c', buf_size) + shell = win32.Shell32.INSTANCE + shell.SHGetFolderPath(None, getattr(win32.ShlObj, csidl_name), None, win32.ShlObj.SHGFP_TYPE_CURRENT, buf) + dir = jna.Native.toString(buf.tostring()).rstrip("\0") + + # Downgrade to short path name if have highbit chars. See + # . + has_high_char = False + for c in dir: + if ord(c) > 255: + has_high_char = True + break + if has_high_char: + buf = array.zeros('c', buf_size) + kernel = win32.Kernel32.INSTANCE + if kernel.GetShortPathName(dir, buf, buf_size): + dir = jna.Native.toString(buf.tostring()).rstrip("\0") + + return dir + +if system == "win32": + try: + import win32com.shell + _get_win_folder = _get_win_folder_with_pywin32 + except ImportError: + try: + from ctypes import windll + _get_win_folder = _get_win_folder_with_ctypes + except ImportError: + try: + import com.sun.jna + _get_win_folder = _get_win_folder_with_jna + except ImportError: + _get_win_folder = _get_win_folder_from_registry + + +#---- self test code + +if __name__ == "__main__": + appname = "MyApp" + appauthor = "MyCompany" + + props = ("user_data_dir", + "user_config_dir", + "user_cache_dir", + "user_state_dir", + "user_log_dir", + "site_data_dir", + "site_config_dir") + + print("-- app dirs %s --" % __version__) + + print("-- app dirs (with optional 'version')") + dirs = AppDirs(appname, appauthor, version="1.0") + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) + + print("\n-- app dirs (without optional 'version')") + dirs = AppDirs(appname, appauthor) + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) + + print("\n-- app dirs (without optional 'appauthor')") + dirs = AppDirs(appname) + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) + + print("\n-- app dirs (with disabled 'appauthor')") + dirs = AppDirs(appname, appauthor=False) + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) diff --git a/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__about__.py b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__about__.py new file mode 100644 index 00000000..c359122f --- /dev/null +++ b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__about__.py @@ -0,0 +1,26 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +__all__ = [ + "__title__", + "__summary__", + "__uri__", + "__version__", + "__author__", + "__email__", + "__license__", + "__copyright__", +] + +__title__ = "packaging" +__summary__ = "Core utilities for Python packages" +__uri__ = "https://github.com/pypa/packaging" + +__version__ = "21.2" + +__author__ = "Donald Stufft and individual contributors" +__email__ = "donald@stufft.io" + +__license__ = "BSD-2-Clause or Apache-2.0" +__copyright__ = "2014-2019 %s" % __author__ diff --git a/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__init__.py b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__init__.py new file mode 100644 index 00000000..3c50c5dc --- /dev/null +++ b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__init__.py @@ -0,0 +1,25 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from .__about__ import ( + __author__, + __copyright__, + __email__, + __license__, + __summary__, + __title__, + __uri__, + __version__, +) + +__all__ = [ + "__title__", + "__summary__", + "__uri__", + "__version__", + "__author__", + "__email__", + "__license__", + "__copyright__", +] diff --git a/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__pycache__/__about__.cpython-310.pyc b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__pycache__/__about__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..661273aa05ddcfcbb0c2918d86932dda8d3532c5 GIT binary patch literal 618 zcmYk2&yLeD5XO@>ZPRuuAr2fkd678ukhl$8KnN{tj|i=(;E;>u#%|(mVn_Z{DY@-K z>dLEv(=M?UhD19ki3^EaIlWtgLO#TZ~I#E4NDv)!?>kDSm7dW$QHSmm4K%&Cu4m z{oW!f84{9-qhOBKs8G^sv2#{w(?4Mk(kt^IO@->8f81`8EV-_!GaSJfAE153lPq2J z7ujn4Isv>vFU{xW)LTJoSW|~!vi6qip-1E~@)&zeJYv9)K+pZ(slwnRmq%QS9W3t6d6lkL*nlYc=MruYCDGg{ urweW{fdZ!VqeU1d-YEz6r)_tN-!+Q4n!oq+o2P&{isn%WcK{c75&i|lYq^pD literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..96a07641efb0d8a2098ac2cac9ea8df770bbebb3 GIT binary patch literal 474 zcmYjNJx{|h5OqG-jo-q??_lH~WMyJx zW#ZgOs3-Z!@BDo4?#yB_B^aL{C)=NZkS|Y;BSdnG$6XulHtAr&gWobZ4!o#EIsp(Wmla-T z0F^9+c48o|RVugLAZlb&`FsMz!a8X*K$hABo$S~{>S5x+PhevYBM%Y6LpJr6 z`P#JTJ}7+tNOmZ4DED86@;Y@1_WPl&Iv2=Kr dx}*S7OYD7FcYS)T3~wuO_y=XM2YG#_8 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__pycache__/_musllinux.cpython-310.pyc b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__pycache__/_musllinux.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c37f5c4e66ef23ff43945858db4b7e722683bfb5 GIT binary patch literal 4640 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__pycache__/markers.cpython-310.pyc b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__pycache__/markers.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a75dab95430257a403f56d6d5b5d42fd3dce406b GIT binary patch literal 9324 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 DA=EQT literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__pycache__/requirements.cpython-310.pyc b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__pycache__/requirements.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa32eb694d2eb17fc960a81a69c2bd044d5e0af2 GIT binary patch literal 4012 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 j!`5|VxcNfY&Hv<$3}h(6(6PWX4I6YCdTBjnt_5 zYm2EB^L_Vr_e{@2VmZmCoD9%+Zr|s*=bn4+x#yfat%-@ehR;9y2ZuI)_lBnZ7GHWl zIlP?1J{OX)_Ylxbv3*+#CEYvfD$ z##m{rF%~ zv+m5gv7nXqsJJ`7ZsKjhopjRff|GgADD8FA?jCo3!*KV>l8c|!oUD_3M{{!S{#$10 z0N(S?7~aQRZS$Zr{+?N~kv8EJkXCSQlsSa=NoNZ0Q|=+WA9i-RX=hrtYpQmg-Tf)+ zmNVnbzGIb^P%)9fbnRn+GG?ZU(_M-ezXP->nhm=KU zKT?i42UHp7VEDb;EvQ&n7dTi?JGOHOrHknAu(O2zmL&AY9sQzKJn{kOaJ4uVWY1UX z^~ze^4Xg{bsvl&Z^<5#~Rm^XRzOtm(ijPvwz`W@CLF&@>7OH11 z-Q03tu81J@QWM2K06AzyJ(#IhnyqH7TB+CCZuy!kJY*KlVB$Ggn z&x*6TAzZh)a>4bk_^qv#7caj2LixFx@2*^SMZ@*jfVXnZZC+cc*Vb0HZu*y7%}*`= zz#}Uj3Z2@jRIgMvTyJIT%0^kZUTa%a@dk7^ot9Wp*|p}zN<36=dF!T_0W^n*I}1Tp z%jz;^0LAeV{|<^pD0a}#(mddO%C(F(_1rW`AF!5Byb7P7L(!%w(MYBp;i$09= zIgjT<;!mzCqy4@l4N};PvXW=}mtEWQ1&C-1Q{y)M3YU@HT1QUJv*T{;dhLp9x7Ie@ zs=sVsLSEcdNQP@}qLFJT%|_gwHfp}-*4Gt%5{OkvZFlv+VtSYU#9n|LfHDDDxqOU+ zFc4T;dp0rl<*>xav5WnvRy5kr#K1=5uGp6=-enuBZ`;MH!ulCK+_l=M0YX#i2H8*o$no zWrOEcTi}AocI@jlaD}E~4I`)FuBq0`adav{=>s{)h7Ec@$`Zp^LsTHh0tPAHeBy9^ zCLYeE$`ulmgkRPr-{n}C=-BJEI@*+szF~VU45s4St!DjZSmyfWTJ>_=7`ehHj6XrF zz)sEYC(L4qkyE2El2$;y662$fji~Iq`f71j%(BoNgFOfWv$9qV3Y9hN43(3D@GSsIY<;4 z$AT2WgG8#7cTf(J3y%?2X)JsjcP8*Q?oQyn5Vea^1!q#pH%Ont`;@Z_<#yq4FpZQP zB%R&P3{rOYq)=M1N;A$LK#*}~QD)v*K+2r6*WH7Zd8F)f7Ll?b`y({_oddF;y>ZGx z$CfGkWQrUI${lhJ%hW}8adSV83^_SA%Q+III}gcT3E9KWQS@;jXF1DA-P6Se;7&Wo zoFcGsQ05-TMtBH#it(l1QR;+q5~U717UZs#V5atkd5FBGSNXN zBR+%PN%j>{y)5PHAhjV{+gm{jOR9#iGRJ`W;CaJLYX5WD|0=pXzA0h*lG*u~zRDPTD=gI3u{-x5rO^ap%|<4~d;G?i9}! z)8c6)hTH&RAqE!Yu=8_ymdlD6{UAz&TU21O!NGA;nuctOf*utVd2{@}VHn?=8$D)K zBQa*~U>vs`^Ee`ozR7Xx?{lh+)wcCT0&R$AktZ%N7$TWUzSu#g$F|1x_TmVvOIdpW zfS69_QClKiU(kG_9Mri0^sdX3?5ZHNVgdmGlCW^XhXE8!mg7^mq^`G6Py#n^w2ur2 zE?Hp!$Ouax64}1Q(Jd$v1ZDA}5m*$w#p(!nJv+4>Gp^$xY6D>d;+A{|9u=aR%|$G@ghf&9Im*?G)ZN{#>jpC zYbjmX2eOBMJ1QTUcj(3u8ppr+DGAL2px6Jn8;nUJvNt{E4 z#9&Cz79L5M;b1_RzOha+8}m()`NK z9%Z~Vf&ysD(nl`mM5T{k&JoX%2$x*uu0ERCJ%PTzg-5MrW5imHrekE!;&rrv36=~^ zyn&ZtoJ?Z;FEH~92u3j0aIYQciu_Ve4AI3whLeEN$t`FYE2pgaSMW$~nKs(?P^#$J zG|bh*F-blQ&IdktCn7G4 zi(+*|O|0M70~M3eNvK@@8!W&RQ~S`+QTCQiNLO6G5g^`KymX-9-_}HiNXF9bk+e1q zvAbhJ4p@2RNrAR zm~2vtpnlmY%hT!~W7$635;MPwM@mWAR9eql`4srEh5dTCloTOK@Q{@lKlui6P-T1X zBlr>+*p9iO?N|;NmlT@DI~118n!bZg=7RRd(oR}VR~n-yp7c&;U9V}K)Gz4o>*CQ) z2AEm_rdE(=BD8K>9hB9ztGgT%GHd!}Y-U$?L3VwzV~O(}3vK4mrb_<|sVjOQh&>~* zc?q~8Le*(&oiQRgNLRMDpcyV^6(-|DWRSU8bL)=l1iPdbyk`=;z_@Z<;y)-z4ZRcT z^7|3Jj2?aik4O45-nJ}DpE0slUY|D7mVs^1;#O!{7EWq!XH!|QvFt}+WW%?`@SJK$ zNpe+fSWA5;R=FDp5~SS3z%t8~CbUyh&q|q+^QF$9Qmyt`zJ8v8jUdRz$J9L2q!9Bd z#tt)hl)*y`xaS2^k-6mg@JJ%5Gnjm-sjw26F(zK18N&%*)hxs$tVsPaHtH~V(8KVj zQMiJ~dk#Tfn=!`qMWZlL$QkCmUNEQ4=`sAJjntHGn0#jnN4wqGV`t8E4xQ&5E-(GX= z&27)OE3l^_Y{Ha;tdsV2*Oq5Y+eKq4ab$DujjFrlOQk-PTfXbT!sG%P+jASWQ`J@- z_6=(M8x?T{YIARUYpW&vlOx-!*wuOqlJ|y9Nv~-milK*tg1S(F?KW8oO2b-nWJ^Fs zL!kw2wi*q9B-^U_M~TC*Nx+GYs8Q%D*19uw&-wx;>z;++NRW~$?jQ$#0tp>@iD0ts z;-m^CUo%XYhV8BbrB-iRE-n;|_huK<_lz@V?iq*hoW^tZ%su_|J^gIR-{J$mSj3u3 zY&s?qC~ETCqv+ymx8z3KG42>R=NM36=)T3cR8-*fS~^d_h}MdoFt^e&1400%9U7&o zfZma`biG!ulR|{sXkC+-1_8Qu)4kziGB+`$q2{}og8pI~JW!CE2HEQs(L_gHU@;a< zUkKZUrRx=y6^ZNd5FxJo2SWs z1#&5jLoBB_o)pN<9)VmQfl4txTD8sQ6Pt%Mw;)hATA{4l44Czh>u5-I2Qq=6wC{H`@j$Y)k#Vg zePEEHgQ9qsKzx-!9}v?q5b-KG2tdT2K-u=;kzmAy9~hKK(j5)Td<;sw0M#lz90PQ0 zBtU&t9vG}tB-xL|(7qU~US^;mdx(U^14F@VpBM?-V7&(hFFtsT1g|@RF$#{y_!>Qd zap4CB1!@;5!;PN6*`b&^6+`pT17S!sfY0K5pP&d$veX{a=p7gWJ}cBM)abnO^3yLq z_58)BgZ#6fU48z=r_Vom@oA~KRkEdI3>1e%SAe|AS9y+?BK9BW>rXOxj{#2w;-?t= zNd!fY zn5w)W#Z*rS_9Ew&(+W6bw?7y6{bw2DYO4m{T+1l^p-=rn`$*&dh=()3iCgUN_v=o+2i|F*dRqtgTq z2}f`ocqlYF%{xaRh(9byd6g8_TrB=2h3_}dbo%6Pyr1cj!AHv7$DVjRZnG;jUxMgU zbD-|6`8VxI#)lY5Hv%c&LN0Z^YGuoXKn3%Rviwqp8q%24f`nzeH(*nZ#Bn8Whdw-! zXnM1?4XZdDXW(uobBt3|zw8;;F z4W|H#?iGx*YB%8iLYZ-0v|y|cU6JD9(|qsLyeeGG;BnSAyi0~aPbqMqbfyGIQ+*hzFVni7w} zkFqF5w^8H>a&R!+Ldo)y118#LZ%fe`2>uE!(hWsaIz~ElUBGZISKwEMEXQ33u~7W? z5MFN$X{oy;!t)d-8dcpU9Z2PBNDiC;s7b~t8PnYT3{1EjsPeBMfvea201(4w{Mr)( z#emh<9y#^tH%=TXKGEB$yVdG(&4_YTQPg>TSw)M-k+l?^eTKCjRqgp+qA7G_9Tt4; zRQdJgHwK|)-myw&tyArYYNt})x{McCK00WS&$XI+JxN2X!OzFFD2LM3L8uD0T^#Hm zBad=>zA>H_^akMpZ~zG`iHfH}jCF`|HNIo4*8$iaQ4Et5EQ4bJFar%Q*{^~nhFlD8 zxj4)w3B3YZy++?WN%0YV4{675!J0!qK2W-}!IQch(gz8Rb|k}Wz}H75w?cR4VI(}b zTZF?8#|MCtBkfY%l7#jXu=x6k*D9yll44^##wo?)AmSx8+JR9LjwDB63O}zezkXaY z!I(>UH{phLgU(FLpGX(Uxk~l`5C`B`f(I)dop9pxYvcwIvj71}kqv9xx9O(`e@ggL zI<*b>u43aL@`tn*A`IKq1`V*oCgt>*HyBaJ$R2aMBY{%2zZqZgEVvX&E15q|L50#X4zr8=!zby}`l~9yYEkp!L zQ_gJ@OAU@0dqPZ%50lk0l5l~&s23fcUc=XGc4obku_FBDG z6~Bxk%D!&a-6s82E3pqikdpgCF{dczGC8=6Gr<1sk}Yy`QzRW;&JEvqpP< z$V|y&W@3^|G!jn|=UbX;fYLk0c%)b_>ax>ewh^fk{ttvkYWR)z;*efRr^9<@?~**G z5uwxLCd^m(#)Mvpj3My%k*ZU=Ee!>M3hDn8fiRtJA07fitjhlb01}%&{^lqMs15%x zh9KL1Xb1#@296->ahjn@Nz4dmBgV^VyeNEtqri*%3XF%+*T~$|y+z_nEK0lqWB4|? zsIC}dk!5&8^sc%3%q5s6B?i~gSTMc@IU=I1yS*&pSCKu;2q*RY-{bH|A8=`*UlA`S zKp{MIzGGyM9v3~p663NSd|Vi{DSePm4H@~VXyoE=p$o7;jyx#P;|izrP;eE08;Rnt zG59qG3vs|TB~2Q8K+hf>V|&M&x57~${DcWk44ji0Pv|^e2=*=gwtgo>0b{Xc6l!{^ z?8$Pj!~9HxK_7fU*PIpac8?KUF2Q-YXAc1`E|~BlOhEP`YQs4T)^+&AXBm?|#5%aG z4yQL5QLe$i3f~3@%my!qNd>$dG4O9oa%h9x{Fc6HiK7lyjgtoV&8)&pOYXvQ@0Gjo zZ4T-V1DQeYEdCOLOV6O}(|o(Y;4d?v1F0b8Mh5n-n^E7i9YvwXi3c*1JYC@6eKn1% z0(r1T%V^Jb@q&FYBn3}8I1XN3BQDS55kJOYg+ZA?k-?CTFQS6*Lw5QOE2hobrA(Zk(3;QOsKd?j1<`J`n0H(FAa}a zL+@lA^8zkE+|s$t!lf}$k<>>teF~4<=f8xd`9^jJ*38?|C02S%V&R4MW?TZx!>wQV zQm}z1xv~6+pkFa;jsFN=YB(f=Aa{BL0?Canadv~_p>zu`MtFVX@54_vAysy*jBy>< z8BJ&a0=*LGYp^j_1B-kW{hKhdh<_6IC1aBA?jmiSU8NBj2bJaT3#LK7b=UFp3Zk}g z*-tpRaO8PBl0g1FA{+QK;4}|%fC#DNZ`yR<0ahq~lN3N8Q4W3^0Xb4KmpQ1@!9@u$ zDvS3`98JmUstzt%z*l!5$AAkU9Q`(QA5wg`M7NcB?Upfv-eoJvvSB~nTHUgQeKXTB z-Ul3E++cjNwp%P`i(w{4YH@}mByM=IlF~6 zK4d#tt$mzo0ztm%Rvcs~;UobUvN z8JeyFsZtuZB25@{c@xqqWx_Xg6Ecgp9P-pnNa+viXe=$;;18bgM^$h~5{BXlluaQ8 z_aKo{=t)txAt%GO)J@1KyzO$QyJ02_JftKcoT!^{hL)IftK9Au$?g zeUU$~!gOapOwp=G8(t^xA?Cy2<0rqYQ`HoUc&Jo~ZQT_nQ z(O^8@RdK6Zx%q(?g+@2wsZD7SXimsPbKx>-T|L{ zW8Y2+H=!)tgyIQMF8EiptCJR%^-Z|)@K5HZqTE-Ji#t*24lsc030QInv(j^L1h3^X z^9bk$d2ODzTDaTC&A1AAT9O^kLxdX#lYfk_K==BHG030h(Va?*vp-In#G@^0 z?P4#gVkf`eWf;}Er=P@O^Y5{B6B!Tc*mG9^~SA>KlP{ReXz}Qv{w#r!8FYk{7+E;bM(Lr`01( zVcz4xBsd6%O z`J)W_nA7Kx_Ol!h_m$`}etSmB-F^P_Tax{}gF=I0z!uczaH3AMM2pCIKg$a=C|~qOBgX z^Pfi@+|#8#!Vw>^bvoJhcVQ##HoL&1?bX{e!#YR|kBpv$ImzOA1{9WYig|(YksTdI z?w>Rr|5eE8@h4A; z|CNvp1MgvE{6kXxe?w#dsG+|QLzI%-6Kjj2)5x{O*@X(TT>j_m=||a9|IEqk2U^DR z2U5i7zIa8ScLU159(UHim{tBjZ}B=_2838a1BwT4g4;E`HbY5&Q;G$=mJO9|qB-J- zk7TiPh)5Qj<`%xG^)XnSh`1|??=VO%3qc0^TWBa`urpfwM1pR5)CR+uEEl4})vq%) zjKi|?-((+hCxVtD4RzX#)uXN@3+(CX<8NS!A7e+7>ONLK@!RO~0q$LjO5EHUV^s!y zSaOi|0h^PgwUEHj$UBvsZLTkV%llnEJY{(Tif$6W3%>kS?OmMCz>}vz57t-pcXe;! zUGQzOK&_sSYgJ#tNlEU!R8VEq&MWubX9fr&g`Pge-Z%@AAf!yUiGJD3dqzkYq$flC z9ty1rIp*&m3fb;Bz7(m*@yjy~2_%CdgaiVa?pR{@(Vfa+)DbfANj5vhU{~D1I%1IV z>UBjsCzv5S{e8w{&va-09UxsQAPT%zNCj8c()acYF%b--?{%I>TOQR*RNeL7YlSv> zAMi>g%otTKn~;5#O3%^XgBtMa1aErt9w;7l@AKGB8hdNnhjB*ELJ_#&58@S1A3ujdFMN++qv)t=pl&iJ#p_+CnYY0upy4U7=HCs@^F%h zc{M3=3n1%IKI?LW?87b5s?OQ4IZD1-j3+n6SBvuo(R%maP*GTCzRgm?sB;_D2)#o(tI+(Hnf z{hM3xpAhI=Q#WAAL(~A%j7ePaATM$Z@(jip5JN#~+ppCSpK R(%#DNpTnKl<@C(-{{(F~TF(Fg literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__pycache__/tags.cpython-310.pyc b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__pycache__/tags.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a66888d2c5067334faec697b4871209b0b91c057 GIT binary patch literal 12242 zcma)CS#TW3d7f)$4=ff3Z&IR$)IqIG01`pdl1)+K0ZND|kQPWC8`;Cb_5fI5cW2Qv zAc^H}LL2l|G2^mx`$!1KIpj(?RZb=4hs2di;yn2&4@qhsHhog?!W)LCm0+oX!!fdzaCop%!``#Q+nzD8^p`Yc*0Rr)2?Vj6M9{%s9(LJ z^SeCWuQ^46dQw;!A7Z4Y7A9| z8pD-gz*{0yA8CwMMjJaSJ9N$0?(Xz=ZRnNV{-D2SpH|uHm;62co_PcHeZm%5krR1Q z5Ch-JRQCH>|A2ph`us;k@ucP(OOJ}dWxaCn6-|`H&}~f&ts9lc#IS!rjQBbKv3=US zB}T=L+g8O9uZmr0-|Zhl-wpwELy-D;$?9f zZ_g!XplD1ddPbbRZC8$A6?CH1a!yPEa&il#+N&kbi&p@BT5DV&?3MH9r{9{IEf-?zT&)_#`SUVp`^{NDigVMEFTJ_CpVXp2#@2;eh@OkB zs1`K6dYqf}S62OI)sHimS6cwhTxp^wwyItj-P18)*@z2n!)vb9Yt7XgR9_A2s&2H_ zVx!uMjTW97o~5;NCbnB^(IQY}t4YObtpTu5lR-0Sv6mFcp6Qv(Q?FloeF~*J`|8El zFS|3RUz?h(Lj&-irB#z!?Nw&gz0C5g(rgtq6?OR z@Pfp)pB1*B!QnuVZ(HknCD&^yia~D4_gYAh38nxs9>BOFj%q}V0?#1cO2D#1?8Mtp zf^(PHjkn?CjYQe1jEF1_ZeMI(_7>V31qXkp9qG5EANtM6qcd=Vc_&)*oyc2oB3Z)` zIP-yYJm>nN@7Ko*JZQQZMjj3+bUf)h)u0*HgfD&J)EbS|h>qCtngV!M6~v+!E>i2b zGqtkn)oan3Q(g3{%eCeLhzq&6F!fkqu-OUJXR(n^UDcfD1)IkkVnj(nHEr>G4-4fVjvi?I!)Bx`~Im>l7*x_$$)ajW8P-vqUPhqc54u zMsN^y3w0~0XHd^1bsKei8SrY)S!k@VmI$6a0IYFoCWxjR5YYyt*B8uc%zBCmi5<&^ zI)ri5w~;EgUDsc6-A|$`97R#kExl+KbmM-(Fn(DujbF42iUA1h!;z4<{b3{|E+cVp z-8X1DK6^doFV*qD1Q|dw3HPHy{Pj(^VEEOOU*ntFw=>XZ#>~(3v9h^M{2scBtm}r6 zbltbpMeNtx18R@l{=gb~N`eR^^d+n#!W!0fVcfydl+Bs;&XYGuw!6;D4p-co=$=aG z9%rcwEwEZPX)G^-WfBiv8z@Ao*md7Y7dP6Ykm@Lp5*eFb!*^X6KDQBw)jHLSu1k6~ z>A@zUBG8=2ZtFo5qH1D$@);n8W|Tq8m(kibWfa@N+>#HAZOsKiT|P&!Cgf5cLrs!y zkfbR4n205hWtzB=kcY%_InB~@+5LtwFsvJvX zBfpADq^)aR5c{q!^bP$TvukwCt_A%J;&15Zv`-)CW}u_fw;f%(WA)w!x>@LSBQiUg zj@`+wn;W{WtsGq0voZ{AuCJ2^W48a1?aU=lhOlL1_CTC#uw}4auzyec;|t@?)z;eN zSTkt)V{kgImL1R}a6D(8{hH8^$YO$p(4H8@#7Mr*b_bdkzyt;(C*j=G$WDq?SlB?# z7=&SMuEs_&TJ)lx^(CXo%c}+rve03&D&SJ)QBHzLu|5tT$3$bC30uGto1PGg&iURN z%Q;D8jV){|jEjBhHjFLk$1u*P3N75^i_J#2@7U?Y(L*$If$l0^S$){qVZFrvJE201 z`#Dq2pznNa5nsmEbx$_8s7VvOJ^6YHFA%?H`=)|@#XCsYlj1dqcW z=fkxSjth{)r3F87Row-`*scc)3-C5F!k=4RfMU6bwaH5;;>nDmQXTQl+;VbEPZceveI zXob30^XI}0{Bh^H z7drDb8A41p4H^zCJ;}-OV+qq!SY9^n5%b)m^+B_mb-x*BB8nG4i{C`!c3KRQ<2dPh zEl7wTb^*b3*lWwHsKVGyS78?5U*x!Hc3nS6k4!2GM|m|IZ`vUe~)gCrj&E zcrsl(G6YQgy3x%>a2|z)T25r5%r&i>?_|THohiI|YV(N#vpIG*JM=n}8T#h=I<4C@ve}75tLCyctVY$Kt) zFP@unXQwMu_lWCj_wNsbt5@X+UL?sv`*5{&Ghv$UO%}rL&DI)NxgJ!zH&?6S@h&7d zn}Ep6G;=Nmh%Evr7o5N6?Ucu^#wHrGN)2Obw1-i_5}tr`eFw!tQF zmSYZ#4mSNlijA`ix zw?hB?(z0b67~qwYCR2O3fLfI#iT;T6sSSP{_3#W@J`14_3lW?yQb@$=U8p3stE{3F z^Ka;1hKDP3u;3Ajm7pf9WlKI8fn_@;m|t%i5E(s!PYY4eMF!S;v}?7=MrS(~vE`fE z%9y1^xwV&cEJfGe#`|>*BaGHmlwT(iGdg7TOArJDzj=sR;Y%7Z@Vfa1My$NHG93-j z>~iso8nQ76wr}SMc3q#sYF`Bmx91*gPsV*aDniICWMizol$Fb`o0#bl_!2bZuC6T# zOXO2%Vql-v$<7uM6d9kReVY>$8cT z?6b;=cD$?t@lb`>P>#X;#~IO>Q*E8xs=QB)odlPlObz*|4~8UV_@MyV?Rgl+I{XoM zDqhpszKU(Q9p`c9VjbCmRFfxC8#*Gm-Xtf8RXeYBdVU)=Cur8!oUr9rYxB_Cj^{MI zC1fK~ivo|W>8wIX$9jGnOg`Y6f)SEOcZBwEgj@lT+__$>*U1&YAS4;mS&z`7k2t`b zKvP;HofcV*rb7_$FnB@~Ydu73bfoaiyeOTqCLLvXomPN|oN_9Z3~2@tfY`g6^A~E( zCJeGNX-eU?Eoi;2ROXGHvPpPOSo;>MOv;MlB69uSBX64o6r!L<8W<2ln23VGtY^p$ zKxQEGd@kosqwdQr@ECb5!)^mFsx@Z)&=Y;@*AywFmmtKN|$BmbEQy zraUND@Dmpk9zgB_aTTd(8Bp;VDoD}AR-ICdNdGi~YkpjKgFLq>DUoy>OhttJ*N|zW z{D6$~nhQRhW%eOh&6A2$zF!g4sn@E=lzQ(b_dURe zXHY;r?=I=Pjhs;=#ckyDoMFIsCM9m%FPV6UiiZzv?A7<84-a}ux_y5bVL6n!F24#i zRXn{v(%&W?Y77sf_$)ir6gzdnByuQ1_G(=xO-Y$6df-aOnc0hw4S2bq!Vvj7733S1 zb+tu4sorrR(j+#KwJH(er+^FT62-Vhxq}k?T=_LZ;GlTGu27bU*LDjPwkx9?;aPB` zOrhV=yBT5JHOM7dfTMyn>%xRZ*%z4<2g0rxt%FfUSppTq!IB`ByQa%A*b!T>9Z75i z>;~J2Y?Or!;s}s!#N^6^FtPJY`-d#B4_C>_CL#H~w1cV)cV65QZRtg z8X`48PNH%^A^fGTnJ57Zj;M)<%u1}|zLj0bER3NQDhAORyN|tb`L3zp)1H;**qUbA z|J#4;9D_sG!=PWaJdrP7{~idc!CT!l1a_Avdtz}ei03FQ*Y2& zDZPaUVS-jnHjnZ;1p}oaze6Kv&vCBWT4k^;3#7%7L&XdH zXH+`+0!K4QrWkS;w3LCcesF=7jFb;94$bdi*}B&J7#I|H1M)}$^H?8DCV_bpFt{g> zpCE0wY=x5v>_-z=UE~-Sl4&9jxC`hnAUc~$aS0?5$l~f`aR;wy@(Na=i&D}ugqF{6 z%P_YP8AoPf`^NJp+!M!aDR;=Hqjkt za1JzH{so%uky9)Gii$r&k+zcP^APziYNzO=z!v5p>gA{rPxy7yHbQb`^bm$3$kv)LgzPpEd>M`p?u#k(?r_N6(Ux@_tp;Yn5l1;<7c!0_ z$3gBsxL^4WZle)a=ixAM_NtSG9vwjLs<2d`-cC*o+)=^yt1HBC_HstvWL!ny%J*>P zJ9$z3U|iJOhpV~bCAfY}EC?ojH_FkcD+#_wKX7eBYHi42VSbckwl~-Khbvzu$=`x& z&n7N0{3}QQEo%*oFOPWVa4_4Ie;P$liaCwtp(ZZQI!G%;expyVUrns@RR?)h1!GGF&Dh1&`HmW}Fd1K6v%jRV3S+ zQI8r9{$}FBYhLvYm2c*RHfI|Xc~^diac=;!z;zCX+5ccI(S8aP3Bnt0;-}!3{En`B zbBkzpA=7_MDEOtAa7Z%<( z?pyB<7ib)fRBhJ(Sv%xEVVW(@IT_n;&xb}1Q2H9+Q;hZKnAhaa+U*Kj(cN-D^ZWA>X=J_Q4!J*L0jw? z;8PF!>_}l@cVU~SFzl+yQ{fCZ=zw+u1KeE@l;R#`Bt~H<3cCC^bP@fw{F!)TqSAFx zKT*@@U40W^Ztg(-3n2Sgg_p${h|yli-dof6X-#x+#;s9Z{y3QoKbwCBs%PP#;AxEYH zl-NG!$?G-T=3F2$morKxZKY6|0hwUD=7;T;59|lDJ+iAs%Z3#O4;$RRh0K82^cv}W zD9OD9D;k-1;XQ8XtpfRwWxaiZH>9p})qdzgQ^i@nBUd?4Ejst$|iOjW6Oqs1Fl zw~6HQ#wM5!c^1B2+{#As>*)VcdP+H~q;pGe1GIQLAv!}orDLc>I3bu(e3(GOB}>Sw zaJIceCJ?T}5<}&RU9@t!JZjukdK5&2W@aC;qa=Su_2^BqH^Kalq($_C4 zF-NrE5CXcMZy?y~+P3ZA1JJ+Iwn_OGEfN>Y9KDZp7xiq>JwyU>`FQ)J%7uOxx`&fo zaAvVNV}_F?Fb2PmZuvthcs*aB+P0Y~dH5eRGm&*z=QTe-ZWDdf7!n#1Oa4GXX}A%- z4>azOwlYb3HE|2LuBC4zBx#+K2v++?50HLaIGKEar+f=C;SW6v#zKalGYu(Q_E7Pa zci0M=bny@%`l|(!KEPAZiLGWFeSzXp`n;jxGy>!(FsSb1>r1+s#m9MGGyVKv1VoU0 zpu$+_R6b=V%=|TSib!{bgoP$Jj1o)rnX&blf9Nk?NVY%c_Fr=)X$LBROGdRbY z@<-z=-O9r;VvV2z(oX>ji7s9iK7|{HVO+%Zvs_bmdUmwdq5*Vjk|afPh`=#LmR@sI z%$9c%<1^y;LzChixRGgunK^4HK{>G(T!Rk~f~@_V33v!H-2v0g`bVSU}S$a#O{MQ ueqgrVr}4J>4()y0D*R&qzWoK`kaeHixv%ic{jZJ=j#{PS-rQ)>vHl-(1EAyp literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__pycache__/utils.cpython-310.pyc b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..63be3402d8d47118395dd617c19a96909b53a91f GIT binary patch literal 3603 zcmb7G-E$ka5eINz*`-mekzUmTo#iZHkXZ(>tIIN>xTZTzy34p^HFjJ7c_+vdP(TLZfdOx)mR=nM+& z!l2kL>ai8NgHpRRD7VX$2uqmUzCeU2Dx$i^E)aa%bHd<`sP3?K?N5Xkc=09SMR8`w zXwSpgNLrR{UNSApIluZk+46SXyy*Z7&2X8Sy!hn>p#4nU~_EV2iC+N9MNS`)`o(>;P^)fb#ZP5C7SSW2 zGN&cB9&ZmtW<4GaL-;(2f;h^|ZYNRuU^`N0nen*O%ZkH}OoXpGJ(#IJh&DT6z@G}4 zAiFc`?2)Y6isb-kW5HXUf#~9%Cm)n~2i{lUHX=xya?)m;whhieMxDb|GaRO~&x|Iqu* zuYcRna$0+4R`7k4?E9JP`-7N|LhP4)|C>=KoKYO#=W*BfWf^omZeCBCgGe>I%_q23 zldt!r5K;4^hDAeZuER9l6WM$F!Esmk7Y9_2T`v%QbCwBhTC!uw88h42~B|%sGfPJrDYY^ zPFVI44-$1`*{|QUY=Y$f!?LO`ydx#G^}XIG;9_QKiGDt|UEVu~bLXMaoyEh^*#7Fe z-*~<Ys}PE#^vyKG7(R2j-l z=?>zGwO-Tffc&$5w-d!t(A5Y9bN9rH%t%J7nH9!cLP{It$O<%dCYRuoSpf8rIB@_} zIXiPo465kCbb{*Cm`mj^kjdH1!{e0J22k+s(pvur9Ys>2KudXkY%7+M9Xg?3lBi-5 zWo$1}g1?m5MpS-|{M-b-;*8Io@u4ibUea98HJ6Ly@5pQ)x?a|_4NWTn zt$c!3(X?$%D+8@^NW;BS&=mYq8Gozs_qZP<9{j;#+g=h3g0Q2*c6P5cd_Pgr_pf=Y zBjxoW*yBie;5HQsj|XcI=N5HG@v$Ru}_TliQ7>RjqewCa-`j z`73Nbz~(A8$5)q*wH27VgY(y*8C#8EoT#zc;9~PNU8z?yH%NjgQ5~?RTt>bUHVD|v z62rLL&+N`{2v<|aq>z_z-dv9ZNC7k3X2wv8%tX1F0b((*-~@F;ddO_+G{Q3rzAAY` zyKLcYHo&tacox@Uv<$xjV?UXu$y{Teeqg%BC5pYHm&u#BQ{gZUllx` zl3gedrZg!l$|3v^q&xIqaE)*%3jPgAeu6xVn^OqPlyYm6%3lJneUf(XowkkrP+n|M$I$A~mGODQ!fj@_mcWAZxXL@#3Mdp^Vo>S9y5(-t?q{OK#(KM;xo807`&d{7r9cLGj=%zoyF?b^D;| zL47VO0G1QHGjtNotS(d$DzlPd7%2G>$jhujM*;4gC#?r}KfiZh-|adqvYM~Az@jKm zB|3d%g+JoPSTq>{%Ie8@G?ZA9)?N8~6lwEL(ie&R9S(}R+Q75xjFZQSn@h&otd#e2 zH7q^IOxW}1;REbYqv_hnKZO6^V2|VzXk3HQ0yPiHrVHP^Uo#8z605=(!U}#lr3PG{ zVKNLh`2{RJ6+&ofbgmAem(V#3Aqg<#kjprLu0(X`&}dOp;kK8TXH*EUBdVhqH;%&~ zr5H$4`plTHi7_!#Bl!e4tYZ$y=&*ibDJwPMYXfxcHO%mKYwR>sM>cw6K=3#ij_({I z<&BEwtq9InE_<;AIKyg$vqtiG>Fr`}dUac(2{Hp}R9(zuu0($Y3WDPpC3PqFc@Z4~ zX-o{{XL?y8hFN)b&-amp+IVS-=MIs8n{oN~?9^oH|eG+Ny9XFUN0VUQVt+Q!p>l f8mrMID6MKn&30{<%^MeOwgLw}m}l-!m#_Q}H|vO% literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__pycache__/version.cpython-310.pyc b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/__pycache__/version.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..848db074444f2303742dd3d6d5380eb0081c86ee GIT binary patch literal 12953 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/_manylinux.py b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/_manylinux.py new file mode 100644 index 00000000..4c379aa6 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/_manylinux.py @@ -0,0 +1,301 @@ +import collections +import functools +import os +import re +import struct +import sys +import warnings +from typing import IO, Dict, Iterator, NamedTuple, Optional, Tuple + + +# Python does not provide platform information at sufficient granularity to +# identify the architecture of the running executable in some cases, so we +# determine it dynamically by reading the information from the running +# process. This only applies on Linux, which uses the ELF format. +class _ELFFileHeader: + # https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header + class _InvalidELFFileHeader(ValueError): + """ + An invalid ELF file header was found. + """ + + ELF_MAGIC_NUMBER = 0x7F454C46 + ELFCLASS32 = 1 + ELFCLASS64 = 2 + ELFDATA2LSB = 1 + ELFDATA2MSB = 2 + EM_386 = 3 + EM_S390 = 22 + EM_ARM = 40 + EM_X86_64 = 62 + EF_ARM_ABIMASK = 0xFF000000 + EF_ARM_ABI_VER5 = 0x05000000 + EF_ARM_ABI_FLOAT_HARD = 0x00000400 + + def __init__(self, file: IO[bytes]) -> None: + def unpack(fmt: str) -> int: + try: + data = file.read(struct.calcsize(fmt)) + result: Tuple[int, ...] = struct.unpack(fmt, data) + except struct.error: + raise _ELFFileHeader._InvalidELFFileHeader() + return result[0] + + self.e_ident_magic = unpack(">I") + if self.e_ident_magic != self.ELF_MAGIC_NUMBER: + raise _ELFFileHeader._InvalidELFFileHeader() + self.e_ident_class = unpack("B") + if self.e_ident_class not in {self.ELFCLASS32, self.ELFCLASS64}: + raise _ELFFileHeader._InvalidELFFileHeader() + self.e_ident_data = unpack("B") + if self.e_ident_data not in {self.ELFDATA2LSB, self.ELFDATA2MSB}: + raise _ELFFileHeader._InvalidELFFileHeader() + self.e_ident_version = unpack("B") + self.e_ident_osabi = unpack("B") + self.e_ident_abiversion = unpack("B") + self.e_ident_pad = file.read(7) + format_h = "H" + format_i = "I" + format_q = "Q" + format_p = format_i if self.e_ident_class == self.ELFCLASS32 else format_q + self.e_type = unpack(format_h) + self.e_machine = unpack(format_h) + self.e_version = unpack(format_i) + self.e_entry = unpack(format_p) + self.e_phoff = unpack(format_p) + self.e_shoff = unpack(format_p) + self.e_flags = unpack(format_i) + self.e_ehsize = unpack(format_h) + self.e_phentsize = unpack(format_h) + self.e_phnum = unpack(format_h) + self.e_shentsize = unpack(format_h) + self.e_shnum = unpack(format_h) + self.e_shstrndx = unpack(format_h) + + +def _get_elf_header() -> Optional[_ELFFileHeader]: + try: + with open(sys.executable, "rb") as f: + elf_header = _ELFFileHeader(f) + except (OSError, TypeError, _ELFFileHeader._InvalidELFFileHeader): + return None + return elf_header + + +def _is_linux_armhf() -> bool: + # hard-float ABI can be detected from the ELF header of the running + # process + # https://static.docs.arm.com/ihi0044/g/aaelf32.pdf + elf_header = _get_elf_header() + if elf_header is None: + return False + result = elf_header.e_ident_class == elf_header.ELFCLASS32 + result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB + result &= elf_header.e_machine == elf_header.EM_ARM + result &= ( + elf_header.e_flags & elf_header.EF_ARM_ABIMASK + ) == elf_header.EF_ARM_ABI_VER5 + result &= ( + elf_header.e_flags & elf_header.EF_ARM_ABI_FLOAT_HARD + ) == elf_header.EF_ARM_ABI_FLOAT_HARD + return result + + +def _is_linux_i686() -> bool: + elf_header = _get_elf_header() + if elf_header is None: + return False + result = elf_header.e_ident_class == elf_header.ELFCLASS32 + result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB + result &= elf_header.e_machine == elf_header.EM_386 + return result + + +def _have_compatible_abi(arch: str) -> bool: + if arch == "armv7l": + return _is_linux_armhf() + if arch == "i686": + return _is_linux_i686() + return arch in {"x86_64", "aarch64", "ppc64", "ppc64le", "s390x"} + + +# If glibc ever changes its major version, we need to know what the last +# minor version was, so we can build the complete list of all versions. +# For now, guess what the highest minor version might be, assume it will +# be 50 for testing. Once this actually happens, update the dictionary +# with the actual value. +_LAST_GLIBC_MINOR: Dict[int, int] = collections.defaultdict(lambda: 50) + + +class _GLibCVersion(NamedTuple): + major: int + minor: int + + +def _glibc_version_string_confstr() -> Optional[str]: + """ + Primary implementation of glibc_version_string using os.confstr. + """ + # os.confstr is quite a bit faster than ctypes.DLL. It's also less likely + # to be broken or missing. This strategy is used in the standard library + # platform module. + # https://github.com/python/cpython/blob/fcf1d003bf4f0100c/Lib/platform.py#L175-L183 + try: + # os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc 2.17". + version_string = os.confstr("CS_GNU_LIBC_VERSION") + assert version_string is not None + _, version = version_string.split() + except (AssertionError, AttributeError, OSError, ValueError): + # os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)... + return None + return version + + +def _glibc_version_string_ctypes() -> Optional[str]: + """ + Fallback implementation of glibc_version_string using ctypes. + """ + try: + import ctypes + except ImportError: + return None + + # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen + # manpage says, "If filename is NULL, then the returned handle is for the + # main program". This way we can let the linker do the work to figure out + # which libc our process is actually using. + # + # We must also handle the special case where the executable is not a + # dynamically linked executable. This can occur when using musl libc, + # for example. In this situation, dlopen() will error, leading to an + # OSError. Interestingly, at least in the case of musl, there is no + # errno set on the OSError. The single string argument used to construct + # OSError comes from libc itself and is therefore not portable to + # hard code here. In any case, failure to call dlopen() means we + # can proceed, so we bail on our attempt. + try: + process_namespace = ctypes.CDLL(None) + except OSError: + return None + + try: + gnu_get_libc_version = process_namespace.gnu_get_libc_version + except AttributeError: + # Symbol doesn't exist -> therefore, we are not linked to + # glibc. + return None + + # Call gnu_get_libc_version, which returns a string like "2.5" + gnu_get_libc_version.restype = ctypes.c_char_p + version_str: str = gnu_get_libc_version() + # py2 / py3 compatibility: + if not isinstance(version_str, str): + version_str = version_str.decode("ascii") + + return version_str + + +def _glibc_version_string() -> Optional[str]: + """Returns glibc version string, or None if not using glibc.""" + return _glibc_version_string_confstr() or _glibc_version_string_ctypes() + + +def _parse_glibc_version(version_str: str) -> Tuple[int, int]: + """Parse glibc version. + + We use a regexp instead of str.split because we want to discard any + random junk that might come after the minor version -- this might happen + in patched/forked versions of glibc (e.g. Linaro's version of glibc + uses version strings like "2.20-2014.11"). See gh-3588. + """ + m = re.match(r"(?P[0-9]+)\.(?P[0-9]+)", version_str) + if not m: + warnings.warn( + "Expected glibc version with 2 components major.minor," + " got: %s" % version_str, + RuntimeWarning, + ) + return -1, -1 + return int(m.group("major")), int(m.group("minor")) + + +@functools.lru_cache() +def _get_glibc_version() -> Tuple[int, int]: + version_str = _glibc_version_string() + if version_str is None: + return (-1, -1) + return _parse_glibc_version(version_str) + + +# From PEP 513, PEP 600 +def _is_compatible(name: str, arch: str, version: _GLibCVersion) -> bool: + sys_glibc = _get_glibc_version() + if sys_glibc < version: + return False + # Check for presence of _manylinux module. + try: + import _manylinux # noqa + except ImportError: + return True + if hasattr(_manylinux, "manylinux_compatible"): + result = _manylinux.manylinux_compatible(version[0], version[1], arch) + if result is not None: + return bool(result) + return True + if version == _GLibCVersion(2, 5): + if hasattr(_manylinux, "manylinux1_compatible"): + return bool(_manylinux.manylinux1_compatible) + if version == _GLibCVersion(2, 12): + if hasattr(_manylinux, "manylinux2010_compatible"): + return bool(_manylinux.manylinux2010_compatible) + if version == _GLibCVersion(2, 17): + if hasattr(_manylinux, "manylinux2014_compatible"): + return bool(_manylinux.manylinux2014_compatible) + return True + + +_LEGACY_MANYLINUX_MAP = { + # CentOS 7 w/ glibc 2.17 (PEP 599) + (2, 17): "manylinux2014", + # CentOS 6 w/ glibc 2.12 (PEP 571) + (2, 12): "manylinux2010", + # CentOS 5 w/ glibc 2.5 (PEP 513) + (2, 5): "manylinux1", +} + + +def platform_tags(linux: str, arch: str) -> Iterator[str]: + if not _have_compatible_abi(arch): + return + # Oldest glibc to be supported regardless of architecture is (2, 17). + too_old_glibc2 = _GLibCVersion(2, 16) + if arch in {"x86_64", "i686"}: + # On x86/i686 also oldest glibc to be supported is (2, 5). + too_old_glibc2 = _GLibCVersion(2, 4) + current_glibc = _GLibCVersion(*_get_glibc_version()) + glibc_max_list = [current_glibc] + # We can assume compatibility across glibc major versions. + # https://sourceware.org/bugzilla/show_bug.cgi?id=24636 + # + # Build a list of maximum glibc versions so that we can + # output the canonical list of all glibc from current_glibc + # down to too_old_glibc2, including all intermediary versions. + for glibc_major in range(current_glibc.major - 1, 1, -1): + glibc_minor = _LAST_GLIBC_MINOR[glibc_major] + glibc_max_list.append(_GLibCVersion(glibc_major, glibc_minor)) + for glibc_max in glibc_max_list: + if glibc_max.major == too_old_glibc2.major: + min_minor = too_old_glibc2.minor + else: + # For other glibc major versions oldest supported is (x, 0). + min_minor = -1 + for glibc_minor in range(glibc_max.minor, min_minor, -1): + glibc_version = _GLibCVersion(glibc_max.major, glibc_minor) + tag = "manylinux_{}_{}".format(*glibc_version) + if _is_compatible(tag, arch, glibc_version): + yield linux.replace("linux", tag) + # Handle the legacy manylinux1, manylinux2010, manylinux2014 tags. + if glibc_version in _LEGACY_MANYLINUX_MAP: + legacy_tag = _LEGACY_MANYLINUX_MAP[glibc_version] + if _is_compatible(legacy_tag, arch, glibc_version): + yield linux.replace("linux", legacy_tag) diff --git a/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/_musllinux.py b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/_musllinux.py new file mode 100644 index 00000000..85450faf --- /dev/null +++ b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/_musllinux.py @@ -0,0 +1,136 @@ +"""PEP 656 support. + +This module implements logic to detect if the currently running Python is +linked against musl, and what musl version is used. +""" + +import contextlib +import functools +import operator +import os +import re +import struct +import subprocess +import sys +from typing import IO, Iterator, NamedTuple, Optional, Tuple + + +def _read_unpacked(f: IO[bytes], fmt: str) -> Tuple[int, ...]: + return struct.unpack(fmt, f.read(struct.calcsize(fmt))) + + +def _parse_ld_musl_from_elf(f: IO[bytes]) -> Optional[str]: + """Detect musl libc location by parsing the Python executable. + + Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca + ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html + """ + f.seek(0) + try: + ident = _read_unpacked(f, "16B") + except struct.error: + return None + if ident[:4] != tuple(b"\x7fELF"): # Invalid magic, not ELF. + return None + f.seek(struct.calcsize("HHI"), 1) # Skip file type, machine, and version. + + try: + # e_fmt: Format for program header. + # p_fmt: Format for section header. + # p_idx: Indexes to find p_type, p_offset, and p_filesz. + e_fmt, p_fmt, p_idx = { + 1: ("IIIIHHH", "IIIIIIII", (0, 1, 4)), # 32-bit. + 2: ("QQQIHHH", "IIQQQQQQ", (0, 2, 5)), # 64-bit. + }[ident[4]] + except KeyError: + return None + else: + p_get = operator.itemgetter(*p_idx) + + # Find the interpreter section and return its content. + try: + _, e_phoff, _, _, _, e_phentsize, e_phnum = _read_unpacked(f, e_fmt) + except struct.error: + return None + for i in range(e_phnum + 1): + f.seek(e_phoff + e_phentsize * i) + try: + p_type, p_offset, p_filesz = p_get(_read_unpacked(f, p_fmt)) + except struct.error: + return None + if p_type != 3: # Not PT_INTERP. + continue + f.seek(p_offset) + interpreter = os.fsdecode(f.read(p_filesz)).strip("\0") + if "musl" not in interpreter: + return None + return interpreter + return None + + +class _MuslVersion(NamedTuple): + major: int + minor: int + + +def _parse_musl_version(output: str) -> Optional[_MuslVersion]: + lines = [n for n in (n.strip() for n in output.splitlines()) if n] + if len(lines) < 2 or lines[0][:4] != "musl": + return None + m = re.match(r"Version (\d+)\.(\d+)", lines[1]) + if not m: + return None + return _MuslVersion(major=int(m.group(1)), minor=int(m.group(2))) + + +@functools.lru_cache() +def _get_musl_version(executable: str) -> Optional[_MuslVersion]: + """Detect currently-running musl runtime version. + + This is done by checking the specified executable's dynamic linking + information, and invoking the loader to parse its output for a version + string. If the loader is musl, the output would be something like:: + + musl libc (x86_64) + Version 1.2.2 + Dynamic Program Loader + """ + with contextlib.ExitStack() as stack: + try: + f = stack.enter_context(open(executable, "rb")) + except IOError: + return None + ld = _parse_ld_musl_from_elf(f) + if not ld: + return None + proc = subprocess.run([ld], stderr=subprocess.PIPE, universal_newlines=True) + return _parse_musl_version(proc.stderr) + + +def platform_tags(arch: str) -> Iterator[str]: + """Generate musllinux tags compatible to the current platform. + + :param arch: Should be the part of platform tag after the ``linux_`` + prefix, e.g. ``x86_64``. The ``linux_`` prefix is assumed as a + prerequisite for the current platform to be musllinux-compatible. + + :returns: An iterator of compatible musllinux tags. + """ + sys_musl = _get_musl_version(sys.executable) + if sys_musl is None: # Python not dynamically linked against musl. + return + for minor in range(sys_musl.minor, -1, -1): + yield f"musllinux_{sys_musl.major}_{minor}_{arch}" + + +if __name__ == "__main__": # pragma: no cover + import sysconfig + + plat = sysconfig.get_platform() + assert plat.startswith("linux-"), "not linux" + + print("plat:", plat) + print("musl:", _get_musl_version(sys.executable)) + print("tags:", end=" ") + for t in platform_tags(re.sub(r"[.-]", "_", plat.split("-", 1)[-1])): + print(t, end="\n ") diff --git a/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/_structures.py b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/_structures.py new file mode 100644 index 00000000..95154975 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/_structures.py @@ -0,0 +1,67 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + + +class InfinityType: + def __repr__(self) -> str: + return "Infinity" + + def __hash__(self) -> int: + return hash(repr(self)) + + def __lt__(self, other: object) -> bool: + return False + + def __le__(self, other: object) -> bool: + return False + + def __eq__(self, other: object) -> bool: + return isinstance(other, self.__class__) + + def __ne__(self, other: object) -> bool: + return not isinstance(other, self.__class__) + + def __gt__(self, other: object) -> bool: + return True + + def __ge__(self, other: object) -> bool: + return True + + def __neg__(self: object) -> "NegativeInfinityType": + return NegativeInfinity + + +Infinity = InfinityType() + + +class NegativeInfinityType: + def __repr__(self) -> str: + return "-Infinity" + + def __hash__(self) -> int: + return hash(repr(self)) + + def __lt__(self, other: object) -> bool: + return True + + def __le__(self, other: object) -> bool: + return True + + def __eq__(self, other: object) -> bool: + return isinstance(other, self.__class__) + + def __ne__(self, other: object) -> bool: + return not isinstance(other, self.__class__) + + def __gt__(self, other: object) -> bool: + return False + + def __ge__(self, other: object) -> bool: + return False + + def __neg__(self: object) -> InfinityType: + return Infinity + + +NegativeInfinity = NegativeInfinityType() diff --git a/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/markers.py b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/markers.py new file mode 100644 index 00000000..18769b09 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/markers.py @@ -0,0 +1,304 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import operator +import os +import platform +import sys +from typing import Any, Callable, Dict, List, Optional, Tuple, Union + +from pkg_resources.extern.pyparsing import ( # noqa: N817 + Forward, + Group, + Literal as L, + ParseException, + ParseResults, + QuotedString, + ZeroOrMore, + stringEnd, + stringStart, +) + +from .specifiers import InvalidSpecifier, Specifier + +__all__ = [ + "InvalidMarker", + "UndefinedComparison", + "UndefinedEnvironmentName", + "Marker", + "default_environment", +] + +Operator = Callable[[str, str], bool] + + +class InvalidMarker(ValueError): + """ + An invalid marker was found, users should refer to PEP 508. + """ + + +class UndefinedComparison(ValueError): + """ + An invalid operation was attempted on a value that doesn't support it. + """ + + +class UndefinedEnvironmentName(ValueError): + """ + A name was attempted to be used that does not exist inside of the + environment. + """ + + +class Node: + def __init__(self, value: Any) -> None: + self.value = value + + def __str__(self) -> str: + return str(self.value) + + def __repr__(self) -> str: + return f"<{self.__class__.__name__}('{self}')>" + + def serialize(self) -> str: + raise NotImplementedError + + +class Variable(Node): + def serialize(self) -> str: + return str(self) + + +class Value(Node): + def serialize(self) -> str: + return f'"{self}"' + + +class Op(Node): + def serialize(self) -> str: + return str(self) + + +VARIABLE = ( + L("implementation_version") + | L("platform_python_implementation") + | L("implementation_name") + | L("python_full_version") + | L("platform_release") + | L("platform_version") + | L("platform_machine") + | L("platform_system") + | L("python_version") + | L("sys_platform") + | L("os_name") + | L("os.name") # PEP-345 + | L("sys.platform") # PEP-345 + | L("platform.version") # PEP-345 + | L("platform.machine") # PEP-345 + | L("platform.python_implementation") # PEP-345 + | L("python_implementation") # undocumented setuptools legacy + | L("extra") # PEP-508 +) +ALIASES = { + "os.name": "os_name", + "sys.platform": "sys_platform", + "platform.version": "platform_version", + "platform.machine": "platform_machine", + "platform.python_implementation": "platform_python_implementation", + "python_implementation": "platform_python_implementation", +} +VARIABLE.setParseAction(lambda s, l, t: Variable(ALIASES.get(t[0], t[0]))) + +VERSION_CMP = ( + L("===") | L("==") | L(">=") | L("<=") | L("!=") | L("~=") | L(">") | L("<") +) + +MARKER_OP = VERSION_CMP | L("not in") | L("in") +MARKER_OP.setParseAction(lambda s, l, t: Op(t[0])) + +MARKER_VALUE = QuotedString("'") | QuotedString('"') +MARKER_VALUE.setParseAction(lambda s, l, t: Value(t[0])) + +BOOLOP = L("and") | L("or") + +MARKER_VAR = VARIABLE | MARKER_VALUE + +MARKER_ITEM = Group(MARKER_VAR + MARKER_OP + MARKER_VAR) +MARKER_ITEM.setParseAction(lambda s, l, t: tuple(t[0])) + +LPAREN = L("(").suppress() +RPAREN = L(")").suppress() + +MARKER_EXPR = Forward() +MARKER_ATOM = MARKER_ITEM | Group(LPAREN + MARKER_EXPR + RPAREN) +MARKER_EXPR << MARKER_ATOM + ZeroOrMore(BOOLOP + MARKER_EXPR) + +MARKER = stringStart + MARKER_EXPR + stringEnd + + +def _coerce_parse_result(results: Union[ParseResults, List[Any]]) -> List[Any]: + if isinstance(results, ParseResults): + return [_coerce_parse_result(i) for i in results] + else: + return results + + +def _format_marker( + marker: Union[List[str], Tuple[Node, ...], str], first: Optional[bool] = True +) -> str: + + assert isinstance(marker, (list, tuple, str)) + + # Sometimes we have a structure like [[...]] which is a single item list + # where the single item is itself it's own list. In that case we want skip + # the rest of this function so that we don't get extraneous () on the + # outside. + if ( + isinstance(marker, list) + and len(marker) == 1 + and isinstance(marker[0], (list, tuple)) + ): + return _format_marker(marker[0]) + + if isinstance(marker, list): + inner = (_format_marker(m, first=False) for m in marker) + if first: + return " ".join(inner) + else: + return "(" + " ".join(inner) + ")" + elif isinstance(marker, tuple): + return " ".join([m.serialize() for m in marker]) + else: + return marker + + +_operators: Dict[str, Operator] = { + "in": lambda lhs, rhs: lhs in rhs, + "not in": lambda lhs, rhs: lhs not in rhs, + "<": operator.lt, + "<=": operator.le, + "==": operator.eq, + "!=": operator.ne, + ">=": operator.ge, + ">": operator.gt, +} + + +def _eval_op(lhs: str, op: Op, rhs: str) -> bool: + try: + spec = Specifier("".join([op.serialize(), rhs])) + except InvalidSpecifier: + pass + else: + return spec.contains(lhs) + + oper: Optional[Operator] = _operators.get(op.serialize()) + if oper is None: + raise UndefinedComparison(f"Undefined {op!r} on {lhs!r} and {rhs!r}.") + + return oper(lhs, rhs) + + +class Undefined: + pass + + +_undefined = Undefined() + + +def _get_env(environment: Dict[str, str], name: str) -> str: + value: Union[str, Undefined] = environment.get(name, _undefined) + + if isinstance(value, Undefined): + raise UndefinedEnvironmentName( + f"{name!r} does not exist in evaluation environment." + ) + + return value + + +def _evaluate_markers(markers: List[Any], environment: Dict[str, str]) -> bool: + groups: List[List[bool]] = [[]] + + for marker in markers: + assert isinstance(marker, (list, tuple, str)) + + if isinstance(marker, list): + groups[-1].append(_evaluate_markers(marker, environment)) + elif isinstance(marker, tuple): + lhs, op, rhs = marker + + if isinstance(lhs, Variable): + lhs_value = _get_env(environment, lhs.value) + rhs_value = rhs.value + else: + lhs_value = lhs.value + rhs_value = _get_env(environment, rhs.value) + + groups[-1].append(_eval_op(lhs_value, op, rhs_value)) + else: + assert marker in ["and", "or"] + if marker == "or": + groups.append([]) + + return any(all(item) for item in groups) + + +def format_full_version(info: "sys._version_info") -> str: + version = "{0.major}.{0.minor}.{0.micro}".format(info) + kind = info.releaselevel + if kind != "final": + version += kind[0] + str(info.serial) + return version + + +def default_environment() -> Dict[str, str]: + iver = format_full_version(sys.implementation.version) + implementation_name = sys.implementation.name + return { + "implementation_name": implementation_name, + "implementation_version": iver, + "os_name": os.name, + "platform_machine": platform.machine(), + "platform_release": platform.release(), + "platform_system": platform.system(), + "platform_version": platform.version(), + "python_full_version": platform.python_version(), + "platform_python_implementation": platform.python_implementation(), + "python_version": ".".join(platform.python_version_tuple()[:2]), + "sys_platform": sys.platform, + } + + +class Marker: + def __init__(self, marker: str) -> None: + try: + self._markers = _coerce_parse_result(MARKER.parseString(marker)) + except ParseException as e: + raise InvalidMarker( + f"Invalid marker: {marker!r}, parse error at " + f"{marker[e.loc : e.loc + 8]!r}" + ) + + def __str__(self) -> str: + return _format_marker(self._markers) + + def __repr__(self) -> str: + return f"" + + def evaluate(self, environment: Optional[Dict[str, str]] = None) -> bool: + """Evaluate a marker. + + Return the boolean from evaluating the given marker against the + environment. environment is an optional argument to override all or + part of the determined environment. + + The environment is determined from the current Python process. + """ + current_environment = default_environment() + if environment is not None: + current_environment.update(environment) + + return _evaluate_markers(self._markers, current_environment) diff --git a/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/requirements.py b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/requirements.py new file mode 100644 index 00000000..6af14ec4 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/requirements.py @@ -0,0 +1,146 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import re +import string +import urllib.parse +from typing import List, Optional as TOptional, Set + +from pkg_resources.extern.pyparsing import ( # noqa + Combine, + Literal as L, + Optional, + ParseException, + Regex, + Word, + ZeroOrMore, + originalTextFor, + stringEnd, + stringStart, +) + +from .markers import MARKER_EXPR, Marker +from .specifiers import LegacySpecifier, Specifier, SpecifierSet + + +class InvalidRequirement(ValueError): + """ + An invalid requirement was found, users should refer to PEP 508. + """ + + +ALPHANUM = Word(string.ascii_letters + string.digits) + +LBRACKET = L("[").suppress() +RBRACKET = L("]").suppress() +LPAREN = L("(").suppress() +RPAREN = L(")").suppress() +COMMA = L(",").suppress() +SEMICOLON = L(";").suppress() +AT = L("@").suppress() + +PUNCTUATION = Word("-_.") +IDENTIFIER_END = ALPHANUM | (ZeroOrMore(PUNCTUATION) + ALPHANUM) +IDENTIFIER = Combine(ALPHANUM + ZeroOrMore(IDENTIFIER_END)) + +NAME = IDENTIFIER("name") +EXTRA = IDENTIFIER + +URI = Regex(r"[^ ]+")("url") +URL = AT + URI + +EXTRAS_LIST = EXTRA + ZeroOrMore(COMMA + EXTRA) +EXTRAS = (LBRACKET + Optional(EXTRAS_LIST) + RBRACKET)("extras") + +VERSION_PEP440 = Regex(Specifier._regex_str, re.VERBOSE | re.IGNORECASE) +VERSION_LEGACY = Regex(LegacySpecifier._regex_str, re.VERBOSE | re.IGNORECASE) + +VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY +VERSION_MANY = Combine( + VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), joinString=",", adjacent=False +)("_raw_spec") +_VERSION_SPEC = Optional((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY) +_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or "") + +VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier") +VERSION_SPEC.setParseAction(lambda s, l, t: t[1]) + +MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker") +MARKER_EXPR.setParseAction( + lambda s, l, t: Marker(s[t._original_start : t._original_end]) +) +MARKER_SEPARATOR = SEMICOLON +MARKER = MARKER_SEPARATOR + MARKER_EXPR + +VERSION_AND_MARKER = VERSION_SPEC + Optional(MARKER) +URL_AND_MARKER = URL + Optional(MARKER) + +NAMED_REQUIREMENT = NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER) + +REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd +# pkg_resources.extern.pyparsing isn't thread safe during initialization, so we do it eagerly, see +# issue #104 +REQUIREMENT.parseString("x[]") + + +class Requirement: + """Parse a requirement. + + Parse a given requirement string into its parts, such as name, specifier, + URL, and extras. Raises InvalidRequirement on a badly-formed requirement + string. + """ + + # TODO: Can we test whether something is contained within a requirement? + # If so how do we do that? Do we need to test against the _name_ of + # the thing as well as the version? What about the markers? + # TODO: Can we normalize the name and extra name? + + def __init__(self, requirement_string: str) -> None: + try: + req = REQUIREMENT.parseString(requirement_string) + except ParseException as e: + raise InvalidRequirement( + f'Parse error at "{ requirement_string[e.loc : e.loc + 8]!r}": {e.msg}' + ) + + self.name: str = req.name + if req.url: + parsed_url = urllib.parse.urlparse(req.url) + if parsed_url.scheme == "file": + if urllib.parse.urlunparse(parsed_url) != req.url: + raise InvalidRequirement("Invalid URL given") + elif not (parsed_url.scheme and parsed_url.netloc) or ( + not parsed_url.scheme and not parsed_url.netloc + ): + raise InvalidRequirement(f"Invalid URL: {req.url}") + self.url: TOptional[str] = req.url + else: + self.url = None + self.extras: Set[str] = set(req.extras.asList() if req.extras else []) + self.specifier: SpecifierSet = SpecifierSet(req.specifier) + self.marker: TOptional[Marker] = req.marker if req.marker else None + + def __str__(self) -> str: + parts: List[str] = [self.name] + + if self.extras: + formatted_extras = ",".join(sorted(self.extras)) + parts.append(f"[{formatted_extras}]") + + if self.specifier: + parts.append(str(self.specifier)) + + if self.url: + parts.append(f"@ {self.url}") + if self.marker: + parts.append(" ") + + if self.marker: + parts.append(f"; {self.marker}") + + return "".join(parts) + + def __repr__(self) -> str: + return f"" diff --git a/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/specifiers.py b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/specifiers.py new file mode 100644 index 00000000..ce66bd4a --- /dev/null +++ b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/specifiers.py @@ -0,0 +1,828 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import abc +import functools +import itertools +import re +import warnings +from typing import ( + Callable, + Dict, + Iterable, + Iterator, + List, + Optional, + Pattern, + Set, + Tuple, + TypeVar, + Union, +) + +from .utils import canonicalize_version +from .version import LegacyVersion, Version, parse + +ParsedVersion = Union[Version, LegacyVersion] +UnparsedVersion = Union[Version, LegacyVersion, str] +VersionTypeVar = TypeVar("VersionTypeVar", bound=UnparsedVersion) +CallableOperator = Callable[[ParsedVersion, str], bool] + + +class InvalidSpecifier(ValueError): + """ + An invalid specifier was found, users should refer to PEP 440. + """ + + +class BaseSpecifier(metaclass=abc.ABCMeta): + @abc.abstractmethod + def __str__(self) -> str: + """ + Returns the str representation of this Specifier like object. This + should be representative of the Specifier itself. + """ + + @abc.abstractmethod + def __hash__(self) -> int: + """ + Returns a hash value for this Specifier like object. + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Returns a boolean representing whether or not the two Specifier like + objects are equal. + """ + + @abc.abstractmethod + def __ne__(self, other: object) -> bool: + """ + Returns a boolean representing whether or not the two Specifier like + objects are not equal. + """ + + @abc.abstractproperty + def prereleases(self) -> Optional[bool]: + """ + Returns whether or not pre-releases as a whole are allowed by this + specifier. + """ + + @prereleases.setter + def prereleases(self, value: bool) -> None: + """ + Sets whether or not pre-releases as a whole are allowed by this + specifier. + """ + + @abc.abstractmethod + def contains(self, item: str, prereleases: Optional[bool] = None) -> bool: + """ + Determines if the given item is contained within this specifier. + """ + + @abc.abstractmethod + def filter( + self, iterable: Iterable[VersionTypeVar], prereleases: Optional[bool] = None + ) -> Iterable[VersionTypeVar]: + """ + Takes an iterable of items and filters them so that only items which + are contained within this specifier are allowed in it. + """ + + +class _IndividualSpecifier(BaseSpecifier): + + _operators: Dict[str, str] = {} + _regex: Pattern[str] + + def __init__(self, spec: str = "", prereleases: Optional[bool] = None) -> None: + match = self._regex.search(spec) + if not match: + raise InvalidSpecifier(f"Invalid specifier: '{spec}'") + + self._spec: Tuple[str, str] = ( + match.group("operator").strip(), + match.group("version").strip(), + ) + + # Store whether or not this Specifier should accept prereleases + self._prereleases = prereleases + + def __repr__(self) -> str: + pre = ( + f", prereleases={self.prereleases!r}" + if self._prereleases is not None + else "" + ) + + return "<{}({!r}{})>".format(self.__class__.__name__, str(self), pre) + + def __str__(self) -> str: + return "{}{}".format(*self._spec) + + @property + def _canonical_spec(self) -> Tuple[str, str]: + return self._spec[0], canonicalize_version(self._spec[1]) + + def __hash__(self) -> int: + return hash(self._canonical_spec) + + def __eq__(self, other: object) -> bool: + if isinstance(other, str): + try: + other = self.__class__(str(other)) + except InvalidSpecifier: + return NotImplemented + elif not isinstance(other, self.__class__): + return NotImplemented + + return self._canonical_spec == other._canonical_spec + + def __ne__(self, other: object) -> bool: + if isinstance(other, str): + try: + other = self.__class__(str(other)) + except InvalidSpecifier: + return NotImplemented + elif not isinstance(other, self.__class__): + return NotImplemented + + return self._spec != other._spec + + def _get_operator(self, op: str) -> CallableOperator: + operator_callable: CallableOperator = getattr( + self, f"_compare_{self._operators[op]}" + ) + return operator_callable + + def _coerce_version(self, version: UnparsedVersion) -> ParsedVersion: + if not isinstance(version, (LegacyVersion, Version)): + version = parse(version) + return version + + @property + def operator(self) -> str: + return self._spec[0] + + @property + def version(self) -> str: + return self._spec[1] + + @property + def prereleases(self) -> Optional[bool]: + return self._prereleases + + @prereleases.setter + def prereleases(self, value: bool) -> None: + self._prereleases = value + + def __contains__(self, item: str) -> bool: + return self.contains(item) + + def contains( + self, item: UnparsedVersion, prereleases: Optional[bool] = None + ) -> bool: + + # Determine if prereleases are to be allowed or not. + if prereleases is None: + prereleases = self.prereleases + + # Normalize item to a Version or LegacyVersion, this allows us to have + # a shortcut for ``"2.0" in Specifier(">=2") + normalized_item = self._coerce_version(item) + + # Determine if we should be supporting prereleases in this specifier + # or not, if we do not support prereleases than we can short circuit + # logic if this version is a prereleases. + if normalized_item.is_prerelease and not prereleases: + return False + + # Actually do the comparison to determine if this item is contained + # within this Specifier or not. + operator_callable: CallableOperator = self._get_operator(self.operator) + return operator_callable(normalized_item, self.version) + + def filter( + self, iterable: Iterable[VersionTypeVar], prereleases: Optional[bool] = None + ) -> Iterable[VersionTypeVar]: + + yielded = False + found_prereleases = [] + + kw = {"prereleases": prereleases if prereleases is not None else True} + + # Attempt to iterate over all the values in the iterable and if any of + # them match, yield them. + for version in iterable: + parsed_version = self._coerce_version(version) + + if self.contains(parsed_version, **kw): + # If our version is a prerelease, and we were not set to allow + # prereleases, then we'll store it for later in case nothing + # else matches this specifier. + if parsed_version.is_prerelease and not ( + prereleases or self.prereleases + ): + found_prereleases.append(version) + # Either this is not a prerelease, or we should have been + # accepting prereleases from the beginning. + else: + yielded = True + yield version + + # Now that we've iterated over everything, determine if we've yielded + # any values, and if we have not and we have any prereleases stored up + # then we will go ahead and yield the prereleases. + if not yielded and found_prereleases: + for version in found_prereleases: + yield version + + +class LegacySpecifier(_IndividualSpecifier): + + _regex_str = r""" + (?P(==|!=|<=|>=|<|>)) + \s* + (?P + [^,;\s)]* # Since this is a "legacy" specifier, and the version + # string can be just about anything, we match everything + # except for whitespace, a semi-colon for marker support, + # a closing paren since versions can be enclosed in + # them, and a comma since it's a version separator. + ) + """ + + _regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE) + + _operators = { + "==": "equal", + "!=": "not_equal", + "<=": "less_than_equal", + ">=": "greater_than_equal", + "<": "less_than", + ">": "greater_than", + } + + def __init__(self, spec: str = "", prereleases: Optional[bool] = None) -> None: + super().__init__(spec, prereleases) + + warnings.warn( + "Creating a LegacyVersion has been deprecated and will be " + "removed in the next major release", + DeprecationWarning, + ) + + def _coerce_version(self, version: UnparsedVersion) -> LegacyVersion: + if not isinstance(version, LegacyVersion): + version = LegacyVersion(str(version)) + return version + + def _compare_equal(self, prospective: LegacyVersion, spec: str) -> bool: + return prospective == self._coerce_version(spec) + + def _compare_not_equal(self, prospective: LegacyVersion, spec: str) -> bool: + return prospective != self._coerce_version(spec) + + def _compare_less_than_equal(self, prospective: LegacyVersion, spec: str) -> bool: + return prospective <= self._coerce_version(spec) + + def _compare_greater_than_equal( + self, prospective: LegacyVersion, spec: str + ) -> bool: + return prospective >= self._coerce_version(spec) + + def _compare_less_than(self, prospective: LegacyVersion, spec: str) -> bool: + return prospective < self._coerce_version(spec) + + def _compare_greater_than(self, prospective: LegacyVersion, spec: str) -> bool: + return prospective > self._coerce_version(spec) + + +def _require_version_compare( + fn: Callable[["Specifier", ParsedVersion, str], bool] +) -> Callable[["Specifier", ParsedVersion, str], bool]: + @functools.wraps(fn) + def wrapped(self: "Specifier", prospective: ParsedVersion, spec: str) -> bool: + if not isinstance(prospective, Version): + return False + return fn(self, prospective, spec) + + return wrapped + + +class Specifier(_IndividualSpecifier): + + _regex_str = r""" + (?P(~=|==|!=|<=|>=|<|>|===)) + (?P + (?: + # The identity operators allow for an escape hatch that will + # do an exact string match of the version you wish to install. + # This will not be parsed by PEP 440 and we cannot determine + # any semantic meaning from it. This operator is discouraged + # but included entirely as an escape hatch. + (?<====) # Only match for the identity operator + \s* + [^\s]* # We just match everything, except for whitespace + # since we are only testing for strict identity. + ) + | + (?: + # The (non)equality operators allow for wild card and local + # versions to be specified so we have to define these two + # operators separately to enable that. + (?<===|!=) # Only match for equals and not equals + + \s* + v? + (?:[0-9]+!)? # epoch + [0-9]+(?:\.[0-9]+)* # release + (?: # pre release + [-_\.]? + (a|b|c|rc|alpha|beta|pre|preview) + [-_\.]? + [0-9]* + )? + (?: # post release + (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) + )? + + # You cannot use a wild card and a dev or local version + # together so group them with a | and make them optional. + (?: + (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release + (?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local + | + \.\* # Wild card syntax of .* + )? + ) + | + (?: + # The compatible operator requires at least two digits in the + # release segment. + (?<=~=) # Only match for the compatible operator + + \s* + v? + (?:[0-9]+!)? # epoch + [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *) + (?: # pre release + [-_\.]? + (a|b|c|rc|alpha|beta|pre|preview) + [-_\.]? + [0-9]* + )? + (?: # post release + (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) + )? + (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release + ) + | + (?: + # All other operators only allow a sub set of what the + # (non)equality operators do. Specifically they do not allow + # local versions to be specified nor do they allow the prefix + # matching wild cards. + (?=": "greater_than_equal", + "<": "less_than", + ">": "greater_than", + "===": "arbitrary", + } + + @_require_version_compare + def _compare_compatible(self, prospective: ParsedVersion, spec: str) -> bool: + + # Compatible releases have an equivalent combination of >= and ==. That + # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to + # implement this in terms of the other specifiers instead of + # implementing it ourselves. The only thing we need to do is construct + # the other specifiers. + + # We want everything but the last item in the version, but we want to + # ignore suffix segments. + prefix = ".".join( + list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1] + ) + + # Add the prefix notation to the end of our string + prefix += ".*" + + return self._get_operator(">=")(prospective, spec) and self._get_operator("==")( + prospective, prefix + ) + + @_require_version_compare + def _compare_equal(self, prospective: ParsedVersion, spec: str) -> bool: + + # We need special logic to handle prefix matching + if spec.endswith(".*"): + # In the case of prefix matching we want to ignore local segment. + prospective = Version(prospective.public) + # Split the spec out by dots, and pretend that there is an implicit + # dot in between a release segment and a pre-release segment. + split_spec = _version_split(spec[:-2]) # Remove the trailing .* + + # Split the prospective version out by dots, and pretend that there + # is an implicit dot in between a release segment and a pre-release + # segment. + split_prospective = _version_split(str(prospective)) + + # Shorten the prospective version to be the same length as the spec + # so that we can determine if the specifier is a prefix of the + # prospective version or not. + shortened_prospective = split_prospective[: len(split_spec)] + + # Pad out our two sides with zeros so that they both equal the same + # length. + padded_spec, padded_prospective = _pad_version( + split_spec, shortened_prospective + ) + + return padded_prospective == padded_spec + else: + # Convert our spec string into a Version + spec_version = Version(spec) + + # If the specifier does not have a local segment, then we want to + # act as if the prospective version also does not have a local + # segment. + if not spec_version.local: + prospective = Version(prospective.public) + + return prospective == spec_version + + @_require_version_compare + def _compare_not_equal(self, prospective: ParsedVersion, spec: str) -> bool: + return not self._compare_equal(prospective, spec) + + @_require_version_compare + def _compare_less_than_equal(self, prospective: ParsedVersion, spec: str) -> bool: + + # NB: Local version identifiers are NOT permitted in the version + # specifier, so local version labels can be universally removed from + # the prospective version. + return Version(prospective.public) <= Version(spec) + + @_require_version_compare + def _compare_greater_than_equal( + self, prospective: ParsedVersion, spec: str + ) -> bool: + + # NB: Local version identifiers are NOT permitted in the version + # specifier, so local version labels can be universally removed from + # the prospective version. + return Version(prospective.public) >= Version(spec) + + @_require_version_compare + def _compare_less_than(self, prospective: ParsedVersion, spec_str: str) -> bool: + + # Convert our spec to a Version instance, since we'll want to work with + # it as a version. + spec = Version(spec_str) + + # Check to see if the prospective version is less than the spec + # version. If it's not we can short circuit and just return False now + # instead of doing extra unneeded work. + if not prospective < spec: + return False + + # This special case is here so that, unless the specifier itself + # includes is a pre-release version, that we do not accept pre-release + # versions for the version mentioned in the specifier (e.g. <3.1 should + # not match 3.1.dev0, but should match 3.0.dev0). + if not spec.is_prerelease and prospective.is_prerelease: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # If we've gotten to here, it means that prospective version is both + # less than the spec version *and* it's not a pre-release of the same + # version in the spec. + return True + + @_require_version_compare + def _compare_greater_than(self, prospective: ParsedVersion, spec_str: str) -> bool: + + # Convert our spec to a Version instance, since we'll want to work with + # it as a version. + spec = Version(spec_str) + + # Check to see if the prospective version is greater than the spec + # version. If it's not we can short circuit and just return False now + # instead of doing extra unneeded work. + if not prospective > spec: + return False + + # This special case is here so that, unless the specifier itself + # includes is a post-release version, that we do not accept + # post-release versions for the version mentioned in the specifier + # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0). + if not spec.is_postrelease and prospective.is_postrelease: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # Ensure that we do not allow a local version of the version mentioned + # in the specifier, which is technically greater than, to match. + if prospective.local is not None: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # If we've gotten to here, it means that prospective version is both + # greater than the spec version *and* it's not a pre-release of the + # same version in the spec. + return True + + def _compare_arbitrary(self, prospective: Version, spec: str) -> bool: + return str(prospective).lower() == str(spec).lower() + + @property + def prereleases(self) -> bool: + + # If there is an explicit prereleases set for this, then we'll just + # blindly use that. + if self._prereleases is not None: + return self._prereleases + + # Look at all of our specifiers and determine if they are inclusive + # operators, and if they are if they are including an explicit + # prerelease. + operator, version = self._spec + if operator in ["==", ">=", "<=", "~=", "==="]: + # The == specifier can include a trailing .*, if it does we + # want to remove before parsing. + if operator == "==" and version.endswith(".*"): + version = version[:-2] + + # Parse the version, and if it is a pre-release than this + # specifier allows pre-releases. + if parse(version).is_prerelease: + return True + + return False + + @prereleases.setter + def prereleases(self, value: bool) -> None: + self._prereleases = value + + +_prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$") + + +def _version_split(version: str) -> List[str]: + result: List[str] = [] + for item in version.split("."): + match = _prefix_regex.search(item) + if match: + result.extend(match.groups()) + else: + result.append(item) + return result + + +def _is_not_suffix(segment: str) -> bool: + return not any( + segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post") + ) + + +def _pad_version(left: List[str], right: List[str]) -> Tuple[List[str], List[str]]: + left_split, right_split = [], [] + + # Get the release segment of our versions + left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left))) + right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right))) + + # Get the rest of our versions + left_split.append(left[len(left_split[0]) :]) + right_split.append(right[len(right_split[0]) :]) + + # Insert our padding + left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0]))) + right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0]))) + + return (list(itertools.chain(*left_split)), list(itertools.chain(*right_split))) + + +class SpecifierSet(BaseSpecifier): + def __init__( + self, specifiers: str = "", prereleases: Optional[bool] = None + ) -> None: + + # Split on , to break each individual specifier into it's own item, and + # strip each item to remove leading/trailing whitespace. + split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()] + + # Parsed each individual specifier, attempting first to make it a + # Specifier and falling back to a LegacySpecifier. + parsed: Set[_IndividualSpecifier] = set() + for specifier in split_specifiers: + try: + parsed.add(Specifier(specifier)) + except InvalidSpecifier: + parsed.add(LegacySpecifier(specifier)) + + # Turn our parsed specifiers into a frozen set and save them for later. + self._specs = frozenset(parsed) + + # Store our prereleases value so we can use it later to determine if + # we accept prereleases or not. + self._prereleases = prereleases + + def __repr__(self) -> str: + pre = ( + f", prereleases={self.prereleases!r}" + if self._prereleases is not None + else "" + ) + + return "".format(str(self), pre) + + def __str__(self) -> str: + return ",".join(sorted(str(s) for s in self._specs)) + + def __hash__(self) -> int: + return hash(self._specs) + + def __and__(self, other: Union["SpecifierSet", str]) -> "SpecifierSet": + if isinstance(other, str): + other = SpecifierSet(other) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + specifier = SpecifierSet() + specifier._specs = frozenset(self._specs | other._specs) + + if self._prereleases is None and other._prereleases is not None: + specifier._prereleases = other._prereleases + elif self._prereleases is not None and other._prereleases is None: + specifier._prereleases = self._prereleases + elif self._prereleases == other._prereleases: + specifier._prereleases = self._prereleases + else: + raise ValueError( + "Cannot combine SpecifierSets with True and False prerelease " + "overrides." + ) + + return specifier + + def __eq__(self, other: object) -> bool: + if isinstance(other, (str, _IndividualSpecifier)): + other = SpecifierSet(str(other)) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + return self._specs == other._specs + + def __ne__(self, other: object) -> bool: + if isinstance(other, (str, _IndividualSpecifier)): + other = SpecifierSet(str(other)) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + return self._specs != other._specs + + def __len__(self) -> int: + return len(self._specs) + + def __iter__(self) -> Iterator[_IndividualSpecifier]: + return iter(self._specs) + + @property + def prereleases(self) -> Optional[bool]: + + # If we have been given an explicit prerelease modifier, then we'll + # pass that through here. + if self._prereleases is not None: + return self._prereleases + + # If we don't have any specifiers, and we don't have a forced value, + # then we'll just return None since we don't know if this should have + # pre-releases or not. + if not self._specs: + return None + + # Otherwise we'll see if any of the given specifiers accept + # prereleases, if any of them do we'll return True, otherwise False. + return any(s.prereleases for s in self._specs) + + @prereleases.setter + def prereleases(self, value: bool) -> None: + self._prereleases = value + + def __contains__(self, item: UnparsedVersion) -> bool: + return self.contains(item) + + def contains( + self, item: UnparsedVersion, prereleases: Optional[bool] = None + ) -> bool: + + # Ensure that our item is a Version or LegacyVersion instance. + if not isinstance(item, (LegacyVersion, Version)): + item = parse(item) + + # Determine if we're forcing a prerelease or not, if we're not forcing + # one for this particular filter call, then we'll use whatever the + # SpecifierSet thinks for whether or not we should support prereleases. + if prereleases is None: + prereleases = self.prereleases + + # We can determine if we're going to allow pre-releases by looking to + # see if any of the underlying items supports them. If none of them do + # and this item is a pre-release then we do not allow it and we can + # short circuit that here. + # Note: This means that 1.0.dev1 would not be contained in something + # like >=1.0.devabc however it would be in >=1.0.debabc,>0.0.dev0 + if not prereleases and item.is_prerelease: + return False + + # We simply dispatch to the underlying specs here to make sure that the + # given version is contained within all of them. + # Note: This use of all() here means that an empty set of specifiers + # will always return True, this is an explicit design decision. + return all(s.contains(item, prereleases=prereleases) for s in self._specs) + + def filter( + self, iterable: Iterable[VersionTypeVar], prereleases: Optional[bool] = None + ) -> Iterable[VersionTypeVar]: + + # Determine if we're forcing a prerelease or not, if we're not forcing + # one for this particular filter call, then we'll use whatever the + # SpecifierSet thinks for whether or not we should support prereleases. + if prereleases is None: + prereleases = self.prereleases + + # If we have any specifiers, then we want to wrap our iterable in the + # filter method for each one, this will act as a logical AND amongst + # each specifier. + if self._specs: + for spec in self._specs: + iterable = spec.filter(iterable, prereleases=bool(prereleases)) + return iterable + # If we do not have any specifiers, then we need to have a rough filter + # which will filter out any pre-releases, unless there are no final + # releases, and which will filter out LegacyVersion in general. + else: + filtered: List[VersionTypeVar] = [] + found_prereleases: List[VersionTypeVar] = [] + + item: UnparsedVersion + parsed_version: Union[Version, LegacyVersion] + + for item in iterable: + # Ensure that we some kind of Version class for this item. + if not isinstance(item, (LegacyVersion, Version)): + parsed_version = parse(item) + else: + parsed_version = item + + # Filter out any item which is parsed as a LegacyVersion + if isinstance(parsed_version, LegacyVersion): + continue + + # Store any item which is a pre-release for later unless we've + # already found a final version or we are accepting prereleases + if parsed_version.is_prerelease and not prereleases: + if not filtered: + found_prereleases.append(item) + else: + filtered.append(item) + + # If we've found no items except for pre-releases, then we'll go + # ahead and use the pre-releases + if not filtered and found_prereleases and prereleases is None: + return found_prereleases + + return filtered diff --git a/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/tags.py b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/tags.py new file mode 100644 index 00000000..e65890a9 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/tags.py @@ -0,0 +1,484 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import logging +import platform +import sys +import sysconfig +from importlib.machinery import EXTENSION_SUFFIXES +from typing import ( + Dict, + FrozenSet, + Iterable, + Iterator, + List, + Optional, + Sequence, + Tuple, + Union, + cast, +) + +from . import _manylinux, _musllinux + +logger = logging.getLogger(__name__) + +PythonVersion = Sequence[int] +MacVersion = Tuple[int, int] + +INTERPRETER_SHORT_NAMES: Dict[str, str] = { + "python": "py", # Generic. + "cpython": "cp", + "pypy": "pp", + "ironpython": "ip", + "jython": "jy", +} + + +_32_BIT_INTERPRETER = sys.maxsize <= 2 ** 32 + + +class Tag: + """ + A representation of the tag triple for a wheel. + + Instances are considered immutable and thus are hashable. Equality checking + is also supported. + """ + + __slots__ = ["_interpreter", "_abi", "_platform", "_hash"] + + def __init__(self, interpreter: str, abi: str, platform: str) -> None: + self._interpreter = interpreter.lower() + self._abi = abi.lower() + self._platform = platform.lower() + # The __hash__ of every single element in a Set[Tag] will be evaluated each time + # that a set calls its `.disjoint()` method, which may be called hundreds of + # times when scanning a page of links for packages with tags matching that + # Set[Tag]. Pre-computing the value here produces significant speedups for + # downstream consumers. + self._hash = hash((self._interpreter, self._abi, self._platform)) + + @property + def interpreter(self) -> str: + return self._interpreter + + @property + def abi(self) -> str: + return self._abi + + @property + def platform(self) -> str: + return self._platform + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Tag): + return NotImplemented + + return ( + (self._hash == other._hash) # Short-circuit ASAP for perf reasons. + and (self._platform == other._platform) + and (self._abi == other._abi) + and (self._interpreter == other._interpreter) + ) + + def __hash__(self) -> int: + return self._hash + + def __str__(self) -> str: + return f"{self._interpreter}-{self._abi}-{self._platform}" + + def __repr__(self) -> str: + return "<{self} @ {self_id}>".format(self=self, self_id=id(self)) + + +def parse_tag(tag: str) -> FrozenSet[Tag]: + """ + Parses the provided tag (e.g. `py3-none-any`) into a frozenset of Tag instances. + + Returning a set is required due to the possibility that the tag is a + compressed tag set. + """ + tags = set() + interpreters, abis, platforms = tag.split("-") + for interpreter in interpreters.split("."): + for abi in abis.split("."): + for platform_ in platforms.split("."): + tags.add(Tag(interpreter, abi, platform_)) + return frozenset(tags) + + +def _get_config_var(name: str, warn: bool = False) -> Union[int, str, None]: + value = sysconfig.get_config_var(name) + if value is None and warn: + logger.debug( + "Config variable '%s' is unset, Python ABI tag may be incorrect", name + ) + return value + + +def _normalize_string(string: str) -> str: + return string.replace(".", "_").replace("-", "_") + + +def _abi3_applies(python_version: PythonVersion) -> bool: + """ + Determine if the Python version supports abi3. + + PEP 384 was first implemented in Python 3.2. + """ + return len(python_version) > 1 and tuple(python_version) >= (3, 2) + + +def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> List[str]: + py_version = tuple(py_version) # To allow for version comparison. + abis = [] + version = _version_nodot(py_version[:2]) + debug = pymalloc = ucs4 = "" + with_debug = _get_config_var("Py_DEBUG", warn) + has_refcount = hasattr(sys, "gettotalrefcount") + # Windows doesn't set Py_DEBUG, so checking for support of debug-compiled + # extension modules is the best option. + # https://github.com/pypa/pip/issues/3383#issuecomment-173267692 + has_ext = "_d.pyd" in EXTENSION_SUFFIXES + if with_debug or (with_debug is None and (has_refcount or has_ext)): + debug = "d" + if py_version < (3, 8): + with_pymalloc = _get_config_var("WITH_PYMALLOC", warn) + if with_pymalloc or with_pymalloc is None: + pymalloc = "m" + if py_version < (3, 3): + unicode_size = _get_config_var("Py_UNICODE_SIZE", warn) + if unicode_size == 4 or ( + unicode_size is None and sys.maxunicode == 0x10FFFF + ): + ucs4 = "u" + elif debug: + # Debug builds can also load "normal" extension modules. + # We can also assume no UCS-4 or pymalloc requirement. + abis.append(f"cp{version}") + abis.insert( + 0, + "cp{version}{debug}{pymalloc}{ucs4}".format( + version=version, debug=debug, pymalloc=pymalloc, ucs4=ucs4 + ), + ) + return abis + + +def cpython_tags( + python_version: Optional[PythonVersion] = None, + abis: Optional[Iterable[str]] = None, + platforms: Optional[Iterable[str]] = None, + *, + warn: bool = False, +) -> Iterator[Tag]: + """ + Yields the tags for a CPython interpreter. + + The tags consist of: + - cp-- + - cp-abi3- + - cp-none- + - cp-abi3- # Older Python versions down to 3.2. + + If python_version only specifies a major version then user-provided ABIs and + the 'none' ABItag will be used. + + If 'abi3' or 'none' are specified in 'abis' then they will be yielded at + their normal position and not at the beginning. + """ + if not python_version: + python_version = sys.version_info[:2] + + interpreter = "cp{}".format(_version_nodot(python_version[:2])) + + if abis is None: + if len(python_version) > 1: + abis = _cpython_abis(python_version, warn) + else: + abis = [] + abis = list(abis) + # 'abi3' and 'none' are explicitly handled later. + for explicit_abi in ("abi3", "none"): + try: + abis.remove(explicit_abi) + except ValueError: + pass + + platforms = list(platforms or platform_tags()) + for abi in abis: + for platform_ in platforms: + yield Tag(interpreter, abi, platform_) + if _abi3_applies(python_version): + yield from (Tag(interpreter, "abi3", platform_) for platform_ in platforms) + yield from (Tag(interpreter, "none", platform_) for platform_ in platforms) + + if _abi3_applies(python_version): + for minor_version in range(python_version[1] - 1, 1, -1): + for platform_ in platforms: + interpreter = "cp{version}".format( + version=_version_nodot((python_version[0], minor_version)) + ) + yield Tag(interpreter, "abi3", platform_) + + +def _generic_abi() -> Iterator[str]: + abi = sysconfig.get_config_var("SOABI") + if abi: + yield _normalize_string(abi) + + +def generic_tags( + interpreter: Optional[str] = None, + abis: Optional[Iterable[str]] = None, + platforms: Optional[Iterable[str]] = None, + *, + warn: bool = False, +) -> Iterator[Tag]: + """ + Yields the tags for a generic interpreter. + + The tags consist of: + - -- + + The "none" ABI will be added if it was not explicitly provided. + """ + if not interpreter: + interp_name = interpreter_name() + interp_version = interpreter_version(warn=warn) + interpreter = "".join([interp_name, interp_version]) + if abis is None: + abis = _generic_abi() + platforms = list(platforms or platform_tags()) + abis = list(abis) + if "none" not in abis: + abis.append("none") + for abi in abis: + for platform_ in platforms: + yield Tag(interpreter, abi, platform_) + + +def _py_interpreter_range(py_version: PythonVersion) -> Iterator[str]: + """ + Yields Python versions in descending order. + + After the latest version, the major-only version will be yielded, and then + all previous versions of that major version. + """ + if len(py_version) > 1: + yield "py{version}".format(version=_version_nodot(py_version[:2])) + yield "py{major}".format(major=py_version[0]) + if len(py_version) > 1: + for minor in range(py_version[1] - 1, -1, -1): + yield "py{version}".format(version=_version_nodot((py_version[0], minor))) + + +def compatible_tags( + python_version: Optional[PythonVersion] = None, + interpreter: Optional[str] = None, + platforms: Optional[Iterable[str]] = None, +) -> Iterator[Tag]: + """ + Yields the sequence of tags that are compatible with a specific version of Python. + + The tags consist of: + - py*-none- + - -none-any # ... if `interpreter` is provided. + - py*-none-any + """ + if not python_version: + python_version = sys.version_info[:2] + platforms = list(platforms or platform_tags()) + for version in _py_interpreter_range(python_version): + for platform_ in platforms: + yield Tag(version, "none", platform_) + if interpreter: + yield Tag(interpreter, "none", "any") + for version in _py_interpreter_range(python_version): + yield Tag(version, "none", "any") + + +def _mac_arch(arch: str, is_32bit: bool = _32_BIT_INTERPRETER) -> str: + if not is_32bit: + return arch + + if arch.startswith("ppc"): + return "ppc" + + return "i386" + + +def _mac_binary_formats(version: MacVersion, cpu_arch: str) -> List[str]: + formats = [cpu_arch] + if cpu_arch == "x86_64": + if version < (10, 4): + return [] + formats.extend(["intel", "fat64", "fat32"]) + + elif cpu_arch == "i386": + if version < (10, 4): + return [] + formats.extend(["intel", "fat32", "fat"]) + + elif cpu_arch == "ppc64": + # TODO: Need to care about 32-bit PPC for ppc64 through 10.2? + if version > (10, 5) or version < (10, 4): + return [] + formats.append("fat64") + + elif cpu_arch == "ppc": + if version > (10, 6): + return [] + formats.extend(["fat32", "fat"]) + + if cpu_arch in {"arm64", "x86_64"}: + formats.append("universal2") + + if cpu_arch in {"x86_64", "i386", "ppc64", "ppc", "intel"}: + formats.append("universal") + + return formats + + +def mac_platforms( + version: Optional[MacVersion] = None, arch: Optional[str] = None +) -> Iterator[str]: + """ + Yields the platform tags for a macOS system. + + The `version` parameter is a two-item tuple specifying the macOS version to + generate platform tags for. The `arch` parameter is the CPU architecture to + generate platform tags for. Both parameters default to the appropriate value + for the current system. + """ + version_str, _, cpu_arch = platform.mac_ver() + if version is None: + version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2]))) + else: + version = version + if arch is None: + arch = _mac_arch(cpu_arch) + else: + arch = arch + + if (10, 0) <= version and version < (11, 0): + # Prior to Mac OS 11, each yearly release of Mac OS bumped the + # "minor" version number. The major version was always 10. + for minor_version in range(version[1], -1, -1): + compat_version = 10, minor_version + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + yield "macosx_{major}_{minor}_{binary_format}".format( + major=10, minor=minor_version, binary_format=binary_format + ) + + if version >= (11, 0): + # Starting with Mac OS 11, each yearly release bumps the major version + # number. The minor versions are now the midyear updates. + for major_version in range(version[0], 10, -1): + compat_version = major_version, 0 + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + yield "macosx_{major}_{minor}_{binary_format}".format( + major=major_version, minor=0, binary_format=binary_format + ) + + if version >= (11, 0): + # Mac OS 11 on x86_64 is compatible with binaries from previous releases. + # Arm64 support was introduced in 11.0, so no Arm binaries from previous + # releases exist. + # + # However, the "universal2" binary format can have a + # macOS version earlier than 11.0 when the x86_64 part of the binary supports + # that version of macOS. + if arch == "x86_64": + for minor_version in range(16, 3, -1): + compat_version = 10, minor_version + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + yield "macosx_{major}_{minor}_{binary_format}".format( + major=compat_version[0], + minor=compat_version[1], + binary_format=binary_format, + ) + else: + for minor_version in range(16, 3, -1): + compat_version = 10, minor_version + binary_format = "universal2" + yield "macosx_{major}_{minor}_{binary_format}".format( + major=compat_version[0], + minor=compat_version[1], + binary_format=binary_format, + ) + + +def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]: + linux = _normalize_string(sysconfig.get_platform()) + if is_32bit: + if linux == "linux_x86_64": + linux = "linux_i686" + elif linux == "linux_aarch64": + linux = "linux_armv7l" + _, arch = linux.split("_", 1) + yield from _manylinux.platform_tags(linux, arch) + yield from _musllinux.platform_tags(arch) + yield linux + + +def _generic_platforms() -> Iterator[str]: + yield _normalize_string(sysconfig.get_platform()) + + +def platform_tags() -> Iterator[str]: + """ + Provides the platform tags for this installation. + """ + if platform.system() == "Darwin": + return mac_platforms() + elif platform.system() == "Linux": + return _linux_platforms() + else: + return _generic_platforms() + + +def interpreter_name() -> str: + """ + Returns the name of the running interpreter. + """ + name = sys.implementation.name + return INTERPRETER_SHORT_NAMES.get(name) or name + + +def interpreter_version(*, warn: bool = False) -> str: + """ + Returns the version of the running interpreter. + """ + version = _get_config_var("py_version_nodot", warn=warn) + if version: + version = str(version) + else: + version = _version_nodot(sys.version_info[:2]) + return version + + +def _version_nodot(version: PythonVersion) -> str: + return "".join(map(str, version)) + + +def sys_tags(*, warn: bool = False) -> Iterator[Tag]: + """ + Returns the sequence of tag triples for the running interpreter. + + The order of the sequence corresponds to priority order for the + interpreter, from most to least important. + """ + + interp_name = interpreter_name() + if interp_name == "cp": + yield from cpython_tags(warn=warn) + else: + yield from generic_tags() + + yield from compatible_tags() diff --git a/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/utils.py b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/utils.py new file mode 100644 index 00000000..bab11b80 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/utils.py @@ -0,0 +1,136 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import re +from typing import FrozenSet, NewType, Tuple, Union, cast + +from .tags import Tag, parse_tag +from .version import InvalidVersion, Version + +BuildTag = Union[Tuple[()], Tuple[int, str]] +NormalizedName = NewType("NormalizedName", str) + + +class InvalidWheelFilename(ValueError): + """ + An invalid wheel filename was found, users should refer to PEP 427. + """ + + +class InvalidSdistFilename(ValueError): + """ + An invalid sdist filename was found, users should refer to the packaging user guide. + """ + + +_canonicalize_regex = re.compile(r"[-_.]+") +# PEP 427: The build number must start with a digit. +_build_tag_regex = re.compile(r"(\d+)(.*)") + + +def canonicalize_name(name: str) -> NormalizedName: + # This is taken from PEP 503. + value = _canonicalize_regex.sub("-", name).lower() + return cast(NormalizedName, value) + + +def canonicalize_version(version: Union[Version, str]) -> str: + """ + This is very similar to Version.__str__, but has one subtle difference + with the way it handles the release segment. + """ + if isinstance(version, str): + try: + parsed = Version(version) + except InvalidVersion: + # Legacy versions cannot be normalized + return version + else: + parsed = version + + parts = [] + + # Epoch + if parsed.epoch != 0: + parts.append(f"{parsed.epoch}!") + + # Release segment + # NB: This strips trailing '.0's to normalize + parts.append(re.sub(r"(\.0)+$", "", ".".join(str(x) for x in parsed.release))) + + # Pre-release + if parsed.pre is not None: + parts.append("".join(str(x) for x in parsed.pre)) + + # Post-release + if parsed.post is not None: + parts.append(f".post{parsed.post}") + + # Development release + if parsed.dev is not None: + parts.append(f".dev{parsed.dev}") + + # Local version segment + if parsed.local is not None: + parts.append(f"+{parsed.local}") + + return "".join(parts) + + +def parse_wheel_filename( + filename: str, +) -> Tuple[NormalizedName, Version, BuildTag, FrozenSet[Tag]]: + if not filename.endswith(".whl"): + raise InvalidWheelFilename( + f"Invalid wheel filename (extension must be '.whl'): {filename}" + ) + + filename = filename[:-4] + dashes = filename.count("-") + if dashes not in (4, 5): + raise InvalidWheelFilename( + f"Invalid wheel filename (wrong number of parts): {filename}" + ) + + parts = filename.split("-", dashes - 2) + name_part = parts[0] + # See PEP 427 for the rules on escaping the project name + if "__" in name_part or re.match(r"^[\w\d._]*$", name_part, re.UNICODE) is None: + raise InvalidWheelFilename(f"Invalid project name: {filename}") + name = canonicalize_name(name_part) + version = Version(parts[1]) + if dashes == 5: + build_part = parts[2] + build_match = _build_tag_regex.match(build_part) + if build_match is None: + raise InvalidWheelFilename( + f"Invalid build number: {build_part} in '{filename}'" + ) + build = cast(BuildTag, (int(build_match.group(1)), build_match.group(2))) + else: + build = () + tags = parse_tag(parts[-1]) + return (name, version, build, tags) + + +def parse_sdist_filename(filename: str) -> Tuple[NormalizedName, Version]: + if filename.endswith(".tar.gz"): + file_stem = filename[: -len(".tar.gz")] + elif filename.endswith(".zip"): + file_stem = filename[: -len(".zip")] + else: + raise InvalidSdistFilename( + f"Invalid sdist filename (extension must be '.tar.gz' or '.zip'):" + f" {filename}" + ) + + # We are requiring a PEP 440 version, which cannot contain dashes, + # so we split on the last dash. + name_part, sep, version_part = file_stem.rpartition("-") + if not sep: + raise InvalidSdistFilename(f"Invalid sdist filename: {filename}") + + name = canonicalize_name(name_part) + version = Version(version_part) + return (name, version) diff --git a/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/version.py b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/version.py new file mode 100644 index 00000000..de9a09a4 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/packaging/version.py @@ -0,0 +1,504 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import collections +import itertools +import re +import warnings +from typing import Callable, Iterator, List, Optional, SupportsInt, Tuple, Union + +from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType + +__all__ = ["parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"] + +InfiniteTypes = Union[InfinityType, NegativeInfinityType] +PrePostDevType = Union[InfiniteTypes, Tuple[str, int]] +SubLocalType = Union[InfiniteTypes, int, str] +LocalType = Union[ + NegativeInfinityType, + Tuple[ + Union[ + SubLocalType, + Tuple[SubLocalType, str], + Tuple[NegativeInfinityType, SubLocalType], + ], + ..., + ], +] +CmpKey = Tuple[ + int, Tuple[int, ...], PrePostDevType, PrePostDevType, PrePostDevType, LocalType +] +LegacyCmpKey = Tuple[int, Tuple[str, ...]] +VersionComparisonMethod = Callable[ + [Union[CmpKey, LegacyCmpKey], Union[CmpKey, LegacyCmpKey]], bool +] + +_Version = collections.namedtuple( + "_Version", ["epoch", "release", "dev", "pre", "post", "local"] +) + + +def parse(version: str) -> Union["LegacyVersion", "Version"]: + """ + Parse the given version string and return either a :class:`Version` object + or a :class:`LegacyVersion` object depending on if the given version is + a valid PEP 440 version or a legacy version. + """ + try: + return Version(version) + except InvalidVersion: + return LegacyVersion(version) + + +class InvalidVersion(ValueError): + """ + An invalid version was found, users should refer to PEP 440. + """ + + +class _BaseVersion: + _key: Union[CmpKey, LegacyCmpKey] + + def __hash__(self) -> int: + return hash(self._key) + + # Please keep the duplicated `isinstance` check + # in the six comparisons hereunder + # unless you find a way to avoid adding overhead function calls. + def __lt__(self, other: "_BaseVersion") -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key < other._key + + def __le__(self, other: "_BaseVersion") -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key <= other._key + + def __eq__(self, other: object) -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key == other._key + + def __ge__(self, other: "_BaseVersion") -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key >= other._key + + def __gt__(self, other: "_BaseVersion") -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key > other._key + + def __ne__(self, other: object) -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key != other._key + + +class LegacyVersion(_BaseVersion): + def __init__(self, version: str) -> None: + self._version = str(version) + self._key = _legacy_cmpkey(self._version) + + warnings.warn( + "Creating a LegacyVersion has been deprecated and will be " + "removed in the next major release", + DeprecationWarning, + ) + + def __str__(self) -> str: + return self._version + + def __repr__(self) -> str: + return f"" + + @property + def public(self) -> str: + return self._version + + @property + def base_version(self) -> str: + return self._version + + @property + def epoch(self) -> int: + return -1 + + @property + def release(self) -> None: + return None + + @property + def pre(self) -> None: + return None + + @property + def post(self) -> None: + return None + + @property + def dev(self) -> None: + return None + + @property + def local(self) -> None: + return None + + @property + def is_prerelease(self) -> bool: + return False + + @property + def is_postrelease(self) -> bool: + return False + + @property + def is_devrelease(self) -> bool: + return False + + +_legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE) + +_legacy_version_replacement_map = { + "pre": "c", + "preview": "c", + "-": "final-", + "rc": "c", + "dev": "@", +} + + +def _parse_version_parts(s: str) -> Iterator[str]: + for part in _legacy_version_component_re.split(s): + part = _legacy_version_replacement_map.get(part, part) + + if not part or part == ".": + continue + + if part[:1] in "0123456789": + # pad for numeric comparison + yield part.zfill(8) + else: + yield "*" + part + + # ensure that alpha/beta/candidate are before final + yield "*final" + + +def _legacy_cmpkey(version: str) -> LegacyCmpKey: + + # We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch + # greater than or equal to 0. This will effectively put the LegacyVersion, + # which uses the defacto standard originally implemented by setuptools, + # as before all PEP 440 versions. + epoch = -1 + + # This scheme is taken from pkg_resources.parse_version setuptools prior to + # it's adoption of the packaging library. + parts: List[str] = [] + for part in _parse_version_parts(version.lower()): + if part.startswith("*"): + # remove "-" before a prerelease tag + if part < "*final": + while parts and parts[-1] == "*final-": + parts.pop() + + # remove trailing zeros from each series of numeric parts + while parts and parts[-1] == "00000000": + parts.pop() + + parts.append(part) + + return epoch, tuple(parts) + + +# Deliberately not anchored to the start and end of the string, to make it +# easier for 3rd party code to reuse +VERSION_PATTERN = r""" + v? + (?: + (?:(?P[0-9]+)!)? # epoch + (?P[0-9]+(?:\.[0-9]+)*) # release segment + (?P
                                          # pre-release
+            [-_\.]?
+            (?P(a|b|c|rc|alpha|beta|pre|preview))
+            [-_\.]?
+            (?P[0-9]+)?
+        )?
+        (?P                                         # post release
+            (?:-(?P[0-9]+))
+            |
+            (?:
+                [-_\.]?
+                (?Ppost|rev|r)
+                [-_\.]?
+                (?P[0-9]+)?
+            )
+        )?
+        (?P                                          # dev release
+            [-_\.]?
+            (?Pdev)
+            [-_\.]?
+            (?P[0-9]+)?
+        )?
+    )
+    (?:\+(?P[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?       # local version
+"""
+
+
+class Version(_BaseVersion):
+
+    _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
+
+    def __init__(self, version: str) -> None:
+
+        # Validate the version and parse it into pieces
+        match = self._regex.search(version)
+        if not match:
+            raise InvalidVersion(f"Invalid version: '{version}'")
+
+        # Store the parsed out pieces of the version
+        self._version = _Version(
+            epoch=int(match.group("epoch")) if match.group("epoch") else 0,
+            release=tuple(int(i) for i in match.group("release").split(".")),
+            pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")),
+            post=_parse_letter_version(
+                match.group("post_l"), match.group("post_n1") or match.group("post_n2")
+            ),
+            dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")),
+            local=_parse_local_version(match.group("local")),
+        )
+
+        # Generate a key which will be used for sorting
+        self._key = _cmpkey(
+            self._version.epoch,
+            self._version.release,
+            self._version.pre,
+            self._version.post,
+            self._version.dev,
+            self._version.local,
+        )
+
+    def __repr__(self) -> str:
+        return f""
+
+    def __str__(self) -> str:
+        parts = []
+
+        # Epoch
+        if self.epoch != 0:
+            parts.append(f"{self.epoch}!")
+
+        # Release segment
+        parts.append(".".join(str(x) for x in self.release))
+
+        # Pre-release
+        if self.pre is not None:
+            parts.append("".join(str(x) for x in self.pre))
+
+        # Post-release
+        if self.post is not None:
+            parts.append(f".post{self.post}")
+
+        # Development release
+        if self.dev is not None:
+            parts.append(f".dev{self.dev}")
+
+        # Local version segment
+        if self.local is not None:
+            parts.append(f"+{self.local}")
+
+        return "".join(parts)
+
+    @property
+    def epoch(self) -> int:
+        _epoch: int = self._version.epoch
+        return _epoch
+
+    @property
+    def release(self) -> Tuple[int, ...]:
+        _release: Tuple[int, ...] = self._version.release
+        return _release
+
+    @property
+    def pre(self) -> Optional[Tuple[str, int]]:
+        _pre: Optional[Tuple[str, int]] = self._version.pre
+        return _pre
+
+    @property
+    def post(self) -> Optional[int]:
+        return self._version.post[1] if self._version.post else None
+
+    @property
+    def dev(self) -> Optional[int]:
+        return self._version.dev[1] if self._version.dev else None
+
+    @property
+    def local(self) -> Optional[str]:
+        if self._version.local:
+            return ".".join(str(x) for x in self._version.local)
+        else:
+            return None
+
+    @property
+    def public(self) -> str:
+        return str(self).split("+", 1)[0]
+
+    @property
+    def base_version(self) -> str:
+        parts = []
+
+        # Epoch
+        if self.epoch != 0:
+            parts.append(f"{self.epoch}!")
+
+        # Release segment
+        parts.append(".".join(str(x) for x in self.release))
+
+        return "".join(parts)
+
+    @property
+    def is_prerelease(self) -> bool:
+        return self.dev is not None or self.pre is not None
+
+    @property
+    def is_postrelease(self) -> bool:
+        return self.post is not None
+
+    @property
+    def is_devrelease(self) -> bool:
+        return self.dev is not None
+
+    @property
+    def major(self) -> int:
+        return self.release[0] if len(self.release) >= 1 else 0
+
+    @property
+    def minor(self) -> int:
+        return self.release[1] if len(self.release) >= 2 else 0
+
+    @property
+    def micro(self) -> int:
+        return self.release[2] if len(self.release) >= 3 else 0
+
+
+def _parse_letter_version(
+    letter: str, number: Union[str, bytes, SupportsInt]
+) -> Optional[Tuple[str, int]]:
+
+    if letter:
+        # We consider there to be an implicit 0 in a pre-release if there is
+        # not a numeral associated with it.
+        if number is None:
+            number = 0
+
+        # We normalize any letters to their lower case form
+        letter = letter.lower()
+
+        # We consider some words to be alternate spellings of other words and
+        # in those cases we want to normalize the spellings to our preferred
+        # spelling.
+        if letter == "alpha":
+            letter = "a"
+        elif letter == "beta":
+            letter = "b"
+        elif letter in ["c", "pre", "preview"]:
+            letter = "rc"
+        elif letter in ["rev", "r"]:
+            letter = "post"
+
+        return letter, int(number)
+    if not letter and number:
+        # We assume if we are given a number, but we are not given a letter
+        # then this is using the implicit post release syntax (e.g. 1.0-1)
+        letter = "post"
+
+        return letter, int(number)
+
+    return None
+
+
+_local_version_separators = re.compile(r"[\._-]")
+
+
+def _parse_local_version(local: str) -> Optional[LocalType]:
+    """
+    Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
+    """
+    if local is not None:
+        return tuple(
+            part.lower() if not part.isdigit() else int(part)
+            for part in _local_version_separators.split(local)
+        )
+    return None
+
+
+def _cmpkey(
+    epoch: int,
+    release: Tuple[int, ...],
+    pre: Optional[Tuple[str, int]],
+    post: Optional[Tuple[str, int]],
+    dev: Optional[Tuple[str, int]],
+    local: Optional[Tuple[SubLocalType]],
+) -> CmpKey:
+
+    # When we compare a release version, we want to compare it with all of the
+    # trailing zeros removed. So we'll use a reverse the list, drop all the now
+    # leading zeros until we come to something non zero, then take the rest
+    # re-reverse it back into the correct order and make it a tuple and use
+    # that for our sorting key.
+    _release = tuple(
+        reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))
+    )
+
+    # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
+    # We'll do this by abusing the pre segment, but we _only_ want to do this
+    # if there is not a pre or a post segment. If we have one of those then
+    # the normal sorting rules will handle this case correctly.
+    if pre is None and post is None and dev is not None:
+        _pre: PrePostDevType = NegativeInfinity
+    # Versions without a pre-release (except as noted above) should sort after
+    # those with one.
+    elif pre is None:
+        _pre = Infinity
+    else:
+        _pre = pre
+
+    # Versions without a post segment should sort before those with one.
+    if post is None:
+        _post: PrePostDevType = NegativeInfinity
+
+    else:
+        _post = post
+
+    # Versions without a development segment should sort after those with one.
+    if dev is None:
+        _dev: PrePostDevType = Infinity
+
+    else:
+        _dev = dev
+
+    if local is None:
+        # Versions without a local segment should sort before those with one.
+        _local: LocalType = NegativeInfinity
+    else:
+        # Versions with a local segment need that segment parsed to implement
+        # the sorting rules in PEP440.
+        # - Alpha numeric segments sort before numeric segments
+        # - Alpha numeric segments sort lexicographically
+        # - Numeric segments sort numerically
+        # - Shorter versions sort before longer versions when the prefixes
+        #   match exactly
+        _local = tuple(
+            (i, "") if isinstance(i, int) else (NegativeInfinity, i) for i in local
+        )
+
+    return epoch, _release, _pre, _post, _dev, _local
diff --git a/venv/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing.py b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing.py
new file mode 100644
index 00000000..cf75e1e5
--- /dev/null
+++ b/venv/lib/python3.10/site-packages/pkg_resources/_vendor/pyparsing.py
@@ -0,0 +1,5742 @@
+# module pyparsing.py
+#
+# Copyright (c) 2003-2018  Paul T. McGuire
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__doc__ = \
+"""
+pyparsing module - Classes and methods to define and execute parsing grammars
+=============================================================================
+
+The pyparsing module is an alternative approach to creating and executing simple grammars,
+vs. the traditional lex/yacc approach, or the use of regular expressions.  With pyparsing, you
+don't need to learn a new syntax for defining grammars or matching expressions - the parsing module
+provides a library of classes that you use to construct the grammar directly in Python.
+
+Here is a program to parse "Hello, World!" (or any greeting of the form 
+C{", !"}), built up using L{Word}, L{Literal}, and L{And} elements 
+(L{'+'} operator gives L{And} expressions, strings are auto-converted to
+L{Literal} expressions)::
+
+    from pyparsing import Word, alphas
+
+    # define grammar of a greeting
+    greet = Word(alphas) + "," + Word(alphas) + "!"
+
+    hello = "Hello, World!"
+    print (hello, "->", greet.parseString(hello))
+
+The program outputs the following::
+
+    Hello, World! -> ['Hello', ',', 'World', '!']
+
+The Python representation of the grammar is quite readable, owing to the self-explanatory
+class names, and the use of '+', '|' and '^' operators.
+
+The L{ParseResults} object returned from L{ParserElement.parseString} can be accessed as a nested list, a dictionary, or an
+object with named attributes.
+
+The pyparsing module handles some of the problems that are typically vexing when writing text parsers:
+ - extra or missing whitespace (the above program will also handle "Hello,World!", "Hello  ,  World  !", etc.)
+ - quoted strings
+ - embedded comments
+
+
+Getting Started -
+-----------------
+Visit the classes L{ParserElement} and L{ParseResults} to see the base classes that most other pyparsing
+classes inherit from. Use the docstrings for examples of how to:
+ - construct literal match expressions from L{Literal} and L{CaselessLiteral} classes
+ - construct character word-group expressions using the L{Word} class
+ - see how to create repetitive expressions using L{ZeroOrMore} and L{OneOrMore} classes
+ - use L{'+'}, L{'|'}, L{'^'}, and L{'&'} operators to combine simple expressions into more complex ones
+ - associate names with your parsed results using L{ParserElement.setResultsName}
+ - find some helpful expression short-cuts like L{delimitedList} and L{oneOf}
+ - find more useful common expressions in the L{pyparsing_common} namespace class
+"""
+
+__version__ = "2.2.1"
+__versionTime__ = "18 Sep 2018 00:49 UTC"
+__author__ = "Paul McGuire "
+
+import string
+from weakref import ref as wkref
+import copy
+import sys
+import warnings
+import re
+import sre_constants
+import collections
+import pprint
+import traceback
+import types
+from datetime import datetime
+
+try:
+    from _thread import RLock
+except ImportError:
+    from threading import RLock
+
+try:
+    # Python 3
+    from collections.abc import Iterable
+    from collections.abc import MutableMapping
+except ImportError:
+    # Python 2.7
+    from collections import Iterable
+    from collections import MutableMapping
+
+try:
+    from collections import OrderedDict as _OrderedDict
+except ImportError:
+    try:
+        from ordereddict import OrderedDict as _OrderedDict
+    except ImportError:
+        _OrderedDict = None
+
+#~ sys.stderr.write( "testing pyparsing module, version %s, %s\n" % (__version__,__versionTime__ ) )
+
+__all__ = [
+'And', 'CaselessKeyword', 'CaselessLiteral', 'CharsNotIn', 'Combine', 'Dict', 'Each', 'Empty',
+'FollowedBy', 'Forward', 'GoToColumn', 'Group', 'Keyword', 'LineEnd', 'LineStart', 'Literal',
+'MatchFirst', 'NoMatch', 'NotAny', 'OneOrMore', 'OnlyOnce', 'Optional', 'Or',
+'ParseBaseException', 'ParseElementEnhance', 'ParseException', 'ParseExpression', 'ParseFatalException',
+'ParseResults', 'ParseSyntaxException', 'ParserElement', 'QuotedString', 'RecursiveGrammarException',
+'Regex', 'SkipTo', 'StringEnd', 'StringStart', 'Suppress', 'Token', 'TokenConverter', 
+'White', 'Word', 'WordEnd', 'WordStart', 'ZeroOrMore',
+'alphanums', 'alphas', 'alphas8bit', 'anyCloseTag', 'anyOpenTag', 'cStyleComment', 'col',
+'commaSeparatedList', 'commonHTMLEntity', 'countedArray', 'cppStyleComment', 'dblQuotedString',
+'dblSlashComment', 'delimitedList', 'dictOf', 'downcaseTokens', 'empty', 'hexnums',
+'htmlComment', 'javaStyleComment', 'line', 'lineEnd', 'lineStart', 'lineno',
+'makeHTMLTags', 'makeXMLTags', 'matchOnlyAtCol', 'matchPreviousExpr', 'matchPreviousLiteral',
+'nestedExpr', 'nullDebugAction', 'nums', 'oneOf', 'opAssoc', 'operatorPrecedence', 'printables',
+'punc8bit', 'pythonStyleComment', 'quotedString', 'removeQuotes', 'replaceHTMLEntity', 
+'replaceWith', 'restOfLine', 'sglQuotedString', 'srange', 'stringEnd',
+'stringStart', 'traceParseAction', 'unicodeString', 'upcaseTokens', 'withAttribute',
+'indentedBlock', 'originalTextFor', 'ungroup', 'infixNotation','locatedExpr', 'withClass',
+'CloseMatch', 'tokenMap', 'pyparsing_common',
+]
+
+system_version = tuple(sys.version_info)[:3]
+PY_3 = system_version[0] == 3
+if PY_3:
+    _MAX_INT = sys.maxsize
+    basestring = str
+    unichr = chr
+    _ustr = str
+
+    # build list of single arg builtins, that can be used as parse actions
+    singleArgBuiltins = [sum, len, sorted, reversed, list, tuple, set, any, all, min, max]
+
+else:
+    _MAX_INT = sys.maxint
+    range = xrange
+
+    def _ustr(obj):
+        """Drop-in replacement for str(obj) that tries to be Unicode friendly. It first tries
+           str(obj). If that fails with a UnicodeEncodeError, then it tries unicode(obj). It
+           then < returns the unicode object | encodes it with the default encoding | ... >.
+        """
+        if isinstance(obj,unicode):
+            return obj
+
+        try:
+            # If this works, then _ustr(obj) has the same behaviour as str(obj), so
+            # it won't break any existing code.
+            return str(obj)
+
+        except UnicodeEncodeError:
+            # Else encode it
+            ret = unicode(obj).encode(sys.getdefaultencoding(), 'xmlcharrefreplace')
+            xmlcharref = Regex(r'&#\d+;')
+            xmlcharref.setParseAction(lambda t: '\\u' + hex(int(t[0][2:-1]))[2:])
+            return xmlcharref.transformString(ret)
+
+    # build list of single arg builtins, tolerant of Python version, that can be used as parse actions
+    singleArgBuiltins = []
+    import __builtin__
+    for fname in "sum len sorted reversed list tuple set any all min max".split():
+        try:
+            singleArgBuiltins.append(getattr(__builtin__,fname))
+        except AttributeError:
+            continue
+            
+_generatorType = type((y for y in range(1)))
+ 
+def _xml_escape(data):
+    """Escape &, <, >, ", ', etc. in a string of data."""
+
+    # ampersand must be replaced first
+    from_symbols = '&><"\''
+    to_symbols = ('&'+s+';' for s in "amp gt lt quot apos".split())
+    for from_,to_ in zip(from_symbols, to_symbols):
+        data = data.replace(from_, to_)
+    return data
+
+class _Constants(object):
+    pass
+
+alphas     = string.ascii_uppercase + string.ascii_lowercase
+nums       = "0123456789"
+hexnums    = nums + "ABCDEFabcdef"
+alphanums  = alphas + nums
+_bslash    = chr(92)
+printables = "".join(c for c in string.printable if c not in string.whitespace)
+
+class ParseBaseException(Exception):
+    """base exception class for all parsing runtime exceptions"""
+    # Performance tuning: we construct a *lot* of these, so keep this
+    # constructor as small and fast as possible
+    def __init__( self, pstr, loc=0, msg=None, elem=None ):
+        self.loc = loc
+        if msg is None:
+            self.msg = pstr
+            self.pstr = ""
+        else:
+            self.msg = msg
+            self.pstr = pstr
+        self.parserElement = elem
+        self.args = (pstr, loc, msg)
+
+    @classmethod
+    def _from_exception(cls, pe):
+        """
+        internal factory method to simplify creating one type of ParseException 
+        from another - avoids having __init__ signature conflicts among subclasses
+        """
+        return cls(pe.pstr, pe.loc, pe.msg, pe.parserElement)
+
+    def __getattr__( self, aname ):
+        """supported attributes by name are:
+            - lineno - returns the line number of the exception text
+            - col - returns the column number of the exception text
+            - line - returns the line containing the exception text
+        """
+        if( aname == "lineno" ):
+            return lineno( self.loc, self.pstr )
+        elif( aname in ("col", "column") ):
+            return col( self.loc, self.pstr )
+        elif( aname == "line" ):
+            return line( self.loc, self.pstr )
+        else:
+            raise AttributeError(aname)
+
+    def __str__( self ):
+        return "%s (at char %d), (line:%d, col:%d)" % \
+                ( self.msg, self.loc, self.lineno, self.column )
+    def __repr__( self ):
+        return _ustr(self)
+    def markInputline( self, markerString = ">!<" ):
+        """Extracts the exception line from the input string, and marks
+           the location of the exception with a special symbol.
+        """
+        line_str = self.line
+        line_column = self.column - 1
+        if markerString:
+            line_str = "".join((line_str[:line_column],
+                                markerString, line_str[line_column:]))
+        return line_str.strip()
+    def __dir__(self):
+        return "lineno col line".split() + dir(type(self))
+
+class ParseException(ParseBaseException):
+    """
+    Exception thrown when parse expressions don't match class;
+    supported attributes by name are:
+     - lineno - returns the line number of the exception text
+     - col - returns the column number of the exception text
+     - line - returns the line containing the exception text
+        
+    Example::
+        try:
+            Word(nums).setName("integer").parseString("ABC")
+        except ParseException as pe:
+            print(pe)
+            print("column: {}".format(pe.col))
+            
+    prints::
+       Expected integer (at char 0), (line:1, col:1)
+        column: 1
+    """
+    pass
+
+class ParseFatalException(ParseBaseException):
+    """user-throwable exception thrown when inconsistent parse content
+       is found; stops all parsing immediately"""
+    pass
+
+class ParseSyntaxException(ParseFatalException):
+    """just like L{ParseFatalException}, but thrown internally when an
+       L{ErrorStop} ('-' operator) indicates that parsing is to stop 
+       immediately because an unbacktrackable syntax error has been found"""
+    pass
+
+#~ class ReparseException(ParseBaseException):
+    #~ """Experimental class - parse actions can raise this exception to cause
+       #~ pyparsing to reparse the input string:
+        #~ - with a modified input string, and/or
+        #~ - with a modified start location
+       #~ Set the values of the ReparseException in the constructor, and raise the
+       #~ exception in a parse action to cause pyparsing to use the new string/location.
+       #~ Setting the values as None causes no change to be made.
+       #~ """
+    #~ def __init_( self, newstring, restartLoc ):
+        #~ self.newParseText = newstring
+        #~ self.reparseLoc = restartLoc
+
+class RecursiveGrammarException(Exception):
+    """exception thrown by L{ParserElement.validate} if the grammar could be improperly recursive"""
+    def __init__( self, parseElementList ):
+        self.parseElementTrace = parseElementList
+
+    def __str__( self ):
+        return "RecursiveGrammarException: %s" % self.parseElementTrace
+
+class _ParseResultsWithOffset(object):
+    def __init__(self,p1,p2):
+        self.tup = (p1,p2)
+    def __getitem__(self,i):
+        return self.tup[i]
+    def __repr__(self):
+        return repr(self.tup[0])
+    def setOffset(self,i):
+        self.tup = (self.tup[0],i)
+
+class ParseResults(object):
+    """
+    Structured parse results, to provide multiple means of access to the parsed data:
+       - as a list (C{len(results)})
+       - by list index (C{results[0], results[1]}, etc.)
+       - by attribute (C{results.} - see L{ParserElement.setResultsName})
+
+    Example::
+        integer = Word(nums)
+        date_str = (integer.setResultsName("year") + '/' 
+                        + integer.setResultsName("month") + '/' 
+                        + integer.setResultsName("day"))
+        # equivalent form:
+        # date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+
+        # parseString returns a ParseResults object
+        result = date_str.parseString("1999/12/31")
+
+        def test(s, fn=repr):
+            print("%s -> %s" % (s, fn(eval(s))))
+        test("list(result)")
+        test("result[0]")
+        test("result['month']")
+        test("result.day")
+        test("'month' in result")
+        test("'minutes' in result")
+        test("result.dump()", str)
+    prints::
+        list(result) -> ['1999', '/', '12', '/', '31']
+        result[0] -> '1999'
+        result['month'] -> '12'
+        result.day -> '31'
+        'month' in result -> True
+        'minutes' in result -> False
+        result.dump() -> ['1999', '/', '12', '/', '31']
+        - day: 31
+        - month: 12
+        - year: 1999
+    """
+    def __new__(cls, toklist=None, name=None, asList=True, modal=True ):
+        if isinstance(toklist, cls):
+            return toklist
+        retobj = object.__new__(cls)
+        retobj.__doinit = True
+        return retobj
+
+    # Performance tuning: we construct a *lot* of these, so keep this
+    # constructor as small and fast as possible
+    def __init__( self, toklist=None, name=None, asList=True, modal=True, isinstance=isinstance ):
+        if self.__doinit:
+            self.__doinit = False
+            self.__name = None
+            self.__parent = None
+            self.__accumNames = {}
+            self.__asList = asList
+            self.__modal = modal
+            if toklist is None:
+                toklist = []
+            if isinstance(toklist, list):
+                self.__toklist = toklist[:]
+            elif isinstance(toklist, _generatorType):
+                self.__toklist = list(toklist)
+            else:
+                self.__toklist = [toklist]
+            self.__tokdict = dict()
+
+        if name is not None and name:
+            if not modal:
+                self.__accumNames[name] = 0
+            if isinstance(name,int):
+                name = _ustr(name) # will always return a str, but use _ustr for consistency
+            self.__name = name
+            if not (isinstance(toklist, (type(None), basestring, list)) and toklist in (None,'',[])):
+                if isinstance(toklist,basestring):
+                    toklist = [ toklist ]
+                if asList:
+                    if isinstance(toklist,ParseResults):
+                        self[name] = _ParseResultsWithOffset(toklist.copy(),0)
+                    else:
+                        self[name] = _ParseResultsWithOffset(ParseResults(toklist[0]),0)
+                    self[name].__name = name
+                else:
+                    try:
+                        self[name] = toklist[0]
+                    except (KeyError,TypeError,IndexError):
+                        self[name] = toklist
+
+    def __getitem__( self, i ):
+        if isinstance( i, (int,slice) ):
+            return self.__toklist[i]
+        else:
+            if i not in self.__accumNames:
+                return self.__tokdict[i][-1][0]
+            else:
+                return ParseResults([ v[0] for v in self.__tokdict[i] ])
+
+    def __setitem__( self, k, v, isinstance=isinstance ):
+        if isinstance(v,_ParseResultsWithOffset):
+            self.__tokdict[k] = self.__tokdict.get(k,list()) + [v]
+            sub = v[0]
+        elif isinstance(k,(int,slice)):
+            self.__toklist[k] = v
+            sub = v
+        else:
+            self.__tokdict[k] = self.__tokdict.get(k,list()) + [_ParseResultsWithOffset(v,0)]
+            sub = v
+        if isinstance(sub,ParseResults):
+            sub.__parent = wkref(self)
+
+    def __delitem__( self, i ):
+        if isinstance(i,(int,slice)):
+            mylen = len( self.__toklist )
+            del self.__toklist[i]
+
+            # convert int to slice
+            if isinstance(i, int):
+                if i < 0:
+                    i += mylen
+                i = slice(i, i+1)
+            # get removed indices
+            removed = list(range(*i.indices(mylen)))
+            removed.reverse()
+            # fixup indices in token dictionary
+            for name,occurrences in self.__tokdict.items():
+                for j in removed:
+                    for k, (value, position) in enumerate(occurrences):
+                        occurrences[k] = _ParseResultsWithOffset(value, position - (position > j))
+        else:
+            del self.__tokdict[i]
+
+    def __contains__( self, k ):
+        return k in self.__tokdict
+
+    def __len__( self ): return len( self.__toklist )
+    def __bool__(self): return ( not not self.__toklist )
+    __nonzero__ = __bool__
+    def __iter__( self ): return iter( self.__toklist )
+    def __reversed__( self ): return iter( self.__toklist[::-1] )
+    def _iterkeys( self ):
+        if hasattr(self.__tokdict, "iterkeys"):
+            return self.__tokdict.iterkeys()
+        else:
+            return iter(self.__tokdict)
+
+    def _itervalues( self ):
+        return (self[k] for k in self._iterkeys())
+            
+    def _iteritems( self ):
+        return ((k, self[k]) for k in self._iterkeys())
+
+    if PY_3:
+        keys = _iterkeys       
+        """Returns an iterator of all named result keys (Python 3.x only)."""
+
+        values = _itervalues
+        """Returns an iterator of all named result values (Python 3.x only)."""
+
+        items = _iteritems
+        """Returns an iterator of all named result key-value tuples (Python 3.x only)."""
+
+    else:
+        iterkeys = _iterkeys
+        """Returns an iterator of all named result keys (Python 2.x only)."""
+
+        itervalues = _itervalues
+        """Returns an iterator of all named result values (Python 2.x only)."""
+
+        iteritems = _iteritems
+        """Returns an iterator of all named result key-value tuples (Python 2.x only)."""
+
+        def keys( self ):
+            """Returns all named result keys (as a list in Python 2.x, as an iterator in Python 3.x)."""
+            return list(self.iterkeys())
+
+        def values( self ):
+            """Returns all named result values (as a list in Python 2.x, as an iterator in Python 3.x)."""
+            return list(self.itervalues())
+                
+        def items( self ):
+            """Returns all named result key-values (as a list of tuples in Python 2.x, as an iterator in Python 3.x)."""
+            return list(self.iteritems())
+
+    def haskeys( self ):
+        """Since keys() returns an iterator, this method is helpful in bypassing
+           code that looks for the existence of any defined results names."""
+        return bool(self.__tokdict)
+        
+    def pop( self, *args, **kwargs):
+        """
+        Removes and returns item at specified index (default=C{last}).
+        Supports both C{list} and C{dict} semantics for C{pop()}. If passed no
+        argument or an integer argument, it will use C{list} semantics
+        and pop tokens from the list of parsed tokens. If passed a 
+        non-integer argument (most likely a string), it will use C{dict}
+        semantics and pop the corresponding value from any defined 
+        results names. A second default return value argument is 
+        supported, just as in C{dict.pop()}.
+
+        Example::
+            def remove_first(tokens):
+                tokens.pop(0)
+            print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321']
+            print(OneOrMore(Word(nums)).addParseAction(remove_first).parseString("0 123 321")) # -> ['123', '321']
+
+            label = Word(alphas)
+            patt = label("LABEL") + OneOrMore(Word(nums))
+            print(patt.parseString("AAB 123 321").dump())
+
+            # Use pop() in a parse action to remove named result (note that corresponding value is not
+            # removed from list form of results)
+            def remove_LABEL(tokens):
+                tokens.pop("LABEL")
+                return tokens
+            patt.addParseAction(remove_LABEL)
+            print(patt.parseString("AAB 123 321").dump())
+        prints::
+            ['AAB', '123', '321']
+            - LABEL: AAB
+
+            ['AAB', '123', '321']
+        """
+        if not args:
+            args = [-1]
+        for k,v in kwargs.items():
+            if k == 'default':
+                args = (args[0], v)
+            else:
+                raise TypeError("pop() got an unexpected keyword argument '%s'" % k)
+        if (isinstance(args[0], int) or 
+                        len(args) == 1 or 
+                        args[0] in self):
+            index = args[0]
+            ret = self[index]
+            del self[index]
+            return ret
+        else:
+            defaultvalue = args[1]
+            return defaultvalue
+
+    def get(self, key, defaultValue=None):
+        """
+        Returns named result matching the given key, or if there is no
+        such name, then returns the given C{defaultValue} or C{None} if no
+        C{defaultValue} is specified.
+
+        Similar to C{dict.get()}.
+        
+        Example::
+            integer = Word(nums)
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")           
+
+            result = date_str.parseString("1999/12/31")
+            print(result.get("year")) # -> '1999'
+            print(result.get("hour", "not specified")) # -> 'not specified'
+            print(result.get("hour")) # -> None
+        """
+        if key in self:
+            return self[key]
+        else:
+            return defaultValue
+
+    def insert( self, index, insStr ):
+        """
+        Inserts new element at location index in the list of parsed tokens.
+        
+        Similar to C{list.insert()}.
+
+        Example::
+            print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321']
+
+            # use a parse action to insert the parse location in the front of the parsed results
+            def insert_locn(locn, tokens):
+                tokens.insert(0, locn)
+            print(OneOrMore(Word(nums)).addParseAction(insert_locn).parseString("0 123 321")) # -> [0, '0', '123', '321']
+        """
+        self.__toklist.insert(index, insStr)
+        # fixup indices in token dictionary
+        for name,occurrences in self.__tokdict.items():
+            for k, (value, position) in enumerate(occurrences):
+                occurrences[k] = _ParseResultsWithOffset(value, position + (position > index))
+
+    def append( self, item ):
+        """
+        Add single element to end of ParseResults list of elements.
+
+        Example::
+            print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321']
+            
+            # use a parse action to compute the sum of the parsed integers, and add it to the end
+            def append_sum(tokens):
+                tokens.append(sum(map(int, tokens)))
+            print(OneOrMore(Word(nums)).addParseAction(append_sum).parseString("0 123 321")) # -> ['0', '123', '321', 444]
+        """
+        self.__toklist.append(item)
+
+    def extend( self, itemseq ):
+        """
+        Add sequence of elements to end of ParseResults list of elements.
+
+        Example::
+            patt = OneOrMore(Word(alphas))
+            
+            # use a parse action to append the reverse of the matched strings, to make a palindrome
+            def make_palindrome(tokens):
+                tokens.extend(reversed([t[::-1] for t in tokens]))
+                return ''.join(tokens)
+            print(patt.addParseAction(make_palindrome).parseString("lskdj sdlkjf lksd")) # -> 'lskdjsdlkjflksddsklfjkldsjdksl'
+        """
+        if isinstance(itemseq, ParseResults):
+            self += itemseq
+        else:
+            self.__toklist.extend(itemseq)
+
+    def clear( self ):
+        """
+        Clear all elements and results names.
+        """
+        del self.__toklist[:]
+        self.__tokdict.clear()
+
+    def __getattr__( self, name ):
+        try:
+            return self[name]
+        except KeyError:
+            return ""
+            
+        if name in self.__tokdict:
+            if name not in self.__accumNames:
+                return self.__tokdict[name][-1][0]
+            else:
+                return ParseResults([ v[0] for v in self.__tokdict[name] ])
+        else:
+            return ""
+
+    def __add__( self, other ):
+        ret = self.copy()
+        ret += other
+        return ret
+
+    def __iadd__( self, other ):
+        if other.__tokdict:
+            offset = len(self.__toklist)
+            addoffset = lambda a: offset if a<0 else a+offset
+            otheritems = other.__tokdict.items()
+            otherdictitems = [(k, _ParseResultsWithOffset(v[0],addoffset(v[1])) )
+                                for (k,vlist) in otheritems for v in vlist]
+            for k,v in otherdictitems:
+                self[k] = v
+                if isinstance(v[0],ParseResults):
+                    v[0].__parent = wkref(self)
+            
+        self.__toklist += other.__toklist
+        self.__accumNames.update( other.__accumNames )
+        return self
+
+    def __radd__(self, other):
+        if isinstance(other,int) and other == 0:
+            # useful for merging many ParseResults using sum() builtin
+            return self.copy()
+        else:
+            # this may raise a TypeError - so be it
+            return other + self
+        
+    def __repr__( self ):
+        return "(%s, %s)" % ( repr( self.__toklist ), repr( self.__tokdict ) )
+
+    def __str__( self ):
+        return '[' + ', '.join(_ustr(i) if isinstance(i, ParseResults) else repr(i) for i in self.__toklist) + ']'
+
+    def _asStringList( self, sep='' ):
+        out = []
+        for item in self.__toklist:
+            if out and sep:
+                out.append(sep)
+            if isinstance( item, ParseResults ):
+                out += item._asStringList()
+            else:
+                out.append( _ustr(item) )
+        return out
+
+    def asList( self ):
+        """
+        Returns the parse results as a nested list of matching tokens, all converted to strings.
+
+        Example::
+            patt = OneOrMore(Word(alphas))
+            result = patt.parseString("sldkj lsdkj sldkj")
+            # even though the result prints in string-like form, it is actually a pyparsing ParseResults
+            print(type(result), result) # ->  ['sldkj', 'lsdkj', 'sldkj']
+            
+            # Use asList() to create an actual list
+            result_list = result.asList()
+            print(type(result_list), result_list) # ->  ['sldkj', 'lsdkj', 'sldkj']
+        """
+        return [res.asList() if isinstance(res,ParseResults) else res for res in self.__toklist]
+
+    def asDict( self ):
+        """
+        Returns the named parse results as a nested dictionary.
+
+        Example::
+            integer = Word(nums)
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+            
+            result = date_str.parseString('12/31/1999')
+            print(type(result), repr(result)) # ->  (['12', '/', '31', '/', '1999'], {'day': [('1999', 4)], 'year': [('12', 0)], 'month': [('31', 2)]})
+            
+            result_dict = result.asDict()
+            print(type(result_dict), repr(result_dict)) # ->  {'day': '1999', 'year': '12', 'month': '31'}
+
+            # even though a ParseResults supports dict-like access, sometime you just need to have a dict
+            import json
+            print(json.dumps(result)) # -> Exception: TypeError: ... is not JSON serializable
+            print(json.dumps(result.asDict())) # -> {"month": "31", "day": "1999", "year": "12"}
+        """
+        if PY_3:
+            item_fn = self.items
+        else:
+            item_fn = self.iteritems
+            
+        def toItem(obj):
+            if isinstance(obj, ParseResults):
+                if obj.haskeys():
+                    return obj.asDict()
+                else:
+                    return [toItem(v) for v in obj]
+            else:
+                return obj
+                
+        return dict((k,toItem(v)) for k,v in item_fn())
+
+    def copy( self ):
+        """
+        Returns a new copy of a C{ParseResults} object.
+        """
+        ret = ParseResults( self.__toklist )
+        ret.__tokdict = self.__tokdict.copy()
+        ret.__parent = self.__parent
+        ret.__accumNames.update( self.__accumNames )
+        ret.__name = self.__name
+        return ret
+
+    def asXML( self, doctag=None, namedItemsOnly=False, indent="", formatted=True ):
+        """
+        (Deprecated) Returns the parse results as XML. Tags are created for tokens and lists that have defined results names.
+        """
+        nl = "\n"
+        out = []
+        namedItems = dict((v[1],k) for (k,vlist) in self.__tokdict.items()
+                                                            for v in vlist)
+        nextLevelIndent = indent + "  "
+
+        # collapse out indents if formatting is not desired
+        if not formatted:
+            indent = ""
+            nextLevelIndent = ""
+            nl = ""
+
+        selfTag = None
+        if doctag is not None:
+            selfTag = doctag
+        else:
+            if self.__name:
+                selfTag = self.__name
+
+        if not selfTag:
+            if namedItemsOnly:
+                return ""
+            else:
+                selfTag = "ITEM"
+
+        out += [ nl, indent, "<", selfTag, ">" ]
+
+        for i,res in enumerate(self.__toklist):
+            if isinstance(res,ParseResults):
+                if i in namedItems:
+                    out += [ res.asXML(namedItems[i],
+                                        namedItemsOnly and doctag is None,
+                                        nextLevelIndent,
+                                        formatted)]
+                else:
+                    out += [ res.asXML(None,
+                                        namedItemsOnly and doctag is None,
+                                        nextLevelIndent,
+                                        formatted)]
+            else:
+                # individual token, see if there is a name for it
+                resTag = None
+                if i in namedItems:
+                    resTag = namedItems[i]
+                if not resTag:
+                    if namedItemsOnly:
+                        continue
+                    else:
+                        resTag = "ITEM"
+                xmlBodyText = _xml_escape(_ustr(res))
+                out += [ nl, nextLevelIndent, "<", resTag, ">",
+                                                xmlBodyText,
+                                                "" ]
+
+        out += [ nl, indent, "" ]
+        return "".join(out)
+
+    def __lookup(self,sub):
+        for k,vlist in self.__tokdict.items():
+            for v,loc in vlist:
+                if sub is v:
+                    return k
+        return None
+
+    def getName(self):
+        r"""
+        Returns the results name for this token expression. Useful when several 
+        different expressions might match at a particular location.
+
+        Example::
+            integer = Word(nums)
+            ssn_expr = Regex(r"\d\d\d-\d\d-\d\d\d\d")
+            house_number_expr = Suppress('#') + Word(nums, alphanums)
+            user_data = (Group(house_number_expr)("house_number") 
+                        | Group(ssn_expr)("ssn")
+                        | Group(integer)("age"))
+            user_info = OneOrMore(user_data)
+            
+            result = user_info.parseString("22 111-22-3333 #221B")
+            for item in result:
+                print(item.getName(), ':', item[0])
+        prints::
+            age : 22
+            ssn : 111-22-3333
+            house_number : 221B
+        """
+        if self.__name:
+            return self.__name
+        elif self.__parent:
+            par = self.__parent()
+            if par:
+                return par.__lookup(self)
+            else:
+                return None
+        elif (len(self) == 1 and
+               len(self.__tokdict) == 1 and
+               next(iter(self.__tokdict.values()))[0][1] in (0,-1)):
+            return next(iter(self.__tokdict.keys()))
+        else:
+            return None
+
+    def dump(self, indent='', depth=0, full=True):
+        """
+        Diagnostic method for listing out the contents of a C{ParseResults}.
+        Accepts an optional C{indent} argument so that this string can be embedded
+        in a nested display of other data.
+
+        Example::
+            integer = Word(nums)
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+            
+            result = date_str.parseString('12/31/1999')
+            print(result.dump())
+        prints::
+            ['12', '/', '31', '/', '1999']
+            - day: 1999
+            - month: 31
+            - year: 12
+        """
+        out = []
+        NL = '\n'
+        out.append( indent+_ustr(self.asList()) )
+        if full:
+            if self.haskeys():
+                items = sorted((str(k), v) for k,v in self.items())
+                for k,v in items:
+                    if out:
+                        out.append(NL)
+                    out.append( "%s%s- %s: " % (indent,('  '*depth), k) )
+                    if isinstance(v,ParseResults):
+                        if v:
+                            out.append( v.dump(indent,depth+1) )
+                        else:
+                            out.append(_ustr(v))
+                    else:
+                        out.append(repr(v))
+            elif any(isinstance(vv,ParseResults) for vv in self):
+                v = self
+                for i,vv in enumerate(v):
+                    if isinstance(vv,ParseResults):
+                        out.append("\n%s%s[%d]:\n%s%s%s" % (indent,('  '*(depth)),i,indent,('  '*(depth+1)),vv.dump(indent,depth+1) ))
+                    else:
+                        out.append("\n%s%s[%d]:\n%s%s%s" % (indent,('  '*(depth)),i,indent,('  '*(depth+1)),_ustr(vv)))
+            
+        return "".join(out)
+
+    def pprint(self, *args, **kwargs):
+        """
+        Pretty-printer for parsed results as a list, using the C{pprint} module.
+        Accepts additional positional or keyword args as defined for the 
+        C{pprint.pprint} method. (U{http://docs.python.org/3/library/pprint.html#pprint.pprint})
+
+        Example::
+            ident = Word(alphas, alphanums)
+            num = Word(nums)
+            func = Forward()
+            term = ident | num | Group('(' + func + ')')
+            func <<= ident + Group(Optional(delimitedList(term)))
+            result = func.parseString("fna a,b,(fnb c,d,200),100")
+            result.pprint(width=40)
+        prints::
+            ['fna',
+             ['a',
+              'b',
+              ['(', 'fnb', ['c', 'd', '200'], ')'],
+              '100']]
+        """
+        pprint.pprint(self.asList(), *args, **kwargs)
+
+    # add support for pickle protocol
+    def __getstate__(self):
+        return ( self.__toklist,
+                 ( self.__tokdict.copy(),
+                   self.__parent is not None and self.__parent() or None,
+                   self.__accumNames,
+                   self.__name ) )
+
+    def __setstate__(self,state):
+        self.__toklist = state[0]
+        (self.__tokdict,
+         par,
+         inAccumNames,
+         self.__name) = state[1]
+        self.__accumNames = {}
+        self.__accumNames.update(inAccumNames)
+        if par is not None:
+            self.__parent = wkref(par)
+        else:
+            self.__parent = None
+
+    def __getnewargs__(self):
+        return self.__toklist, self.__name, self.__asList, self.__modal
+
+    def __dir__(self):
+        return (dir(type(self)) + list(self.keys()))
+
+MutableMapping.register(ParseResults)
+
+def col (loc,strg):
+    """Returns current column within a string, counting newlines as line separators.
+   The first column is number 1.
+
+   Note: the default parsing behavior is to expand tabs in the input string
+   before starting the parsing process.  See L{I{ParserElement.parseString}} for more information
+   on parsing strings containing C{}s, and suggested methods to maintain a
+   consistent view of the parsed string, the parse location, and line and column
+   positions within the parsed string.
+   """
+    s = strg
+    return 1 if 0} for more information
+   on parsing strings containing C{}s, and suggested methods to maintain a
+   consistent view of the parsed string, the parse location, and line and column
+   positions within the parsed string.
+   """
+    return strg.count("\n",0,loc) + 1
+
+def line( loc, strg ):
+    """Returns the line of text containing loc within a string, counting newlines as line separators.
+       """
+    lastCR = strg.rfind("\n", 0, loc)
+    nextCR = strg.find("\n", loc)
+    if nextCR >= 0:
+        return strg[lastCR+1:nextCR]
+    else:
+        return strg[lastCR+1:]
+
+def _defaultStartDebugAction( instring, loc, expr ):
+    print (("Match " + _ustr(expr) + " at loc " + _ustr(loc) + "(%d,%d)" % ( lineno(loc,instring), col(loc,instring) )))
+
+def _defaultSuccessDebugAction( instring, startloc, endloc, expr, toks ):
+    print ("Matched " + _ustr(expr) + " -> " + str(toks.asList()))
+
+def _defaultExceptionDebugAction( instring, loc, expr, exc ):
+    print ("Exception raised:" + _ustr(exc))
+
+def nullDebugAction(*args):
+    """'Do-nothing' debug action, to suppress debugging output during parsing."""
+    pass
+
+# Only works on Python 3.x - nonlocal is toxic to Python 2 installs
+#~ 'decorator to trim function calls to match the arity of the target'
+#~ def _trim_arity(func, maxargs=3):
+    #~ if func in singleArgBuiltins:
+        #~ return lambda s,l,t: func(t)
+    #~ limit = 0
+    #~ foundArity = False
+    #~ def wrapper(*args):
+        #~ nonlocal limit,foundArity
+        #~ while 1:
+            #~ try:
+                #~ ret = func(*args[limit:])
+                #~ foundArity = True
+                #~ return ret
+            #~ except TypeError:
+                #~ if limit == maxargs or foundArity:
+                    #~ raise
+                #~ limit += 1
+                #~ continue
+    #~ return wrapper
+
+# this version is Python 2.x-3.x cross-compatible
+'decorator to trim function calls to match the arity of the target'
+def _trim_arity(func, maxargs=2):
+    if func in singleArgBuiltins:
+        return lambda s,l,t: func(t)
+    limit = [0]
+    foundArity = [False]
+    
+    # traceback return data structure changed in Py3.5 - normalize back to plain tuples
+    if system_version[:2] >= (3,5):
+        def extract_stack(limit=0):
+            # special handling for Python 3.5.0 - extra deep call stack by 1
+            offset = -3 if system_version == (3,5,0) else -2
+            frame_summary = traceback.extract_stack(limit=-offset+limit-1)[offset]
+            return [frame_summary[:2]]
+        def extract_tb(tb, limit=0):
+            frames = traceback.extract_tb(tb, limit=limit)
+            frame_summary = frames[-1]
+            return [frame_summary[:2]]
+    else:
+        extract_stack = traceback.extract_stack
+        extract_tb = traceback.extract_tb
+    
+    # synthesize what would be returned by traceback.extract_stack at the call to 
+    # user's parse action 'func', so that we don't incur call penalty at parse time
+    
+    LINE_DIFF = 6
+    # IF ANY CODE CHANGES, EVEN JUST COMMENTS OR BLANK LINES, BETWEEN THE NEXT LINE AND 
+    # THE CALL TO FUNC INSIDE WRAPPER, LINE_DIFF MUST BE MODIFIED!!!!
+    this_line = extract_stack(limit=2)[-1]
+    pa_call_line_synth = (this_line[0], this_line[1]+LINE_DIFF)
+
+    def wrapper(*args):
+        while 1:
+            try:
+                ret = func(*args[limit[0]:])
+                foundArity[0] = True
+                return ret
+            except TypeError:
+                # re-raise TypeErrors if they did not come from our arity testing
+                if foundArity[0]:
+                    raise
+                else:
+                    try:
+                        tb = sys.exc_info()[-1]
+                        if not extract_tb(tb, limit=2)[-1][:2] == pa_call_line_synth:
+                            raise
+                    finally:
+                        del tb
+
+                if limit[0] <= maxargs:
+                    limit[0] += 1
+                    continue
+                raise
+
+    # copy func name to wrapper for sensible debug output
+    func_name = ""
+    try:
+        func_name = getattr(func, '__name__', 
+                            getattr(func, '__class__').__name__)
+    except Exception:
+        func_name = str(func)
+    wrapper.__name__ = func_name
+
+    return wrapper
+
+class ParserElement(object):
+    """Abstract base level parser element class."""
+    DEFAULT_WHITE_CHARS = " \n\t\r"
+    verbose_stacktrace = False
+
+    @staticmethod
+    def setDefaultWhitespaceChars( chars ):
+        r"""
+        Overrides the default whitespace chars
+
+        Example::
+            # default whitespace chars are space,  and newline
+            OneOrMore(Word(alphas)).parseString("abc def\nghi jkl")  # -> ['abc', 'def', 'ghi', 'jkl']
+            
+            # change to just treat newline as significant
+            ParserElement.setDefaultWhitespaceChars(" \t")
+            OneOrMore(Word(alphas)).parseString("abc def\nghi jkl")  # -> ['abc', 'def']
+        """
+        ParserElement.DEFAULT_WHITE_CHARS = chars
+
+    @staticmethod
+    def inlineLiteralsUsing(cls):
+        """
+        Set class to be used for inclusion of string literals into a parser.
+        
+        Example::
+            # default literal class used is Literal
+            integer = Word(nums)
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")           
+
+            date_str.parseString("1999/12/31")  # -> ['1999', '/', '12', '/', '31']
+
+
+            # change to Suppress
+            ParserElement.inlineLiteralsUsing(Suppress)
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")           
+
+            date_str.parseString("1999/12/31")  # -> ['1999', '12', '31']
+        """
+        ParserElement._literalStringClass = cls
+
+    def __init__( self, savelist=False ):
+        self.parseAction = list()
+        self.failAction = None
+        #~ self.name = ""  # don't define self.name, let subclasses try/except upcall
+        self.strRepr = None
+        self.resultsName = None
+        self.saveAsList = savelist
+        self.skipWhitespace = True
+        self.whiteChars = ParserElement.DEFAULT_WHITE_CHARS
+        self.copyDefaultWhiteChars = True
+        self.mayReturnEmpty = False # used when checking for left-recursion
+        self.keepTabs = False
+        self.ignoreExprs = list()
+        self.debug = False
+        self.streamlined = False
+        self.mayIndexError = True # used to optimize exception handling for subclasses that don't advance parse index
+        self.errmsg = ""
+        self.modalResults = True # used to mark results names as modal (report only last) or cumulative (list all)
+        self.debugActions = ( None, None, None ) #custom debug actions
+        self.re = None
+        self.callPreparse = True # used to avoid redundant calls to preParse
+        self.callDuringTry = False
+
+    def copy( self ):
+        """
+        Make a copy of this C{ParserElement}.  Useful for defining different parse actions
+        for the same parsing pattern, using copies of the original parse element.
+        
+        Example::
+            integer = Word(nums).setParseAction(lambda toks: int(toks[0]))
+            integerK = integer.copy().addParseAction(lambda toks: toks[0]*1024) + Suppress("K")
+            integerM = integer.copy().addParseAction(lambda toks: toks[0]*1024*1024) + Suppress("M")
+            
+            print(OneOrMore(integerK | integerM | integer).parseString("5K 100 640K 256M"))
+        prints::
+            [5120, 100, 655360, 268435456]
+        Equivalent form of C{expr.copy()} is just C{expr()}::
+            integerM = integer().addParseAction(lambda toks: toks[0]*1024*1024) + Suppress("M")
+        """
+        cpy = copy.copy( self )
+        cpy.parseAction = self.parseAction[:]
+        cpy.ignoreExprs = self.ignoreExprs[:]
+        if self.copyDefaultWhiteChars:
+            cpy.whiteChars = ParserElement.DEFAULT_WHITE_CHARS
+        return cpy
+
+    def setName( self, name ):
+        """
+        Define name for this expression, makes debugging and exception messages clearer.
+        
+        Example::
+            Word(nums).parseString("ABC")  # -> Exception: Expected W:(0123...) (at char 0), (line:1, col:1)
+            Word(nums).setName("integer").parseString("ABC")  # -> Exception: Expected integer (at char 0), (line:1, col:1)
+        """
+        self.name = name
+        self.errmsg = "Expected " + self.name
+        if hasattr(self,"exception"):
+            self.exception.msg = self.errmsg
+        return self
+
+    def setResultsName( self, name, listAllMatches=False ):
+        """
+        Define name for referencing matching tokens as a nested attribute
+        of the returned parse results.
+        NOTE: this returns a *copy* of the original C{ParserElement} object;
+        this is so that the client can define a basic element, such as an
+        integer, and reference it in multiple places with different names.
+
+        You can also set results names using the abbreviated syntax,
+        C{expr("name")} in place of C{expr.setResultsName("name")} - 
+        see L{I{__call__}<__call__>}.
+
+        Example::
+            date_str = (integer.setResultsName("year") + '/' 
+                        + integer.setResultsName("month") + '/' 
+                        + integer.setResultsName("day"))
+
+            # equivalent form:
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+        """
+        newself = self.copy()
+        if name.endswith("*"):
+            name = name[:-1]
+            listAllMatches=True
+        newself.resultsName = name
+        newself.modalResults = not listAllMatches
+        return newself
+
+    def setBreak(self,breakFlag = True):
+        """Method to invoke the Python pdb debugger when this element is
+           about to be parsed. Set C{breakFlag} to True to enable, False to
+           disable.
+        """
+        if breakFlag:
+            _parseMethod = self._parse
+            def breaker(instring, loc, doActions=True, callPreParse=True):
+                import pdb
+                pdb.set_trace()
+                return _parseMethod( instring, loc, doActions, callPreParse )
+            breaker._originalParseMethod = _parseMethod
+            self._parse = breaker
+        else:
+            if hasattr(self._parse,"_originalParseMethod"):
+                self._parse = self._parse._originalParseMethod
+        return self
+
+    def setParseAction( self, *fns, **kwargs ):
+        """
+        Define one or more actions to perform when successfully matching parse element definition.
+        Parse action fn is a callable method with 0-3 arguments, called as C{fn(s,loc,toks)},
+        C{fn(loc,toks)}, C{fn(toks)}, or just C{fn()}, where:
+         - s   = the original string being parsed (see note below)
+         - loc = the location of the matching substring
+         - toks = a list of the matched tokens, packaged as a C{L{ParseResults}} object
+        If the functions in fns modify the tokens, they can return them as the return
+        value from fn, and the modified list of tokens will replace the original.
+        Otherwise, fn does not need to return any value.
+
+        Optional keyword arguments:
+         - callDuringTry = (default=C{False}) indicate if parse action should be run during lookaheads and alternate testing
+
+        Note: the default parsing behavior is to expand tabs in the input string
+        before starting the parsing process.  See L{I{parseString}} for more information
+        on parsing strings containing C{}s, and suggested methods to maintain a
+        consistent view of the parsed string, the parse location, and line and column
+        positions within the parsed string.
+        
+        Example::
+            integer = Word(nums)
+            date_str = integer + '/' + integer + '/' + integer
+
+            date_str.parseString("1999/12/31")  # -> ['1999', '/', '12', '/', '31']
+
+            # use parse action to convert to ints at parse time
+            integer = Word(nums).setParseAction(lambda toks: int(toks[0]))
+            date_str = integer + '/' + integer + '/' + integer
+
+            # note that integer fields are now ints, not strings
+            date_str.parseString("1999/12/31")  # -> [1999, '/', 12, '/', 31]
+        """
+        self.parseAction = list(map(_trim_arity, list(fns)))
+        self.callDuringTry = kwargs.get("callDuringTry", False)
+        return self
+
+    def addParseAction( self, *fns, **kwargs ):
+        """
+        Add one or more parse actions to expression's list of parse actions. See L{I{setParseAction}}.
+        
+        See examples in L{I{copy}}.
+        """
+        self.parseAction += list(map(_trim_arity, list(fns)))
+        self.callDuringTry = self.callDuringTry or kwargs.get("callDuringTry", False)
+        return self
+
+    def addCondition(self, *fns, **kwargs):
+        """Add a boolean predicate function to expression's list of parse actions. See 
+        L{I{setParseAction}} for function call signatures. Unlike C{setParseAction}, 
+        functions passed to C{addCondition} need to return boolean success/fail of the condition.
+
+        Optional keyword arguments:
+         - message = define a custom message to be used in the raised exception
+         - fatal   = if True, will raise ParseFatalException to stop parsing immediately; otherwise will raise ParseException
+         
+        Example::
+            integer = Word(nums).setParseAction(lambda toks: int(toks[0]))
+            year_int = integer.copy()
+            year_int.addCondition(lambda toks: toks[0] >= 2000, message="Only support years 2000 and later")
+            date_str = year_int + '/' + integer + '/' + integer
+
+            result = date_str.parseString("1999/12/31")  # -> Exception: Only support years 2000 and later (at char 0), (line:1, col:1)
+        """
+        msg = kwargs.get("message", "failed user-defined condition")
+        exc_type = ParseFatalException if kwargs.get("fatal", False) else ParseException
+        for fn in fns:
+            def pa(s,l,t):
+                if not bool(_trim_arity(fn)(s,l,t)):
+                    raise exc_type(s,l,msg)
+            self.parseAction.append(pa)
+        self.callDuringTry = self.callDuringTry or kwargs.get("callDuringTry", False)
+        return self
+
+    def setFailAction( self, fn ):
+        """Define action to perform if parsing fails at this expression.
+           Fail acton fn is a callable function that takes the arguments
+           C{fn(s,loc,expr,err)} where:
+            - s = string being parsed
+            - loc = location where expression match was attempted and failed
+            - expr = the parse expression that failed
+            - err = the exception thrown
+           The function returns no value.  It may throw C{L{ParseFatalException}}
+           if it is desired to stop parsing immediately."""
+        self.failAction = fn
+        return self
+
+    def _skipIgnorables( self, instring, loc ):
+        exprsFound = True
+        while exprsFound:
+            exprsFound = False
+            for e in self.ignoreExprs:
+                try:
+                    while 1:
+                        loc,dummy = e._parse( instring, loc )
+                        exprsFound = True
+                except ParseException:
+                    pass
+        return loc
+
+    def preParse( self, instring, loc ):
+        if self.ignoreExprs:
+            loc = self._skipIgnorables( instring, loc )
+
+        if self.skipWhitespace:
+            wt = self.whiteChars
+            instrlen = len(instring)
+            while loc < instrlen and instring[loc] in wt:
+                loc += 1
+
+        return loc
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        return loc, []
+
+    def postParse( self, instring, loc, tokenlist ):
+        return tokenlist
+
+    #~ @profile
+    def _parseNoCache( self, instring, loc, doActions=True, callPreParse=True ):
+        debugging = ( self.debug ) #and doActions )
+
+        if debugging or self.failAction:
+            #~ print ("Match",self,"at loc",loc,"(%d,%d)" % ( lineno(loc,instring), col(loc,instring) ))
+            if (self.debugActions[0] ):
+                self.debugActions[0]( instring, loc, self )
+            if callPreParse and self.callPreparse:
+                preloc = self.preParse( instring, loc )
+            else:
+                preloc = loc
+            tokensStart = preloc
+            try:
+                try:
+                    loc,tokens = self.parseImpl( instring, preloc, doActions )
+                except IndexError:
+                    raise ParseException( instring, len(instring), self.errmsg, self )
+            except ParseBaseException as err:
+                #~ print ("Exception raised:", err)
+                if self.debugActions[2]:
+                    self.debugActions[2]( instring, tokensStart, self, err )
+                if self.failAction:
+                    self.failAction( instring, tokensStart, self, err )
+                raise
+        else:
+            if callPreParse and self.callPreparse:
+                preloc = self.preParse( instring, loc )
+            else:
+                preloc = loc
+            tokensStart = preloc
+            if self.mayIndexError or preloc >= len(instring):
+                try:
+                    loc,tokens = self.parseImpl( instring, preloc, doActions )
+                except IndexError:
+                    raise ParseException( instring, len(instring), self.errmsg, self )
+            else:
+                loc,tokens = self.parseImpl( instring, preloc, doActions )
+
+        tokens = self.postParse( instring, loc, tokens )
+
+        retTokens = ParseResults( tokens, self.resultsName, asList=self.saveAsList, modal=self.modalResults )
+        if self.parseAction and (doActions or self.callDuringTry):
+            if debugging:
+                try:
+                    for fn in self.parseAction:
+                        tokens = fn( instring, tokensStart, retTokens )
+                        if tokens is not None:
+                            retTokens = ParseResults( tokens,
+                                                      self.resultsName,
+                                                      asList=self.saveAsList and isinstance(tokens,(ParseResults,list)),
+                                                      modal=self.modalResults )
+                except ParseBaseException as err:
+                    #~ print "Exception raised in user parse action:", err
+                    if (self.debugActions[2] ):
+                        self.debugActions[2]( instring, tokensStart, self, err )
+                    raise
+            else:
+                for fn in self.parseAction:
+                    tokens = fn( instring, tokensStart, retTokens )
+                    if tokens is not None:
+                        retTokens = ParseResults( tokens,
+                                                  self.resultsName,
+                                                  asList=self.saveAsList and isinstance(tokens,(ParseResults,list)),
+                                                  modal=self.modalResults )
+        if debugging:
+            #~ print ("Matched",self,"->",retTokens.asList())
+            if (self.debugActions[1] ):
+                self.debugActions[1]( instring, tokensStart, loc, self, retTokens )
+
+        return loc, retTokens
+
+    def tryParse( self, instring, loc ):
+        try:
+            return self._parse( instring, loc, doActions=False )[0]
+        except ParseFatalException:
+            raise ParseException( instring, loc, self.errmsg, self)
+    
+    def canParseNext(self, instring, loc):
+        try:
+            self.tryParse(instring, loc)
+        except (ParseException, IndexError):
+            return False
+        else:
+            return True
+
+    class _UnboundedCache(object):
+        def __init__(self):
+            cache = {}
+            self.not_in_cache = not_in_cache = object()
+
+            def get(self, key):
+                return cache.get(key, not_in_cache)
+
+            def set(self, key, value):
+                cache[key] = value
+
+            def clear(self):
+                cache.clear()
+                
+            def cache_len(self):
+                return len(cache)
+
+            self.get = types.MethodType(get, self)
+            self.set = types.MethodType(set, self)
+            self.clear = types.MethodType(clear, self)
+            self.__len__ = types.MethodType(cache_len, self)
+
+    if _OrderedDict is not None:
+        class _FifoCache(object):
+            def __init__(self, size):
+                self.not_in_cache = not_in_cache = object()
+
+                cache = _OrderedDict()
+
+                def get(self, key):
+                    return cache.get(key, not_in_cache)
+
+                def set(self, key, value):
+                    cache[key] = value
+                    while len(cache) > size:
+                        try:
+                            cache.popitem(False)
+                        except KeyError:
+                            pass
+
+                def clear(self):
+                    cache.clear()
+
+                def cache_len(self):
+                    return len(cache)
+
+                self.get = types.MethodType(get, self)
+                self.set = types.MethodType(set, self)
+                self.clear = types.MethodType(clear, self)
+                self.__len__ = types.MethodType(cache_len, self)
+
+    else:
+        class _FifoCache(object):
+            def __init__(self, size):
+                self.not_in_cache = not_in_cache = object()
+
+                cache = {}
+                key_fifo = collections.deque([], size)
+
+                def get(self, key):
+                    return cache.get(key, not_in_cache)
+
+                def set(self, key, value):
+                    cache[key] = value
+                    while len(key_fifo) > size:
+                        cache.pop(key_fifo.popleft(), None)
+                    key_fifo.append(key)
+
+                def clear(self):
+                    cache.clear()
+                    key_fifo.clear()
+
+                def cache_len(self):
+                    return len(cache)
+
+                self.get = types.MethodType(get, self)
+                self.set = types.MethodType(set, self)
+                self.clear = types.MethodType(clear, self)
+                self.__len__ = types.MethodType(cache_len, self)
+
+    # argument cache for optimizing repeated calls when backtracking through recursive expressions
+    packrat_cache = {} # this is set later by enabledPackrat(); this is here so that resetCache() doesn't fail
+    packrat_cache_lock = RLock()
+    packrat_cache_stats = [0, 0]
+
+    # this method gets repeatedly called during backtracking with the same arguments -
+    # we can cache these arguments and save ourselves the trouble of re-parsing the contained expression
+    def _parseCache( self, instring, loc, doActions=True, callPreParse=True ):
+        HIT, MISS = 0, 1
+        lookup = (self, instring, loc, callPreParse, doActions)
+        with ParserElement.packrat_cache_lock:
+            cache = ParserElement.packrat_cache
+            value = cache.get(lookup)
+            if value is cache.not_in_cache:
+                ParserElement.packrat_cache_stats[MISS] += 1
+                try:
+                    value = self._parseNoCache(instring, loc, doActions, callPreParse)
+                except ParseBaseException as pe:
+                    # cache a copy of the exception, without the traceback
+                    cache.set(lookup, pe.__class__(*pe.args))
+                    raise
+                else:
+                    cache.set(lookup, (value[0], value[1].copy()))
+                    return value
+            else:
+                ParserElement.packrat_cache_stats[HIT] += 1
+                if isinstance(value, Exception):
+                    raise value
+                return (value[0], value[1].copy())
+
+    _parse = _parseNoCache
+
+    @staticmethod
+    def resetCache():
+        ParserElement.packrat_cache.clear()
+        ParserElement.packrat_cache_stats[:] = [0] * len(ParserElement.packrat_cache_stats)
+
+    _packratEnabled = False
+    @staticmethod
+    def enablePackrat(cache_size_limit=128):
+        """Enables "packrat" parsing, which adds memoizing to the parsing logic.
+           Repeated parse attempts at the same string location (which happens
+           often in many complex grammars) can immediately return a cached value,
+           instead of re-executing parsing/validating code.  Memoizing is done of
+           both valid results and parsing exceptions.
+           
+           Parameters:
+            - cache_size_limit - (default=C{128}) - if an integer value is provided
+              will limit the size of the packrat cache; if None is passed, then
+              the cache size will be unbounded; if 0 is passed, the cache will
+              be effectively disabled.
+            
+           This speedup may break existing programs that use parse actions that
+           have side-effects.  For this reason, packrat parsing is disabled when
+           you first import pyparsing.  To activate the packrat feature, your
+           program must call the class method C{ParserElement.enablePackrat()}.  If
+           your program uses C{psyco} to "compile as you go", you must call
+           C{enablePackrat} before calling C{psyco.full()}.  If you do not do this,
+           Python will crash.  For best results, call C{enablePackrat()} immediately
+           after importing pyparsing.
+           
+           Example::
+               import pyparsing
+               pyparsing.ParserElement.enablePackrat()
+        """
+        if not ParserElement._packratEnabled:
+            ParserElement._packratEnabled = True
+            if cache_size_limit is None:
+                ParserElement.packrat_cache = ParserElement._UnboundedCache()
+            else:
+                ParserElement.packrat_cache = ParserElement._FifoCache(cache_size_limit)
+            ParserElement._parse = ParserElement._parseCache
+
+    def parseString( self, instring, parseAll=False ):
+        """
+        Execute the parse expression with the given string.
+        This is the main interface to the client code, once the complete
+        expression has been built.
+
+        If you want the grammar to require that the entire input string be
+        successfully parsed, then set C{parseAll} to True (equivalent to ending
+        the grammar with C{L{StringEnd()}}).
+
+        Note: C{parseString} implicitly calls C{expandtabs()} on the input string,
+        in order to report proper column numbers in parse actions.
+        If the input string contains tabs and
+        the grammar uses parse actions that use the C{loc} argument to index into the
+        string being parsed, you can ensure you have a consistent view of the input
+        string by:
+         - calling C{parseWithTabs} on your grammar before calling C{parseString}
+           (see L{I{parseWithTabs}})
+         - define your parse action using the full C{(s,loc,toks)} signature, and
+           reference the input string using the parse action's C{s} argument
+         - explictly expand the tabs in your input string before calling
+           C{parseString}
+        
+        Example::
+            Word('a').parseString('aaaaabaaa')  # -> ['aaaaa']
+            Word('a').parseString('aaaaabaaa', parseAll=True)  # -> Exception: Expected end of text
+        """
+        ParserElement.resetCache()
+        if not self.streamlined:
+            self.streamline()
+            #~ self.saveAsList = True
+        for e in self.ignoreExprs:
+            e.streamline()
+        if not self.keepTabs:
+            instring = instring.expandtabs()
+        try:
+            loc, tokens = self._parse( instring, 0 )
+            if parseAll:
+                loc = self.preParse( instring, loc )
+                se = Empty() + StringEnd()
+                se._parse( instring, loc )
+        except ParseBaseException as exc:
+            if ParserElement.verbose_stacktrace:
+                raise
+            else:
+                # catch and re-raise exception from here, clears out pyparsing internal stack trace
+                raise exc
+        else:
+            return tokens
+
+    def scanString( self, instring, maxMatches=_MAX_INT, overlap=False ):
+        """
+        Scan the input string for expression matches.  Each match will return the
+        matching tokens, start location, and end location.  May be called with optional
+        C{maxMatches} argument, to clip scanning after 'n' matches are found.  If
+        C{overlap} is specified, then overlapping matches will be reported.
+
+        Note that the start and end locations are reported relative to the string
+        being parsed.  See L{I{parseString}} for more information on parsing
+        strings with embedded tabs.
+
+        Example::
+            source = "sldjf123lsdjjkf345sldkjf879lkjsfd987"
+            print(source)
+            for tokens,start,end in Word(alphas).scanString(source):
+                print(' '*start + '^'*(end-start))
+                print(' '*start + tokens[0])
+        
+        prints::
+        
+            sldjf123lsdjjkf345sldkjf879lkjsfd987
+            ^^^^^
+            sldjf
+                    ^^^^^^^
+                    lsdjjkf
+                              ^^^^^^
+                              sldkjf
+                                       ^^^^^^
+                                       lkjsfd
+        """
+        if not self.streamlined:
+            self.streamline()
+        for e in self.ignoreExprs:
+            e.streamline()
+
+        if not self.keepTabs:
+            instring = _ustr(instring).expandtabs()
+        instrlen = len(instring)
+        loc = 0
+        preparseFn = self.preParse
+        parseFn = self._parse
+        ParserElement.resetCache()
+        matches = 0
+        try:
+            while loc <= instrlen and matches < maxMatches:
+                try:
+                    preloc = preparseFn( instring, loc )
+                    nextLoc,tokens = parseFn( instring, preloc, callPreParse=False )
+                except ParseException:
+                    loc = preloc+1
+                else:
+                    if nextLoc > loc:
+                        matches += 1
+                        yield tokens, preloc, nextLoc
+                        if overlap:
+                            nextloc = preparseFn( instring, loc )
+                            if nextloc > loc:
+                                loc = nextLoc
+                            else:
+                                loc += 1
+                        else:
+                            loc = nextLoc
+                    else:
+                        loc = preloc+1
+        except ParseBaseException as exc:
+            if ParserElement.verbose_stacktrace:
+                raise
+            else:
+                # catch and re-raise exception from here, clears out pyparsing internal stack trace
+                raise exc
+
+    def transformString( self, instring ):
+        """
+        Extension to C{L{scanString}}, to modify matching text with modified tokens that may
+        be returned from a parse action.  To use C{transformString}, define a grammar and
+        attach a parse action to it that modifies the returned token list.
+        Invoking C{transformString()} on a target string will then scan for matches,
+        and replace the matched text patterns according to the logic in the parse
+        action.  C{transformString()} returns the resulting transformed string.
+        
+        Example::
+            wd = Word(alphas)
+            wd.setParseAction(lambda toks: toks[0].title())
+            
+            print(wd.transformString("now is the winter of our discontent made glorious summer by this sun of york."))
+        Prints::
+            Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York.
+        """
+        out = []
+        lastE = 0
+        # force preservation of s, to minimize unwanted transformation of string, and to
+        # keep string locs straight between transformString and scanString
+        self.keepTabs = True
+        try:
+            for t,s,e in self.scanString( instring ):
+                out.append( instring[lastE:s] )
+                if t:
+                    if isinstance(t,ParseResults):
+                        out += t.asList()
+                    elif isinstance(t,list):
+                        out += t
+                    else:
+                        out.append(t)
+                lastE = e
+            out.append(instring[lastE:])
+            out = [o for o in out if o]
+            return "".join(map(_ustr,_flatten(out)))
+        except ParseBaseException as exc:
+            if ParserElement.verbose_stacktrace:
+                raise
+            else:
+                # catch and re-raise exception from here, clears out pyparsing internal stack trace
+                raise exc
+
+    def searchString( self, instring, maxMatches=_MAX_INT ):
+        """
+        Another extension to C{L{scanString}}, simplifying the access to the tokens found
+        to match the given parse expression.  May be called with optional
+        C{maxMatches} argument, to clip searching after 'n' matches are found.
+        
+        Example::
+            # a capitalized word starts with an uppercase letter, followed by zero or more lowercase letters
+            cap_word = Word(alphas.upper(), alphas.lower())
+            
+            print(cap_word.searchString("More than Iron, more than Lead, more than Gold I need Electricity"))
+
+            # the sum() builtin can be used to merge results into a single ParseResults object
+            print(sum(cap_word.searchString("More than Iron, more than Lead, more than Gold I need Electricity")))
+        prints::
+            [['More'], ['Iron'], ['Lead'], ['Gold'], ['I'], ['Electricity']]
+            ['More', 'Iron', 'Lead', 'Gold', 'I', 'Electricity']
+        """
+        try:
+            return ParseResults([ t for t,s,e in self.scanString( instring, maxMatches ) ])
+        except ParseBaseException as exc:
+            if ParserElement.verbose_stacktrace:
+                raise
+            else:
+                # catch and re-raise exception from here, clears out pyparsing internal stack trace
+                raise exc
+
+    def split(self, instring, maxsplit=_MAX_INT, includeSeparators=False):
+        """
+        Generator method to split a string using the given expression as a separator.
+        May be called with optional C{maxsplit} argument, to limit the number of splits;
+        and the optional C{includeSeparators} argument (default=C{False}), if the separating
+        matching text should be included in the split results.
+        
+        Example::        
+            punc = oneOf(list(".,;:/-!?"))
+            print(list(punc.split("This, this?, this sentence, is badly punctuated!")))
+        prints::
+            ['This', ' this', '', ' this sentence', ' is badly punctuated', '']
+        """
+        splits = 0
+        last = 0
+        for t,s,e in self.scanString(instring, maxMatches=maxsplit):
+            yield instring[last:s]
+            if includeSeparators:
+                yield t[0]
+            last = e
+        yield instring[last:]
+
+    def __add__(self, other ):
+        """
+        Implementation of + operator - returns C{L{And}}. Adding strings to a ParserElement
+        converts them to L{Literal}s by default.
+        
+        Example::
+            greet = Word(alphas) + "," + Word(alphas) + "!"
+            hello = "Hello, World!"
+            print (hello, "->", greet.parseString(hello))
+        Prints::
+            Hello, World! -> ['Hello', ',', 'World', '!']
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return And( [ self, other ] )
+
+    def __radd__(self, other ):
+        """
+        Implementation of + operator when left operand is not a C{L{ParserElement}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return other + self
+
+    def __sub__(self, other):
+        """
+        Implementation of - operator, returns C{L{And}} with error stop
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return self + And._ErrorStop() + other
+
+    def __rsub__(self, other ):
+        """
+        Implementation of - operator when left operand is not a C{L{ParserElement}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return other - self
+
+    def __mul__(self,other):
+        """
+        Implementation of * operator, allows use of C{expr * 3} in place of
+        C{expr + expr + expr}.  Expressions may also me multiplied by a 2-integer
+        tuple, similar to C{{min,max}} multipliers in regular expressions.  Tuples
+        may also include C{None} as in:
+         - C{expr*(n,None)} or C{expr*(n,)} is equivalent
+              to C{expr*n + L{ZeroOrMore}(expr)}
+              (read as "at least n instances of C{expr}")
+         - C{expr*(None,n)} is equivalent to C{expr*(0,n)}
+              (read as "0 to n instances of C{expr}")
+         - C{expr*(None,None)} is equivalent to C{L{ZeroOrMore}(expr)}
+         - C{expr*(1,None)} is equivalent to C{L{OneOrMore}(expr)}
+
+        Note that C{expr*(None,n)} does not raise an exception if
+        more than n exprs exist in the input stream; that is,
+        C{expr*(None,n)} does not enforce a maximum number of expr
+        occurrences.  If this behavior is desired, then write
+        C{expr*(None,n) + ~expr}
+        """
+        if isinstance(other,int):
+            minElements, optElements = other,0
+        elif isinstance(other,tuple):
+            other = (other + (None, None))[:2]
+            if other[0] is None:
+                other = (0, other[1])
+            if isinstance(other[0],int) and other[1] is None:
+                if other[0] == 0:
+                    return ZeroOrMore(self)
+                if other[0] == 1:
+                    return OneOrMore(self)
+                else:
+                    return self*other[0] + ZeroOrMore(self)
+            elif isinstance(other[0],int) and isinstance(other[1],int):
+                minElements, optElements = other
+                optElements -= minElements
+            else:
+                raise TypeError("cannot multiply 'ParserElement' and ('%s','%s') objects", type(other[0]),type(other[1]))
+        else:
+            raise TypeError("cannot multiply 'ParserElement' and '%s' objects", type(other))
+
+        if minElements < 0:
+            raise ValueError("cannot multiply ParserElement by negative value")
+        if optElements < 0:
+            raise ValueError("second tuple value must be greater or equal to first tuple value")
+        if minElements == optElements == 0:
+            raise ValueError("cannot multiply ParserElement by 0 or (0,0)")
+
+        if (optElements):
+            def makeOptionalList(n):
+                if n>1:
+                    return Optional(self + makeOptionalList(n-1))
+                else:
+                    return Optional(self)
+            if minElements:
+                if minElements == 1:
+                    ret = self + makeOptionalList(optElements)
+                else:
+                    ret = And([self]*minElements) + makeOptionalList(optElements)
+            else:
+                ret = makeOptionalList(optElements)
+        else:
+            if minElements == 1:
+                ret = self
+            else:
+                ret = And([self]*minElements)
+        return ret
+
+    def __rmul__(self, other):
+        return self.__mul__(other)
+
+    def __or__(self, other ):
+        """
+        Implementation of | operator - returns C{L{MatchFirst}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return MatchFirst( [ self, other ] )
+
+    def __ror__(self, other ):
+        """
+        Implementation of | operator when left operand is not a C{L{ParserElement}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return other | self
+
+    def __xor__(self, other ):
+        """
+        Implementation of ^ operator - returns C{L{Or}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return Or( [ self, other ] )
+
+    def __rxor__(self, other ):
+        """
+        Implementation of ^ operator when left operand is not a C{L{ParserElement}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return other ^ self
+
+    def __and__(self, other ):
+        """
+        Implementation of & operator - returns C{L{Each}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return Each( [ self, other ] )
+
+    def __rand__(self, other ):
+        """
+        Implementation of & operator when left operand is not a C{L{ParserElement}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return other & self
+
+    def __invert__( self ):
+        """
+        Implementation of ~ operator - returns C{L{NotAny}}
+        """
+        return NotAny( self )
+
+    def __call__(self, name=None):
+        """
+        Shortcut for C{L{setResultsName}}, with C{listAllMatches=False}.
+        
+        If C{name} is given with a trailing C{'*'} character, then C{listAllMatches} will be
+        passed as C{True}.
+           
+        If C{name} is omitted, same as calling C{L{copy}}.
+
+        Example::
+            # these are equivalent
+            userdata = Word(alphas).setResultsName("name") + Word(nums+"-").setResultsName("socsecno")
+            userdata = Word(alphas)("name") + Word(nums+"-")("socsecno")             
+        """
+        if name is not None:
+            return self.setResultsName(name)
+        else:
+            return self.copy()
+
+    def suppress( self ):
+        """
+        Suppresses the output of this C{ParserElement}; useful to keep punctuation from
+        cluttering up returned output.
+        """
+        return Suppress( self )
+
+    def leaveWhitespace( self ):
+        """
+        Disables the skipping of whitespace before matching the characters in the
+        C{ParserElement}'s defined pattern.  This is normally only used internally by
+        the pyparsing module, but may be needed in some whitespace-sensitive grammars.
+        """
+        self.skipWhitespace = False
+        return self
+
+    def setWhitespaceChars( self, chars ):
+        """
+        Overrides the default whitespace chars
+        """
+        self.skipWhitespace = True
+        self.whiteChars = chars
+        self.copyDefaultWhiteChars = False
+        return self
+
+    def parseWithTabs( self ):
+        """
+        Overrides default behavior to expand C{}s to spaces before parsing the input string.
+        Must be called before C{parseString} when the input grammar contains elements that
+        match C{} characters.
+        """
+        self.keepTabs = True
+        return self
+
+    def ignore( self, other ):
+        """
+        Define expression to be ignored (e.g., comments) while doing pattern
+        matching; may be called repeatedly, to define multiple comment or other
+        ignorable patterns.
+        
+        Example::
+            patt = OneOrMore(Word(alphas))
+            patt.parseString('ablaj /* comment */ lskjd') # -> ['ablaj']
+            
+            patt.ignore(cStyleComment)
+            patt.parseString('ablaj /* comment */ lskjd') # -> ['ablaj', 'lskjd']
+        """
+        if isinstance(other, basestring):
+            other = Suppress(other)
+
+        if isinstance( other, Suppress ):
+            if other not in self.ignoreExprs:
+                self.ignoreExprs.append(other)
+        else:
+            self.ignoreExprs.append( Suppress( other.copy() ) )
+        return self
+
+    def setDebugActions( self, startAction, successAction, exceptionAction ):
+        """
+        Enable display of debugging messages while doing pattern matching.
+        """
+        self.debugActions = (startAction or _defaultStartDebugAction,
+                             successAction or _defaultSuccessDebugAction,
+                             exceptionAction or _defaultExceptionDebugAction)
+        self.debug = True
+        return self
+
+    def setDebug( self, flag=True ):
+        """
+        Enable display of debugging messages while doing pattern matching.
+        Set C{flag} to True to enable, False to disable.
+
+        Example::
+            wd = Word(alphas).setName("alphaword")
+            integer = Word(nums).setName("numword")
+            term = wd | integer
+            
+            # turn on debugging for wd
+            wd.setDebug()
+
+            OneOrMore(term).parseString("abc 123 xyz 890")
+        
+        prints::
+            Match alphaword at loc 0(1,1)
+            Matched alphaword -> ['abc']
+            Match alphaword at loc 3(1,4)
+            Exception raised:Expected alphaword (at char 4), (line:1, col:5)
+            Match alphaword at loc 7(1,8)
+            Matched alphaword -> ['xyz']
+            Match alphaword at loc 11(1,12)
+            Exception raised:Expected alphaword (at char 12), (line:1, col:13)
+            Match alphaword at loc 15(1,16)
+            Exception raised:Expected alphaword (at char 15), (line:1, col:16)
+
+        The output shown is that produced by the default debug actions - custom debug actions can be
+        specified using L{setDebugActions}. Prior to attempting
+        to match the C{wd} expression, the debugging message C{"Match  at loc (,)"}
+        is shown. Then if the parse succeeds, a C{"Matched"} message is shown, or an C{"Exception raised"}
+        message is shown. Also note the use of L{setName} to assign a human-readable name to the expression,
+        which makes debugging and exception messages easier to understand - for instance, the default
+        name created for the C{Word} expression without calling C{setName} is C{"W:(ABCD...)"}.
+        """
+        if flag:
+            self.setDebugActions( _defaultStartDebugAction, _defaultSuccessDebugAction, _defaultExceptionDebugAction )
+        else:
+            self.debug = False
+        return self
+
+    def __str__( self ):
+        return self.name
+
+    def __repr__( self ):
+        return _ustr(self)
+
+    def streamline( self ):
+        self.streamlined = True
+        self.strRepr = None
+        return self
+
+    def checkRecursion( self, parseElementList ):
+        pass
+
+    def validate( self, validateTrace=[] ):
+        """
+        Check defined expressions for valid structure, check for infinite recursive definitions.
+        """
+        self.checkRecursion( [] )
+
+    def parseFile( self, file_or_filename, parseAll=False ):
+        """
+        Execute the parse expression on the given file or filename.
+        If a filename is specified (instead of a file object),
+        the entire file is opened, read, and closed before parsing.
+        """
+        try:
+            file_contents = file_or_filename.read()
+        except AttributeError:
+            with open(file_or_filename, "r") as f:
+                file_contents = f.read()
+        try:
+            return self.parseString(file_contents, parseAll)
+        except ParseBaseException as exc:
+            if ParserElement.verbose_stacktrace:
+                raise
+            else:
+                # catch and re-raise exception from here, clears out pyparsing internal stack trace
+                raise exc
+
+    def __eq__(self,other):
+        if isinstance(other, ParserElement):
+            return self is other or vars(self) == vars(other)
+        elif isinstance(other, basestring):
+            return self.matches(other)
+        else:
+            return super(ParserElement,self)==other
+
+    def __ne__(self,other):
+        return not (self == other)
+
+    def __hash__(self):
+        return hash(id(self))
+
+    def __req__(self,other):
+        return self == other
+
+    def __rne__(self,other):
+        return not (self == other)
+
+    def matches(self, testString, parseAll=True):
+        """
+        Method for quick testing of a parser against a test string. Good for simple 
+        inline microtests of sub expressions while building up larger parser.
+           
+        Parameters:
+         - testString - to test against this expression for a match
+         - parseAll - (default=C{True}) - flag to pass to C{L{parseString}} when running tests
+            
+        Example::
+            expr = Word(nums)
+            assert expr.matches("100")
+        """
+        try:
+            self.parseString(_ustr(testString), parseAll=parseAll)
+            return True
+        except ParseBaseException:
+            return False
+                
+    def runTests(self, tests, parseAll=True, comment='#', fullDump=True, printResults=True, failureTests=False):
+        """
+        Execute the parse expression on a series of test strings, showing each
+        test, the parsed results or where the parse failed. Quick and easy way to
+        run a parse expression against a list of sample strings.
+           
+        Parameters:
+         - tests - a list of separate test strings, or a multiline string of test strings
+         - parseAll - (default=C{True}) - flag to pass to C{L{parseString}} when running tests           
+         - comment - (default=C{'#'}) - expression for indicating embedded comments in the test 
+              string; pass None to disable comment filtering
+         - fullDump - (default=C{True}) - dump results as list followed by results names in nested outline;
+              if False, only dump nested list
+         - printResults - (default=C{True}) prints test output to stdout
+         - failureTests - (default=C{False}) indicates if these tests are expected to fail parsing
+
+        Returns: a (success, results) tuple, where success indicates that all tests succeeded
+        (or failed if C{failureTests} is True), and the results contain a list of lines of each 
+        test's output
+        
+        Example::
+            number_expr = pyparsing_common.number.copy()
+
+            result = number_expr.runTests('''
+                # unsigned integer
+                100
+                # negative integer
+                -100
+                # float with scientific notation
+                6.02e23
+                # integer with scientific notation
+                1e-12
+                ''')
+            print("Success" if result[0] else "Failed!")
+
+            result = number_expr.runTests('''
+                # stray character
+                100Z
+                # missing leading digit before '.'
+                -.100
+                # too many '.'
+                3.14.159
+                ''', failureTests=True)
+            print("Success" if result[0] else "Failed!")
+        prints::
+            # unsigned integer
+            100
+            [100]
+
+            # negative integer
+            -100
+            [-100]
+
+            # float with scientific notation
+            6.02e23
+            [6.02e+23]
+
+            # integer with scientific notation
+            1e-12
+            [1e-12]
+
+            Success
+            
+            # stray character
+            100Z
+               ^
+            FAIL: Expected end of text (at char 3), (line:1, col:4)
+
+            # missing leading digit before '.'
+            -.100
+            ^
+            FAIL: Expected {real number with scientific notation | real number | signed integer} (at char 0), (line:1, col:1)
+
+            # too many '.'
+            3.14.159
+                ^
+            FAIL: Expected end of text (at char 4), (line:1, col:5)
+
+            Success
+
+        Each test string must be on a single line. If you want to test a string that spans multiple
+        lines, create a test like this::
+
+            expr.runTest(r"this is a test\\n of strings that spans \\n 3 lines")
+        
+        (Note that this is a raw string literal, you must include the leading 'r'.)
+        """
+        if isinstance(tests, basestring):
+            tests = list(map(str.strip, tests.rstrip().splitlines()))
+        if isinstance(comment, basestring):
+            comment = Literal(comment)
+        allResults = []
+        comments = []
+        success = True
+        for t in tests:
+            if comment is not None and comment.matches(t, False) or comments and not t:
+                comments.append(t)
+                continue
+            if not t:
+                continue
+            out = ['\n'.join(comments), t]
+            comments = []
+            try:
+                t = t.replace(r'\n','\n')
+                result = self.parseString(t, parseAll=parseAll)
+                out.append(result.dump(full=fullDump))
+                success = success and not failureTests
+            except ParseBaseException as pe:
+                fatal = "(FATAL)" if isinstance(pe, ParseFatalException) else ""
+                if '\n' in t:
+                    out.append(line(pe.loc, t))
+                    out.append(' '*(col(pe.loc,t)-1) + '^' + fatal)
+                else:
+                    out.append(' '*pe.loc + '^' + fatal)
+                out.append("FAIL: " + str(pe))
+                success = success and failureTests
+                result = pe
+            except Exception as exc:
+                out.append("FAIL-EXCEPTION: " + str(exc))
+                success = success and failureTests
+                result = exc
+
+            if printResults:
+                if fullDump:
+                    out.append('')
+                print('\n'.join(out))
+
+            allResults.append((t, result))
+        
+        return success, allResults
+
+        
+class Token(ParserElement):
+    """
+    Abstract C{ParserElement} subclass, for defining atomic matching patterns.
+    """
+    def __init__( self ):
+        super(Token,self).__init__( savelist=False )
+
+
+class Empty(Token):
+    """
+    An empty token, will always match.
+    """
+    def __init__( self ):
+        super(Empty,self).__init__()
+        self.name = "Empty"
+        self.mayReturnEmpty = True
+        self.mayIndexError = False
+
+
+class NoMatch(Token):
+    """
+    A token that will never match.
+    """
+    def __init__( self ):
+        super(NoMatch,self).__init__()
+        self.name = "NoMatch"
+        self.mayReturnEmpty = True
+        self.mayIndexError = False
+        self.errmsg = "Unmatchable token"
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        raise ParseException(instring, loc, self.errmsg, self)
+
+
+class Literal(Token):
+    """
+    Token to exactly match a specified string.
+    
+    Example::
+        Literal('blah').parseString('blah')  # -> ['blah']
+        Literal('blah').parseString('blahfooblah')  # -> ['blah']
+        Literal('blah').parseString('bla')  # -> Exception: Expected "blah"
+    
+    For case-insensitive matching, use L{CaselessLiteral}.
+    
+    For keyword matching (force word break before and after the matched string),
+    use L{Keyword} or L{CaselessKeyword}.
+    """
+    def __init__( self, matchString ):
+        super(Literal,self).__init__()
+        self.match = matchString
+        self.matchLen = len(matchString)
+        try:
+            self.firstMatchChar = matchString[0]
+        except IndexError:
+            warnings.warn("null string passed to Literal; use Empty() instead",
+                            SyntaxWarning, stacklevel=2)
+            self.__class__ = Empty
+        self.name = '"%s"' % _ustr(self.match)
+        self.errmsg = "Expected " + self.name
+        self.mayReturnEmpty = False
+        self.mayIndexError = False
+
+    # Performance tuning: this routine gets called a *lot*
+    # if this is a single character match string  and the first character matches,
+    # short-circuit as quickly as possible, and avoid calling startswith
+    #~ @profile
+    def parseImpl( self, instring, loc, doActions=True ):
+        if (instring[loc] == self.firstMatchChar and
+            (self.matchLen==1 or instring.startswith(self.match,loc)) ):
+            return loc+self.matchLen, self.match
+        raise ParseException(instring, loc, self.errmsg, self)
+_L = Literal
+ParserElement._literalStringClass = Literal
+
+class Keyword(Token):
+    """
+    Token to exactly match a specified string as a keyword, that is, it must be
+    immediately followed by a non-keyword character.  Compare with C{L{Literal}}:
+     - C{Literal("if")} will match the leading C{'if'} in C{'ifAndOnlyIf'}.
+     - C{Keyword("if")} will not; it will only match the leading C{'if'} in C{'if x=1'}, or C{'if(y==2)'}
+    Accepts two optional constructor arguments in addition to the keyword string:
+     - C{identChars} is a string of characters that would be valid identifier characters,
+          defaulting to all alphanumerics + "_" and "$"
+     - C{caseless} allows case-insensitive matching, default is C{False}.
+       
+    Example::
+        Keyword("start").parseString("start")  # -> ['start']
+        Keyword("start").parseString("starting")  # -> Exception
+
+    For case-insensitive matching, use L{CaselessKeyword}.
+    """
+    DEFAULT_KEYWORD_CHARS = alphanums+"_$"
+
+    def __init__( self, matchString, identChars=None, caseless=False ):
+        super(Keyword,self).__init__()
+        if identChars is None:
+            identChars = Keyword.DEFAULT_KEYWORD_CHARS
+        self.match = matchString
+        self.matchLen = len(matchString)
+        try:
+            self.firstMatchChar = matchString[0]
+        except IndexError:
+            warnings.warn("null string passed to Keyword; use Empty() instead",
+                            SyntaxWarning, stacklevel=2)
+        self.name = '"%s"' % self.match
+        self.errmsg = "Expected " + self.name
+        self.mayReturnEmpty = False
+        self.mayIndexError = False
+        self.caseless = caseless
+        if caseless:
+            self.caselessmatch = matchString.upper()
+            identChars = identChars.upper()
+        self.identChars = set(identChars)
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if self.caseless:
+            if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and
+                 (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) and
+                 (loc == 0 or instring[loc-1].upper() not in self.identChars) ):
+                return loc+self.matchLen, self.match
+        else:
+            if (instring[loc] == self.firstMatchChar and
+                (self.matchLen==1 or instring.startswith(self.match,loc)) and
+                (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen] not in self.identChars) and
+                (loc == 0 or instring[loc-1] not in self.identChars) ):
+                return loc+self.matchLen, self.match
+        raise ParseException(instring, loc, self.errmsg, self)
+
+    def copy(self):
+        c = super(Keyword,self).copy()
+        c.identChars = Keyword.DEFAULT_KEYWORD_CHARS
+        return c
+
+    @staticmethod
+    def setDefaultKeywordChars( chars ):
+        """Overrides the default Keyword chars
+        """
+        Keyword.DEFAULT_KEYWORD_CHARS = chars
+
+class CaselessLiteral(Literal):
+    """
+    Token to match a specified string, ignoring case of letters.
+    Note: the matched results will always be in the case of the given
+    match string, NOT the case of the input text.
+
+    Example::
+        OneOrMore(CaselessLiteral("CMD")).parseString("cmd CMD Cmd10") # -> ['CMD', 'CMD', 'CMD']
+        
+    (Contrast with example for L{CaselessKeyword}.)
+    """
+    def __init__( self, matchString ):
+        super(CaselessLiteral,self).__init__( matchString.upper() )
+        # Preserve the defining literal.
+        self.returnString = matchString
+        self.name = "'%s'" % self.returnString
+        self.errmsg = "Expected " + self.name
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if instring[ loc:loc+self.matchLen ].upper() == self.match:
+            return loc+self.matchLen, self.returnString
+        raise ParseException(instring, loc, self.errmsg, self)
+
+class CaselessKeyword(Keyword):
+    """
+    Caseless version of L{Keyword}.
+
+    Example::
+        OneOrMore(CaselessKeyword("CMD")).parseString("cmd CMD Cmd10") # -> ['CMD', 'CMD']
+        
+    (Contrast with example for L{CaselessLiteral}.)
+    """
+    def __init__( self, matchString, identChars=None ):
+        super(CaselessKeyword,self).__init__( matchString, identChars, caseless=True )
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and
+             (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) ):
+            return loc+self.matchLen, self.match
+        raise ParseException(instring, loc, self.errmsg, self)
+
+class CloseMatch(Token):
+    """
+    A variation on L{Literal} which matches "close" matches, that is, 
+    strings with at most 'n' mismatching characters. C{CloseMatch} takes parameters:
+     - C{match_string} - string to be matched
+     - C{maxMismatches} - (C{default=1}) maximum number of mismatches allowed to count as a match
+    
+    The results from a successful parse will contain the matched text from the input string and the following named results:
+     - C{mismatches} - a list of the positions within the match_string where mismatches were found
+     - C{original} - the original match_string used to compare against the input string
+    
+    If C{mismatches} is an empty list, then the match was an exact match.
+    
+    Example::
+        patt = CloseMatch("ATCATCGAATGGA")
+        patt.parseString("ATCATCGAAXGGA") # -> (['ATCATCGAAXGGA'], {'mismatches': [[9]], 'original': ['ATCATCGAATGGA']})
+        patt.parseString("ATCAXCGAAXGGA") # -> Exception: Expected 'ATCATCGAATGGA' (with up to 1 mismatches) (at char 0), (line:1, col:1)
+
+        # exact match
+        patt.parseString("ATCATCGAATGGA") # -> (['ATCATCGAATGGA'], {'mismatches': [[]], 'original': ['ATCATCGAATGGA']})
+
+        # close match allowing up to 2 mismatches
+        patt = CloseMatch("ATCATCGAATGGA", maxMismatches=2)
+        patt.parseString("ATCAXCGAAXGGA") # -> (['ATCAXCGAAXGGA'], {'mismatches': [[4, 9]], 'original': ['ATCATCGAATGGA']})
+    """
+    def __init__(self, match_string, maxMismatches=1):
+        super(CloseMatch,self).__init__()
+        self.name = match_string
+        self.match_string = match_string
+        self.maxMismatches = maxMismatches
+        self.errmsg = "Expected %r (with up to %d mismatches)" % (self.match_string, self.maxMismatches)
+        self.mayIndexError = False
+        self.mayReturnEmpty = False
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        start = loc
+        instrlen = len(instring)
+        maxloc = start + len(self.match_string)
+
+        if maxloc <= instrlen:
+            match_string = self.match_string
+            match_stringloc = 0
+            mismatches = []
+            maxMismatches = self.maxMismatches
+
+            for match_stringloc,s_m in enumerate(zip(instring[loc:maxloc], self.match_string)):
+                src,mat = s_m
+                if src != mat:
+                    mismatches.append(match_stringloc)
+                    if len(mismatches) > maxMismatches:
+                        break
+            else:
+                loc = match_stringloc + 1
+                results = ParseResults([instring[start:loc]])
+                results['original'] = self.match_string
+                results['mismatches'] = mismatches
+                return loc, results
+
+        raise ParseException(instring, loc, self.errmsg, self)
+
+
+class Word(Token):
+    """
+    Token for matching words composed of allowed character sets.
+    Defined with string containing all allowed initial characters,
+    an optional string containing allowed body characters (if omitted,
+    defaults to the initial character set), and an optional minimum,
+    maximum, and/or exact length.  The default value for C{min} is 1 (a
+    minimum value < 1 is not valid); the default values for C{max} and C{exact}
+    are 0, meaning no maximum or exact length restriction. An optional
+    C{excludeChars} parameter can list characters that might be found in 
+    the input C{bodyChars} string; useful to define a word of all printables
+    except for one or two characters, for instance.
+    
+    L{srange} is useful for defining custom character set strings for defining 
+    C{Word} expressions, using range notation from regular expression character sets.
+    
+    A common mistake is to use C{Word} to match a specific literal string, as in 
+    C{Word("Address")}. Remember that C{Word} uses the string argument to define
+    I{sets} of matchable characters. This expression would match "Add", "AAA",
+    "dAred", or any other word made up of the characters 'A', 'd', 'r', 'e', and 's'.
+    To match an exact literal string, use L{Literal} or L{Keyword}.
+
+    pyparsing includes helper strings for building Words:
+     - L{alphas}
+     - L{nums}
+     - L{alphanums}
+     - L{hexnums}
+     - L{alphas8bit} (alphabetic characters in ASCII range 128-255 - accented, tilded, umlauted, etc.)
+     - L{punc8bit} (non-alphabetic characters in ASCII range 128-255 - currency, symbols, superscripts, diacriticals, etc.)
+     - L{printables} (any non-whitespace character)
+
+    Example::
+        # a word composed of digits
+        integer = Word(nums) # equivalent to Word("0123456789") or Word(srange("0-9"))
+        
+        # a word with a leading capital, and zero or more lowercase
+        capital_word = Word(alphas.upper(), alphas.lower())
+
+        # hostnames are alphanumeric, with leading alpha, and '-'
+        hostname = Word(alphas, alphanums+'-')
+        
+        # roman numeral (not a strict parser, accepts invalid mix of characters)
+        roman = Word("IVXLCDM")
+        
+        # any string of non-whitespace characters, except for ','
+        csv_value = Word(printables, excludeChars=",")
+    """
+    def __init__( self, initChars, bodyChars=None, min=1, max=0, exact=0, asKeyword=False, excludeChars=None ):
+        super(Word,self).__init__()
+        if excludeChars:
+            initChars = ''.join(c for c in initChars if c not in excludeChars)
+            if bodyChars:
+                bodyChars = ''.join(c for c in bodyChars if c not in excludeChars)
+        self.initCharsOrig = initChars
+        self.initChars = set(initChars)
+        if bodyChars :
+            self.bodyCharsOrig = bodyChars
+            self.bodyChars = set(bodyChars)
+        else:
+            self.bodyCharsOrig = initChars
+            self.bodyChars = set(initChars)
+
+        self.maxSpecified = max > 0
+
+        if min < 1:
+            raise ValueError("cannot specify a minimum length < 1; use Optional(Word()) if zero-length word is permitted")
+
+        self.minLen = min
+
+        if max > 0:
+            self.maxLen = max
+        else:
+            self.maxLen = _MAX_INT
+
+        if exact > 0:
+            self.maxLen = exact
+            self.minLen = exact
+
+        self.name = _ustr(self)
+        self.errmsg = "Expected " + self.name
+        self.mayIndexError = False
+        self.asKeyword = asKeyword
+
+        if ' ' not in self.initCharsOrig+self.bodyCharsOrig and (min==1 and max==0 and exact==0):
+            if self.bodyCharsOrig == self.initCharsOrig:
+                self.reString = "[%s]+" % _escapeRegexRangeChars(self.initCharsOrig)
+            elif len(self.initCharsOrig) == 1:
+                self.reString = "%s[%s]*" % \
+                                      (re.escape(self.initCharsOrig),
+                                      _escapeRegexRangeChars(self.bodyCharsOrig),)
+            else:
+                self.reString = "[%s][%s]*" % \
+                                      (_escapeRegexRangeChars(self.initCharsOrig),
+                                      _escapeRegexRangeChars(self.bodyCharsOrig),)
+            if self.asKeyword:
+                self.reString = r"\b"+self.reString+r"\b"
+            try:
+                self.re = re.compile( self.reString )
+            except Exception:
+                self.re = None
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if self.re:
+            result = self.re.match(instring,loc)
+            if not result:
+                raise ParseException(instring, loc, self.errmsg, self)
+
+            loc = result.end()
+            return loc, result.group()
+
+        if not(instring[ loc ] in self.initChars):
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        start = loc
+        loc += 1
+        instrlen = len(instring)
+        bodychars = self.bodyChars
+        maxloc = start + self.maxLen
+        maxloc = min( maxloc, instrlen )
+        while loc < maxloc and instring[loc] in bodychars:
+            loc += 1
+
+        throwException = False
+        if loc - start < self.minLen:
+            throwException = True
+        if self.maxSpecified and loc < instrlen and instring[loc] in bodychars:
+            throwException = True
+        if self.asKeyword:
+            if (start>0 and instring[start-1] in bodychars) or (loc4:
+                    return s[:4]+"..."
+                else:
+                    return s
+
+            if ( self.initCharsOrig != self.bodyCharsOrig ):
+                self.strRepr = "W:(%s,%s)" % ( charsAsStr(self.initCharsOrig), charsAsStr(self.bodyCharsOrig) )
+            else:
+                self.strRepr = "W:(%s)" % charsAsStr(self.initCharsOrig)
+
+        return self.strRepr
+
+
+class Regex(Token):
+    r"""
+    Token for matching strings that match a given regular expression.
+    Defined with string specifying the regular expression in a form recognized by the inbuilt Python re module.
+    If the given regex contains named groups (defined using C{(?P...)}), these will be preserved as 
+    named parse results.
+
+    Example::
+        realnum = Regex(r"[+-]?\d+\.\d*")
+        date = Regex(r'(?P\d{4})-(?P\d\d?)-(?P\d\d?)')
+        # ref: http://stackoverflow.com/questions/267399/how-do-you-match-only-valid-roman-numerals-with-a-regular-expression
+        roman = Regex(r"M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})")
+    """
+    compiledREtype = type(re.compile("[A-Z]"))
+    def __init__( self, pattern, flags=0):
+        """The parameters C{pattern} and C{flags} are passed to the C{re.compile()} function as-is. See the Python C{re} module for an explanation of the acceptable patterns and flags."""
+        super(Regex,self).__init__()
+
+        if isinstance(pattern, basestring):
+            if not pattern:
+                warnings.warn("null string passed to Regex; use Empty() instead",
+                        SyntaxWarning, stacklevel=2)
+
+            self.pattern = pattern
+            self.flags = flags
+
+            try:
+                self.re = re.compile(self.pattern, self.flags)
+                self.reString = self.pattern
+            except sre_constants.error:
+                warnings.warn("invalid pattern (%s) passed to Regex" % pattern,
+                    SyntaxWarning, stacklevel=2)
+                raise
+
+        elif isinstance(pattern, Regex.compiledREtype):
+            self.re = pattern
+            self.pattern = \
+            self.reString = str(pattern)
+            self.flags = flags
+            
+        else:
+            raise ValueError("Regex may only be constructed with a string or a compiled RE object")
+
+        self.name = _ustr(self)
+        self.errmsg = "Expected " + self.name
+        self.mayIndexError = False
+        self.mayReturnEmpty = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        result = self.re.match(instring,loc)
+        if not result:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        loc = result.end()
+        d = result.groupdict()
+        ret = ParseResults(result.group())
+        if d:
+            for k in d:
+                ret[k] = d[k]
+        return loc,ret
+
+    def __str__( self ):
+        try:
+            return super(Regex,self).__str__()
+        except Exception:
+            pass
+
+        if self.strRepr is None:
+            self.strRepr = "Re:(%s)" % repr(self.pattern)
+
+        return self.strRepr
+
+
+class QuotedString(Token):
+    r"""
+    Token for matching strings that are delimited by quoting characters.
+    
+    Defined with the following parameters:
+        - quoteChar - string of one or more characters defining the quote delimiting string
+        - escChar - character to escape quotes, typically backslash (default=C{None})
+        - escQuote - special quote sequence to escape an embedded quote string (such as SQL's "" to escape an embedded ") (default=C{None})
+        - multiline - boolean indicating whether quotes can span multiple lines (default=C{False})
+        - unquoteResults - boolean indicating whether the matched text should be unquoted (default=C{True})
+        - endQuoteChar - string of one or more characters defining the end of the quote delimited string (default=C{None} => same as quoteChar)
+        - convertWhitespaceEscapes - convert escaped whitespace (C{'\t'}, C{'\n'}, etc.) to actual whitespace (default=C{True})
+
+    Example::
+        qs = QuotedString('"')
+        print(qs.searchString('lsjdf "This is the quote" sldjf'))
+        complex_qs = QuotedString('{{', endQuoteChar='}}')
+        print(complex_qs.searchString('lsjdf {{This is the "quote"}} sldjf'))
+        sql_qs = QuotedString('"', escQuote='""')
+        print(sql_qs.searchString('lsjdf "This is the quote with ""embedded"" quotes" sldjf'))
+    prints::
+        [['This is the quote']]
+        [['This is the "quote"']]
+        [['This is the quote with "embedded" quotes']]
+    """
+    def __init__( self, quoteChar, escChar=None, escQuote=None, multiline=False, unquoteResults=True, endQuoteChar=None, convertWhitespaceEscapes=True):
+        super(QuotedString,self).__init__()
+
+        # remove white space from quote chars - wont work anyway
+        quoteChar = quoteChar.strip()
+        if not quoteChar:
+            warnings.warn("quoteChar cannot be the empty string",SyntaxWarning,stacklevel=2)
+            raise SyntaxError()
+
+        if endQuoteChar is None:
+            endQuoteChar = quoteChar
+        else:
+            endQuoteChar = endQuoteChar.strip()
+            if not endQuoteChar:
+                warnings.warn("endQuoteChar cannot be the empty string",SyntaxWarning,stacklevel=2)
+                raise SyntaxError()
+
+        self.quoteChar = quoteChar
+        self.quoteCharLen = len(quoteChar)
+        self.firstQuoteChar = quoteChar[0]
+        self.endQuoteChar = endQuoteChar
+        self.endQuoteCharLen = len(endQuoteChar)
+        self.escChar = escChar
+        self.escQuote = escQuote
+        self.unquoteResults = unquoteResults
+        self.convertWhitespaceEscapes = convertWhitespaceEscapes
+
+        if multiline:
+            self.flags = re.MULTILINE | re.DOTALL
+            self.pattern = r'%s(?:[^%s%s]' % \
+                ( re.escape(self.quoteChar),
+                  _escapeRegexRangeChars(self.endQuoteChar[0]),
+                  (escChar is not None and _escapeRegexRangeChars(escChar) or '') )
+        else:
+            self.flags = 0
+            self.pattern = r'%s(?:[^%s\n\r%s]' % \
+                ( re.escape(self.quoteChar),
+                  _escapeRegexRangeChars(self.endQuoteChar[0]),
+                  (escChar is not None and _escapeRegexRangeChars(escChar) or '') )
+        if len(self.endQuoteChar) > 1:
+            self.pattern += (
+                '|(?:' + ')|(?:'.join("%s[^%s]" % (re.escape(self.endQuoteChar[:i]),
+                                               _escapeRegexRangeChars(self.endQuoteChar[i]))
+                                    for i in range(len(self.endQuoteChar)-1,0,-1)) + ')'
+                )
+        if escQuote:
+            self.pattern += (r'|(?:%s)' % re.escape(escQuote))
+        if escChar:
+            self.pattern += (r'|(?:%s.)' % re.escape(escChar))
+            self.escCharReplacePattern = re.escape(self.escChar)+"(.)"
+        self.pattern += (r')*%s' % re.escape(self.endQuoteChar))
+
+        try:
+            self.re = re.compile(self.pattern, self.flags)
+            self.reString = self.pattern
+        except sre_constants.error:
+            warnings.warn("invalid pattern (%s) passed to Regex" % self.pattern,
+                SyntaxWarning, stacklevel=2)
+            raise
+
+        self.name = _ustr(self)
+        self.errmsg = "Expected " + self.name
+        self.mayIndexError = False
+        self.mayReturnEmpty = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        result = instring[loc] == self.firstQuoteChar and self.re.match(instring,loc) or None
+        if not result:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        loc = result.end()
+        ret = result.group()
+
+        if self.unquoteResults:
+
+            # strip off quotes
+            ret = ret[self.quoteCharLen:-self.endQuoteCharLen]
+
+            if isinstance(ret,basestring):
+                # replace escaped whitespace
+                if '\\' in ret and self.convertWhitespaceEscapes:
+                    ws_map = {
+                        r'\t' : '\t',
+                        r'\n' : '\n',
+                        r'\f' : '\f',
+                        r'\r' : '\r',
+                    }
+                    for wslit,wschar in ws_map.items():
+                        ret = ret.replace(wslit, wschar)
+
+                # replace escaped characters
+                if self.escChar:
+                    ret = re.sub(self.escCharReplacePattern, r"\g<1>", ret)
+
+                # replace escaped quotes
+                if self.escQuote:
+                    ret = ret.replace(self.escQuote, self.endQuoteChar)
+
+        return loc, ret
+
+    def __str__( self ):
+        try:
+            return super(QuotedString,self).__str__()
+        except Exception:
+            pass
+
+        if self.strRepr is None:
+            self.strRepr = "quoted string, starting with %s ending with %s" % (self.quoteChar, self.endQuoteChar)
+
+        return self.strRepr
+
+
+class CharsNotIn(Token):
+    """
+    Token for matching words composed of characters I{not} in a given set (will
+    include whitespace in matched characters if not listed in the provided exclusion set - see example).
+    Defined with string containing all disallowed characters, and an optional
+    minimum, maximum, and/or exact length.  The default value for C{min} is 1 (a
+    minimum value < 1 is not valid); the default values for C{max} and C{exact}
+    are 0, meaning no maximum or exact length restriction.
+
+    Example::
+        # define a comma-separated-value as anything that is not a ','
+        csv_value = CharsNotIn(',')
+        print(delimitedList(csv_value).parseString("dkls,lsdkjf,s12 34,@!#,213"))
+    prints::
+        ['dkls', 'lsdkjf', 's12 34', '@!#', '213']
+    """
+    def __init__( self, notChars, min=1, max=0, exact=0 ):
+        super(CharsNotIn,self).__init__()
+        self.skipWhitespace = False
+        self.notChars = notChars
+
+        if min < 1:
+            raise ValueError("cannot specify a minimum length < 1; use Optional(CharsNotIn()) if zero-length char group is permitted")
+
+        self.minLen = min
+
+        if max > 0:
+            self.maxLen = max
+        else:
+            self.maxLen = _MAX_INT
+
+        if exact > 0:
+            self.maxLen = exact
+            self.minLen = exact
+
+        self.name = _ustr(self)
+        self.errmsg = "Expected " + self.name
+        self.mayReturnEmpty = ( self.minLen == 0 )
+        self.mayIndexError = False
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if instring[loc] in self.notChars:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        start = loc
+        loc += 1
+        notchars = self.notChars
+        maxlen = min( start+self.maxLen, len(instring) )
+        while loc < maxlen and \
+              (instring[loc] not in notchars):
+            loc += 1
+
+        if loc - start < self.minLen:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        return loc, instring[start:loc]
+
+    def __str__( self ):
+        try:
+            return super(CharsNotIn, self).__str__()
+        except Exception:
+            pass
+
+        if self.strRepr is None:
+            if len(self.notChars) > 4:
+                self.strRepr = "!W:(%s...)" % self.notChars[:4]
+            else:
+                self.strRepr = "!W:(%s)" % self.notChars
+
+        return self.strRepr
+
+class White(Token):
+    """
+    Special matching class for matching whitespace.  Normally, whitespace is ignored
+    by pyparsing grammars.  This class is included when some whitespace structures
+    are significant.  Define with a string containing the whitespace characters to be
+    matched; default is C{" \\t\\r\\n"}.  Also takes optional C{min}, C{max}, and C{exact} arguments,
+    as defined for the C{L{Word}} class.
+    """
+    whiteStrs = {
+        " " : "",
+        "\t": "",
+        "\n": "",
+        "\r": "",
+        "\f": "",
+        }
+    def __init__(self, ws=" \t\r\n", min=1, max=0, exact=0):
+        super(White,self).__init__()
+        self.matchWhite = ws
+        self.setWhitespaceChars( "".join(c for c in self.whiteChars if c not in self.matchWhite) )
+        #~ self.leaveWhitespace()
+        self.name = ("".join(White.whiteStrs[c] for c in self.matchWhite))
+        self.mayReturnEmpty = True
+        self.errmsg = "Expected " + self.name
+
+        self.minLen = min
+
+        if max > 0:
+            self.maxLen = max
+        else:
+            self.maxLen = _MAX_INT
+
+        if exact > 0:
+            self.maxLen = exact
+            self.minLen = exact
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if not(instring[ loc ] in self.matchWhite):
+            raise ParseException(instring, loc, self.errmsg, self)
+        start = loc
+        loc += 1
+        maxloc = start + self.maxLen
+        maxloc = min( maxloc, len(instring) )
+        while loc < maxloc and instring[loc] in self.matchWhite:
+            loc += 1
+
+        if loc - start < self.minLen:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        return loc, instring[start:loc]
+
+
+class _PositionToken(Token):
+    def __init__( self ):
+        super(_PositionToken,self).__init__()
+        self.name=self.__class__.__name__
+        self.mayReturnEmpty = True
+        self.mayIndexError = False
+
+class GoToColumn(_PositionToken):
+    """
+    Token to advance to a specific column of input text; useful for tabular report scraping.
+    """
+    def __init__( self, colno ):
+        super(GoToColumn,self).__init__()
+        self.col = colno
+
+    def preParse( self, instring, loc ):
+        if col(loc,instring) != self.col:
+            instrlen = len(instring)
+            if self.ignoreExprs:
+                loc = self._skipIgnorables( instring, loc )
+            while loc < instrlen and instring[loc].isspace() and col( loc, instring ) != self.col :
+                loc += 1
+        return loc
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        thiscol = col( loc, instring )
+        if thiscol > self.col:
+            raise ParseException( instring, loc, "Text not in expected column", self )
+        newloc = loc + self.col - thiscol
+        ret = instring[ loc: newloc ]
+        return newloc, ret
+
+
+class LineStart(_PositionToken):
+    """
+    Matches if current position is at the beginning of a line within the parse string
+    
+    Example::
+    
+        test = '''\
+        AAA this line
+        AAA and this line
+          AAA but not this one
+        B AAA and definitely not this one
+        '''
+
+        for t in (LineStart() + 'AAA' + restOfLine).searchString(test):
+            print(t)
+    
+    Prints::
+        ['AAA', ' this line']
+        ['AAA', ' and this line']    
+
+    """
+    def __init__( self ):
+        super(LineStart,self).__init__()
+        self.errmsg = "Expected start of line"
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if col(loc, instring) == 1:
+            return loc, []
+        raise ParseException(instring, loc, self.errmsg, self)
+
+class LineEnd(_PositionToken):
+    """
+    Matches if current position is at the end of a line within the parse string
+    """
+    def __init__( self ):
+        super(LineEnd,self).__init__()
+        self.setWhitespaceChars( ParserElement.DEFAULT_WHITE_CHARS.replace("\n","") )
+        self.errmsg = "Expected end of line"
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if loc len(instring):
+            return loc, []
+        else:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+class WordStart(_PositionToken):
+    """
+    Matches if the current position is at the beginning of a Word, and
+    is not preceded by any character in a given set of C{wordChars}
+    (default=C{printables}). To emulate the C{\b} behavior of regular expressions,
+    use C{WordStart(alphanums)}. C{WordStart} will also match at the beginning of
+    the string being parsed, or at the beginning of a line.
+    """
+    def __init__(self, wordChars = printables):
+        super(WordStart,self).__init__()
+        self.wordChars = set(wordChars)
+        self.errmsg = "Not at the start of a word"
+
+    def parseImpl(self, instring, loc, doActions=True ):
+        if loc != 0:
+            if (instring[loc-1] in self.wordChars or
+                instring[loc] not in self.wordChars):
+                raise ParseException(instring, loc, self.errmsg, self)
+        return loc, []
+
+class WordEnd(_PositionToken):
+    """
+    Matches if the current position is at the end of a Word, and
+    is not followed by any character in a given set of C{wordChars}
+    (default=C{printables}). To emulate the C{\b} behavior of regular expressions,
+    use C{WordEnd(alphanums)}. C{WordEnd} will also match at the end of
+    the string being parsed, or at the end of a line.
+    """
+    def __init__(self, wordChars = printables):
+        super(WordEnd,self).__init__()
+        self.wordChars = set(wordChars)
+        self.skipWhitespace = False
+        self.errmsg = "Not at the end of a word"
+
+    def parseImpl(self, instring, loc, doActions=True ):
+        instrlen = len(instring)
+        if instrlen>0 and loc maxExcLoc:
+                    maxException = err
+                    maxExcLoc = err.loc
+            except IndexError:
+                if len(instring) > maxExcLoc:
+                    maxException = ParseException(instring,len(instring),e.errmsg,self)
+                    maxExcLoc = len(instring)
+            else:
+                # save match among all matches, to retry longest to shortest
+                matches.append((loc2, e))
+
+        if matches:
+            matches.sort(key=lambda x: -x[0])
+            for _,e in matches:
+                try:
+                    return e._parse( instring, loc, doActions )
+                except ParseException as err:
+                    err.__traceback__ = None
+                    if err.loc > maxExcLoc:
+                        maxException = err
+                        maxExcLoc = err.loc
+
+        if maxException is not None:
+            maxException.msg = self.errmsg
+            raise maxException
+        else:
+            raise ParseException(instring, loc, "no defined alternatives to match", self)
+
+
+    def __ixor__(self, other ):
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        return self.append( other ) #Or( [ self, other ] )
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "{" + " ^ ".join(_ustr(e) for e in self.exprs) + "}"
+
+        return self.strRepr
+
+    def checkRecursion( self, parseElementList ):
+        subRecCheckList = parseElementList[:] + [ self ]
+        for e in self.exprs:
+            e.checkRecursion( subRecCheckList )
+
+
+class MatchFirst(ParseExpression):
+    """
+    Requires that at least one C{ParseExpression} is found.
+    If two expressions match, the first one listed is the one that will match.
+    May be constructed using the C{'|'} operator.
+
+    Example::
+        # construct MatchFirst using '|' operator
+        
+        # watch the order of expressions to match
+        number = Word(nums) | Combine(Word(nums) + '.' + Word(nums))
+        print(number.searchString("123 3.1416 789")) #  Fail! -> [['123'], ['3'], ['1416'], ['789']]
+
+        # put more selective expression first
+        number = Combine(Word(nums) + '.' + Word(nums)) | Word(nums)
+        print(number.searchString("123 3.1416 789")) #  Better -> [['123'], ['3.1416'], ['789']]
+    """
+    def __init__( self, exprs, savelist = False ):
+        super(MatchFirst,self).__init__(exprs, savelist)
+        if self.exprs:
+            self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs)
+        else:
+            self.mayReturnEmpty = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        maxExcLoc = -1
+        maxException = None
+        for e in self.exprs:
+            try:
+                ret = e._parse( instring, loc, doActions )
+                return ret
+            except ParseException as err:
+                if err.loc > maxExcLoc:
+                    maxException = err
+                    maxExcLoc = err.loc
+            except IndexError:
+                if len(instring) > maxExcLoc:
+                    maxException = ParseException(instring,len(instring),e.errmsg,self)
+                    maxExcLoc = len(instring)
+
+        # only got here if no expression matched, raise exception for match that made it the furthest
+        else:
+            if maxException is not None:
+                maxException.msg = self.errmsg
+                raise maxException
+            else:
+                raise ParseException(instring, loc, "no defined alternatives to match", self)
+
+    def __ior__(self, other ):
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        return self.append( other ) #MatchFirst( [ self, other ] )
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "{" + " | ".join(_ustr(e) for e in self.exprs) + "}"
+
+        return self.strRepr
+
+    def checkRecursion( self, parseElementList ):
+        subRecCheckList = parseElementList[:] + [ self ]
+        for e in self.exprs:
+            e.checkRecursion( subRecCheckList )
+
+
+class Each(ParseExpression):
+    """
+    Requires all given C{ParseExpression}s to be found, but in any order.
+    Expressions may be separated by whitespace.
+    May be constructed using the C{'&'} operator.
+
+    Example::
+        color = oneOf("RED ORANGE YELLOW GREEN BLUE PURPLE BLACK WHITE BROWN")
+        shape_type = oneOf("SQUARE CIRCLE TRIANGLE STAR HEXAGON OCTAGON")
+        integer = Word(nums)
+        shape_attr = "shape:" + shape_type("shape")
+        posn_attr = "posn:" + Group(integer("x") + ',' + integer("y"))("posn")
+        color_attr = "color:" + color("color")
+        size_attr = "size:" + integer("size")
+
+        # use Each (using operator '&') to accept attributes in any order 
+        # (shape and posn are required, color and size are optional)
+        shape_spec = shape_attr & posn_attr & Optional(color_attr) & Optional(size_attr)
+
+        shape_spec.runTests('''
+            shape: SQUARE color: BLACK posn: 100, 120
+            shape: CIRCLE size: 50 color: BLUE posn: 50,80
+            color:GREEN size:20 shape:TRIANGLE posn:20,40
+            '''
+            )
+    prints::
+        shape: SQUARE color: BLACK posn: 100, 120
+        ['shape:', 'SQUARE', 'color:', 'BLACK', 'posn:', ['100', ',', '120']]
+        - color: BLACK
+        - posn: ['100', ',', '120']
+          - x: 100
+          - y: 120
+        - shape: SQUARE
+
+
+        shape: CIRCLE size: 50 color: BLUE posn: 50,80
+        ['shape:', 'CIRCLE', 'size:', '50', 'color:', 'BLUE', 'posn:', ['50', ',', '80']]
+        - color: BLUE
+        - posn: ['50', ',', '80']
+          - x: 50
+          - y: 80
+        - shape: CIRCLE
+        - size: 50
+
+
+        color: GREEN size: 20 shape: TRIANGLE posn: 20,40
+        ['color:', 'GREEN', 'size:', '20', 'shape:', 'TRIANGLE', 'posn:', ['20', ',', '40']]
+        - color: GREEN
+        - posn: ['20', ',', '40']
+          - x: 20
+          - y: 40
+        - shape: TRIANGLE
+        - size: 20
+    """
+    def __init__( self, exprs, savelist = True ):
+        super(Each,self).__init__(exprs, savelist)
+        self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs)
+        self.skipWhitespace = True
+        self.initExprGroups = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if self.initExprGroups:
+            self.opt1map = dict((id(e.expr),e) for e in self.exprs if isinstance(e,Optional))
+            opt1 = [ e.expr for e in self.exprs if isinstance(e,Optional) ]
+            opt2 = [ e for e in self.exprs if e.mayReturnEmpty and not isinstance(e,Optional)]
+            self.optionals = opt1 + opt2
+            self.multioptionals = [ e.expr for e in self.exprs if isinstance(e,ZeroOrMore) ]
+            self.multirequired = [ e.expr for e in self.exprs if isinstance(e,OneOrMore) ]
+            self.required = [ e for e in self.exprs if not isinstance(e,(Optional,ZeroOrMore,OneOrMore)) ]
+            self.required += self.multirequired
+            self.initExprGroups = False
+        tmpLoc = loc
+        tmpReqd = self.required[:]
+        tmpOpt  = self.optionals[:]
+        matchOrder = []
+
+        keepMatching = True
+        while keepMatching:
+            tmpExprs = tmpReqd + tmpOpt + self.multioptionals + self.multirequired
+            failed = []
+            for e in tmpExprs:
+                try:
+                    tmpLoc = e.tryParse( instring, tmpLoc )
+                except ParseException:
+                    failed.append(e)
+                else:
+                    matchOrder.append(self.opt1map.get(id(e),e))
+                    if e in tmpReqd:
+                        tmpReqd.remove(e)
+                    elif e in tmpOpt:
+                        tmpOpt.remove(e)
+            if len(failed) == len(tmpExprs):
+                keepMatching = False
+
+        if tmpReqd:
+            missing = ", ".join(_ustr(e) for e in tmpReqd)
+            raise ParseException(instring,loc,"Missing one or more required elements (%s)" % missing )
+
+        # add any unmatched Optionals, in case they have default values defined
+        matchOrder += [e for e in self.exprs if isinstance(e,Optional) and e.expr in tmpOpt]
+
+        resultlist = []
+        for e in matchOrder:
+            loc,results = e._parse(instring,loc,doActions)
+            resultlist.append(results)
+
+        finalResults = sum(resultlist, ParseResults([]))
+        return loc, finalResults
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "{" + " & ".join(_ustr(e) for e in self.exprs) + "}"
+
+        return self.strRepr
+
+    def checkRecursion( self, parseElementList ):
+        subRecCheckList = parseElementList[:] + [ self ]
+        for e in self.exprs:
+            e.checkRecursion( subRecCheckList )
+
+
+class ParseElementEnhance(ParserElement):
+    """
+    Abstract subclass of C{ParserElement}, for combining and post-processing parsed tokens.
+    """
+    def __init__( self, expr, savelist=False ):
+        super(ParseElementEnhance,self).__init__(savelist)
+        if isinstance( expr, basestring ):
+            if issubclass(ParserElement._literalStringClass, Token):
+                expr = ParserElement._literalStringClass(expr)
+            else:
+                expr = ParserElement._literalStringClass(Literal(expr))
+        self.expr = expr
+        self.strRepr = None
+        if expr is not None:
+            self.mayIndexError = expr.mayIndexError
+            self.mayReturnEmpty = expr.mayReturnEmpty
+            self.setWhitespaceChars( expr.whiteChars )
+            self.skipWhitespace = expr.skipWhitespace
+            self.saveAsList = expr.saveAsList
+            self.callPreparse = expr.callPreparse
+            self.ignoreExprs.extend(expr.ignoreExprs)
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if self.expr is not None:
+            return self.expr._parse( instring, loc, doActions, callPreParse=False )
+        else:
+            raise ParseException("",loc,self.errmsg,self)
+
+    def leaveWhitespace( self ):
+        self.skipWhitespace = False
+        self.expr = self.expr.copy()
+        if self.expr is not None:
+            self.expr.leaveWhitespace()
+        return self
+
+    def ignore( self, other ):
+        if isinstance( other, Suppress ):
+            if other not in self.ignoreExprs:
+                super( ParseElementEnhance, self).ignore( other )
+                if self.expr is not None:
+                    self.expr.ignore( self.ignoreExprs[-1] )
+        else:
+            super( ParseElementEnhance, self).ignore( other )
+            if self.expr is not None:
+                self.expr.ignore( self.ignoreExprs[-1] )
+        return self
+
+    def streamline( self ):
+        super(ParseElementEnhance,self).streamline()
+        if self.expr is not None:
+            self.expr.streamline()
+        return self
+
+    def checkRecursion( self, parseElementList ):
+        if self in parseElementList:
+            raise RecursiveGrammarException( parseElementList+[self] )
+        subRecCheckList = parseElementList[:] + [ self ]
+        if self.expr is not None:
+            self.expr.checkRecursion( subRecCheckList )
+
+    def validate( self, validateTrace=[] ):
+        tmp = validateTrace[:]+[self]
+        if self.expr is not None:
+            self.expr.validate(tmp)
+        self.checkRecursion( [] )
+
+    def __str__( self ):
+        try:
+            return super(ParseElementEnhance,self).__str__()
+        except Exception:
+            pass
+
+        if self.strRepr is None and self.expr is not None:
+            self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.expr) )
+        return self.strRepr
+
+
+class FollowedBy(ParseElementEnhance):
+    """
+    Lookahead matching of the given parse expression.  C{FollowedBy}
+    does I{not} advance the parsing position within the input string, it only
+    verifies that the specified parse expression matches at the current
+    position.  C{FollowedBy} always returns a null token list.
+
+    Example::
+        # use FollowedBy to match a label only if it is followed by a ':'
+        data_word = Word(alphas)
+        label = data_word + FollowedBy(':')
+        attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
+        
+        OneOrMore(attr_expr).parseString("shape: SQUARE color: BLACK posn: upper left").pprint()
+    prints::
+        [['shape', 'SQUARE'], ['color', 'BLACK'], ['posn', 'upper left']]
+    """
+    def __init__( self, expr ):
+        super(FollowedBy,self).__init__(expr)
+        self.mayReturnEmpty = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        self.expr.tryParse( instring, loc )
+        return loc, []
+
+
+class NotAny(ParseElementEnhance):
+    """
+    Lookahead to disallow matching with the given parse expression.  C{NotAny}
+    does I{not} advance the parsing position within the input string, it only
+    verifies that the specified parse expression does I{not} match at the current
+    position.  Also, C{NotAny} does I{not} skip over leading whitespace. C{NotAny}
+    always returns a null token list.  May be constructed using the '~' operator.
+
+    Example::
+        
+    """
+    def __init__( self, expr ):
+        super(NotAny,self).__init__(expr)
+        #~ self.leaveWhitespace()
+        self.skipWhitespace = False  # do NOT use self.leaveWhitespace(), don't want to propagate to exprs
+        self.mayReturnEmpty = True
+        self.errmsg = "Found unwanted token, "+_ustr(self.expr)
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if self.expr.canParseNext(instring, loc):
+            raise ParseException(instring, loc, self.errmsg, self)
+        return loc, []
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "~{" + _ustr(self.expr) + "}"
+
+        return self.strRepr
+
+class _MultipleMatch(ParseElementEnhance):
+    def __init__( self, expr, stopOn=None):
+        super(_MultipleMatch, self).__init__(expr)
+        self.saveAsList = True
+        ender = stopOn
+        if isinstance(ender, basestring):
+            ender = ParserElement._literalStringClass(ender)
+        self.not_ender = ~ender if ender is not None else None
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        self_expr_parse = self.expr._parse
+        self_skip_ignorables = self._skipIgnorables
+        check_ender = self.not_ender is not None
+        if check_ender:
+            try_not_ender = self.not_ender.tryParse
+        
+        # must be at least one (but first see if we are the stopOn sentinel;
+        # if so, fail)
+        if check_ender:
+            try_not_ender(instring, loc)
+        loc, tokens = self_expr_parse( instring, loc, doActions, callPreParse=False )
+        try:
+            hasIgnoreExprs = (not not self.ignoreExprs)
+            while 1:
+                if check_ender:
+                    try_not_ender(instring, loc)
+                if hasIgnoreExprs:
+                    preloc = self_skip_ignorables( instring, loc )
+                else:
+                    preloc = loc
+                loc, tmptokens = self_expr_parse( instring, preloc, doActions )
+                if tmptokens or tmptokens.haskeys():
+                    tokens += tmptokens
+        except (ParseException,IndexError):
+            pass
+
+        return loc, tokens
+        
+class OneOrMore(_MultipleMatch):
+    """
+    Repetition of one or more of the given expression.
+    
+    Parameters:
+     - expr - expression that must match one or more times
+     - stopOn - (default=C{None}) - expression for a terminating sentinel
+          (only required if the sentinel would ordinarily match the repetition 
+          expression)          
+
+    Example::
+        data_word = Word(alphas)
+        label = data_word + FollowedBy(':')
+        attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).setParseAction(' '.join))
+
+        text = "shape: SQUARE posn: upper left color: BLACK"
+        OneOrMore(attr_expr).parseString(text).pprint()  # Fail! read 'color' as data instead of next label -> [['shape', 'SQUARE color']]
+
+        # use stopOn attribute for OneOrMore to avoid reading label string as part of the data
+        attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
+        OneOrMore(attr_expr).parseString(text).pprint() # Better -> [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'BLACK']]
+        
+        # could also be written as
+        (attr_expr * (1,)).parseString(text).pprint()
+    """
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "{" + _ustr(self.expr) + "}..."
+
+        return self.strRepr
+
+class ZeroOrMore(_MultipleMatch):
+    """
+    Optional repetition of zero or more of the given expression.
+    
+    Parameters:
+     - expr - expression that must match zero or more times
+     - stopOn - (default=C{None}) - expression for a terminating sentinel
+          (only required if the sentinel would ordinarily match the repetition 
+          expression)          
+
+    Example: similar to L{OneOrMore}
+    """
+    def __init__( self, expr, stopOn=None):
+        super(ZeroOrMore,self).__init__(expr, stopOn=stopOn)
+        self.mayReturnEmpty = True
+        
+    def parseImpl( self, instring, loc, doActions=True ):
+        try:
+            return super(ZeroOrMore, self).parseImpl(instring, loc, doActions)
+        except (ParseException,IndexError):
+            return loc, []
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "[" + _ustr(self.expr) + "]..."
+
+        return self.strRepr
+
+class _NullToken(object):
+    def __bool__(self):
+        return False
+    __nonzero__ = __bool__
+    def __str__(self):
+        return ""
+
+_optionalNotMatched = _NullToken()
+class Optional(ParseElementEnhance):
+    """
+    Optional matching of the given expression.
+
+    Parameters:
+     - expr - expression that must match zero or more times
+     - default (optional) - value to be returned if the optional expression is not found.
+
+    Example::
+        # US postal code can be a 5-digit zip, plus optional 4-digit qualifier
+        zip = Combine(Word(nums, exact=5) + Optional('-' + Word(nums, exact=4)))
+        zip.runTests('''
+            # traditional ZIP code
+            12345
+            
+            # ZIP+4 form
+            12101-0001
+            
+            # invalid ZIP
+            98765-
+            ''')
+    prints::
+        # traditional ZIP code
+        12345
+        ['12345']
+
+        # ZIP+4 form
+        12101-0001
+        ['12101-0001']
+
+        # invalid ZIP
+        98765-
+             ^
+        FAIL: Expected end of text (at char 5), (line:1, col:6)
+    """
+    def __init__( self, expr, default=_optionalNotMatched ):
+        super(Optional,self).__init__( expr, savelist=False )
+        self.saveAsList = self.expr.saveAsList
+        self.defaultValue = default
+        self.mayReturnEmpty = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        try:
+            loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False )
+        except (ParseException,IndexError):
+            if self.defaultValue is not _optionalNotMatched:
+                if self.expr.resultsName:
+                    tokens = ParseResults([ self.defaultValue ])
+                    tokens[self.expr.resultsName] = self.defaultValue
+                else:
+                    tokens = [ self.defaultValue ]
+            else:
+                tokens = []
+        return loc, tokens
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "[" + _ustr(self.expr) + "]"
+
+        return self.strRepr
+
+class SkipTo(ParseElementEnhance):
+    """
+    Token for skipping over all undefined text until the matched expression is found.
+
+    Parameters:
+     - expr - target expression marking the end of the data to be skipped
+     - include - (default=C{False}) if True, the target expression is also parsed 
+          (the skipped text and target expression are returned as a 2-element list).
+     - ignore - (default=C{None}) used to define grammars (typically quoted strings and 
+          comments) that might contain false matches to the target expression
+     - failOn - (default=C{None}) define expressions that are not allowed to be 
+          included in the skipped test; if found before the target expression is found, 
+          the SkipTo is not a match
+
+    Example::
+        report = '''
+            Outstanding Issues Report - 1 Jan 2000
+
+               # | Severity | Description                               |  Days Open
+            -----+----------+-------------------------------------------+-----------
+             101 | Critical | Intermittent system crash                 |          6
+              94 | Cosmetic | Spelling error on Login ('log|n')         |         14
+              79 | Minor    | System slow when running too many reports |         47
+            '''
+        integer = Word(nums)
+        SEP = Suppress('|')
+        # use SkipTo to simply match everything up until the next SEP
+        # - ignore quoted strings, so that a '|' character inside a quoted string does not match
+        # - parse action will call token.strip() for each matched token, i.e., the description body
+        string_data = SkipTo(SEP, ignore=quotedString)
+        string_data.setParseAction(tokenMap(str.strip))
+        ticket_expr = (integer("issue_num") + SEP 
+                      + string_data("sev") + SEP 
+                      + string_data("desc") + SEP 
+                      + integer("days_open"))
+        
+        for tkt in ticket_expr.searchString(report):
+            print tkt.dump()
+    prints::
+        ['101', 'Critical', 'Intermittent system crash', '6']
+        - days_open: 6
+        - desc: Intermittent system crash
+        - issue_num: 101
+        - sev: Critical
+        ['94', 'Cosmetic', "Spelling error on Login ('log|n')", '14']
+        - days_open: 14
+        - desc: Spelling error on Login ('log|n')
+        - issue_num: 94
+        - sev: Cosmetic
+        ['79', 'Minor', 'System slow when running too many reports', '47']
+        - days_open: 47
+        - desc: System slow when running too many reports
+        - issue_num: 79
+        - sev: Minor
+    """
+    def __init__( self, other, include=False, ignore=None, failOn=None ):
+        super( SkipTo, self ).__init__( other )
+        self.ignoreExpr = ignore
+        self.mayReturnEmpty = True
+        self.mayIndexError = False
+        self.includeMatch = include
+        self.asList = False
+        if isinstance(failOn, basestring):
+            self.failOn = ParserElement._literalStringClass(failOn)
+        else:
+            self.failOn = failOn
+        self.errmsg = "No match found for "+_ustr(self.expr)
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        startloc = loc
+        instrlen = len(instring)
+        expr = self.expr
+        expr_parse = self.expr._parse
+        self_failOn_canParseNext = self.failOn.canParseNext if self.failOn is not None else None
+        self_ignoreExpr_tryParse = self.ignoreExpr.tryParse if self.ignoreExpr is not None else None
+        
+        tmploc = loc
+        while tmploc <= instrlen:
+            if self_failOn_canParseNext is not None:
+                # break if failOn expression matches
+                if self_failOn_canParseNext(instring, tmploc):
+                    break
+                    
+            if self_ignoreExpr_tryParse is not None:
+                # advance past ignore expressions
+                while 1:
+                    try:
+                        tmploc = self_ignoreExpr_tryParse(instring, tmploc)
+                    except ParseBaseException:
+                        break
+            
+            try:
+                expr_parse(instring, tmploc, doActions=False, callPreParse=False)
+            except (ParseException, IndexError):
+                # no match, advance loc in string
+                tmploc += 1
+            else:
+                # matched skipto expr, done
+                break
+
+        else:
+            # ran off the end of the input string without matching skipto expr, fail
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        # build up return values
+        loc = tmploc
+        skiptext = instring[startloc:loc]
+        skipresult = ParseResults(skiptext)
+        
+        if self.includeMatch:
+            loc, mat = expr_parse(instring,loc,doActions,callPreParse=False)
+            skipresult += mat
+
+        return loc, skipresult
+
+class Forward(ParseElementEnhance):
+    """
+    Forward declaration of an expression to be defined later -
+    used for recursive grammars, such as algebraic infix notation.
+    When the expression is known, it is assigned to the C{Forward} variable using the '<<' operator.
+
+    Note: take care when assigning to C{Forward} not to overlook precedence of operators.
+    Specifically, '|' has a lower precedence than '<<', so that::
+        fwdExpr << a | b | c
+    will actually be evaluated as::
+        (fwdExpr << a) | b | c
+    thereby leaving b and c out as parseable alternatives.  It is recommended that you
+    explicitly group the values inserted into the C{Forward}::
+        fwdExpr << (a | b | c)
+    Converting to use the '<<=' operator instead will avoid this problem.
+
+    See L{ParseResults.pprint} for an example of a recursive parser created using
+    C{Forward}.
+    """
+    def __init__( self, other=None ):
+        super(Forward,self).__init__( other, savelist=False )
+
+    def __lshift__( self, other ):
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass(other)
+        self.expr = other
+        self.strRepr = None
+        self.mayIndexError = self.expr.mayIndexError
+        self.mayReturnEmpty = self.expr.mayReturnEmpty
+        self.setWhitespaceChars( self.expr.whiteChars )
+        self.skipWhitespace = self.expr.skipWhitespace
+        self.saveAsList = self.expr.saveAsList
+        self.ignoreExprs.extend(self.expr.ignoreExprs)
+        return self
+        
+    def __ilshift__(self, other):
+        return self << other
+    
+    def leaveWhitespace( self ):
+        self.skipWhitespace = False
+        return self
+
+    def streamline( self ):
+        if not self.streamlined:
+            self.streamlined = True
+            if self.expr is not None:
+                self.expr.streamline()
+        return self
+
+    def validate( self, validateTrace=[] ):
+        if self not in validateTrace:
+            tmp = validateTrace[:]+[self]
+            if self.expr is not None:
+                self.expr.validate(tmp)
+        self.checkRecursion([])
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+        return self.__class__.__name__ + ": ..."
+
+        # stubbed out for now - creates awful memory and perf issues
+        self._revertClass = self.__class__
+        self.__class__ = _ForwardNoRecurse
+        try:
+            if self.expr is not None:
+                retString = _ustr(self.expr)
+            else:
+                retString = "None"
+        finally:
+            self.__class__ = self._revertClass
+        return self.__class__.__name__ + ": " + retString
+
+    def copy(self):
+        if self.expr is not None:
+            return super(Forward,self).copy()
+        else:
+            ret = Forward()
+            ret <<= self
+            return ret
+
+class _ForwardNoRecurse(Forward):
+    def __str__( self ):
+        return "..."
+
+class TokenConverter(ParseElementEnhance):
+    """
+    Abstract subclass of C{ParseExpression}, for converting parsed results.
+    """
+    def __init__( self, expr, savelist=False ):
+        super(TokenConverter,self).__init__( expr )#, savelist )
+        self.saveAsList = False
+
+class Combine(TokenConverter):
+    """
+    Converter to concatenate all matching tokens to a single string.
+    By default, the matching patterns must also be contiguous in the input string;
+    this can be disabled by specifying C{'adjacent=False'} in the constructor.
+
+    Example::
+        real = Word(nums) + '.' + Word(nums)
+        print(real.parseString('3.1416')) # -> ['3', '.', '1416']
+        # will also erroneously match the following
+        print(real.parseString('3. 1416')) # -> ['3', '.', '1416']
+
+        real = Combine(Word(nums) + '.' + Word(nums))
+        print(real.parseString('3.1416')) # -> ['3.1416']
+        # no match when there are internal spaces
+        print(real.parseString('3. 1416')) # -> Exception: Expected W:(0123...)
+    """
+    def __init__( self, expr, joinString="", adjacent=True ):
+        super(Combine,self).__init__( expr )
+        # suppress whitespace-stripping in contained parse expressions, but re-enable it on the Combine itself
+        if adjacent:
+            self.leaveWhitespace()
+        self.adjacent = adjacent
+        self.skipWhitespace = True
+        self.joinString = joinString
+        self.callPreparse = True
+
+    def ignore( self, other ):
+        if self.adjacent:
+            ParserElement.ignore(self, other)
+        else:
+            super( Combine, self).ignore( other )
+        return self
+
+    def postParse( self, instring, loc, tokenlist ):
+        retToks = tokenlist.copy()
+        del retToks[:]
+        retToks += ParseResults([ "".join(tokenlist._asStringList(self.joinString)) ], modal=self.modalResults)
+
+        if self.resultsName and retToks.haskeys():
+            return [ retToks ]
+        else:
+            return retToks
+
+class Group(TokenConverter):
+    """
+    Converter to return the matched tokens as a list - useful for returning tokens of C{L{ZeroOrMore}} and C{L{OneOrMore}} expressions.
+
+    Example::
+        ident = Word(alphas)
+        num = Word(nums)
+        term = ident | num
+        func = ident + Optional(delimitedList(term))
+        print(func.parseString("fn a,b,100"))  # -> ['fn', 'a', 'b', '100']
+
+        func = ident + Group(Optional(delimitedList(term)))
+        print(func.parseString("fn a,b,100"))  # -> ['fn', ['a', 'b', '100']]
+    """
+    def __init__( self, expr ):
+        super(Group,self).__init__( expr )
+        self.saveAsList = True
+
+    def postParse( self, instring, loc, tokenlist ):
+        return [ tokenlist ]
+
+class Dict(TokenConverter):
+    """
+    Converter to return a repetitive expression as a list, but also as a dictionary.
+    Each element can also be referenced using the first token in the expression as its key.
+    Useful for tabular report scraping when the first column can be used as a item key.
+
+    Example::
+        data_word = Word(alphas)
+        label = data_word + FollowedBy(':')
+        attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).setParseAction(' '.join))
+
+        text = "shape: SQUARE posn: upper left color: light blue texture: burlap"
+        attr_expr = (label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
+        
+        # print attributes as plain groups
+        print(OneOrMore(attr_expr).parseString(text).dump())
+        
+        # instead of OneOrMore(expr), parse using Dict(OneOrMore(Group(expr))) - Dict will auto-assign names
+        result = Dict(OneOrMore(Group(attr_expr))).parseString(text)
+        print(result.dump())
+        
+        # access named fields as dict entries, or output as dict
+        print(result['shape'])        
+        print(result.asDict())
+    prints::
+        ['shape', 'SQUARE', 'posn', 'upper left', 'color', 'light blue', 'texture', 'burlap']
+
+        [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']]
+        - color: light blue
+        - posn: upper left
+        - shape: SQUARE
+        - texture: burlap
+        SQUARE
+        {'color': 'light blue', 'posn': 'upper left', 'texture': 'burlap', 'shape': 'SQUARE'}
+    See more examples at L{ParseResults} of accessing fields by results name.
+    """
+    def __init__( self, expr ):
+        super(Dict,self).__init__( expr )
+        self.saveAsList = True
+
+    def postParse( self, instring, loc, tokenlist ):
+        for i,tok in enumerate(tokenlist):
+            if len(tok) == 0:
+                continue
+            ikey = tok[0]
+            if isinstance(ikey,int):
+                ikey = _ustr(tok[0]).strip()
+            if len(tok)==1:
+                tokenlist[ikey] = _ParseResultsWithOffset("",i)
+            elif len(tok)==2 and not isinstance(tok[1],ParseResults):
+                tokenlist[ikey] = _ParseResultsWithOffset(tok[1],i)
+            else:
+                dictvalue = tok.copy() #ParseResults(i)
+                del dictvalue[0]
+                if len(dictvalue)!= 1 or (isinstance(dictvalue,ParseResults) and dictvalue.haskeys()):
+                    tokenlist[ikey] = _ParseResultsWithOffset(dictvalue,i)
+                else:
+                    tokenlist[ikey] = _ParseResultsWithOffset(dictvalue[0],i)
+
+        if self.resultsName:
+            return [ tokenlist ]
+        else:
+            return tokenlist
+
+
+class Suppress(TokenConverter):
+    """
+    Converter for ignoring the results of a parsed expression.
+
+    Example::
+        source = "a, b, c,d"
+        wd = Word(alphas)
+        wd_list1 = wd + ZeroOrMore(',' + wd)
+        print(wd_list1.parseString(source))
+
+        # often, delimiters that are useful during parsing are just in the
+        # way afterward - use Suppress to keep them out of the parsed output
+        wd_list2 = wd + ZeroOrMore(Suppress(',') + wd)
+        print(wd_list2.parseString(source))
+    prints::
+        ['a', ',', 'b', ',', 'c', ',', 'd']
+        ['a', 'b', 'c', 'd']
+    (See also L{delimitedList}.)
+    """
+    def postParse( self, instring, loc, tokenlist ):
+        return []
+
+    def suppress( self ):
+        return self
+
+
+class OnlyOnce(object):
+    """
+    Wrapper for parse actions, to ensure they are only called once.
+    """
+    def __init__(self, methodCall):
+        self.callable = _trim_arity(methodCall)
+        self.called = False
+    def __call__(self,s,l,t):
+        if not self.called:
+            results = self.callable(s,l,t)
+            self.called = True
+            return results
+        raise ParseException(s,l,"")
+    def reset(self):
+        self.called = False
+
+def traceParseAction(f):
+    """
+    Decorator for debugging parse actions. 
+    
+    When the parse action is called, this decorator will print C{">> entering I{method-name}(line:I{current_source_line}, I{parse_location}, I{matched_tokens})".}
+    When the parse action completes, the decorator will print C{"<<"} followed by the returned value, or any exception that the parse action raised.
+
+    Example::
+        wd = Word(alphas)
+
+        @traceParseAction
+        def remove_duplicate_chars(tokens):
+            return ''.join(sorted(set(''.join(tokens))))
+
+        wds = OneOrMore(wd).setParseAction(remove_duplicate_chars)
+        print(wds.parseString("slkdjs sld sldd sdlf sdljf"))
+    prints::
+        >>entering remove_duplicate_chars(line: 'slkdjs sld sldd sdlf sdljf', 0, (['slkdjs', 'sld', 'sldd', 'sdlf', 'sdljf'], {}))
+        <3:
+            thisFunc = paArgs[0].__class__.__name__ + '.' + thisFunc
+        sys.stderr.write( ">>entering %s(line: '%s', %d, %r)\n" % (thisFunc,line(l,s),l,t) )
+        try:
+            ret = f(*paArgs)
+        except Exception as exc:
+            sys.stderr.write( "< ['aa', 'bb', 'cc']
+        delimitedList(Word(hexnums), delim=':', combine=True).parseString("AA:BB:CC:DD:EE") # -> ['AA:BB:CC:DD:EE']
+    """
+    dlName = _ustr(expr)+" ["+_ustr(delim)+" "+_ustr(expr)+"]..."
+    if combine:
+        return Combine( expr + ZeroOrMore( delim + expr ) ).setName(dlName)
+    else:
+        return ( expr + ZeroOrMore( Suppress( delim ) + expr ) ).setName(dlName)
+
+def countedArray( expr, intExpr=None ):
+    """
+    Helper to define a counted list of expressions.
+    This helper defines a pattern of the form::
+        integer expr expr expr...
+    where the leading integer tells how many expr expressions follow.
+    The matched tokens returns the array of expr tokens as a list - the leading count token is suppressed.
+    
+    If C{intExpr} is specified, it should be a pyparsing expression that produces an integer value.
+
+    Example::
+        countedArray(Word(alphas)).parseString('2 ab cd ef')  # -> ['ab', 'cd']
+
+        # in this parser, the leading integer value is given in binary,
+        # '10' indicating that 2 values are in the array
+        binaryConstant = Word('01').setParseAction(lambda t: int(t[0], 2))
+        countedArray(Word(alphas), intExpr=binaryConstant).parseString('10 ab cd ef')  # -> ['ab', 'cd']
+    """
+    arrayExpr = Forward()
+    def countFieldParseAction(s,l,t):
+        n = t[0]
+        arrayExpr << (n and Group(And([expr]*n)) or Group(empty))
+        return []
+    if intExpr is None:
+        intExpr = Word(nums).setParseAction(lambda t:int(t[0]))
+    else:
+        intExpr = intExpr.copy()
+    intExpr.setName("arrayLen")
+    intExpr.addParseAction(countFieldParseAction, callDuringTry=True)
+    return ( intExpr + arrayExpr ).setName('(len) ' + _ustr(expr) + '...')
+
+def _flatten(L):
+    ret = []
+    for i in L:
+        if isinstance(i,list):
+            ret.extend(_flatten(i))
+        else:
+            ret.append(i)
+    return ret
+
+def matchPreviousLiteral(expr):
+    """
+    Helper to define an expression that is indirectly defined from
+    the tokens matched in a previous expression, that is, it looks
+    for a 'repeat' of a previous expression.  For example::
+        first = Word(nums)
+        second = matchPreviousLiteral(first)
+        matchExpr = first + ":" + second
+    will match C{"1:1"}, but not C{"1:2"}.  Because this matches a
+    previous literal, will also match the leading C{"1:1"} in C{"1:10"}.
+    If this is not desired, use C{matchPreviousExpr}.
+    Do I{not} use with packrat parsing enabled.
+    """
+    rep = Forward()
+    def copyTokenToRepeater(s,l,t):
+        if t:
+            if len(t) == 1:
+                rep << t[0]
+            else:
+                # flatten t tokens
+                tflat = _flatten(t.asList())
+                rep << And(Literal(tt) for tt in tflat)
+        else:
+            rep << Empty()
+    expr.addParseAction(copyTokenToRepeater, callDuringTry=True)
+    rep.setName('(prev) ' + _ustr(expr))
+    return rep
+
+def matchPreviousExpr(expr):
+    """
+    Helper to define an expression that is indirectly defined from
+    the tokens matched in a previous expression, that is, it looks
+    for a 'repeat' of a previous expression.  For example::
+        first = Word(nums)
+        second = matchPreviousExpr(first)
+        matchExpr = first + ":" + second
+    will match C{"1:1"}, but not C{"1:2"}.  Because this matches by
+    expressions, will I{not} match the leading C{"1:1"} in C{"1:10"};
+    the expressions are evaluated first, and then compared, so
+    C{"1"} is compared with C{"10"}.
+    Do I{not} use with packrat parsing enabled.
+    """
+    rep = Forward()
+    e2 = expr.copy()
+    rep <<= e2
+    def copyTokenToRepeater(s,l,t):
+        matchTokens = _flatten(t.asList())
+        def mustMatchTheseTokens(s,l,t):
+            theseTokens = _flatten(t.asList())
+            if  theseTokens != matchTokens:
+                raise ParseException("",0,"")
+        rep.setParseAction( mustMatchTheseTokens, callDuringTry=True )
+    expr.addParseAction(copyTokenToRepeater, callDuringTry=True)
+    rep.setName('(prev) ' + _ustr(expr))
+    return rep
+
+def _escapeRegexRangeChars(s):
+    #~  escape these chars: ^-]
+    for c in r"\^-]":
+        s = s.replace(c,_bslash+c)
+    s = s.replace("\n",r"\n")
+    s = s.replace("\t",r"\t")
+    return _ustr(s)
+
+def oneOf( strs, caseless=False, useRegex=True ):
+    """
+    Helper to quickly define a set of alternative Literals, and makes sure to do
+    longest-first testing when there is a conflict, regardless of the input order,
+    but returns a C{L{MatchFirst}} for best performance.
+
+    Parameters:
+     - strs - a string of space-delimited literals, or a collection of string literals
+     - caseless - (default=C{False}) - treat all literals as caseless
+     - useRegex - (default=C{True}) - as an optimization, will generate a Regex
+          object; otherwise, will generate a C{MatchFirst} object (if C{caseless=True}, or
+          if creating a C{Regex} raises an exception)
+
+    Example::
+        comp_oper = oneOf("< = > <= >= !=")
+        var = Word(alphas)
+        number = Word(nums)
+        term = var | number
+        comparison_expr = term + comp_oper + term
+        print(comparison_expr.searchString("B = 12  AA=23 B<=AA AA>12"))
+    prints::
+        [['B', '=', '12'], ['AA', '=', '23'], ['B', '<=', 'AA'], ['AA', '>', '12']]
+    """
+    if caseless:
+        isequal = ( lambda a,b: a.upper() == b.upper() )
+        masks = ( lambda a,b: b.upper().startswith(a.upper()) )
+        parseElementClass = CaselessLiteral
+    else:
+        isequal = ( lambda a,b: a == b )
+        masks = ( lambda a,b: b.startswith(a) )
+        parseElementClass = Literal
+
+    symbols = []
+    if isinstance(strs,basestring):
+        symbols = strs.split()
+    elif isinstance(strs, Iterable):
+        symbols = list(strs)
+    else:
+        warnings.warn("Invalid argument to oneOf, expected string or iterable",
+                SyntaxWarning, stacklevel=2)
+    if not symbols:
+        return NoMatch()
+
+    i = 0
+    while i < len(symbols)-1:
+        cur = symbols[i]
+        for j,other in enumerate(symbols[i+1:]):
+            if ( isequal(other, cur) ):
+                del symbols[i+j+1]
+                break
+            elif ( masks(cur, other) ):
+                del symbols[i+j+1]
+                symbols.insert(i,other)
+                cur = other
+                break
+        else:
+            i += 1
+
+    if not caseless and useRegex:
+        #~ print (strs,"->", "|".join( [ _escapeRegexChars(sym) for sym in symbols] ))
+        try:
+            if len(symbols)==len("".join(symbols)):
+                return Regex( "[%s]" % "".join(_escapeRegexRangeChars(sym) for sym in symbols) ).setName(' | '.join(symbols))
+            else:
+                return Regex( "|".join(re.escape(sym) for sym in symbols) ).setName(' | '.join(symbols))
+        except Exception:
+            warnings.warn("Exception creating Regex for oneOf, building MatchFirst",
+                    SyntaxWarning, stacklevel=2)
+
+
+    # last resort, just use MatchFirst
+    return MatchFirst(parseElementClass(sym) for sym in symbols).setName(' | '.join(symbols))
+
+def dictOf( key, value ):
+    """
+    Helper to easily and clearly define a dictionary by specifying the respective patterns
+    for the key and value.  Takes care of defining the C{L{Dict}}, C{L{ZeroOrMore}}, and C{L{Group}} tokens
+    in the proper order.  The key pattern can include delimiting markers or punctuation,
+    as long as they are suppressed, thereby leaving the significant key text.  The value
+    pattern can include named results, so that the C{Dict} results can include named token
+    fields.
+
+    Example::
+        text = "shape: SQUARE posn: upper left color: light blue texture: burlap"
+        attr_expr = (label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
+        print(OneOrMore(attr_expr).parseString(text).dump())
+        
+        attr_label = label
+        attr_value = Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join)
+
+        # similar to Dict, but simpler call format
+        result = dictOf(attr_label, attr_value).parseString(text)
+        print(result.dump())
+        print(result['shape'])
+        print(result.shape)  # object attribute access works too
+        print(result.asDict())
+    prints::
+        [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']]
+        - color: light blue
+        - posn: upper left
+        - shape: SQUARE
+        - texture: burlap
+        SQUARE
+        SQUARE
+        {'color': 'light blue', 'shape': 'SQUARE', 'posn': 'upper left', 'texture': 'burlap'}
+    """
+    return Dict( ZeroOrMore( Group ( key + value ) ) )
+
+def originalTextFor(expr, asString=True):
+    """
+    Helper to return the original, untokenized text for a given expression.  Useful to
+    restore the parsed fields of an HTML start tag into the raw tag text itself, or to
+    revert separate tokens with intervening whitespace back to the original matching
+    input text. By default, returns astring containing the original parsed text.  
+       
+    If the optional C{asString} argument is passed as C{False}, then the return value is a 
+    C{L{ParseResults}} containing any results names that were originally matched, and a 
+    single token containing the original matched text from the input string.  So if 
+    the expression passed to C{L{originalTextFor}} contains expressions with defined
+    results names, you must set C{asString} to C{False} if you want to preserve those
+    results name values.
+
+    Example::
+        src = "this is test  bold text  normal text "
+        for tag in ("b","i"):
+            opener,closer = makeHTMLTags(tag)
+            patt = originalTextFor(opener + SkipTo(closer) + closer)
+            print(patt.searchString(src)[0])
+    prints::
+        [' bold text ']
+        ['text']
+    """
+    locMarker = Empty().setParseAction(lambda s,loc,t: loc)
+    endlocMarker = locMarker.copy()
+    endlocMarker.callPreparse = False
+    matchExpr = locMarker("_original_start") + expr + endlocMarker("_original_end")
+    if asString:
+        extractText = lambda s,l,t: s[t._original_start:t._original_end]
+    else:
+        def extractText(s,l,t):
+            t[:] = [s[t.pop('_original_start'):t.pop('_original_end')]]
+    matchExpr.setParseAction(extractText)
+    matchExpr.ignoreExprs = expr.ignoreExprs
+    return matchExpr
+
+def ungroup(expr): 
+    """
+    Helper to undo pyparsing's default grouping of And expressions, even
+    if all but one are non-empty.
+    """
+    return TokenConverter(expr).setParseAction(lambda t:t[0])
+
+def locatedExpr(expr):
+    """
+    Helper to decorate a returned token with its starting and ending locations in the input string.
+    This helper adds the following results names:
+     - locn_start = location where matched expression begins
+     - locn_end = location where matched expression ends
+     - value = the actual parsed results
+
+    Be careful if the input text contains C{} characters, you may want to call
+    C{L{ParserElement.parseWithTabs}}
+
+    Example::
+        wd = Word(alphas)
+        for match in locatedExpr(wd).searchString("ljsdf123lksdjjf123lkkjj1222"):
+            print(match)
+    prints::
+        [[0, 'ljsdf', 5]]
+        [[8, 'lksdjjf', 15]]
+        [[18, 'lkkjj', 23]]
+    """
+    locator = Empty().setParseAction(lambda s,l,t: l)
+    return Group(locator("locn_start") + expr("value") + locator.copy().leaveWhitespace()("locn_end"))
+
+
+# convenience constants for positional expressions
+empty       = Empty().setName("empty")
+lineStart   = LineStart().setName("lineStart")
+lineEnd     = LineEnd().setName("lineEnd")
+stringStart = StringStart().setName("stringStart")
+stringEnd   = StringEnd().setName("stringEnd")
+
+_escapedPunc = Word( _bslash, r"\[]-*.$+^?()~ ", exact=2 ).setParseAction(lambda s,l,t:t[0][1])
+_escapedHexChar = Regex(r"\\0?[xX][0-9a-fA-F]+").setParseAction(lambda s,l,t:unichr(int(t[0].lstrip(r'\0x'),16)))
+_escapedOctChar = Regex(r"\\0[0-7]+").setParseAction(lambda s,l,t:unichr(int(t[0][1:],8)))
+_singleChar = _escapedPunc | _escapedHexChar | _escapedOctChar | CharsNotIn(r'\]', exact=1)
+_charRange = Group(_singleChar + Suppress("-") + _singleChar)
+_reBracketExpr = Literal("[") + Optional("^").setResultsName("negate") + Group( OneOrMore( _charRange | _singleChar ) ).setResultsName("body") + "]"
+
+def srange(s):
+    r"""
+    Helper to easily define string ranges for use in Word construction.  Borrows
+    syntax from regexp '[]' string range definitions::
+        srange("[0-9]")   -> "0123456789"
+        srange("[a-z]")   -> "abcdefghijklmnopqrstuvwxyz"
+        srange("[a-z$_]") -> "abcdefghijklmnopqrstuvwxyz$_"
+    The input string must be enclosed in []'s, and the returned string is the expanded
+    character set joined into a single string.
+    The values enclosed in the []'s may be:
+     - a single character
+     - an escaped character with a leading backslash (such as C{\-} or C{\]})
+     - an escaped hex character with a leading C{'\x'} (C{\x21}, which is a C{'!'} character) 
+         (C{\0x##} is also supported for backwards compatibility) 
+     - an escaped octal character with a leading C{'\0'} (C{\041}, which is a C{'!'} character)
+     - a range of any of the above, separated by a dash (C{'a-z'}, etc.)
+     - any combination of the above (C{'aeiouy'}, C{'a-zA-Z0-9_$'}, etc.)
+    """
+    _expanded = lambda p: p if not isinstance(p,ParseResults) else ''.join(unichr(c) for c in range(ord(p[0]),ord(p[1])+1))
+    try:
+        return "".join(_expanded(part) for part in _reBracketExpr.parseString(s).body)
+    except Exception:
+        return ""
+
+def matchOnlyAtCol(n):
+    """
+    Helper method for defining parse actions that require matching at a specific
+    column in the input text.
+    """
+    def verifyCol(strg,locn,toks):
+        if col(locn,strg) != n:
+            raise ParseException(strg,locn,"matched token not at column %d" % n)
+    return verifyCol
+
+def replaceWith(replStr):
+    """
+    Helper method for common parse actions that simply return a literal value.  Especially
+    useful when used with C{L{transformString}()}.
+
+    Example::
+        num = Word(nums).setParseAction(lambda toks: int(toks[0]))
+        na = oneOf("N/A NA").setParseAction(replaceWith(math.nan))
+        term = na | num
+        
+        OneOrMore(term).parseString("324 234 N/A 234") # -> [324, 234, nan, 234]
+    """
+    return lambda s,l,t: [replStr]
+
+def removeQuotes(s,l,t):
+    """
+    Helper parse action for removing quotation marks from parsed quoted strings.
+
+    Example::
+        # by default, quotation marks are included in parsed results
+        quotedString.parseString("'Now is the Winter of our Discontent'") # -> ["'Now is the Winter of our Discontent'"]
+
+        # use removeQuotes to strip quotation marks from parsed results
+        quotedString.setParseAction(removeQuotes)
+        quotedString.parseString("'Now is the Winter of our Discontent'") # -> ["Now is the Winter of our Discontent"]
+    """
+    return t[0][1:-1]
+
+def tokenMap(func, *args):
+    """
+    Helper to define a parse action by mapping a function to all elements of a ParseResults list.If any additional 
+    args are passed, they are forwarded to the given function as additional arguments after
+    the token, as in C{hex_integer = Word(hexnums).setParseAction(tokenMap(int, 16))}, which will convert the
+    parsed data to an integer using base 16.
+
+    Example (compare the last to example in L{ParserElement.transformString}::
+        hex_ints = OneOrMore(Word(hexnums)).setParseAction(tokenMap(int, 16))
+        hex_ints.runTests('''
+            00 11 22 aa FF 0a 0d 1a
+            ''')
+        
+        upperword = Word(alphas).setParseAction(tokenMap(str.upper))
+        OneOrMore(upperword).runTests('''
+            my kingdom for a horse
+            ''')
+
+        wd = Word(alphas).setParseAction(tokenMap(str.title))
+        OneOrMore(wd).setParseAction(' '.join).runTests('''
+            now is the winter of our discontent made glorious summer by this sun of york
+            ''')
+    prints::
+        00 11 22 aa FF 0a 0d 1a
+        [0, 17, 34, 170, 255, 10, 13, 26]
+
+        my kingdom for a horse
+        ['MY', 'KINGDOM', 'FOR', 'A', 'HORSE']
+
+        now is the winter of our discontent made glorious summer by this sun of york
+        ['Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York']
+    """
+    def pa(s,l,t):
+        return [func(tokn, *args) for tokn in t]
+
+    try:
+        func_name = getattr(func, '__name__', 
+                            getattr(func, '__class__').__name__)
+    except Exception:
+        func_name = str(func)
+    pa.__name__ = func_name
+
+    return pa
+
+upcaseTokens = tokenMap(lambda t: _ustr(t).upper())
+"""(Deprecated) Helper parse action to convert tokens to upper case. Deprecated in favor of L{pyparsing_common.upcaseTokens}"""
+
+downcaseTokens = tokenMap(lambda t: _ustr(t).lower())
+"""(Deprecated) Helper parse action to convert tokens to lower case. Deprecated in favor of L{pyparsing_common.downcaseTokens}"""
+    
+def _makeTags(tagStr, xml):
+    """Internal helper to construct opening and closing tag expressions, given a tag name"""
+    if isinstance(tagStr,basestring):
+        resname = tagStr
+        tagStr = Keyword(tagStr, caseless=not xml)
+    else:
+        resname = tagStr.name
+
+    tagAttrName = Word(alphas,alphanums+"_-:")
+    if (xml):
+        tagAttrValue = dblQuotedString.copy().setParseAction( removeQuotes )
+        openTag = Suppress("<") + tagStr("tag") + \
+                Dict(ZeroOrMore(Group( tagAttrName + Suppress("=") + tagAttrValue ))) + \
+                Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">")
+    else:
+        printablesLessRAbrack = "".join(c for c in printables if c not in ">")
+        tagAttrValue = quotedString.copy().setParseAction( removeQuotes ) | Word(printablesLessRAbrack)
+        openTag = Suppress("<") + tagStr("tag") + \
+                Dict(ZeroOrMore(Group( tagAttrName.setParseAction(downcaseTokens) + \
+                Optional( Suppress("=") + tagAttrValue ) ))) + \
+                Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">")
+    closeTag = Combine(_L("")
+
+    openTag = openTag.setResultsName("start"+"".join(resname.replace(":"," ").title().split())).setName("<%s>" % resname)
+    closeTag = closeTag.setResultsName("end"+"".join(resname.replace(":"," ").title().split())).setName("" % resname)
+    openTag.tag = resname
+    closeTag.tag = resname
+    return openTag, closeTag
+
+def makeHTMLTags(tagStr):
+    """
+    Helper to construct opening and closing tag expressions for HTML, given a tag name. Matches
+    tags in either upper or lower case, attributes with namespaces and with quoted or unquoted values.
+
+    Example::
+        text = 'More info at the pyparsing wiki page'
+        # makeHTMLTags returns pyparsing expressions for the opening and closing tags as a 2-tuple
+        a,a_end = makeHTMLTags("A")
+        link_expr = a + SkipTo(a_end)("link_text") + a_end
+        
+        for link in link_expr.searchString(text):
+            # attributes in the  tag (like "href" shown here) are also accessible as named results
+            print(link.link_text, '->', link.href)
+    prints::
+        pyparsing -> http://pyparsing.wikispaces.com
+    """
+    return _makeTags( tagStr, False )
+
+def makeXMLTags(tagStr):
+    """
+    Helper to construct opening and closing tag expressions for XML, given a tag name. Matches
+    tags only in the given upper/lower case.
+
+    Example: similar to L{makeHTMLTags}
+    """
+    return _makeTags( tagStr, True )
+
+def withAttribute(*args,**attrDict):
+    """
+    Helper to create a validating parse action to be used with start tags created
+    with C{L{makeXMLTags}} or C{L{makeHTMLTags}}. Use C{withAttribute} to qualify a starting tag
+    with a required attribute value, to avoid false matches on common tags such as
+    C{} or C{
}. + + Call C{withAttribute} with a series of attribute names and values. Specify the list + of filter attributes names and values as: + - keyword arguments, as in C{(align="right")}, or + - as an explicit dict with C{**} operator, when an attribute name is also a Python + reserved word, as in C{**{"class":"Customer", "align":"right"}} + - a list of name-value tuples, as in ( ("ns1:class", "Customer"), ("ns2:align","right") ) + For attribute names with a namespace prefix, you must use the second form. Attribute + names are matched insensitive to upper/lower case. + + If just testing for C{class} (with or without a namespace), use C{L{withClass}}. + + To verify that the attribute exists, but without specifying a value, pass + C{withAttribute.ANY_VALUE} as the value. + + Example:: + html = ''' +
+ Some text +
1 4 0 1 0
+
1,3 2,3 1,1
+
this has no type
+
+ + ''' + div,div_end = makeHTMLTags("div") + + # only match div tag having a type attribute with value "grid" + div_grid = div().setParseAction(withAttribute(type="grid")) + grid_expr = div_grid + SkipTo(div | div_end)("body") + for grid_header in grid_expr.searchString(html): + print(grid_header.body) + + # construct a match with any div tag having a type attribute, regardless of the value + div_any_type = div().setParseAction(withAttribute(type=withAttribute.ANY_VALUE)) + div_expr = div_any_type + SkipTo(div | div_end)("body") + for div_header in div_expr.searchString(html): + print(div_header.body) + prints:: + 1 4 0 1 0 + + 1 4 0 1 0 + 1,3 2,3 1,1 + """ + if args: + attrs = args[:] + else: + attrs = attrDict.items() + attrs = [(k,v) for k,v in attrs] + def pa(s,l,tokens): + for attrName,attrValue in attrs: + if attrName not in tokens: + raise ParseException(s,l,"no matching attribute " + attrName) + if attrValue != withAttribute.ANY_VALUE and tokens[attrName] != attrValue: + raise ParseException(s,l,"attribute '%s' has value '%s', must be '%s'" % + (attrName, tokens[attrName], attrValue)) + return pa +withAttribute.ANY_VALUE = object() + +def withClass(classname, namespace=''): + """ + Simplified version of C{L{withAttribute}} when matching on a div class - made + difficult because C{class} is a reserved word in Python. + + Example:: + html = ''' +
+ Some text +
1 4 0 1 0
+
1,3 2,3 1,1
+
this <div> has no class
+
+ + ''' + div,div_end = makeHTMLTags("div") + div_grid = div().setParseAction(withClass("grid")) + + grid_expr = div_grid + SkipTo(div | div_end)("body") + for grid_header in grid_expr.searchString(html): + print(grid_header.body) + + div_any_type = div().setParseAction(withClass(withAttribute.ANY_VALUE)) + div_expr = div_any_type + SkipTo(div | div_end)("body") + for div_header in div_expr.searchString(html): + print(div_header.body) + prints:: + 1 4 0 1 0 + + 1 4 0 1 0 + 1,3 2,3 1,1 + """ + classattr = "%s:class" % namespace if namespace else "class" + return withAttribute(**{classattr : classname}) + +opAssoc = _Constants() +opAssoc.LEFT = object() +opAssoc.RIGHT = object() + +def infixNotation( baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')') ): + """ + Helper method for constructing grammars of expressions made up of + operators working in a precedence hierarchy. Operators may be unary or + binary, left- or right-associative. Parse actions can also be attached + to operator expressions. The generated parser will also recognize the use + of parentheses to override operator precedences (see example below). + + Note: if you define a deep operator list, you may see performance issues + when using infixNotation. See L{ParserElement.enablePackrat} for a + mechanism to potentially improve your parser performance. + + Parameters: + - baseExpr - expression representing the most basic element for the nested + - opList - list of tuples, one for each operator precedence level in the + expression grammar; each tuple is of the form + (opExpr, numTerms, rightLeftAssoc, parseAction), where: + - opExpr is the pyparsing expression for the operator; + may also be a string, which will be converted to a Literal; + if numTerms is 3, opExpr is a tuple of two expressions, for the + two operators separating the 3 terms + - numTerms is the number of terms for this operator (must + be 1, 2, or 3) + - rightLeftAssoc is the indicator whether the operator is + right or left associative, using the pyparsing-defined + constants C{opAssoc.RIGHT} and C{opAssoc.LEFT}. + - parseAction is the parse action to be associated with + expressions matching this operator expression (the + parse action tuple member may be omitted); if the parse action + is passed a tuple or list of functions, this is equivalent to + calling C{setParseAction(*fn)} (L{ParserElement.setParseAction}) + - lpar - expression for matching left-parentheses (default=C{Suppress('(')}) + - rpar - expression for matching right-parentheses (default=C{Suppress(')')}) + + Example:: + # simple example of four-function arithmetic with ints and variable names + integer = pyparsing_common.signed_integer + varname = pyparsing_common.identifier + + arith_expr = infixNotation(integer | varname, + [ + ('-', 1, opAssoc.RIGHT), + (oneOf('* /'), 2, opAssoc.LEFT), + (oneOf('+ -'), 2, opAssoc.LEFT), + ]) + + arith_expr.runTests(''' + 5+3*6 + (5+3)*6 + -2--11 + ''', fullDump=False) + prints:: + 5+3*6 + [[5, '+', [3, '*', 6]]] + + (5+3)*6 + [[[5, '+', 3], '*', 6]] + + -2--11 + [[['-', 2], '-', ['-', 11]]] + """ + ret = Forward() + lastExpr = baseExpr | ( lpar + ret + rpar ) + for i,operDef in enumerate(opList): + opExpr,arity,rightLeftAssoc,pa = (operDef + (None,))[:4] + termName = "%s term" % opExpr if arity < 3 else "%s%s term" % opExpr + if arity == 3: + if opExpr is None or len(opExpr) != 2: + raise ValueError("if numterms=3, opExpr must be a tuple or list of two expressions") + opExpr1, opExpr2 = opExpr + thisExpr = Forward().setName(termName) + if rightLeftAssoc == opAssoc.LEFT: + if arity == 1: + matchExpr = FollowedBy(lastExpr + opExpr) + Group( lastExpr + OneOrMore( opExpr ) ) + elif arity == 2: + if opExpr is not None: + matchExpr = FollowedBy(lastExpr + opExpr + lastExpr) + Group( lastExpr + OneOrMore( opExpr + lastExpr ) ) + else: + matchExpr = FollowedBy(lastExpr+lastExpr) + Group( lastExpr + OneOrMore(lastExpr) ) + elif arity == 3: + matchExpr = FollowedBy(lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr) + \ + Group( lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr ) + else: + raise ValueError("operator must be unary (1), binary (2), or ternary (3)") + elif rightLeftAssoc == opAssoc.RIGHT: + if arity == 1: + # try to avoid LR with this extra test + if not isinstance(opExpr, Optional): + opExpr = Optional(opExpr) + matchExpr = FollowedBy(opExpr.expr + thisExpr) + Group( opExpr + thisExpr ) + elif arity == 2: + if opExpr is not None: + matchExpr = FollowedBy(lastExpr + opExpr + thisExpr) + Group( lastExpr + OneOrMore( opExpr + thisExpr ) ) + else: + matchExpr = FollowedBy(lastExpr + thisExpr) + Group( lastExpr + OneOrMore( thisExpr ) ) + elif arity == 3: + matchExpr = FollowedBy(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr) + \ + Group( lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr ) + else: + raise ValueError("operator must be unary (1), binary (2), or ternary (3)") + else: + raise ValueError("operator must indicate right or left associativity") + if pa: + if isinstance(pa, (tuple, list)): + matchExpr.setParseAction(*pa) + else: + matchExpr.setParseAction(pa) + thisExpr <<= ( matchExpr.setName(termName) | lastExpr ) + lastExpr = thisExpr + ret <<= lastExpr + return ret + +operatorPrecedence = infixNotation +"""(Deprecated) Former name of C{L{infixNotation}}, will be dropped in a future release.""" + +dblQuotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*')+'"').setName("string enclosed in double quotes") +sglQuotedString = Combine(Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*")+"'").setName("string enclosed in single quotes") +quotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*')+'"'| + Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*")+"'").setName("quotedString using single or double quotes") +unicodeString = Combine(_L('u') + quotedString.copy()).setName("unicode string literal") + +def nestedExpr(opener="(", closer=")", content=None, ignoreExpr=quotedString.copy()): + """ + Helper method for defining nested lists enclosed in opening and closing + delimiters ("(" and ")" are the default). + + Parameters: + - opener - opening character for a nested list (default=C{"("}); can also be a pyparsing expression + - closer - closing character for a nested list (default=C{")"}); can also be a pyparsing expression + - content - expression for items within the nested lists (default=C{None}) + - ignoreExpr - expression for ignoring opening and closing delimiters (default=C{quotedString}) + + If an expression is not provided for the content argument, the nested + expression will capture all whitespace-delimited content between delimiters + as a list of separate values. + + Use the C{ignoreExpr} argument to define expressions that may contain + opening or closing characters that should not be treated as opening + or closing characters for nesting, such as quotedString or a comment + expression. Specify multiple expressions using an C{L{Or}} or C{L{MatchFirst}}. + The default is L{quotedString}, but if no expressions are to be ignored, + then pass C{None} for this argument. + + Example:: + data_type = oneOf("void int short long char float double") + decl_data_type = Combine(data_type + Optional(Word('*'))) + ident = Word(alphas+'_', alphanums+'_') + number = pyparsing_common.number + arg = Group(decl_data_type + ident) + LPAR,RPAR = map(Suppress, "()") + + code_body = nestedExpr('{', '}', ignoreExpr=(quotedString | cStyleComment)) + + c_function = (decl_data_type("type") + + ident("name") + + LPAR + Optional(delimitedList(arg), [])("args") + RPAR + + code_body("body")) + c_function.ignore(cStyleComment) + + source_code = ''' + int is_odd(int x) { + return (x%2); + } + + int dec_to_hex(char hchar) { + if (hchar >= '0' && hchar <= '9') { + return (ord(hchar)-ord('0')); + } else { + return (10+ord(hchar)-ord('A')); + } + } + ''' + for func in c_function.searchString(source_code): + print("%(name)s (%(type)s) args: %(args)s" % func) + + prints:: + is_odd (int) args: [['int', 'x']] + dec_to_hex (int) args: [['char', 'hchar']] + """ + if opener == closer: + raise ValueError("opening and closing strings cannot be the same") + if content is None: + if isinstance(opener,basestring) and isinstance(closer,basestring): + if len(opener) == 1 and len(closer)==1: + if ignoreExpr is not None: + content = (Combine(OneOrMore(~ignoreExpr + + CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + content = (empty.copy()+CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS + ).setParseAction(lambda t:t[0].strip())) + else: + if ignoreExpr is not None: + content = (Combine(OneOrMore(~ignoreExpr + + ~Literal(opener) + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + content = (Combine(OneOrMore(~Literal(opener) + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + raise ValueError("opening and closing arguments must be strings if no content expression is given") + ret = Forward() + if ignoreExpr is not None: + ret <<= Group( Suppress(opener) + ZeroOrMore( ignoreExpr | ret | content ) + Suppress(closer) ) + else: + ret <<= Group( Suppress(opener) + ZeroOrMore( ret | content ) + Suppress(closer) ) + ret.setName('nested %s%s expression' % (opener,closer)) + return ret + +def indentedBlock(blockStatementExpr, indentStack, indent=True): + """ + Helper method for defining space-delimited indentation blocks, such as + those used to define block statements in Python source code. + + Parameters: + - blockStatementExpr - expression defining syntax of statement that + is repeated within the indented block + - indentStack - list created by caller to manage indentation stack + (multiple statementWithIndentedBlock expressions within a single grammar + should share a common indentStack) + - indent - boolean indicating whether block must be indented beyond the + the current level; set to False for block of left-most statements + (default=C{True}) + + A valid block must contain at least one C{blockStatement}. + + Example:: + data = ''' + def A(z): + A1 + B = 100 + G = A2 + A2 + A3 + B + def BB(a,b,c): + BB1 + def BBA(): + bba1 + bba2 + bba3 + C + D + def spam(x,y): + def eggs(z): + pass + ''' + + + indentStack = [1] + stmt = Forward() + + identifier = Word(alphas, alphanums) + funcDecl = ("def" + identifier + Group( "(" + Optional( delimitedList(identifier) ) + ")" ) + ":") + func_body = indentedBlock(stmt, indentStack) + funcDef = Group( funcDecl + func_body ) + + rvalue = Forward() + funcCall = Group(identifier + "(" + Optional(delimitedList(rvalue)) + ")") + rvalue << (funcCall | identifier | Word(nums)) + assignment = Group(identifier + "=" + rvalue) + stmt << ( funcDef | assignment | identifier ) + + module_body = OneOrMore(stmt) + + parseTree = module_body.parseString(data) + parseTree.pprint() + prints:: + [['def', + 'A', + ['(', 'z', ')'], + ':', + [['A1'], [['B', '=', '100']], [['G', '=', 'A2']], ['A2'], ['A3']]], + 'B', + ['def', + 'BB', + ['(', 'a', 'b', 'c', ')'], + ':', + [['BB1'], [['def', 'BBA', ['(', ')'], ':', [['bba1'], ['bba2'], ['bba3']]]]]], + 'C', + 'D', + ['def', + 'spam', + ['(', 'x', 'y', ')'], + ':', + [[['def', 'eggs', ['(', 'z', ')'], ':', [['pass']]]]]]] + """ + def checkPeerIndent(s,l,t): + if l >= len(s): return + curCol = col(l,s) + if curCol != indentStack[-1]: + if curCol > indentStack[-1]: + raise ParseFatalException(s,l,"illegal nesting") + raise ParseException(s,l,"not a peer entry") + + def checkSubIndent(s,l,t): + curCol = col(l,s) + if curCol > indentStack[-1]: + indentStack.append( curCol ) + else: + raise ParseException(s,l,"not a subentry") + + def checkUnindent(s,l,t): + if l >= len(s): return + curCol = col(l,s) + if not(indentStack and curCol < indentStack[-1] and curCol <= indentStack[-2]): + raise ParseException(s,l,"not an unindent") + indentStack.pop() + + NL = OneOrMore(LineEnd().setWhitespaceChars("\t ").suppress()) + INDENT = (Empty() + Empty().setParseAction(checkSubIndent)).setName('INDENT') + PEER = Empty().setParseAction(checkPeerIndent).setName('') + UNDENT = Empty().setParseAction(checkUnindent).setName('UNINDENT') + if indent: + smExpr = Group( Optional(NL) + + #~ FollowedBy(blockStatementExpr) + + INDENT + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) + UNDENT) + else: + smExpr = Group( Optional(NL) + + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) ) + blockStatementExpr.ignore(_bslash + LineEnd()) + return smExpr.setName('indented block') + +alphas8bit = srange(r"[\0xc0-\0xd6\0xd8-\0xf6\0xf8-\0xff]") +punc8bit = srange(r"[\0xa1-\0xbf\0xd7\0xf7]") + +anyOpenTag,anyCloseTag = makeHTMLTags(Word(alphas,alphanums+"_:").setName('any tag')) +_htmlEntityMap = dict(zip("gt lt amp nbsp quot apos".split(),'><& "\'')) +commonHTMLEntity = Regex('&(?P' + '|'.join(_htmlEntityMap.keys()) +");").setName("common HTML entity") +def replaceHTMLEntity(t): + """Helper parser action to replace common HTML entities with their special characters""" + return _htmlEntityMap.get(t.entity) + +# it's easy to get these comment structures wrong - they're very common, so may as well make them available +cStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/').setName("C style comment") +"Comment of the form C{/* ... */}" + +htmlComment = Regex(r"").setName("HTML comment") +"Comment of the form C{}" + +restOfLine = Regex(r".*").leaveWhitespace().setName("rest of line") +dblSlashComment = Regex(r"//(?:\\\n|[^\n])*").setName("// comment") +"Comment of the form C{// ... (to end of line)}" + +cppStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/'| dblSlashComment).setName("C++ style comment") +"Comment of either form C{L{cStyleComment}} or C{L{dblSlashComment}}" + +javaStyleComment = cppStyleComment +"Same as C{L{cppStyleComment}}" + +pythonStyleComment = Regex(r"#.*").setName("Python style comment") +"Comment of the form C{# ... (to end of line)}" + +_commasepitem = Combine(OneOrMore(Word(printables, excludeChars=',') + + Optional( Word(" \t") + + ~Literal(",") + ~LineEnd() ) ) ).streamline().setName("commaItem") +commaSeparatedList = delimitedList( Optional( quotedString.copy() | _commasepitem, default="") ).setName("commaSeparatedList") +"""(Deprecated) Predefined expression of 1 or more printable words or quoted strings, separated by commas. + This expression is deprecated in favor of L{pyparsing_common.comma_separated_list}.""" + +# some other useful expressions - using lower-case class name since we are really using this as a namespace +class pyparsing_common: + """ + Here are some common low-level expressions that may be useful in jump-starting parser development: + - numeric forms (L{integers}, L{reals}, L{scientific notation}) + - common L{programming identifiers} + - network addresses (L{MAC}, L{IPv4}, L{IPv6}) + - ISO8601 L{dates} and L{datetime} + - L{UUID} + - L{comma-separated list} + Parse actions: + - C{L{convertToInteger}} + - C{L{convertToFloat}} + - C{L{convertToDate}} + - C{L{convertToDatetime}} + - C{L{stripHTMLTags}} + - C{L{upcaseTokens}} + - C{L{downcaseTokens}} + + Example:: + pyparsing_common.number.runTests(''' + # any int or real number, returned as the appropriate type + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + ''') + + pyparsing_common.fnumber.runTests(''' + # any int or real number, returned as float + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + ''') + + pyparsing_common.hex_integer.runTests(''' + # hex numbers + 100 + FF + ''') + + pyparsing_common.fraction.runTests(''' + # fractions + 1/2 + -3/4 + ''') + + pyparsing_common.mixed_integer.runTests(''' + # mixed fractions + 1 + 1/2 + -3/4 + 1-3/4 + ''') + + import uuid + pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID)) + pyparsing_common.uuid.runTests(''' + # uuid + 12345678-1234-5678-1234-567812345678 + ''') + prints:: + # any int or real number, returned as the appropriate type + 100 + [100] + + -100 + [-100] + + +100 + [100] + + 3.14159 + [3.14159] + + 6.02e23 + [6.02e+23] + + 1e-12 + [1e-12] + + # any int or real number, returned as float + 100 + [100.0] + + -100 + [-100.0] + + +100 + [100.0] + + 3.14159 + [3.14159] + + 6.02e23 + [6.02e+23] + + 1e-12 + [1e-12] + + # hex numbers + 100 + [256] + + FF + [255] + + # fractions + 1/2 + [0.5] + + -3/4 + [-0.75] + + # mixed fractions + 1 + [1] + + 1/2 + [0.5] + + -3/4 + [-0.75] + + 1-3/4 + [1.75] + + # uuid + 12345678-1234-5678-1234-567812345678 + [UUID('12345678-1234-5678-1234-567812345678')] + """ + + convertToInteger = tokenMap(int) + """ + Parse action for converting parsed integers to Python int + """ + + convertToFloat = tokenMap(float) + """ + Parse action for converting parsed numbers to Python float + """ + + integer = Word(nums).setName("integer").setParseAction(convertToInteger) + """expression that parses an unsigned integer, returns an int""" + + hex_integer = Word(hexnums).setName("hex integer").setParseAction(tokenMap(int,16)) + """expression that parses a hexadecimal integer, returns an int""" + + signed_integer = Regex(r'[+-]?\d+').setName("signed integer").setParseAction(convertToInteger) + """expression that parses an integer with optional leading sign, returns an int""" + + fraction = (signed_integer().setParseAction(convertToFloat) + '/' + signed_integer().setParseAction(convertToFloat)).setName("fraction") + """fractional expression of an integer divided by an integer, returns a float""" + fraction.addParseAction(lambda t: t[0]/t[-1]) + + mixed_integer = (fraction | signed_integer + Optional(Optional('-').suppress() + fraction)).setName("fraction or mixed integer-fraction") + """mixed integer of the form 'integer - fraction', with optional leading integer, returns float""" + mixed_integer.addParseAction(sum) + + real = Regex(r'[+-]?\d+\.\d*').setName("real number").setParseAction(convertToFloat) + """expression that parses a floating point number and returns a float""" + + sci_real = Regex(r'[+-]?\d+([eE][+-]?\d+|\.\d*([eE][+-]?\d+)?)').setName("real number with scientific notation").setParseAction(convertToFloat) + """expression that parses a floating point number with optional scientific notation and returns a float""" + + # streamlining this expression makes the docs nicer-looking + number = (sci_real | real | signed_integer).streamline() + """any numeric expression, returns the corresponding Python type""" + + fnumber = Regex(r'[+-]?\d+\.?\d*([eE][+-]?\d+)?').setName("fnumber").setParseAction(convertToFloat) + """any int or real number, returned as float""" + + identifier = Word(alphas+'_', alphanums+'_').setName("identifier") + """typical code identifier (leading alpha or '_', followed by 0 or more alphas, nums, or '_')""" + + ipv4_address = Regex(r'(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})){3}').setName("IPv4 address") + "IPv4 address (C{0.0.0.0 - 255.255.255.255})" + + _ipv6_part = Regex(r'[0-9a-fA-F]{1,4}').setName("hex_integer") + _full_ipv6_address = (_ipv6_part + (':' + _ipv6_part)*7).setName("full IPv6 address") + _short_ipv6_address = (Optional(_ipv6_part + (':' + _ipv6_part)*(0,6)) + "::" + Optional(_ipv6_part + (':' + _ipv6_part)*(0,6))).setName("short IPv6 address") + _short_ipv6_address.addCondition(lambda t: sum(1 for tt in t if pyparsing_common._ipv6_part.matches(tt)) < 8) + _mixed_ipv6_address = ("::ffff:" + ipv4_address).setName("mixed IPv6 address") + ipv6_address = Combine((_full_ipv6_address | _mixed_ipv6_address | _short_ipv6_address).setName("IPv6 address")).setName("IPv6 address") + "IPv6 address (long, short, or mixed form)" + + mac_address = Regex(r'[0-9a-fA-F]{2}([:.-])[0-9a-fA-F]{2}(?:\1[0-9a-fA-F]{2}){4}').setName("MAC address") + "MAC address xx:xx:xx:xx:xx (may also have '-' or '.' delimiters)" + + @staticmethod + def convertToDate(fmt="%Y-%m-%d"): + """ + Helper to create a parse action for converting parsed date string to Python datetime.date + + Params - + - fmt - format to be passed to datetime.strptime (default=C{"%Y-%m-%d"}) + + Example:: + date_expr = pyparsing_common.iso8601_date.copy() + date_expr.setParseAction(pyparsing_common.convertToDate()) + print(date_expr.parseString("1999-12-31")) + prints:: + [datetime.date(1999, 12, 31)] + """ + def cvt_fn(s,l,t): + try: + return datetime.strptime(t[0], fmt).date() + except ValueError as ve: + raise ParseException(s, l, str(ve)) + return cvt_fn + + @staticmethod + def convertToDatetime(fmt="%Y-%m-%dT%H:%M:%S.%f"): + """ + Helper to create a parse action for converting parsed datetime string to Python datetime.datetime + + Params - + - fmt - format to be passed to datetime.strptime (default=C{"%Y-%m-%dT%H:%M:%S.%f"}) + + Example:: + dt_expr = pyparsing_common.iso8601_datetime.copy() + dt_expr.setParseAction(pyparsing_common.convertToDatetime()) + print(dt_expr.parseString("1999-12-31T23:59:59.999")) + prints:: + [datetime.datetime(1999, 12, 31, 23, 59, 59, 999000)] + """ + def cvt_fn(s,l,t): + try: + return datetime.strptime(t[0], fmt) + except ValueError as ve: + raise ParseException(s, l, str(ve)) + return cvt_fn + + iso8601_date = Regex(r'(?P\d{4})(?:-(?P\d\d)(?:-(?P\d\d))?)?').setName("ISO8601 date") + "ISO8601 date (C{yyyy-mm-dd})" + + iso8601_datetime = Regex(r'(?P\d{4})-(?P\d\d)-(?P\d\d)[T ](?P\d\d):(?P\d\d)(:(?P\d\d(\.\d*)?)?)?(?PZ|[+-]\d\d:?\d\d)?').setName("ISO8601 datetime") + "ISO8601 datetime (C{yyyy-mm-ddThh:mm:ss.s(Z|+-00:00)}) - trailing seconds, milliseconds, and timezone optional; accepts separating C{'T'} or C{' '}" + + uuid = Regex(r'[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}').setName("UUID") + "UUID (C{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx})" + + _html_stripper = anyOpenTag.suppress() | anyCloseTag.suppress() + @staticmethod + def stripHTMLTags(s, l, tokens): + """ + Parse action to remove HTML tags from web page HTML source + + Example:: + # strip HTML links from normal text + text = 'More info at the
pyparsing wiki page' + td,td_end = makeHTMLTags("TD") + table_text = td + SkipTo(td_end).setParseAction(pyparsing_common.stripHTMLTags)("body") + td_end + + print(table_text.parseString(text).body) # -> 'More info at the pyparsing wiki page' + """ + return pyparsing_common._html_stripper.transformString(tokens[0]) + + _commasepitem = Combine(OneOrMore(~Literal(",") + ~LineEnd() + Word(printables, excludeChars=',') + + Optional( White(" \t") ) ) ).streamline().setName("commaItem") + comma_separated_list = delimitedList( Optional( quotedString.copy() | _commasepitem, default="") ).setName("comma separated list") + """Predefined expression of 1 or more printable words or quoted strings, separated by commas.""" + + upcaseTokens = staticmethod(tokenMap(lambda t: _ustr(t).upper())) + """Parse action to convert tokens to upper case.""" + + downcaseTokens = staticmethod(tokenMap(lambda t: _ustr(t).lower())) + """Parse action to convert tokens to lower case.""" + + +if __name__ == "__main__": + + selectToken = CaselessLiteral("select") + fromToken = CaselessLiteral("from") + + ident = Word(alphas, alphanums + "_$") + + columnName = delimitedList(ident, ".", combine=True).setParseAction(upcaseTokens) + columnNameList = Group(delimitedList(columnName)).setName("columns") + columnSpec = ('*' | columnNameList) + + tableName = delimitedList(ident, ".", combine=True).setParseAction(upcaseTokens) + tableNameList = Group(delimitedList(tableName)).setName("tables") + + simpleSQL = selectToken("command") + columnSpec("columns") + fromToken + tableNameList("tables") + + # demo runTests method, including embedded comments in test string + simpleSQL.runTests(""" + # '*' as column list and dotted table name + select * from SYS.XYZZY + + # caseless match on "SELECT", and casts back to "select" + SELECT * from XYZZY, ABC + + # list of column names, and mixed case SELECT keyword + Select AA,BB,CC from Sys.dual + + # multiple tables + Select A, B, C from Sys.dual, Table2 + + # invalid SELECT keyword - should fail + Xelect A, B, C from Sys.dual + + # incomplete command - should fail + Select + + # invalid column name - should fail + Select ^^^ frox Sys.dual + + """) + + pyparsing_common.number.runTests(""" + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + """) + + # any int or real number, returned as float + pyparsing_common.fnumber.runTests(""" + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + """) + + pyparsing_common.hex_integer.runTests(""" + 100 + FF + """) + + import uuid + pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID)) + pyparsing_common.uuid.runTests(""" + 12345678-1234-5678-1234-567812345678 + """) diff --git a/venv/lib/python3.10/site-packages/pkg_resources/extern/__init__.py b/venv/lib/python3.10/site-packages/pkg_resources/extern/__init__.py new file mode 100644 index 00000000..fed59295 --- /dev/null +++ b/venv/lib/python3.10/site-packages/pkg_resources/extern/__init__.py @@ -0,0 +1,73 @@ +import importlib.util +import sys + + +class VendorImporter: + """ + A PEP 302 meta path importer for finding optionally-vendored + or otherwise naturally-installed packages from root_name. + """ + + def __init__(self, root_name, vendored_names=(), vendor_pkg=None): + self.root_name = root_name + self.vendored_names = set(vendored_names) + self.vendor_pkg = vendor_pkg or root_name.replace('extern', '_vendor') + + @property + def search_path(self): + """ + Search first the vendor package then as a natural package. + """ + yield self.vendor_pkg + '.' + yield '' + + def _module_matches_namespace(self, fullname): + """Figure out if the target module is vendored.""" + root, base, target = fullname.partition(self.root_name + '.') + return not root and any(map(target.startswith, self.vendored_names)) + + def load_module(self, fullname): + """ + Iterate over the search path to locate and load fullname. + """ + root, base, target = fullname.partition(self.root_name + '.') + for prefix in self.search_path: + try: + extant = prefix + target + __import__(extant) + mod = sys.modules[extant] + sys.modules[fullname] = mod + return mod + except ImportError: + pass + else: + raise ImportError( + "The '{target}' package is required; " + "normally this is bundled with this package so if you get " + "this warning, consult the packager of your " + "distribution.".format(**locals()) + ) + + def create_module(self, spec): + return self.load_module(spec.name) + + def exec_module(self, module): + pass + + def find_spec(self, fullname, path=None, target=None): + """Return a module spec for vendored names.""" + return ( + importlib.util.spec_from_loader(fullname, self) + if self._module_matches_namespace(fullname) else None + ) + + def install(self): + """ + Install this importer into sys.meta_path if not already present. + """ + if self not in sys.meta_path: + sys.meta_path.append(self) + + +names = 'packaging', 'pyparsing', 'appdirs' +VendorImporter(__name__, names).install() diff --git a/venv/lib/python3.10/site-packages/pkg_resources/tests/data/my-test-package-source/setup.py b/venv/lib/python3.10/site-packages/pkg_resources/tests/data/my-test-package-source/setup.py new file mode 100644 index 00000000..fe80d28f --- /dev/null +++ b/venv/lib/python3.10/site-packages/pkg_resources/tests/data/my-test-package-source/setup.py @@ -0,0 +1,6 @@ +import setuptools +setuptools.setup( + name="my-test-package", + version="1.0", + zip_safe=True, +) diff --git a/venv/lib/python3.10/site-packages/setuptools-59.6.0.dist-info/LICENSE b/venv/lib/python3.10/site-packages/setuptools-59.6.0.dist-info/LICENSE new file mode 100644 index 00000000..353924be --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools-59.6.0.dist-info/LICENSE @@ -0,0 +1,19 @@ +Copyright Jason R. Coombs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/venv/lib/python3.10/site-packages/setuptools-59.6.0.dist-info/METADATA b/venv/lib/python3.10/site-packages/setuptools-59.6.0.dist-info/METADATA new file mode 100644 index 00000000..35a436da --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools-59.6.0.dist-info/METADATA @@ -0,0 +1,125 @@ +Metadata-Version: 2.1 +Name: setuptools +Version: 59.6.0 +Summary: Easily download, build, install, upgrade, and uninstall Python packages +Home-page: https://github.com/pypa/setuptools +Author: Python Packaging Authority +Author-email: distutils-sig@python.org +License: UNKNOWN +Project-URL: Documentation, https://setuptools.pypa.io/ +Keywords: CPAN PyPI distutils eggs package management +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: System :: Archiving :: Packaging +Classifier: Topic :: System :: Systems Administration +Classifier: Topic :: Utilities +Requires-Python: >=3.6 +License-File: LICENSE +Provides-Extra: certs +Provides-Extra: docs +Requires-Dist: furo ; extra == 'docs' +Requires-Dist: jaraco.packaging (>=8.2) ; extra == 'docs' +Requires-Dist: jaraco.tidelift (>=1.4) ; extra == 'docs' +Requires-Dist: pygments-github-lexers (==0.0.5) ; extra == 'docs' +Requires-Dist: rst.linker (>=1.9) ; extra == 'docs' +Requires-Dist: sphinx ; extra == 'docs' +Requires-Dist: sphinx-inline-tabs ; extra == 'docs' +Requires-Dist: sphinxcontrib-towncrier ; extra == 'docs' +Provides-Extra: ssl +Provides-Extra: testing +Requires-Dist: flake8-2020 ; extra == 'testing' +Requires-Dist: jaraco.envs (>=2.2) ; extra == 'testing' +Requires-Dist: jaraco.path (>=3.2.0) ; extra == 'testing' +Requires-Dist: mock ; extra == 'testing' +Requires-Dist: paver ; extra == 'testing' +Requires-Dist: pip (>=19.1) ; extra == 'testing' +Requires-Dist: pytest-checkdocs (>=2.4) ; extra == 'testing' +Requires-Dist: pytest-cov ; extra == 'testing' +Requires-Dist: pytest-enabler (>=1.0.1) ; extra == 'testing' +Requires-Dist: pytest-flake8 ; extra == 'testing' +Requires-Dist: pytest-subprocess ; extra == 'testing' +Requires-Dist: pytest-virtualenv (>=1.2.7) ; extra == 'testing' +Requires-Dist: pytest-xdist ; extra == 'testing' +Requires-Dist: pytest (>=6) ; extra == 'testing' +Requires-Dist: sphinx ; extra == 'testing' +Requires-Dist: virtualenv (>=13.0.0) ; extra == 'testing' +Requires-Dist: wheel ; extra == 'testing' +Requires-Dist: pytest-black (>=0.3.7) ; (platform_python_implementation != "PyPy") and extra == 'testing' +Requires-Dist: pytest-mypy ; (platform_python_implementation != "PyPy") and extra == 'testing' + +.. image:: https://raw.githubusercontent.com/pypa/setuptools/main/docs/images/banner-640x320.svg + :align: center + +| + +.. image:: https://img.shields.io/pypi/v/setuptools.svg + :target: `PyPI link`_ + +.. image:: https://img.shields.io/pypi/pyversions/setuptools.svg + :target: `PyPI link`_ + +.. _PyPI link: https://pypi.org/project/setuptools + +.. image:: https://github.com/pypa/setuptools/workflows/tests/badge.svg + :target: https://github.com/pypa/setuptools/actions?query=workflow%3A%22tests%22 + :alt: tests + +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/psf/black + :alt: Code style: Black + +.. image:: https://img.shields.io/readthedocs/setuptools/latest.svg + :target: https://setuptools.pypa.io + +.. image:: https://img.shields.io/badge/skeleton-2021-informational + :target: https://blog.jaraco.com/skeleton + +.. image:: https://img.shields.io/codecov/c/github/pypa/setuptools/master.svg?logo=codecov&logoColor=white + :target: https://codecov.io/gh/pypa/setuptools + +.. image:: https://tidelift.com/badges/github/pypa/setuptools?style=flat + :target: https://tidelift.com/subscription/pkg/pypi-setuptools?utm_source=pypi-setuptools&utm_medium=readme + +See the `Installation Instructions +`_ in the Python Packaging +User's Guide for instructions on installing, upgrading, and uninstalling +Setuptools. + +Questions and comments should be directed to the `distutils-sig +mailing list `_. +Bug reports and especially tested patches may be +submitted directly to the `bug tracker +`_. + + +Code of Conduct +=============== + +Everyone interacting in the setuptools project's codebases, issue trackers, +chat rooms, and mailing lists is expected to follow the +`PSF Code of Conduct `_. + + +For Enterprise +============== + +Available as part of the Tidelift Subscription. + +Setuptools and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use. + +`Learn more `_. + + +Security Contact +================ + +To report a security vulnerability, please use the +`Tidelift security contact `_. +Tidelift will coordinate the fix and disclosure. + + diff --git a/venv/lib/python3.10/site-packages/setuptools-59.6.0.dist-info/RECORD b/venv/lib/python3.10/site-packages/setuptools-59.6.0.dist-info/RECORD new file mode 100644 index 00000000..6467365d --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools-59.6.0.dist-info/RECORD @@ -0,0 +1,157 @@ +distutils-precedence.pth,sha256=fqf_7z_ioRfuEsaO1lU2F_DX_S8FkCV8JcSElZo7c3M,152 +_distutils_hack/__init__.py,sha256=TCUx2qEhWNyruLzj4DOGZAQH39hm2fJ_wDd90olNOmo,3759 +_distutils_hack/override.py,sha256=Eu_s-NF6VIZ4Cqd0tbbA5wtWky2IZPNd8et6GLt1mzo,44 +pkg_resources/__init__.py,sha256=uAnPq8FsTXHAEHFWK7UU9AhdNjE4o5Skfk8CyfbztO8,108573 +pkg_resources/_vendor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pkg_resources/_vendor/appdirs.py,sha256=MievUEuv3l_mQISH5SF0shDk_BNhHHzYiAPrT3ITN4I,24701 +pkg_resources/_vendor/pyparsing.py,sha256=tmrp-lu-qO1i75ZzIN5A12nKRRD1Cm4Vpk-5LR9rims,232055 +pkg_resources/_vendor/packaging/__about__.py,sha256=IIRHpOsJlJSgkjq1UoeBoMTqhvNp3gN9FyMb5Kf8El4,661 +pkg_resources/_vendor/packaging/__init__.py,sha256=b9Kk5MF7KxhhLgcDmiUWukN-LatWFxPdNug0joPhHSk,497 +pkg_resources/_vendor/packaging/_manylinux.py,sha256=XcbiXB-qcjv3bcohp6N98TMpOP4_j3m-iOA8ptK2GWY,11488 +pkg_resources/_vendor/packaging/_musllinux.py,sha256=z5yeG1ygOPx4uUyLdqj-p8Dk5UBb5H_b0NIjW9yo8oA,4378 +pkg_resources/_vendor/packaging/_structures.py,sha256=TMiAgFbdUOPmIfDIfiHc3KFhSJ8kMjof2QS5I-2NyQ8,1629 +pkg_resources/_vendor/packaging/markers.py,sha256=gFSKoBTb0sKDw1v_apJy15lPr0v2mEvuEkfooTtcWx4,8496 +pkg_resources/_vendor/packaging/requirements.py,sha256=uJ4cjwm3_nrfHJLCcGU9mT5aw8SXfw8v1aBUD7OFuVs,4706 +pkg_resources/_vendor/packaging/specifiers.py,sha256=MZ-fYcNL3u7pNrt-6g2EQO7AbRXkjc-SPEYwXMQbLmc,30964 +pkg_resources/_vendor/packaging/tags.py,sha256=vGybAUQYlPKMcukzX_2e65fmafnFFuMbD25naYTEwtc,15710 +pkg_resources/_vendor/packaging/utils.py,sha256=dJjeat3BS-TYn1RrUFVwufUMasbtzLfYRoy_HXENeFQ,4200 +pkg_resources/_vendor/packaging/version.py,sha256=_fLRNrFrxYcHVfyo8vk9j8s6JM8N_xsSxVFr6RJyco8,14665 +pkg_resources/extern/__init__.py,sha256=3PixaT9Tzzd4NoyV6CVhGd7S_9Z-U5yvMWAftZKvC6k,2362 +pkg_resources/tests/data/my-test-package-source/setup.py,sha256=Mrezl3nqxkYkjCYpIxmjhhg4AR8hgi4QZdEYmk-I7R8,104 +setuptools/__init__.py,sha256=l7ULo8jGk-4-8jbacmJ58cYpSRX4swS1ccbJaJVAGdM,7448 +setuptools/_deprecation_warning.py,sha256=jU9-dtfv6cKmtQJOXN8nP1mm7gONw5kKEtiPtbwnZyI,218 +setuptools/_imp.py,sha256=HmF91IbitRfsD5z-g4_wmcuH-RahyIONbPgiCOFgtzA,2392 +setuptools/archive_util.py,sha256=maJDbozRbDeSPw53VT0cb_IS3W0Ap73lJR8tX8RZDx0,7077 +setuptools/build_meta.py,sha256=hCU742vjgXHY6oKPYttBkie-n4DVNAJrUOgn0O_V3nc,10536 +setuptools/cli-32.exe,sha256=dfEuovMNnA2HLa3jRfMPVi5tk4R7alCbpTvuxtCyw0Y,65536 +setuptools/cli-64.exe,sha256=KLABu5pyrnokJCv6skjXZ6GsXeyYHGcqOUT3oHI3Xpo,74752 +setuptools/cli-arm64.exe,sha256=o9amxowudZ98NvNWh_a2DRY8LhoIRqTAekxABqltiMc,137216 +setuptools/cli.exe,sha256=dfEuovMNnA2HLa3jRfMPVi5tk4R7alCbpTvuxtCyw0Y,65536 +setuptools/config.py,sha256=O-T_28163qkEeaX8bLgqJLuOLYur15cC2_xpA0RENfM,23153 +setuptools/dep_util.py,sha256=BDx1BkzNQntvAB4alypHbW5UVBzjqths000PrUL4Zqc,949 +setuptools/depends.py,sha256=QYQIadr5DwLxPzkErhNt5hmRhvGhWxoXZMRXCm_jcQ0,5499 +setuptools/dist.py,sha256=73utfl0NHQ_Xfp5m3-wlbo7YaA31S_dkleh5P3GTKws,43162 +setuptools/errors.py,sha256=t4Rm85eXm71Ti0-PO1gAQMRK3V7NN3x1tcbcw0-xGSI,1555 +setuptools/extension.py,sha256=NMM46XjNdVelWemc0x8CyVKA5Ks6Zm3xTWSA2SS6xZM,1684 +setuptools/glob.py,sha256=1oZjbfjAHSXbgdhSuR6YGU8jKob9L8NtEmBYqcPTLYk,4873 +setuptools/gui-32.exe,sha256=XBr0bHMA6Hpz2s9s9Bzjl-PwXfa9nH4ie0rFn4V2kWA,65536 +setuptools/gui-64.exe,sha256=aYKMhX1IJLn4ULHgWX0sE0yREUt6B3TEHf_jOw6yNyE,75264 +setuptools/gui-arm64.exe,sha256=TEFnOKDi-mq3ZszxqbCoCXTnM_lhUWjdIqBpr6fVs40,137728 +setuptools/gui.exe,sha256=XBr0bHMA6Hpz2s9s9Bzjl-PwXfa9nH4ie0rFn4V2kWA,65536 +setuptools/installer.py,sha256=s6DQfsoICBJxbUqbduhOJtl1oG0S4yegRCg3EAs0i3M,3824 +setuptools/launch.py,sha256=TyPT-Ic1T2EnYvGO26gfNRP4ysBlrhpbRjQxWsiO414,812 +setuptools/monkey.py,sha256=0e3HdVKXHL415O7np-AUqhEFXPPuDdJKbI47chQ_DE4,5217 +setuptools/msvc.py,sha256=3LLt938e6OR7wWPzIvCQu7LCWZSIKqoKV6w3r8jV3kY,50561 +setuptools/namespaces.py,sha256=PMqGVPXPYQgjUTvEg9bGccRAkIODrQ6NmsDg_fwErwI,3093 +setuptools/package_index.py,sha256=MqctRM2Pw98yJgwy0JTafFzkLuM4ouD1fAO2x4aBfMQ,41872 +setuptools/py34compat.py,sha256=KYOd6ybRxjBW8NJmYD8t_UyyVmysppFXqHpFLdslGXU,245 +setuptools/sandbox.py,sha256=mR83i-mu-ZUU_7TaMgYCeRSyzkqv8loJ_GR9xhS2DDw,14348 +setuptools/script (dev).tmpl,sha256=RUzQzCQUaXtwdLtYHWYbIQmOaES5Brqq1FvUA_tu-5I,218 +setuptools/script.tmpl,sha256=WGTt5piezO27c-Dbx6l5Q4T3Ff20A5z7872hv3aAhYY,138 +setuptools/unicode_utils.py,sha256=aOOFo4JGwAsiBttGYDsqFS7YqWQeZ2j6DWiCuctR_00,941 +setuptools/version.py,sha256=og_cuZQb0QI6ukKZFfZWPlr1HgJBPPn2vO2m_bI9ZTE,144 +setuptools/wheel.py,sha256=0P8tSk105uF_Ub-30N2HU2X2v7MKDSdjpeQlRRW3SkI,8288 +setuptools/windows_support.py,sha256=5GrfqSP2-dLGJoZTq2g6dCKkyQxxa2n5IQiXlJCoYEE,714 +setuptools/_distutils/__init__.py,sha256=3YtkfadGoU57VMEQFk2TNyMZVud1kDkakWQLhWg2Fm8,536 +setuptools/_distutils/_msvccompiler.py,sha256=jR0JM5A1JMnZ6xMDicQzhXWgXTVXs1lWAeUexC1z198,20813 +setuptools/_distutils/archive_util.py,sha256=qW-uiGwYexTvK5e-iSel_31Dshx-CqTanNPK6snwf98,8572 +setuptools/_distutils/bcppcompiler.py,sha256=OJDVpCUmX6H8v_7lV1zifV1fcx92Cr2dhiUh6989UJI,14894 +setuptools/_distutils/ccompiler.py,sha256=YbernlpGZZqKnfzZSfJ814fINca8cicZiUlBjyUPyaM,47644 +setuptools/_distutils/cmd.py,sha256=eco6LAGUtobLuPafuhmgKgkwRRL_WY8KJ4YeDCHpcls,18079 +setuptools/_distutils/config.py,sha256=dtHgblx9JhfyrKx1-J7Jlxw_f7s8ZbPFQii2UWMTZpY,4827 +setuptools/_distutils/core.py,sha256=0v7Emh9y0AW9o4AEjfVMhDxKzTFWFxUQn46spFSL56g,9282 +setuptools/_distutils/cygwinccompiler.py,sha256=MhRmF3G0-5doB6XqCuNCvHIXcgUva-OulDwJRAjZzHY,17330 +setuptools/_distutils/debug.py,sha256=N6MrTAqK6l9SVk6tWweR108PM8Ol7qNlfyV-nHcLhsY,139 +setuptools/_distutils/dep_util.py,sha256=GuR9Iw_jzZRkyemJ5HX8rB_wRGxkIBcBm1qh54r7zhk,3491 +setuptools/_distutils/dir_util.py,sha256=UwhBOUTcV65GTwce4SPuTXR8Z8q3LYEcmttqcGb0bYo,7778 +setuptools/_distutils/dist.py,sha256=Biuf6ca8uiFfMScRFsYUKtb5neMPtxKxRtXn50_1f3U,50421 +setuptools/_distutils/errors.py,sha256=Yr6tKZGdzBoNi53vBtiq0UJ__X05CmxSdQJqOWaw6SY,3577 +setuptools/_distutils/extension.py,sha256=bTb3Q0CoevGKYv5dX1ls--Ln8tlB0-UEOsi9BwzlZ-s,10515 +setuptools/_distutils/fancy_getopt.py,sha256=OPxp2CxHi1Yp_d1D8JxW4Ueq9fC71tegQFaafh58GGU,17784 +setuptools/_distutils/file_util.py,sha256=0hUqfItN_x2DVihR0MHdA4KCMVCOO8VoByaFp_a6MDg,8148 +setuptools/_distutils/filelist.py,sha256=Z9f5hvepZnpniZ2IFmCnWIjdviWozs8sbARBhWajwoM,13407 +setuptools/_distutils/log.py,sha256=hWBmdUC2K927QcVv3REMW3HMPclxccPQngxLSuUXQl0,1969 +setuptools/_distutils/msvc9compiler.py,sha256=X623B92g0v8A3BEM9qpRf396AEd_hfjkfDUVTKu0hcE,30453 +setuptools/_distutils/msvccompiler.py,sha256=qruALeGRq8-CjtjE2tLQ8W26QnchcYedWzFme8AxZ4Q,23540 +setuptools/_distutils/py35compat.py,sha256=-sk1vBIsOgH-AobjIYbK_OEjdJF_54Ul_D1EiE9XM_c,455 +setuptools/_distutils/py38compat.py,sha256=II7ddBxOijC7uNN4z_46HYUjwYTJYMNiLJoGTormZm0,212 +setuptools/_distutils/spawn.py,sha256=4uE9k3VZWijxy7E_Rlcmh1MoamaPJ8rajdNBagKxjgU,3498 +setuptools/_distutils/sysconfig.py,sha256=k3fzINx3-qjge0udI6fC1UQSDPYpMGrxeSuV9cY4rmU,22151 +setuptools/_distutils/text_file.py,sha256=PsuAJeWdKJoLSV_6N6IpB5-0Pa84KzLUucJMFRazw3I,12483 +setuptools/_distutils/unixccompiler.py,sha256=u2Sfs6LRmqQux4nZW08GwDtoFMded6wYnkiaO2TvKC4,14538 +setuptools/_distutils/util.py,sha256=0v7B6nIsAXP11A7xqS6FC6lFAdaIqzxz_C-at4aMcgs,20655 +setuptools/_distutils/version.py,sha256=syRvPxuMQxnftpuIKeRE-2ELQ_ZMCwMJ-o8ie-lxdZo,13015 +setuptools/_distutils/versionpredicate.py,sha256=vx4ND3BtMgxFR9iZ4_t3WFa-NdIKxO8vtOd0twBppxc,5277 +setuptools/_distutils/command/__init__.py,sha256=2TA-rlNDlzeI-csbWHXFjGD8uOYqALMfyWOhT49nC6g,799 +setuptools/_distutils/command/bdist.py,sha256=2z4eudRl_n7m3lG9leL0IYqes4bsm8c0fxfZuiafjMg,5562 +setuptools/_distutils/command/bdist_dumb.py,sha256=BTur9jcIppyP7Piavjfsk7YjElqvxeYO2npUyPPOekc,4913 +setuptools/_distutils/command/bdist_msi.py,sha256=EVFQYN_X-ExeeP8gmdV9JcINsuUGsLJUz9afMU0Rt8c,35579 +setuptools/_distutils/command/bdist_rpm.py,sha256=gjOw22GhDSbcq0bdq25cTb-n6HWWm0bShLQad_mkJ4k,21537 +setuptools/_distutils/command/bdist_wininst.py,sha256=iGlaI-VfElHOneeczKHWnSN5a10-7IMcJaXuR1mdS3c,16030 +setuptools/_distutils/command/build.py,sha256=1AF-dxN_NlOEyoydBz19AwpeWYPSYCZvOLJSN_PdatY,5773 +setuptools/_distutils/command/build_clib.py,sha256=bgVTHh28eLQA2Gkw68amApd_j7qQBX4MTI-zTvAK_J4,8022 +setuptools/_distutils/command/build_ext.py,sha256=KgxpopuD6sqep0LsumMH15joWih0VdbnXpYm-ETNjoE,31612 +setuptools/_distutils/command/build_py.py,sha256=hXesMrH_epNj6K8SUtJdipgEis3EdICKeZ8VWe_ndck,16495 +setuptools/_distutils/command/build_scripts.py,sha256=urdn6wPxPMW5dLqpqFkZ8dqaFG1tf9TiAao6U9LCoEI,5963 +setuptools/_distutils/command/check.py,sha256=5qDtI75ccZg3sAItQWeaIu8y3FR314O4rr9Smz4HsEo,5637 +setuptools/_distutils/command/clean.py,sha256=2TCt47ru4hZZM0RfVfUYj5bbpicpGLP4Qhw5jBtvp9k,2776 +setuptools/_distutils/command/config.py,sha256=2aTjww3PwjMB8-ZibCe4P7B-qG1hM1gn_rJXYyxRz6c,13117 +setuptools/_distutils/command/install.py,sha256=zX_OITRItDnNAv0iVjXdFVitf3f63tHzK_mZ1sIxsuc,28970 +setuptools/_distutils/command/install_data.py,sha256=YhGOAwh3gJPqF7em5XA0rmpR42z1bLh80ooElzDyUvk,2822 +setuptools/_distutils/command/install_egg_info.py,sha256=WijZ7cHMAkNMMCwrZ--KoqV9M2RtLouU4-qSbiCwv70,2753 +setuptools/_distutils/command/install_headers.py,sha256=XQ6idkbIDfr1ljXCOznuVUMvOFpHBn6cK0Wz9gIM2b4,1298 +setuptools/_distutils/command/install_lib.py,sha256=9AofR-MO9lAtjwwuukCptepOaJEKMZW2VHiyR5hU7HA,8397 +setuptools/_distutils/command/install_scripts.py,sha256=_CLUeQwGJRcY2kik7azPMn5IdtDCrjWdUvZ1khlG6ck,2017 +setuptools/_distutils/command/py37compat.py,sha256=qzRhhvTihqx_PZZt2ZYECxh1X3Oj255VqatzelYFAKw,671 +setuptools/_distutils/command/register.py,sha256=2jaq9968rt2puRVDBx1HbNiXv27uOk8idE_4lPf_3VM,11712 +setuptools/_distutils/command/sdist.py,sha256=qotJjAOzyhJjq2-oDImjNFrOtaSneEFDJTB-sEk1wnU,19005 +setuptools/_distutils/command/upload.py,sha256=BLO1w7eSAqsCjCLXtf_CRVSjwF1WmyOByGVGNdcQ8oY,7597 +setuptools/_vendor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +setuptools/_vendor/ordered_set.py,sha256=dbaCcs27dyN9gnMWGF5nA_BrVn6Q-NrjKYJpV9_fgBs,15130 +setuptools/_vendor/pyparsing.py,sha256=tmrp-lu-qO1i75ZzIN5A12nKRRD1Cm4Vpk-5LR9rims,232055 +setuptools/_vendor/more_itertools/__init__.py,sha256=C7sXffHTXM3P-iaLPPfqfmDoxOflQMJLcM7ed9p3jak,82 +setuptools/_vendor/more_itertools/more.py,sha256=DlZa8v6JihVwfQ5zHidOA-xDE0orcQIUyxVnCaUoDKE,117968 +setuptools/_vendor/more_itertools/recipes.py,sha256=UkNkrsZyqiwgLHANBTmvMhCvaNSvSNYhyOpz_Jc55DY,16256 +setuptools/_vendor/packaging/__about__.py,sha256=IIRHpOsJlJSgkjq1UoeBoMTqhvNp3gN9FyMb5Kf8El4,661 +setuptools/_vendor/packaging/__init__.py,sha256=b9Kk5MF7KxhhLgcDmiUWukN-LatWFxPdNug0joPhHSk,497 +setuptools/_vendor/packaging/_manylinux.py,sha256=XcbiXB-qcjv3bcohp6N98TMpOP4_j3m-iOA8ptK2GWY,11488 +setuptools/_vendor/packaging/_musllinux.py,sha256=z5yeG1ygOPx4uUyLdqj-p8Dk5UBb5H_b0NIjW9yo8oA,4378 +setuptools/_vendor/packaging/_structures.py,sha256=TMiAgFbdUOPmIfDIfiHc3KFhSJ8kMjof2QS5I-2NyQ8,1629 +setuptools/_vendor/packaging/markers.py,sha256=lihRgqpZjLM-JW-vxlLPqU3kmVe79g9vypy1kxmTRuQ,8493 +setuptools/_vendor/packaging/requirements.py,sha256=Opd0FjqgdEiWkzBLyo1oLU0Dj01uIFwTAnAJQrr6j2A,4700 +setuptools/_vendor/packaging/specifiers.py,sha256=MZ-fYcNL3u7pNrt-6g2EQO7AbRXkjc-SPEYwXMQbLmc,30964 +setuptools/_vendor/packaging/tags.py,sha256=vGybAUQYlPKMcukzX_2e65fmafnFFuMbD25naYTEwtc,15710 +setuptools/_vendor/packaging/utils.py,sha256=dJjeat3BS-TYn1RrUFVwufUMasbtzLfYRoy_HXENeFQ,4200 +setuptools/_vendor/packaging/version.py,sha256=_fLRNrFrxYcHVfyo8vk9j8s6JM8N_xsSxVFr6RJyco8,14665 +setuptools/command/__init__.py,sha256=e-8TJOikUe3St0fw2b2p9u5EDdSxl5zHUBJJKifbcQ8,217 +setuptools/command/alias.py,sha256=1sLQxZcNh6dDQpDmm4G7UGGTol83nY1NTPmNBbm2siI,2381 +setuptools/command/bdist_egg.py,sha256=-upiB6fFtm8cQSQj1LRDVpG1-T143DsXCvV0fh03u7U,16604 +setuptools/command/bdist_rpm.py,sha256=PxrgoHPNaw2Pw2qNjjHDPC-Ay_IaDbCqP3d_5N-cj2A,1182 +setuptools/command/build_clib.py,sha256=fWHSFGkk10VCddBWCszvNhowbG9Z9CZXVjQ2uSInoOs,4415 +setuptools/command/build_ext.py,sha256=SNK042HfB2ezlDQbSVRGFqI1IM5A4AsjU1wpV3fgskE,13212 +setuptools/command/build_py.py,sha256=c90V1nVPEtYkdye-xvo-B48V5RLvSgD8JBMfPtUbtYw,8751 +setuptools/command/develop.py,sha256=5_Ss7ENd1_B_jVMY1tF5UV_y1Xu6jbVzAPG8oKeluGA,7012 +setuptools/command/dist_info.py,sha256=5t6kOfrdgALT-P3ogss6PF9k-Leyesueycuk3dUyZnI,960 +setuptools/command/easy_install.py,sha256=oXURojITuMmOQ2ZcOJ_IKkm5ahyoT5tnA89jZ70CTds,87973 +setuptools/command/egg_info.py,sha256=5rV9PH6Eeics9xkpzx-nsTBL54S1S-Bf0r1liCtYPVI,26134 +setuptools/command/install.py,sha256=UynjFBgRyyHrDZRVAmXrXG0vChJAMx-sxnOO3JoAzVo,4906 +setuptools/command/install_egg_info.py,sha256=4zq_Ad3jE-EffParuyDEnvxU6efB-Xhrzdr8aB6Ln_8,3195 +setuptools/command/install_lib.py,sha256=4zK0nihAAwMYIoOS0UOBLZKSOCBbXXPKsTraO_a8qmk,5036 +setuptools/command/install_scripts.py,sha256=o0jN_ex7yYYk8W5clymTFOXwkFMKzW9q_zd9Npcex7M,2593 +setuptools/command/launcher manifest.xml,sha256=xlLbjWrB01tKC0-hlVkOKkiSPbzMml2eOPtJ_ucCnbE,628 +setuptools/command/py36compat.py,sha256=7yLWzQj179Enx3pJ8V1cDDCzeLMFMd9XJXlK-iZTq5Y,4946 +setuptools/command/register.py,sha256=kk3DxXCb5lXTvqnhfwx2g6q7iwbUmgTyXUCaBooBOUk,468 +setuptools/command/rotate.py,sha256=SvsQPasezIojPjvMnfkqzh8P0U0tCj0daczF8uc3NQM,2128 +setuptools/command/saveopts.py,sha256=za7QCBcQimKKriWcoCcbhxPjUz30gSB74zuTL47xpP4,658 +setuptools/command/sdist.py,sha256=2onJidYBPFpUgcX6J4KjZX5ilwciHPRB8VkID5YVaL0,6413 +setuptools/command/setopt.py,sha256=okxhqD1NM1nQlbSVDCNv6P7Y7g680sc2r-tUW7wPH1Y,5086 +setuptools/command/test.py,sha256=qGY-Hx1RPCndlVh2rsrEs5479CgmxRsrEflVLr98jVA,8088 +setuptools/command/upload.py,sha256=XT3YFVfYPAmA5qhGg0euluU98ftxRUW-PzKcODMLxUs,462 +setuptools/command/upload_docs.py,sha256=ba5kOyedD_u62weinrxqqnvpuQvBIuamXehJG6tAvO0,7218 +setuptools/extern/__init__.py,sha256=Hhf9W73WAitw9TdRJfDIb6YFjmK56CF61afds1Mg0HY,2407 +setuptools-59.6.0.dist-info/LICENSE,sha256=2z8CRrH5J48VhFuZ_sR4uLUG63ZIeZNyL4xuJUKF-vg,1050 +setuptools-59.6.0.dist-info/METADATA,sha256=MrFrS_r3YeU3-S5Oc90GA1rUKVAviWQc7IDlDSxTRp0,5017 +setuptools-59.6.0.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92 +setuptools-59.6.0.dist-info/entry_points.txt,sha256=wpnhLrbtyk4hZ1qCCw48cCSxoQPzULMhIuaFqsB7GxQ,2636 +setuptools-59.6.0.dist-info/top_level.txt,sha256=1Euo4fJMWPMc6iG8BrvoHx4c65FnzA7Mv_p3en0BDds,48 +setuptools-59.6.0.dist-info/RECORD,, diff --git a/venv/lib/python3.10/site-packages/setuptools-59.6.0.dist-info/WHEEL b/venv/lib/python3.10/site-packages/setuptools-59.6.0.dist-info/WHEEL new file mode 100644 index 00000000..becc9a66 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools-59.6.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.1) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/venv/lib/python3.10/site-packages/setuptools-59.6.0.dist-info/entry_points.txt b/venv/lib/python3.10/site-packages/setuptools-59.6.0.dist-info/entry_points.txt new file mode 100644 index 00000000..9466bf63 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools-59.6.0.dist-info/entry_points.txt @@ -0,0 +1,56 @@ +[distutils.commands] +alias = setuptools.command.alias:alias +bdist_egg = setuptools.command.bdist_egg:bdist_egg +bdist_rpm = setuptools.command.bdist_rpm:bdist_rpm +build_clib = setuptools.command.build_clib:build_clib +build_ext = setuptools.command.build_ext:build_ext +build_py = setuptools.command.build_py:build_py +develop = setuptools.command.develop:develop +dist_info = setuptools.command.dist_info:dist_info +easy_install = setuptools.command.easy_install:easy_install +egg_info = setuptools.command.egg_info:egg_info +install = setuptools.command.install:install +install_egg_info = setuptools.command.install_egg_info:install_egg_info +install_lib = setuptools.command.install_lib:install_lib +install_scripts = setuptools.command.install_scripts:install_scripts +rotate = setuptools.command.rotate:rotate +saveopts = setuptools.command.saveopts:saveopts +sdist = setuptools.command.sdist:sdist +setopt = setuptools.command.setopt:setopt +test = setuptools.command.test:test +upload_docs = setuptools.command.upload_docs:upload_docs + +[distutils.setup_keywords] +dependency_links = setuptools.dist:assert_string_list +eager_resources = setuptools.dist:assert_string_list +entry_points = setuptools.dist:check_entry_points +exclude_package_data = setuptools.dist:check_package_data +extras_require = setuptools.dist:check_extras +include_package_data = setuptools.dist:assert_bool +install_requires = setuptools.dist:check_requirements +namespace_packages = setuptools.dist:check_nsp +package_data = setuptools.dist:check_package_data +packages = setuptools.dist:check_packages +python_requires = setuptools.dist:check_specifier +setup_requires = setuptools.dist:check_requirements +test_loader = setuptools.dist:check_importable +test_runner = setuptools.dist:check_importable +test_suite = setuptools.dist:check_test_suite +tests_require = setuptools.dist:check_requirements +use_2to3 = setuptools.dist:invalid_unless_false +zip_safe = setuptools.dist:assert_bool + +[egg_info.writers] +PKG-INFO = setuptools.command.egg_info:write_pkg_info +dependency_links.txt = setuptools.command.egg_info:overwrite_arg +depends.txt = setuptools.command.egg_info:warn_depends_obsolete +eager_resources.txt = setuptools.command.egg_info:overwrite_arg +entry_points.txt = setuptools.command.egg_info:write_entries +namespace_packages.txt = setuptools.command.egg_info:overwrite_arg +requires.txt = setuptools.command.egg_info:write_requirements +top_level.txt = setuptools.command.egg_info:write_toplevel_names + +[setuptools.finalize_distribution_options] +keywords = setuptools.dist:Distribution._finalize_setup_keywords +parent_finalize = setuptools.dist:_Distribution.finalize_options + diff --git a/venv/lib/python3.10/site-packages/setuptools-59.6.0.dist-info/top_level.txt b/venv/lib/python3.10/site-packages/setuptools-59.6.0.dist-info/top_level.txt new file mode 100644 index 00000000..37020324 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools-59.6.0.dist-info/top_level.txt @@ -0,0 +1,4 @@ +_distutils_hack +debian +pkg_resources +setuptools diff --git a/venv/lib/python3.10/site-packages/setuptools/__init__.py b/venv/lib/python3.10/site-packages/setuptools/__init__.py new file mode 100644 index 00000000..9d6f0bc0 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/__init__.py @@ -0,0 +1,242 @@ +"""Extensions to the 'distutils' for large or complex distributions""" + +from fnmatch import fnmatchcase +import functools +import os +import re + +import _distutils_hack.override # noqa: F401 + +import distutils.core +from distutils.errors import DistutilsOptionError +from distutils.util import convert_path + +from ._deprecation_warning import SetuptoolsDeprecationWarning + +import setuptools.version +from setuptools.extension import Extension +from setuptools.dist import Distribution +from setuptools.depends import Require +from . import monkey + + +__all__ = [ + 'setup', + 'Distribution', + 'Command', + 'Extension', + 'Require', + 'SetuptoolsDeprecationWarning', + 'find_packages', + 'find_namespace_packages', +] + +__version__ = setuptools.version.__version__ + +bootstrap_install_from = None + + +class PackageFinder: + """ + Generate a list of all Python packages found within a directory + """ + + @classmethod + def find(cls, where='.', exclude=(), include=('*',)): + """Return a list all Python packages found within directory 'where' + + 'where' is the root directory which will be searched for packages. It + should be supplied as a "cross-platform" (i.e. URL-style) path; it will + be converted to the appropriate local path syntax. + + 'exclude' is a sequence of package names to exclude; '*' can be used + as a wildcard in the names, such that 'foo.*' will exclude all + subpackages of 'foo' (but not 'foo' itself). + + 'include' is a sequence of package names to include. If it's + specified, only the named packages will be included. If it's not + specified, all found packages will be included. 'include' can contain + shell style wildcard patterns just like 'exclude'. + """ + + return list( + cls._find_packages_iter( + convert_path(where), + cls._build_filter('ez_setup', '*__pycache__', *exclude), + cls._build_filter(*include), + ) + ) + + @classmethod + def _find_packages_iter(cls, where, exclude, include): + """ + All the packages found in 'where' that pass the 'include' filter, but + not the 'exclude' filter. + """ + for root, dirs, files in os.walk(where, followlinks=True): + # Copy dirs to iterate over it, then empty dirs. + all_dirs = dirs[:] + dirs[:] = [] + + for dir in all_dirs: + full_path = os.path.join(root, dir) + rel_path = os.path.relpath(full_path, where) + package = rel_path.replace(os.path.sep, '.') + + # Skip directory trees that are not valid packages + if '.' in dir or not cls._looks_like_package(full_path): + continue + + # Should this package be included? + if include(package) and not exclude(package): + yield package + + # Keep searching subdirectories, as there may be more packages + # down there, even if the parent was excluded. + dirs.append(dir) + + @staticmethod + def _looks_like_package(path): + """Does a directory look like a package?""" + return os.path.isfile(os.path.join(path, '__init__.py')) + + @staticmethod + def _build_filter(*patterns): + """ + Given a list of patterns, return a callable that will be true only if + the input matches at least one of the patterns. + """ + return lambda name: any(fnmatchcase(name, pat=pat) for pat in patterns) + + +class PEP420PackageFinder(PackageFinder): + @staticmethod + def _looks_like_package(path): + return True + + +find_packages = PackageFinder.find +find_namespace_packages = PEP420PackageFinder.find + + +def _install_setup_requires(attrs): + # Note: do not use `setuptools.Distribution` directly, as + # our PEP 517 backend patch `distutils.core.Distribution`. + class MinimalDistribution(distutils.core.Distribution): + """ + A minimal version of a distribution for supporting the + fetch_build_eggs interface. + """ + + def __init__(self, attrs): + _incl = 'dependency_links', 'setup_requires' + filtered = {k: attrs[k] for k in set(_incl) & set(attrs)} + distutils.core.Distribution.__init__(self, filtered) + + def finalize_options(self): + """ + Disable finalize_options to avoid building the working set. + Ref #2158. + """ + + dist = MinimalDistribution(attrs) + + # Honor setup.cfg's options. + dist.parse_config_files(ignore_option_errors=True) + if dist.setup_requires: + dist.fetch_build_eggs(dist.setup_requires) + + +def setup(**attrs): + # Make sure we have any requirements needed to interpret 'attrs'. + _install_setup_requires(attrs) + return distutils.core.setup(**attrs) + + +setup.__doc__ = distutils.core.setup.__doc__ + + +_Command = monkey.get_unpatched(distutils.core.Command) + + +class Command(_Command): + __doc__ = _Command.__doc__ + + command_consumes_arguments = False + + def __init__(self, dist, **kw): + """ + Construct the command for dist, updating + vars(self) with any keyword parameters. + """ + _Command.__init__(self, dist) + vars(self).update(kw) + + def _ensure_stringlike(self, option, what, default=None): + val = getattr(self, option) + if val is None: + setattr(self, option, default) + return default + elif not isinstance(val, str): + raise DistutilsOptionError( + "'%s' must be a %s (got `%s`)" % (option, what, val) + ) + return val + + def ensure_string_list(self, option): + r"""Ensure that 'option' is a list of strings. If 'option' is + currently a string, we split it either on /,\s*/ or /\s+/, so + "foo bar baz", "foo,bar,baz", and "foo, bar baz" all become + ["foo", "bar", "baz"]. + """ + val = getattr(self, option) + if val is None: + return + elif isinstance(val, str): + setattr(self, option, re.split(r',\s*|\s+', val)) + else: + if isinstance(val, list): + ok = all(isinstance(v, str) for v in val) + else: + ok = False + if not ok: + raise DistutilsOptionError( + "'%s' must be a list of strings (got %r)" % (option, val) + ) + + def reinitialize_command(self, command, reinit_subcommands=0, **kw): + cmd = _Command.reinitialize_command(self, command, reinit_subcommands) + vars(cmd).update(kw) + return cmd + + +def _find_all_simple(path): + """ + Find all files under 'path' + """ + results = ( + os.path.join(base, file) + for base, dirs, files in os.walk(path, followlinks=True) + for file in files + ) + return filter(os.path.isfile, results) + + +def findall(dir=os.curdir): + """ + Find all files under 'dir' and return the list of full filenames. + Unless dir is '.', return full filenames with dir prepended. + """ + files = _find_all_simple(dir) + if dir == os.curdir: + make_rel = functools.partial(os.path.relpath, start=dir) + files = map(make_rel, files) + return list(files) + + +class sic(str): + """Treat this string as-is (https://en.wikipedia.org/wiki/Sic)""" + + +# Apply monkey patches +monkey.patch_all() diff --git a/venv/lib/python3.10/site-packages/setuptools/_deprecation_warning.py b/venv/lib/python3.10/site-packages/setuptools/_deprecation_warning.py new file mode 100644 index 00000000..086b64dd --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_deprecation_warning.py @@ -0,0 +1,7 @@ +class SetuptoolsDeprecationWarning(Warning): + """ + Base class for warning deprecations in ``setuptools`` + + This class is not derived from ``DeprecationWarning``, and as such is + visible by default. + """ diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/__init__.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/__init__.py new file mode 100644 index 00000000..8fd493b4 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/__init__.py @@ -0,0 +1,24 @@ +"""distutils + +The main package for the Python Module Distribution Utilities. Normally +used from a setup script as + + from distutils.core import setup + + setup (...) +""" + +import sys +import importlib + +__version__ = sys.version[:sys.version.index(' ')] + + +try: + # Allow Debian and pkgsrc (only) to customize system + # behavior. Ref pypa/distutils#2 and pypa/distutils#16. + # This hook is deprecated and no other environments + # should use it. + importlib.import_module('_distutils_system_mod') +except ImportError: + pass diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/_msvccompiler.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/_msvccompiler.py new file mode 100644 index 00000000..b7a06082 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/_msvccompiler.py @@ -0,0 +1,561 @@ +"""distutils._msvccompiler + +Contains MSVCCompiler, an implementation of the abstract CCompiler class +for Microsoft Visual Studio 2015. + +The module is compatible with VS 2015 and later. You can find legacy support +for older versions in distutils.msvc9compiler and distutils.msvccompiler. +""" + +# Written by Perry Stoll +# hacked by Robin Becker and Thomas Heller to do a better job of +# finding DevStudio (through the registry) +# ported to VS 2005 and VS 2008 by Christian Heimes +# ported to VS 2015 by Steve Dower + +import os +import subprocess +import contextlib +import warnings +import unittest.mock +with contextlib.suppress(ImportError): + import winreg + +from distutils.errors import DistutilsExecError, DistutilsPlatformError, \ + CompileError, LibError, LinkError +from distutils.ccompiler import CCompiler, gen_lib_options +from distutils import log +from distutils.util import get_platform + +from itertools import count + +def _find_vc2015(): + try: + key = winreg.OpenKeyEx( + winreg.HKEY_LOCAL_MACHINE, + r"Software\Microsoft\VisualStudio\SxS\VC7", + access=winreg.KEY_READ | winreg.KEY_WOW64_32KEY + ) + except OSError: + log.debug("Visual C++ is not registered") + return None, None + + best_version = 0 + best_dir = None + with key: + for i in count(): + try: + v, vc_dir, vt = winreg.EnumValue(key, i) + except OSError: + break + if v and vt == winreg.REG_SZ and os.path.isdir(vc_dir): + try: + version = int(float(v)) + except (ValueError, TypeError): + continue + if version >= 14 and version > best_version: + best_version, best_dir = version, vc_dir + return best_version, best_dir + +def _find_vc2017(): + """Returns "15, path" based on the result of invoking vswhere.exe + If no install is found, returns "None, None" + + The version is returned to avoid unnecessarily changing the function + result. It may be ignored when the path is not None. + + If vswhere.exe is not available, by definition, VS 2017 is not + installed. + """ + root = os.environ.get("ProgramFiles(x86)") or os.environ.get("ProgramFiles") + if not root: + return None, None + + try: + path = subprocess.check_output([ + os.path.join(root, "Microsoft Visual Studio", "Installer", "vswhere.exe"), + "-latest", + "-prerelease", + "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", + "-property", "installationPath", + "-products", "*", + ], encoding="mbcs", errors="strict").strip() + except (subprocess.CalledProcessError, OSError, UnicodeDecodeError): + return None, None + + path = os.path.join(path, "VC", "Auxiliary", "Build") + if os.path.isdir(path): + return 15, path + + return None, None + +PLAT_SPEC_TO_RUNTIME = { + 'x86' : 'x86', + 'x86_amd64' : 'x64', + 'x86_arm' : 'arm', + 'x86_arm64' : 'arm64' +} + +def _find_vcvarsall(plat_spec): + # bpo-38597: Removed vcruntime return value + _, best_dir = _find_vc2017() + + if not best_dir: + best_version, best_dir = _find_vc2015() + + if not best_dir: + log.debug("No suitable Visual C++ version found") + return None, None + + vcvarsall = os.path.join(best_dir, "vcvarsall.bat") + if not os.path.isfile(vcvarsall): + log.debug("%s cannot be found", vcvarsall) + return None, None + + return vcvarsall, None + +def _get_vc_env(plat_spec): + if os.getenv("DISTUTILS_USE_SDK"): + return { + key.lower(): value + for key, value in os.environ.items() + } + + vcvarsall, _ = _find_vcvarsall(plat_spec) + if not vcvarsall: + raise DistutilsPlatformError("Unable to find vcvarsall.bat") + + try: + out = subprocess.check_output( + 'cmd /u /c "{}" {} && set'.format(vcvarsall, plat_spec), + stderr=subprocess.STDOUT, + ).decode('utf-16le', errors='replace') + except subprocess.CalledProcessError as exc: + log.error(exc.output) + raise DistutilsPlatformError("Error executing {}" + .format(exc.cmd)) + + env = { + key.lower(): value + for key, _, value in + (line.partition('=') for line in out.splitlines()) + if key and value + } + + return env + +def _find_exe(exe, paths=None): + """Return path to an MSVC executable program. + + Tries to find the program in several places: first, one of the + MSVC program search paths from the registry; next, the directories + in the PATH environment variable. If any of those work, return an + absolute path that is known to exist. If none of them work, just + return the original program name, 'exe'. + """ + if not paths: + paths = os.getenv('path').split(os.pathsep) + for p in paths: + fn = os.path.join(os.path.abspath(p), exe) + if os.path.isfile(fn): + return fn + return exe + +# A map keyed by get_platform() return values to values accepted by +# 'vcvarsall.bat'. Always cross-compile from x86 to work with the +# lighter-weight MSVC installs that do not include native 64-bit tools. +PLAT_TO_VCVARS = { + 'win32' : 'x86', + 'win-amd64' : 'x86_amd64', + 'win-arm32' : 'x86_arm', + 'win-arm64' : 'x86_arm64' +} + +class MSVCCompiler(CCompiler) : + """Concrete class that implements an interface to Microsoft Visual C++, + as defined by the CCompiler abstract class.""" + + compiler_type = 'msvc' + + # Just set this so CCompiler's constructor doesn't barf. We currently + # don't use the 'set_executables()' bureaucracy provided by CCompiler, + # as it really isn't necessary for this sort of single-compiler class. + # Would be nice to have a consistent interface with UnixCCompiler, + # though, so it's worth thinking about. + executables = {} + + # Private class data (need to distinguish C from C++ source for compiler) + _c_extensions = ['.c'] + _cpp_extensions = ['.cc', '.cpp', '.cxx'] + _rc_extensions = ['.rc'] + _mc_extensions = ['.mc'] + + # Needed for the filename generation methods provided by the + # base class, CCompiler. + src_extensions = (_c_extensions + _cpp_extensions + + _rc_extensions + _mc_extensions) + res_extension = '.res' + obj_extension = '.obj' + static_lib_extension = '.lib' + shared_lib_extension = '.dll' + static_lib_format = shared_lib_format = '%s%s' + exe_extension = '.exe' + + + def __init__(self, verbose=0, dry_run=0, force=0): + CCompiler.__init__ (self, verbose, dry_run, force) + # target platform (.plat_name is consistent with 'bdist') + self.plat_name = None + self.initialized = False + + def initialize(self, plat_name=None): + # multi-init means we would need to check platform same each time... + assert not self.initialized, "don't init multiple times" + if plat_name is None: + plat_name = get_platform() + # sanity check for platforms to prevent obscure errors later. + if plat_name not in PLAT_TO_VCVARS: + raise DistutilsPlatformError("--plat-name must be one of {}" + .format(tuple(PLAT_TO_VCVARS))) + + # Get the vcvarsall.bat spec for the requested platform. + plat_spec = PLAT_TO_VCVARS[plat_name] + + vc_env = _get_vc_env(plat_spec) + if not vc_env: + raise DistutilsPlatformError("Unable to find a compatible " + "Visual Studio installation.") + + self._paths = vc_env.get('path', '') + paths = self._paths.split(os.pathsep) + self.cc = _find_exe("cl.exe", paths) + self.linker = _find_exe("link.exe", paths) + self.lib = _find_exe("lib.exe", paths) + self.rc = _find_exe("rc.exe", paths) # resource compiler + self.mc = _find_exe("mc.exe", paths) # message compiler + self.mt = _find_exe("mt.exe", paths) # message compiler + + for dir in vc_env.get('include', '').split(os.pathsep): + if dir: + self.add_include_dir(dir.rstrip(os.sep)) + + for dir in vc_env.get('lib', '').split(os.pathsep): + if dir: + self.add_library_dir(dir.rstrip(os.sep)) + + self.preprocess_options = None + # bpo-38597: Always compile with dynamic linking + # Future releases of Python 3.x will include all past + # versions of vcruntime*.dll for compatibility. + self.compile_options = [ + '/nologo', '/O2', '/W3', '/GL', '/DNDEBUG', '/MD' + ] + + self.compile_options_debug = [ + '/nologo', '/Od', '/MDd', '/Zi', '/W3', '/D_DEBUG' + ] + + ldflags = [ + '/nologo', '/INCREMENTAL:NO', '/LTCG' + ] + + ldflags_debug = [ + '/nologo', '/INCREMENTAL:NO', '/LTCG', '/DEBUG:FULL' + ] + + self.ldflags_exe = [*ldflags, '/MANIFEST:EMBED,ID=1'] + self.ldflags_exe_debug = [*ldflags_debug, '/MANIFEST:EMBED,ID=1'] + self.ldflags_shared = [*ldflags, '/DLL', '/MANIFEST:EMBED,ID=2', '/MANIFESTUAC:NO'] + self.ldflags_shared_debug = [*ldflags_debug, '/DLL', '/MANIFEST:EMBED,ID=2', '/MANIFESTUAC:NO'] + self.ldflags_static = [*ldflags] + self.ldflags_static_debug = [*ldflags_debug] + + self._ldflags = { + (CCompiler.EXECUTABLE, None): self.ldflags_exe, + (CCompiler.EXECUTABLE, False): self.ldflags_exe, + (CCompiler.EXECUTABLE, True): self.ldflags_exe_debug, + (CCompiler.SHARED_OBJECT, None): self.ldflags_shared, + (CCompiler.SHARED_OBJECT, False): self.ldflags_shared, + (CCompiler.SHARED_OBJECT, True): self.ldflags_shared_debug, + (CCompiler.SHARED_LIBRARY, None): self.ldflags_static, + (CCompiler.SHARED_LIBRARY, False): self.ldflags_static, + (CCompiler.SHARED_LIBRARY, True): self.ldflags_static_debug, + } + + self.initialized = True + + # -- Worker methods ------------------------------------------------ + + def object_filenames(self, + source_filenames, + strip_dir=0, + output_dir=''): + ext_map = { + **{ext: self.obj_extension for ext in self.src_extensions}, + **{ext: self.res_extension for ext in self._rc_extensions + self._mc_extensions}, + } + + output_dir = output_dir or '' + + def make_out_path(p): + base, ext = os.path.splitext(p) + if strip_dir: + base = os.path.basename(base) + else: + _, base = os.path.splitdrive(base) + if base.startswith((os.path.sep, os.path.altsep)): + base = base[1:] + try: + # XXX: This may produce absurdly long paths. We should check + # the length of the result and trim base until we fit within + # 260 characters. + return os.path.join(output_dir, base + ext_map[ext]) + except LookupError: + # Better to raise an exception instead of silently continuing + # and later complain about sources and targets having + # different lengths + raise CompileError("Don't know how to compile {}".format(p)) + + return list(map(make_out_path, source_filenames)) + + + def compile(self, sources, + output_dir=None, macros=None, include_dirs=None, debug=0, + extra_preargs=None, extra_postargs=None, depends=None): + + if not self.initialized: + self.initialize() + compile_info = self._setup_compile(output_dir, macros, include_dirs, + sources, depends, extra_postargs) + macros, objects, extra_postargs, pp_opts, build = compile_info + + compile_opts = extra_preargs or [] + compile_opts.append('/c') + if debug: + compile_opts.extend(self.compile_options_debug) + else: + compile_opts.extend(self.compile_options) + + + add_cpp_opts = False + + for obj in objects: + try: + src, ext = build[obj] + except KeyError: + continue + if debug: + # pass the full pathname to MSVC in debug mode, + # this allows the debugger to find the source file + # without asking the user to browse for it + src = os.path.abspath(src) + + if ext in self._c_extensions: + input_opt = "/Tc" + src + elif ext in self._cpp_extensions: + input_opt = "/Tp" + src + add_cpp_opts = True + elif ext in self._rc_extensions: + # compile .RC to .RES file + input_opt = src + output_opt = "/fo" + obj + try: + self.spawn([self.rc] + pp_opts + [output_opt, input_opt]) + except DistutilsExecError as msg: + raise CompileError(msg) + continue + elif ext in self._mc_extensions: + # Compile .MC to .RC file to .RES file. + # * '-h dir' specifies the directory for the + # generated include file + # * '-r dir' specifies the target directory of the + # generated RC file and the binary message resource + # it includes + # + # For now (since there are no options to change this), + # we use the source-directory for the include file and + # the build directory for the RC file and message + # resources. This works at least for win32all. + h_dir = os.path.dirname(src) + rc_dir = os.path.dirname(obj) + try: + # first compile .MC to .RC and .H file + self.spawn([self.mc, '-h', h_dir, '-r', rc_dir, src]) + base, _ = os.path.splitext(os.path.basename (src)) + rc_file = os.path.join(rc_dir, base + '.rc') + # then compile .RC to .RES file + self.spawn([self.rc, "/fo" + obj, rc_file]) + + except DistutilsExecError as msg: + raise CompileError(msg) + continue + else: + # how to handle this file? + raise CompileError("Don't know how to compile {} to {}" + .format(src, obj)) + + args = [self.cc] + compile_opts + pp_opts + if add_cpp_opts: + args.append('/EHsc') + args.append(input_opt) + args.append("/Fo" + obj) + args.extend(extra_postargs) + + try: + self.spawn(args) + except DistutilsExecError as msg: + raise CompileError(msg) + + return objects + + + def create_static_lib(self, + objects, + output_libname, + output_dir=None, + debug=0, + target_lang=None): + + if not self.initialized: + self.initialize() + objects, output_dir = self._fix_object_args(objects, output_dir) + output_filename = self.library_filename(output_libname, + output_dir=output_dir) + + if self._need_link(objects, output_filename): + lib_args = objects + ['/OUT:' + output_filename] + if debug: + pass # XXX what goes here? + try: + log.debug('Executing "%s" %s', self.lib, ' '.join(lib_args)) + self.spawn([self.lib] + lib_args) + except DistutilsExecError as msg: + raise LibError(msg) + else: + log.debug("skipping %s (up-to-date)", output_filename) + + + def link(self, + target_desc, + objects, + output_filename, + output_dir=None, + libraries=None, + library_dirs=None, + runtime_library_dirs=None, + export_symbols=None, + debug=0, + extra_preargs=None, + extra_postargs=None, + build_temp=None, + target_lang=None): + + if not self.initialized: + self.initialize() + objects, output_dir = self._fix_object_args(objects, output_dir) + fixed_args = self._fix_lib_args(libraries, library_dirs, + runtime_library_dirs) + libraries, library_dirs, runtime_library_dirs = fixed_args + + if runtime_library_dirs: + self.warn("I don't know what to do with 'runtime_library_dirs': " + + str(runtime_library_dirs)) + + lib_opts = gen_lib_options(self, + library_dirs, runtime_library_dirs, + libraries) + if output_dir is not None: + output_filename = os.path.join(output_dir, output_filename) + + if self._need_link(objects, output_filename): + ldflags = self._ldflags[target_desc, debug] + + export_opts = ["/EXPORT:" + sym for sym in (export_symbols or [])] + + ld_args = (ldflags + lib_opts + export_opts + + objects + ['/OUT:' + output_filename]) + + # The MSVC linker generates .lib and .exp files, which cannot be + # suppressed by any linker switches. The .lib files may even be + # needed! Make sure they are generated in the temporary build + # directory. Since they have different names for debug and release + # builds, they can go into the same directory. + build_temp = os.path.dirname(objects[0]) + if export_symbols is not None: + (dll_name, dll_ext) = os.path.splitext( + os.path.basename(output_filename)) + implib_file = os.path.join( + build_temp, + self.library_filename(dll_name)) + ld_args.append ('/IMPLIB:' + implib_file) + + if extra_preargs: + ld_args[:0] = extra_preargs + if extra_postargs: + ld_args.extend(extra_postargs) + + output_dir = os.path.dirname(os.path.abspath(output_filename)) + self.mkpath(output_dir) + try: + log.debug('Executing "%s" %s', self.linker, ' '.join(ld_args)) + self.spawn([self.linker] + ld_args) + except DistutilsExecError as msg: + raise LinkError(msg) + else: + log.debug("skipping %s (up-to-date)", output_filename) + + def spawn(self, cmd): + env = dict(os.environ, PATH=self._paths) + with self._fallback_spawn(cmd, env) as fallback: + return super().spawn(cmd, env=env) + return fallback.value + + @contextlib.contextmanager + def _fallback_spawn(self, cmd, env): + """ + Discovered in pypa/distutils#15, some tools monkeypatch the compiler, + so the 'env' kwarg causes a TypeError. Detect this condition and + restore the legacy, unsafe behavior. + """ + bag = type('Bag', (), {})() + try: + yield bag + except TypeError as exc: + if "unexpected keyword argument 'env'" not in str(exc): + raise + else: + return + warnings.warn( + "Fallback spawn triggered. Please update distutils monkeypatch.") + with unittest.mock.patch('os.environ', env): + bag.value = super().spawn(cmd) + + # -- Miscellaneous methods ----------------------------------------- + # These are all used by the 'gen_lib_options() function, in + # ccompiler.py. + + def library_dir_option(self, dir): + return "/LIBPATH:" + dir + + def runtime_library_dir_option(self, dir): + raise DistutilsPlatformError( + "don't know how to set runtime library search path for MSVC") + + def library_option(self, lib): + return self.library_filename(lib) + + def find_library_file(self, dirs, lib, debug=0): + # Prefer a debugging library if found (and requested), but deal + # with it if we don't have one. + if debug: + try_names = [lib + "_d", lib] + else: + try_names = [lib] + for dir in dirs: + for name in try_names: + libfile = os.path.join(dir, self.library_filename(name)) + if os.path.isfile(libfile): + return libfile + else: + # Oops, didn't find it in *any* of 'dirs' + return None diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/archive_util.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/archive_util.py new file mode 100644 index 00000000..565a3117 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/archive_util.py @@ -0,0 +1,256 @@ +"""distutils.archive_util + +Utility functions for creating archive files (tarballs, zip files, +that sort of thing).""" + +import os +from warnings import warn +import sys + +try: + import zipfile +except ImportError: + zipfile = None + + +from distutils.errors import DistutilsExecError +from distutils.spawn import spawn +from distutils.dir_util import mkpath +from distutils import log + +try: + from pwd import getpwnam +except ImportError: + getpwnam = None + +try: + from grp import getgrnam +except ImportError: + getgrnam = None + +def _get_gid(name): + """Returns a gid, given a group name.""" + if getgrnam is None or name is None: + return None + try: + result = getgrnam(name) + except KeyError: + result = None + if result is not None: + return result[2] + return None + +def _get_uid(name): + """Returns an uid, given a user name.""" + if getpwnam is None or name is None: + return None + try: + result = getpwnam(name) + except KeyError: + result = None + if result is not None: + return result[2] + return None + +def make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0, + owner=None, group=None): + """Create a (possibly compressed) tar file from all the files under + 'base_dir'. + + 'compress' must be "gzip" (the default), "bzip2", "xz", "compress", or + None. ("compress" will be deprecated in Python 3.2) + + 'owner' and 'group' can be used to define an owner and a group for the + archive that is being built. If not provided, the current owner and group + will be used. + + The output tar file will be named 'base_dir' + ".tar", possibly plus + the appropriate compression extension (".gz", ".bz2", ".xz" or ".Z"). + + Returns the output filename. + """ + tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', 'xz': 'xz', None: '', + 'compress': ''} + compress_ext = {'gzip': '.gz', 'bzip2': '.bz2', 'xz': '.xz', + 'compress': '.Z'} + + # flags for compression program, each element of list will be an argument + if compress is not None and compress not in compress_ext.keys(): + raise ValueError( + "bad value for 'compress': must be None, 'gzip', 'bzip2', " + "'xz' or 'compress'") + + archive_name = base_name + '.tar' + if compress != 'compress': + archive_name += compress_ext.get(compress, '') + + mkpath(os.path.dirname(archive_name), dry_run=dry_run) + + # creating the tarball + import tarfile # late import so Python build itself doesn't break + + log.info('Creating tar archive') + + uid = _get_uid(owner) + gid = _get_gid(group) + + def _set_uid_gid(tarinfo): + if gid is not None: + tarinfo.gid = gid + tarinfo.gname = group + if uid is not None: + tarinfo.uid = uid + tarinfo.uname = owner + return tarinfo + + if not dry_run: + tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress]) + try: + tar.add(base_dir, filter=_set_uid_gid) + finally: + tar.close() + + # compression using `compress` + if compress == 'compress': + warn("'compress' will be deprecated.", PendingDeprecationWarning) + # the option varies depending on the platform + compressed_name = archive_name + compress_ext[compress] + if sys.platform == 'win32': + cmd = [compress, archive_name, compressed_name] + else: + cmd = [compress, '-f', archive_name] + spawn(cmd, dry_run=dry_run) + return compressed_name + + return archive_name + +def make_zipfile(base_name, base_dir, verbose=0, dry_run=0): + """Create a zip file from all the files under 'base_dir'. + + The output zip file will be named 'base_name' + ".zip". Uses either the + "zipfile" Python module (if available) or the InfoZIP "zip" utility + (if installed and found on the default search path). If neither tool is + available, raises DistutilsExecError. Returns the name of the output zip + file. + """ + zip_filename = base_name + ".zip" + mkpath(os.path.dirname(zip_filename), dry_run=dry_run) + + # If zipfile module is not available, try spawning an external + # 'zip' command. + if zipfile is None: + if verbose: + zipoptions = "-r" + else: + zipoptions = "-rq" + + try: + spawn(["zip", zipoptions, zip_filename, base_dir], + dry_run=dry_run) + except DistutilsExecError: + # XXX really should distinguish between "couldn't find + # external 'zip' command" and "zip failed". + raise DistutilsExecError(("unable to create zip file '%s': " + "could neither import the 'zipfile' module nor " + "find a standalone zip utility") % zip_filename) + + else: + log.info("creating '%s' and adding '%s' to it", + zip_filename, base_dir) + + if not dry_run: + try: + zip = zipfile.ZipFile(zip_filename, "w", + compression=zipfile.ZIP_DEFLATED) + except RuntimeError: + zip = zipfile.ZipFile(zip_filename, "w", + compression=zipfile.ZIP_STORED) + + with zip: + if base_dir != os.curdir: + path = os.path.normpath(os.path.join(base_dir, '')) + zip.write(path, path) + log.info("adding '%s'", path) + for dirpath, dirnames, filenames in os.walk(base_dir): + for name in dirnames: + path = os.path.normpath(os.path.join(dirpath, name, '')) + zip.write(path, path) + log.info("adding '%s'", path) + for name in filenames: + path = os.path.normpath(os.path.join(dirpath, name)) + if os.path.isfile(path): + zip.write(path, path) + log.info("adding '%s'", path) + + return zip_filename + +ARCHIVE_FORMATS = { + 'gztar': (make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"), + 'bztar': (make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"), + 'xztar': (make_tarball, [('compress', 'xz')], "xz'ed tar-file"), + 'ztar': (make_tarball, [('compress', 'compress')], "compressed tar file"), + 'tar': (make_tarball, [('compress', None)], "uncompressed tar file"), + 'zip': (make_zipfile, [],"ZIP file") + } + +def check_archive_formats(formats): + """Returns the first format from the 'format' list that is unknown. + + If all formats are known, returns None + """ + for format in formats: + if format not in ARCHIVE_FORMATS: + return format + return None + +def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0, + dry_run=0, owner=None, group=None): + """Create an archive file (eg. zip or tar). + + 'base_name' is the name of the file to create, minus any format-specific + extension; 'format' is the archive format: one of "zip", "tar", "gztar", + "bztar", "xztar", or "ztar". + + 'root_dir' is a directory that will be the root directory of the + archive; ie. we typically chdir into 'root_dir' before creating the + archive. 'base_dir' is the directory where we start archiving from; + ie. 'base_dir' will be the common prefix of all files and + directories in the archive. 'root_dir' and 'base_dir' both default + to the current directory. Returns the name of the archive file. + + 'owner' and 'group' are used when creating a tar archive. By default, + uses the current owner and group. + """ + save_cwd = os.getcwd() + if root_dir is not None: + log.debug("changing into '%s'", root_dir) + base_name = os.path.abspath(base_name) + if not dry_run: + os.chdir(root_dir) + + if base_dir is None: + base_dir = os.curdir + + kwargs = {'dry_run': dry_run} + + try: + format_info = ARCHIVE_FORMATS[format] + except KeyError: + raise ValueError("unknown archive format '%s'" % format) + + func = format_info[0] + for arg, val in format_info[1]: + kwargs[arg] = val + + if format != 'zip': + kwargs['owner'] = owner + kwargs['group'] = group + + try: + filename = func(base_name, base_dir, **kwargs) + finally: + if root_dir is not None: + log.debug("changing back to '%s'", save_cwd) + os.chdir(save_cwd) + + return filename diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/bcppcompiler.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/bcppcompiler.py new file mode 100644 index 00000000..071fea5d --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/bcppcompiler.py @@ -0,0 +1,393 @@ +"""distutils.bcppcompiler + +Contains BorlandCCompiler, an implementation of the abstract CCompiler class +for the Borland C++ compiler. +""" + +# This implementation by Lyle Johnson, based on the original msvccompiler.py +# module and using the directions originally published by Gordon Williams. + +# XXX looks like there's a LOT of overlap between these two classes: +# someone should sit down and factor out the common code as +# WindowsCCompiler! --GPW + + +import os +from distutils.errors import \ + DistutilsExecError, \ + CompileError, LibError, LinkError, UnknownFileError +from distutils.ccompiler import \ + CCompiler, gen_preprocess_options +from distutils.file_util import write_file +from distutils.dep_util import newer +from distutils import log + +class BCPPCompiler(CCompiler) : + """Concrete class that implements an interface to the Borland C/C++ + compiler, as defined by the CCompiler abstract class. + """ + + compiler_type = 'bcpp' + + # Just set this so CCompiler's constructor doesn't barf. We currently + # don't use the 'set_executables()' bureaucracy provided by CCompiler, + # as it really isn't necessary for this sort of single-compiler class. + # Would be nice to have a consistent interface with UnixCCompiler, + # though, so it's worth thinking about. + executables = {} + + # Private class data (need to distinguish C from C++ source for compiler) + _c_extensions = ['.c'] + _cpp_extensions = ['.cc', '.cpp', '.cxx'] + + # Needed for the filename generation methods provided by the + # base class, CCompiler. + src_extensions = _c_extensions + _cpp_extensions + obj_extension = '.obj' + static_lib_extension = '.lib' + shared_lib_extension = '.dll' + static_lib_format = shared_lib_format = '%s%s' + exe_extension = '.exe' + + + def __init__ (self, + verbose=0, + dry_run=0, + force=0): + + CCompiler.__init__ (self, verbose, dry_run, force) + + # These executables are assumed to all be in the path. + # Borland doesn't seem to use any special registry settings to + # indicate their installation locations. + + self.cc = "bcc32.exe" + self.linker = "ilink32.exe" + self.lib = "tlib.exe" + + self.preprocess_options = None + self.compile_options = ['/tWM', '/O2', '/q', '/g0'] + self.compile_options_debug = ['/tWM', '/Od', '/q', '/g0'] + + self.ldflags_shared = ['/Tpd', '/Gn', '/q', '/x'] + self.ldflags_shared_debug = ['/Tpd', '/Gn', '/q', '/x'] + self.ldflags_static = [] + self.ldflags_exe = ['/Gn', '/q', '/x'] + self.ldflags_exe_debug = ['/Gn', '/q', '/x','/r'] + + + # -- Worker methods ------------------------------------------------ + + def compile(self, sources, + output_dir=None, macros=None, include_dirs=None, debug=0, + extra_preargs=None, extra_postargs=None, depends=None): + + macros, objects, extra_postargs, pp_opts, build = \ + self._setup_compile(output_dir, macros, include_dirs, sources, + depends, extra_postargs) + compile_opts = extra_preargs or [] + compile_opts.append ('-c') + if debug: + compile_opts.extend (self.compile_options_debug) + else: + compile_opts.extend (self.compile_options) + + for obj in objects: + try: + src, ext = build[obj] + except KeyError: + continue + # XXX why do the normpath here? + src = os.path.normpath(src) + obj = os.path.normpath(obj) + # XXX _setup_compile() did a mkpath() too but before the normpath. + # Is it possible to skip the normpath? + self.mkpath(os.path.dirname(obj)) + + if ext == '.res': + # This is already a binary file -- skip it. + continue # the 'for' loop + if ext == '.rc': + # This needs to be compiled to a .res file -- do it now. + try: + self.spawn (["brcc32", "-fo", obj, src]) + except DistutilsExecError as msg: + raise CompileError(msg) + continue # the 'for' loop + + # The next two are both for the real compiler. + if ext in self._c_extensions: + input_opt = "" + elif ext in self._cpp_extensions: + input_opt = "-P" + else: + # Unknown file type -- no extra options. The compiler + # will probably fail, but let it just in case this is a + # file the compiler recognizes even if we don't. + input_opt = "" + + output_opt = "-o" + obj + + # Compiler command line syntax is: "bcc32 [options] file(s)". + # Note that the source file names must appear at the end of + # the command line. + try: + self.spawn ([self.cc] + compile_opts + pp_opts + + [input_opt, output_opt] + + extra_postargs + [src]) + except DistutilsExecError as msg: + raise CompileError(msg) + + return objects + + # compile () + + + def create_static_lib (self, + objects, + output_libname, + output_dir=None, + debug=0, + target_lang=None): + + (objects, output_dir) = self._fix_object_args (objects, output_dir) + output_filename = \ + self.library_filename (output_libname, output_dir=output_dir) + + if self._need_link (objects, output_filename): + lib_args = [output_filename, '/u'] + objects + if debug: + pass # XXX what goes here? + try: + self.spawn ([self.lib] + lib_args) + except DistutilsExecError as msg: + raise LibError(msg) + else: + log.debug("skipping %s (up-to-date)", output_filename) + + # create_static_lib () + + + def link (self, + target_desc, + objects, + output_filename, + output_dir=None, + libraries=None, + library_dirs=None, + runtime_library_dirs=None, + export_symbols=None, + debug=0, + extra_preargs=None, + extra_postargs=None, + build_temp=None, + target_lang=None): + + # XXX this ignores 'build_temp'! should follow the lead of + # msvccompiler.py + + (objects, output_dir) = self._fix_object_args (objects, output_dir) + (libraries, library_dirs, runtime_library_dirs) = \ + self._fix_lib_args (libraries, library_dirs, runtime_library_dirs) + + if runtime_library_dirs: + log.warn("I don't know what to do with 'runtime_library_dirs': %s", + str(runtime_library_dirs)) + + if output_dir is not None: + output_filename = os.path.join (output_dir, output_filename) + + if self._need_link (objects, output_filename): + + # Figure out linker args based on type of target. + if target_desc == CCompiler.EXECUTABLE: + startup_obj = 'c0w32' + if debug: + ld_args = self.ldflags_exe_debug[:] + else: + ld_args = self.ldflags_exe[:] + else: + startup_obj = 'c0d32' + if debug: + ld_args = self.ldflags_shared_debug[:] + else: + ld_args = self.ldflags_shared[:] + + + # Create a temporary exports file for use by the linker + if export_symbols is None: + def_file = '' + else: + head, tail = os.path.split (output_filename) + modname, ext = os.path.splitext (tail) + temp_dir = os.path.dirname(objects[0]) # preserve tree structure + def_file = os.path.join (temp_dir, '%s.def' % modname) + contents = ['EXPORTS'] + for sym in (export_symbols or []): + contents.append(' %s=_%s' % (sym, sym)) + self.execute(write_file, (def_file, contents), + "writing %s" % def_file) + + # Borland C++ has problems with '/' in paths + objects2 = map(os.path.normpath, objects) + # split objects in .obj and .res files + # Borland C++ needs them at different positions in the command line + objects = [startup_obj] + resources = [] + for file in objects2: + (base, ext) = os.path.splitext(os.path.normcase(file)) + if ext == '.res': + resources.append(file) + else: + objects.append(file) + + + for l in library_dirs: + ld_args.append("/L%s" % os.path.normpath(l)) + ld_args.append("/L.") # we sometimes use relative paths + + # list of object files + ld_args.extend(objects) + + # XXX the command-line syntax for Borland C++ is a bit wonky; + # certain filenames are jammed together in one big string, but + # comma-delimited. This doesn't mesh too well with the + # Unix-centric attitude (with a DOS/Windows quoting hack) of + # 'spawn()', so constructing the argument list is a bit + # awkward. Note that doing the obvious thing and jamming all + # the filenames and commas into one argument would be wrong, + # because 'spawn()' would quote any filenames with spaces in + # them. Arghghh!. Apparently it works fine as coded... + + # name of dll/exe file + ld_args.extend([',',output_filename]) + # no map file and start libraries + ld_args.append(',,') + + for lib in libraries: + # see if we find it and if there is a bcpp specific lib + # (xxx_bcpp.lib) + libfile = self.find_library_file(library_dirs, lib, debug) + if libfile is None: + ld_args.append(lib) + # probably a BCPP internal library -- don't warn + else: + # full name which prefers bcpp_xxx.lib over xxx.lib + ld_args.append(libfile) + + # some default libraries + ld_args.append ('import32') + ld_args.append ('cw32mt') + + # def file for export symbols + ld_args.extend([',',def_file]) + # add resource files + ld_args.append(',') + ld_args.extend(resources) + + + if extra_preargs: + ld_args[:0] = extra_preargs + if extra_postargs: + ld_args.extend(extra_postargs) + + self.mkpath (os.path.dirname (output_filename)) + try: + self.spawn ([self.linker] + ld_args) + except DistutilsExecError as msg: + raise LinkError(msg) + + else: + log.debug("skipping %s (up-to-date)", output_filename) + + # link () + + # -- Miscellaneous methods ----------------------------------------- + + + def find_library_file (self, dirs, lib, debug=0): + # List of effective library names to try, in order of preference: + # xxx_bcpp.lib is better than xxx.lib + # and xxx_d.lib is better than xxx.lib if debug is set + # + # The "_bcpp" suffix is to handle a Python installation for people + # with multiple compilers (primarily Distutils hackers, I suspect + # ;-). The idea is they'd have one static library for each + # compiler they care about, since (almost?) every Windows compiler + # seems to have a different format for static libraries. + if debug: + dlib = (lib + "_d") + try_names = (dlib + "_bcpp", lib + "_bcpp", dlib, lib) + else: + try_names = (lib + "_bcpp", lib) + + for dir in dirs: + for name in try_names: + libfile = os.path.join(dir, self.library_filename(name)) + if os.path.exists(libfile): + return libfile + else: + # Oops, didn't find it in *any* of 'dirs' + return None + + # overwrite the one from CCompiler to support rc and res-files + def object_filenames (self, + source_filenames, + strip_dir=0, + output_dir=''): + if output_dir is None: output_dir = '' + obj_names = [] + for src_name in source_filenames: + # use normcase to make sure '.rc' is really '.rc' and not '.RC' + (base, ext) = os.path.splitext (os.path.normcase(src_name)) + if ext not in (self.src_extensions + ['.rc','.res']): + raise UnknownFileError("unknown file type '%s' (from '%s')" % \ + (ext, src_name)) + if strip_dir: + base = os.path.basename (base) + if ext == '.res': + # these can go unchanged + obj_names.append (os.path.join (output_dir, base + ext)) + elif ext == '.rc': + # these need to be compiled to .res-files + obj_names.append (os.path.join (output_dir, base + '.res')) + else: + obj_names.append (os.path.join (output_dir, + base + self.obj_extension)) + return obj_names + + # object_filenames () + + def preprocess (self, + source, + output_file=None, + macros=None, + include_dirs=None, + extra_preargs=None, + extra_postargs=None): + + (_, macros, include_dirs) = \ + self._fix_compile_args(None, macros, include_dirs) + pp_opts = gen_preprocess_options(macros, include_dirs) + pp_args = ['cpp32.exe'] + pp_opts + if output_file is not None: + pp_args.append('-o' + output_file) + if extra_preargs: + pp_args[:0] = extra_preargs + if extra_postargs: + pp_args.extend(extra_postargs) + pp_args.append(source) + + # We need to preprocess: either we're being forced to, or the + # source file is newer than the target (or the target doesn't + # exist). + if self.force or output_file is None or newer(source, output_file): + if output_file: + self.mkpath(os.path.dirname(output_file)) + try: + self.spawn(pp_args) + except DistutilsExecError as msg: + print(msg) + raise CompileError(msg) + + # preprocess() diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/ccompiler.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/ccompiler.py new file mode 100644 index 00000000..777fc661 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/ccompiler.py @@ -0,0 +1,1123 @@ +"""distutils.ccompiler + +Contains CCompiler, an abstract base class that defines the interface +for the Distutils compiler abstraction model.""" + +import sys, os, re +from distutils.errors import * +from distutils.spawn import spawn +from distutils.file_util import move_file +from distutils.dir_util import mkpath +from distutils.dep_util import newer_group +from distutils.util import split_quoted, execute +from distutils import log + +class CCompiler: + """Abstract base class to define the interface that must be implemented + by real compiler classes. Also has some utility methods used by + several compiler classes. + + The basic idea behind a compiler abstraction class is that each + instance can be used for all the compile/link steps in building a + single project. Thus, attributes common to all of those compile and + link steps -- include directories, macros to define, libraries to link + against, etc. -- are attributes of the compiler instance. To allow for + variability in how individual files are treated, most of those + attributes may be varied on a per-compilation or per-link basis. + """ + + # 'compiler_type' is a class attribute that identifies this class. It + # keeps code that wants to know what kind of compiler it's dealing with + # from having to import all possible compiler classes just to do an + # 'isinstance'. In concrete CCompiler subclasses, 'compiler_type' + # should really, really be one of the keys of the 'compiler_class' + # dictionary (see below -- used by the 'new_compiler()' factory + # function) -- authors of new compiler interface classes are + # responsible for updating 'compiler_class'! + compiler_type = None + + # XXX things not handled by this compiler abstraction model: + # * client can't provide additional options for a compiler, + # e.g. warning, optimization, debugging flags. Perhaps this + # should be the domain of concrete compiler abstraction classes + # (UnixCCompiler, MSVCCompiler, etc.) -- or perhaps the base + # class should have methods for the common ones. + # * can't completely override the include or library searchg + # path, ie. no "cc -I -Idir1 -Idir2" or "cc -L -Ldir1 -Ldir2". + # I'm not sure how widely supported this is even by Unix + # compilers, much less on other platforms. And I'm even less + # sure how useful it is; maybe for cross-compiling, but + # support for that is a ways off. (And anyways, cross + # compilers probably have a dedicated binary with the + # right paths compiled in. I hope.) + # * can't do really freaky things with the library list/library + # dirs, e.g. "-Ldir1 -lfoo -Ldir2 -lfoo" to link against + # different versions of libfoo.a in different locations. I + # think this is useless without the ability to null out the + # library search path anyways. + + + # Subclasses that rely on the standard filename generation methods + # implemented below should override these; see the comment near + # those methods ('object_filenames()' et. al.) for details: + src_extensions = None # list of strings + obj_extension = None # string + static_lib_extension = None + shared_lib_extension = None # string + static_lib_format = None # format string + shared_lib_format = None # prob. same as static_lib_format + exe_extension = None # string + + # Default language settings. language_map is used to detect a source + # file or Extension target language, checking source filenames. + # language_order is used to detect the language precedence, when deciding + # what language to use when mixing source types. For example, if some + # extension has two files with ".c" extension, and one with ".cpp", it + # is still linked as c++. + language_map = {".c" : "c", + ".cc" : "c++", + ".cpp" : "c++", + ".cxx" : "c++", + ".m" : "objc", + } + language_order = ["c++", "objc", "c"] + + def __init__(self, verbose=0, dry_run=0, force=0): + self.dry_run = dry_run + self.force = force + self.verbose = verbose + + # 'output_dir': a common output directory for object, library, + # shared object, and shared library files + self.output_dir = None + + # 'macros': a list of macro definitions (or undefinitions). A + # macro definition is a 2-tuple (name, value), where the value is + # either a string or None (no explicit value). A macro + # undefinition is a 1-tuple (name,). + self.macros = [] + + # 'include_dirs': a list of directories to search for include files + self.include_dirs = [] + + # 'libraries': a list of libraries to include in any link + # (library names, not filenames: eg. "foo" not "libfoo.a") + self.libraries = [] + + # 'library_dirs': a list of directories to search for libraries + self.library_dirs = [] + + # 'runtime_library_dirs': a list of directories to search for + # shared libraries/objects at runtime + self.runtime_library_dirs = [] + + # 'objects': a list of object files (or similar, such as explicitly + # named library files) to include on any link + self.objects = [] + + for key in self.executables.keys(): + self.set_executable(key, self.executables[key]) + + def set_executables(self, **kwargs): + """Define the executables (and options for them) that will be run + to perform the various stages of compilation. The exact set of + executables that may be specified here depends on the compiler + class (via the 'executables' class attribute), but most will have: + compiler the C/C++ compiler + linker_so linker used to create shared objects and libraries + linker_exe linker used to create binary executables + archiver static library creator + + On platforms with a command-line (Unix, DOS/Windows), each of these + is a string that will be split into executable name and (optional) + list of arguments. (Splitting the string is done similarly to how + Unix shells operate: words are delimited by spaces, but quotes and + backslashes can override this. See + 'distutils.util.split_quoted()'.) + """ + + # Note that some CCompiler implementation classes will define class + # attributes 'cpp', 'cc', etc. with hard-coded executable names; + # this is appropriate when a compiler class is for exactly one + # compiler/OS combination (eg. MSVCCompiler). Other compiler + # classes (UnixCCompiler, in particular) are driven by information + # discovered at run-time, since there are many different ways to do + # basically the same things with Unix C compilers. + + for key in kwargs: + if key not in self.executables: + raise ValueError("unknown executable '%s' for class %s" % + (key, self.__class__.__name__)) + self.set_executable(key, kwargs[key]) + + def set_executable(self, key, value): + if isinstance(value, str): + setattr(self, key, split_quoted(value)) + else: + setattr(self, key, value) + + def _find_macro(self, name): + i = 0 + for defn in self.macros: + if defn[0] == name: + return i + i += 1 + return None + + def _check_macro_definitions(self, definitions): + """Ensures that every element of 'definitions' is a valid macro + definition, ie. either (name,value) 2-tuple or a (name,) tuple. Do + nothing if all definitions are OK, raise TypeError otherwise. + """ + for defn in definitions: + if not (isinstance(defn, tuple) and + (len(defn) in (1, 2) and + (isinstance (defn[1], str) or defn[1] is None)) and + isinstance (defn[0], str)): + raise TypeError(("invalid macro definition '%s': " % defn) + \ + "must be tuple (string,), (string, string), or " + \ + "(string, None)") + + + # -- Bookkeeping methods ------------------------------------------- + + def define_macro(self, name, value=None): + """Define a preprocessor macro for all compilations driven by this + compiler object. The optional parameter 'value' should be a + string; if it is not supplied, then the macro will be defined + without an explicit value and the exact outcome depends on the + compiler used (XXX true? does ANSI say anything about this?) + """ + # Delete from the list of macro definitions/undefinitions if + # already there (so that this one will take precedence). + i = self._find_macro (name) + if i is not None: + del self.macros[i] + + self.macros.append((name, value)) + + def undefine_macro(self, name): + """Undefine a preprocessor macro for all compilations driven by + this compiler object. If the same macro is defined by + 'define_macro()' and undefined by 'undefine_macro()' the last call + takes precedence (including multiple redefinitions or + undefinitions). If the macro is redefined/undefined on a + per-compilation basis (ie. in the call to 'compile()'), then that + takes precedence. + """ + # Delete from the list of macro definitions/undefinitions if + # already there (so that this one will take precedence). + i = self._find_macro (name) + if i is not None: + del self.macros[i] + + undefn = (name,) + self.macros.append(undefn) + + def add_include_dir(self, dir): + """Add 'dir' to the list of directories that will be searched for + header files. The compiler is instructed to search directories in + the order in which they are supplied by successive calls to + 'add_include_dir()'. + """ + self.include_dirs.append(dir) + + def set_include_dirs(self, dirs): + """Set the list of directories that will be searched to 'dirs' (a + list of strings). Overrides any preceding calls to + 'add_include_dir()'; subsequence calls to 'add_include_dir()' add + to the list passed to 'set_include_dirs()'. This does not affect + any list of standard include directories that the compiler may + search by default. + """ + self.include_dirs = dirs[:] + + def add_library(self, libname): + """Add 'libname' to the list of libraries that will be included in + all links driven by this compiler object. Note that 'libname' + should *not* be the name of a file containing a library, but the + name of the library itself: the actual filename will be inferred by + the linker, the compiler, or the compiler class (depending on the + platform). + + The linker will be instructed to link against libraries in the + order they were supplied to 'add_library()' and/or + 'set_libraries()'. It is perfectly valid to duplicate library + names; the linker will be instructed to link against libraries as + many times as they are mentioned. + """ + self.libraries.append(libname) + + def set_libraries(self, libnames): + """Set the list of libraries to be included in all links driven by + this compiler object to 'libnames' (a list of strings). This does + not affect any standard system libraries that the linker may + include by default. + """ + self.libraries = libnames[:] + + def add_library_dir(self, dir): + """Add 'dir' to the list of directories that will be searched for + libraries specified to 'add_library()' and 'set_libraries()'. The + linker will be instructed to search for libraries in the order they + are supplied to 'add_library_dir()' and/or 'set_library_dirs()'. + """ + self.library_dirs.append(dir) + + def set_library_dirs(self, dirs): + """Set the list of library search directories to 'dirs' (a list of + strings). This does not affect any standard library search path + that the linker may search by default. + """ + self.library_dirs = dirs[:] + + def add_runtime_library_dir(self, dir): + """Add 'dir' to the list of directories that will be searched for + shared libraries at runtime. + """ + self.runtime_library_dirs.append(dir) + + def set_runtime_library_dirs(self, dirs): + """Set the list of directories to search for shared libraries at + runtime to 'dirs' (a list of strings). This does not affect any + standard search path that the runtime linker may search by + default. + """ + self.runtime_library_dirs = dirs[:] + + def add_link_object(self, object): + """Add 'object' to the list of object files (or analogues, such as + explicitly named library files or the output of "resource + compilers") to be included in every link driven by this compiler + object. + """ + self.objects.append(object) + + def set_link_objects(self, objects): + """Set the list of object files (or analogues) to be included in + every link to 'objects'. This does not affect any standard object + files that the linker may include by default (such as system + libraries). + """ + self.objects = objects[:] + + + # -- Private utility methods -------------------------------------- + # (here for the convenience of subclasses) + + # Helper method to prep compiler in subclass compile() methods + + def _setup_compile(self, outdir, macros, incdirs, sources, depends, + extra): + """Process arguments and decide which source files to compile.""" + if outdir is None: + outdir = self.output_dir + elif not isinstance(outdir, str): + raise TypeError("'output_dir' must be a string or None") + + if macros is None: + macros = self.macros + elif isinstance(macros, list): + macros = macros + (self.macros or []) + else: + raise TypeError("'macros' (if supplied) must be a list of tuples") + + if incdirs is None: + incdirs = self.include_dirs + elif isinstance(incdirs, (list, tuple)): + incdirs = list(incdirs) + (self.include_dirs or []) + else: + raise TypeError( + "'include_dirs' (if supplied) must be a list of strings") + + if extra is None: + extra = [] + + # Get the list of expected output (object) files + objects = self.object_filenames(sources, strip_dir=0, + output_dir=outdir) + assert len(objects) == len(sources) + + pp_opts = gen_preprocess_options(macros, incdirs) + + build = {} + for i in range(len(sources)): + src = sources[i] + obj = objects[i] + ext = os.path.splitext(src)[1] + self.mkpath(os.path.dirname(obj)) + build[obj] = (src, ext) + + return macros, objects, extra, pp_opts, build + + def _get_cc_args(self, pp_opts, debug, before): + # works for unixccompiler, cygwinccompiler + cc_args = pp_opts + ['-c'] + if debug: + cc_args[:0] = ['-g'] + if before: + cc_args[:0] = before + return cc_args + + def _fix_compile_args(self, output_dir, macros, include_dirs): + """Typecheck and fix-up some of the arguments to the 'compile()' + method, and return fixed-up values. Specifically: if 'output_dir' + is None, replaces it with 'self.output_dir'; ensures that 'macros' + is a list, and augments it with 'self.macros'; ensures that + 'include_dirs' is a list, and augments it with 'self.include_dirs'. + Guarantees that the returned values are of the correct type, + i.e. for 'output_dir' either string or None, and for 'macros' and + 'include_dirs' either list or None. + """ + if output_dir is None: + output_dir = self.output_dir + elif not isinstance(output_dir, str): + raise TypeError("'output_dir' must be a string or None") + + if macros is None: + macros = self.macros + elif isinstance(macros, list): + macros = macros + (self.macros or []) + else: + raise TypeError("'macros' (if supplied) must be a list of tuples") + + if include_dirs is None: + include_dirs = self.include_dirs + elif isinstance(include_dirs, (list, tuple)): + include_dirs = list(include_dirs) + (self.include_dirs or []) + else: + raise TypeError( + "'include_dirs' (if supplied) must be a list of strings") + + return output_dir, macros, include_dirs + + def _prep_compile(self, sources, output_dir, depends=None): + """Decide which source files must be recompiled. + + Determine the list of object files corresponding to 'sources', + and figure out which ones really need to be recompiled. + Return a list of all object files and a dictionary telling + which source files can be skipped. + """ + # Get the list of expected output (object) files + objects = self.object_filenames(sources, output_dir=output_dir) + assert len(objects) == len(sources) + + # Return an empty dict for the "which source files can be skipped" + # return value to preserve API compatibility. + return objects, {} + + def _fix_object_args(self, objects, output_dir): + """Typecheck and fix up some arguments supplied to various methods. + Specifically: ensure that 'objects' is a list; if output_dir is + None, replace with self.output_dir. Return fixed versions of + 'objects' and 'output_dir'. + """ + if not isinstance(objects, (list, tuple)): + raise TypeError("'objects' must be a list or tuple of strings") + objects = list(objects) + + if output_dir is None: + output_dir = self.output_dir + elif not isinstance(output_dir, str): + raise TypeError("'output_dir' must be a string or None") + + return (objects, output_dir) + + def _fix_lib_args(self, libraries, library_dirs, runtime_library_dirs): + """Typecheck and fix up some of the arguments supplied to the + 'link_*' methods. Specifically: ensure that all arguments are + lists, and augment them with their permanent versions + (eg. 'self.libraries' augments 'libraries'). Return a tuple with + fixed versions of all arguments. + """ + if libraries is None: + libraries = self.libraries + elif isinstance(libraries, (list, tuple)): + libraries = list (libraries) + (self.libraries or []) + else: + raise TypeError( + "'libraries' (if supplied) must be a list of strings") + + if library_dirs is None: + library_dirs = self.library_dirs + elif isinstance(library_dirs, (list, tuple)): + library_dirs = list (library_dirs) + (self.library_dirs or []) + else: + raise TypeError( + "'library_dirs' (if supplied) must be a list of strings") + + if runtime_library_dirs is None: + runtime_library_dirs = self.runtime_library_dirs + elif isinstance(runtime_library_dirs, (list, tuple)): + runtime_library_dirs = (list(runtime_library_dirs) + + (self.runtime_library_dirs or [])) + else: + raise TypeError("'runtime_library_dirs' (if supplied) " + "must be a list of strings") + + return (libraries, library_dirs, runtime_library_dirs) + + def _need_link(self, objects, output_file): + """Return true if we need to relink the files listed in 'objects' + to recreate 'output_file'. + """ + if self.force: + return True + else: + if self.dry_run: + newer = newer_group (objects, output_file, missing='newer') + else: + newer = newer_group (objects, output_file) + return newer + + def detect_language(self, sources): + """Detect the language of a given file, or list of files. Uses + language_map, and language_order to do the job. + """ + if not isinstance(sources, list): + sources = [sources] + lang = None + index = len(self.language_order) + for source in sources: + base, ext = os.path.splitext(source) + extlang = self.language_map.get(ext) + try: + extindex = self.language_order.index(extlang) + if extindex < index: + lang = extlang + index = extindex + except ValueError: + pass + return lang + + + # -- Worker methods ------------------------------------------------ + # (must be implemented by subclasses) + + def preprocess(self, source, output_file=None, macros=None, + include_dirs=None, extra_preargs=None, extra_postargs=None): + """Preprocess a single C/C++ source file, named in 'source'. + Output will be written to file named 'output_file', or stdout if + 'output_file' not supplied. 'macros' is a list of macro + definitions as for 'compile()', which will augment the macros set + with 'define_macro()' and 'undefine_macro()'. 'include_dirs' is a + list of directory names that will be added to the default list. + + Raises PreprocessError on failure. + """ + pass + + def compile(self, sources, output_dir=None, macros=None, + include_dirs=None, debug=0, extra_preargs=None, + extra_postargs=None, depends=None): + """Compile one or more source files. + + 'sources' must be a list of filenames, most likely C/C++ + files, but in reality anything that can be handled by a + particular compiler and compiler class (eg. MSVCCompiler can + handle resource files in 'sources'). Return a list of object + filenames, one per source filename in 'sources'. Depending on + the implementation, not all source files will necessarily be + compiled, but all corresponding object filenames will be + returned. + + If 'output_dir' is given, object files will be put under it, while + retaining their original path component. That is, "foo/bar.c" + normally compiles to "foo/bar.o" (for a Unix implementation); if + 'output_dir' is "build", then it would compile to + "build/foo/bar.o". + + 'macros', if given, must be a list of macro definitions. A macro + definition is either a (name, value) 2-tuple or a (name,) 1-tuple. + The former defines a macro; if the value is None, the macro is + defined without an explicit value. The 1-tuple case undefines a + macro. Later definitions/redefinitions/ undefinitions take + precedence. + + 'include_dirs', if given, must be a list of strings, the + directories to add to the default include file search path for this + compilation only. + + 'debug' is a boolean; if true, the compiler will be instructed to + output debug symbols in (or alongside) the object file(s). + + 'extra_preargs' and 'extra_postargs' are implementation- dependent. + On platforms that have the notion of a command-line (e.g. Unix, + DOS/Windows), they are most likely lists of strings: extra + command-line arguments to prepend/append to the compiler command + line. On other platforms, consult the implementation class + documentation. In any event, they are intended as an escape hatch + for those occasions when the abstract compiler framework doesn't + cut the mustard. + + 'depends', if given, is a list of filenames that all targets + depend on. If a source file is older than any file in + depends, then the source file will be recompiled. This + supports dependency tracking, but only at a coarse + granularity. + + Raises CompileError on failure. + """ + # A concrete compiler class can either override this method + # entirely or implement _compile(). + macros, objects, extra_postargs, pp_opts, build = \ + self._setup_compile(output_dir, macros, include_dirs, sources, + depends, extra_postargs) + cc_args = self._get_cc_args(pp_opts, debug, extra_preargs) + + for obj in objects: + try: + src, ext = build[obj] + except KeyError: + continue + self._compile(obj, src, ext, cc_args, extra_postargs, pp_opts) + + # Return *all* object filenames, not just the ones we just built. + return objects + + def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts): + """Compile 'src' to product 'obj'.""" + # A concrete compiler class that does not override compile() + # should implement _compile(). + pass + + def create_static_lib(self, objects, output_libname, output_dir=None, + debug=0, target_lang=None): + """Link a bunch of stuff together to create a static library file. + The "bunch of stuff" consists of the list of object files supplied + as 'objects', the extra object files supplied to + 'add_link_object()' and/or 'set_link_objects()', the libraries + supplied to 'add_library()' and/or 'set_libraries()', and the + libraries supplied as 'libraries' (if any). + + 'output_libname' should be a library name, not a filename; the + filename will be inferred from the library name. 'output_dir' is + the directory where the library file will be put. + + 'debug' is a boolean; if true, debugging information will be + included in the library (note that on most platforms, it is the + compile step where this matters: the 'debug' flag is included here + just for consistency). + + 'target_lang' is the target language for which the given objects + are being compiled. This allows specific linkage time treatment of + certain languages. + + Raises LibError on failure. + """ + pass + + + # values for target_desc parameter in link() + SHARED_OBJECT = "shared_object" + SHARED_LIBRARY = "shared_library" + EXECUTABLE = "executable" + + def link(self, + target_desc, + objects, + output_filename, + output_dir=None, + libraries=None, + library_dirs=None, + runtime_library_dirs=None, + export_symbols=None, + debug=0, + extra_preargs=None, + extra_postargs=None, + build_temp=None, + target_lang=None): + """Link a bunch of stuff together to create an executable or + shared library file. + + The "bunch of stuff" consists of the list of object files supplied + as 'objects'. 'output_filename' should be a filename. If + 'output_dir' is supplied, 'output_filename' is relative to it + (i.e. 'output_filename' can provide directory components if + needed). + + 'libraries' is a list of libraries to link against. These are + library names, not filenames, since they're translated into + filenames in a platform-specific way (eg. "foo" becomes "libfoo.a" + on Unix and "foo.lib" on DOS/Windows). However, they can include a + directory component, which means the linker will look in that + specific directory rather than searching all the normal locations. + + 'library_dirs', if supplied, should be a list of directories to + search for libraries that were specified as bare library names + (ie. no directory component). These are on top of the system + default and those supplied to 'add_library_dir()' and/or + 'set_library_dirs()'. 'runtime_library_dirs' is a list of + directories that will be embedded into the shared library and used + to search for other shared libraries that *it* depends on at + run-time. (This may only be relevant on Unix.) + + 'export_symbols' is a list of symbols that the shared library will + export. (This appears to be relevant only on Windows.) + + 'debug' is as for 'compile()' and 'create_static_lib()', with the + slight distinction that it actually matters on most platforms (as + opposed to 'create_static_lib()', which includes a 'debug' flag + mostly for form's sake). + + 'extra_preargs' and 'extra_postargs' are as for 'compile()' (except + of course that they supply command-line arguments for the + particular linker being used). + + 'target_lang' is the target language for which the given objects + are being compiled. This allows specific linkage time treatment of + certain languages. + + Raises LinkError on failure. + """ + raise NotImplementedError + + + # Old 'link_*()' methods, rewritten to use the new 'link()' method. + + def link_shared_lib(self, + objects, + output_libname, + output_dir=None, + libraries=None, + library_dirs=None, + runtime_library_dirs=None, + export_symbols=None, + debug=0, + extra_preargs=None, + extra_postargs=None, + build_temp=None, + target_lang=None): + self.link(CCompiler.SHARED_LIBRARY, objects, + self.library_filename(output_libname, lib_type='shared'), + output_dir, + libraries, library_dirs, runtime_library_dirs, + export_symbols, debug, + extra_preargs, extra_postargs, build_temp, target_lang) + + + def link_shared_object(self, + objects, + output_filename, + output_dir=None, + libraries=None, + library_dirs=None, + runtime_library_dirs=None, + export_symbols=None, + debug=0, + extra_preargs=None, + extra_postargs=None, + build_temp=None, + target_lang=None): + self.link(CCompiler.SHARED_OBJECT, objects, + output_filename, output_dir, + libraries, library_dirs, runtime_library_dirs, + export_symbols, debug, + extra_preargs, extra_postargs, build_temp, target_lang) + + + def link_executable(self, + objects, + output_progname, + output_dir=None, + libraries=None, + library_dirs=None, + runtime_library_dirs=None, + debug=0, + extra_preargs=None, + extra_postargs=None, + target_lang=None): + self.link(CCompiler.EXECUTABLE, objects, + self.executable_filename(output_progname), output_dir, + libraries, library_dirs, runtime_library_dirs, None, + debug, extra_preargs, extra_postargs, None, target_lang) + + + # -- Miscellaneous methods ----------------------------------------- + # These are all used by the 'gen_lib_options() function; there is + # no appropriate default implementation so subclasses should + # implement all of these. + + def library_dir_option(self, dir): + """Return the compiler option to add 'dir' to the list of + directories searched for libraries. + """ + raise NotImplementedError + + def runtime_library_dir_option(self, dir): + """Return the compiler option to add 'dir' to the list of + directories searched for runtime libraries. + """ + raise NotImplementedError + + def library_option(self, lib): + """Return the compiler option to add 'lib' to the list of libraries + linked into the shared library or executable. + """ + raise NotImplementedError + + def has_function(self, funcname, includes=None, include_dirs=None, + libraries=None, library_dirs=None): + """Return a boolean indicating whether funcname is supported on + the current platform. The optional arguments can be used to + augment the compilation environment. + """ + # this can't be included at module scope because it tries to + # import math which might not be available at that point - maybe + # the necessary logic should just be inlined? + import tempfile + if includes is None: + includes = [] + if include_dirs is None: + include_dirs = [] + if libraries is None: + libraries = [] + if library_dirs is None: + library_dirs = [] + fd, fname = tempfile.mkstemp(".c", funcname, text=True) + f = os.fdopen(fd, "w") + try: + for incl in includes: + f.write("""#include "%s"\n""" % incl) + f.write("""\ +int main (int argc, char **argv) { + %s(); + return 0; +} +""" % funcname) + finally: + f.close() + try: + objects = self.compile([fname], include_dirs=include_dirs) + except CompileError: + return False + finally: + os.remove(fname) + + try: + self.link_executable(objects, "a.out", + libraries=libraries, + library_dirs=library_dirs) + except (LinkError, TypeError): + return False + else: + os.remove(os.path.join(self.output_dir or '', "a.out")) + finally: + for fn in objects: + os.remove(fn) + return True + + def find_library_file (self, dirs, lib, debug=0): + """Search the specified list of directories for a static or shared + library file 'lib' and return the full path to that file. If + 'debug' true, look for a debugging version (if that makes sense on + the current platform). Return None if 'lib' wasn't found in any of + the specified directories. + """ + raise NotImplementedError + + # -- Filename generation methods ----------------------------------- + + # The default implementation of the filename generating methods are + # prejudiced towards the Unix/DOS/Windows view of the world: + # * object files are named by replacing the source file extension + # (eg. .c/.cpp -> .o/.obj) + # * library files (shared or static) are named by plugging the + # library name and extension into a format string, eg. + # "lib%s.%s" % (lib_name, ".a") for Unix static libraries + # * executables are named by appending an extension (possibly + # empty) to the program name: eg. progname + ".exe" for + # Windows + # + # To reduce redundant code, these methods expect to find + # several attributes in the current object (presumably defined + # as class attributes): + # * src_extensions - + # list of C/C++ source file extensions, eg. ['.c', '.cpp'] + # * obj_extension - + # object file extension, eg. '.o' or '.obj' + # * static_lib_extension - + # extension for static library files, eg. '.a' or '.lib' + # * shared_lib_extension - + # extension for shared library/object files, eg. '.so', '.dll' + # * static_lib_format - + # format string for generating static library filenames, + # eg. 'lib%s.%s' or '%s.%s' + # * shared_lib_format + # format string for generating shared library filenames + # (probably same as static_lib_format, since the extension + # is one of the intended parameters to the format string) + # * exe_extension - + # extension for executable files, eg. '' or '.exe' + + def object_filenames(self, source_filenames, strip_dir=0, output_dir=''): + if output_dir is None: + output_dir = '' + obj_names = [] + for src_name in source_filenames: + base, ext = os.path.splitext(src_name) + base = os.path.splitdrive(base)[1] # Chop off the drive + base = base[os.path.isabs(base):] # If abs, chop off leading / + if ext not in self.src_extensions: + raise UnknownFileError( + "unknown file type '%s' (from '%s')" % (ext, src_name)) + if strip_dir: + base = os.path.basename(base) + obj_names.append(os.path.join(output_dir, + base + self.obj_extension)) + return obj_names + + def shared_object_filename(self, basename, strip_dir=0, output_dir=''): + assert output_dir is not None + if strip_dir: + basename = os.path.basename(basename) + return os.path.join(output_dir, basename + self.shared_lib_extension) + + def executable_filename(self, basename, strip_dir=0, output_dir=''): + assert output_dir is not None + if strip_dir: + basename = os.path.basename(basename) + return os.path.join(output_dir, basename + (self.exe_extension or '')) + + def library_filename(self, libname, lib_type='static', # or 'shared' + strip_dir=0, output_dir=''): + assert output_dir is not None + if lib_type not in ("static", "shared", "dylib", "xcode_stub"): + raise ValueError( + "'lib_type' must be \"static\", \"shared\", \"dylib\", or \"xcode_stub\"") + fmt = getattr(self, lib_type + "_lib_format") + ext = getattr(self, lib_type + "_lib_extension") + + dir, base = os.path.split(libname) + filename = fmt % (base, ext) + if strip_dir: + dir = '' + + return os.path.join(output_dir, dir, filename) + + + # -- Utility methods ----------------------------------------------- + + def announce(self, msg, level=1): + log.debug(msg) + + def debug_print(self, msg): + from distutils.debug import DEBUG + if DEBUG: + print(msg) + + def warn(self, msg): + sys.stderr.write("warning: %s\n" % msg) + + def execute(self, func, args, msg=None, level=1): + execute(func, args, msg, self.dry_run) + + def spawn(self, cmd, **kwargs): + spawn(cmd, dry_run=self.dry_run, **kwargs) + + def move_file(self, src, dst): + return move_file(src, dst, dry_run=self.dry_run) + + def mkpath (self, name, mode=0o777): + mkpath(name, mode, dry_run=self.dry_run) + + +# Map a sys.platform/os.name ('posix', 'nt') to the default compiler +# type for that platform. Keys are interpreted as re match +# patterns. Order is important; platform mappings are preferred over +# OS names. +_default_compilers = ( + + # Platform string mappings + + # on a cygwin built python we can use gcc like an ordinary UNIXish + # compiler + ('cygwin.*', 'unix'), + + # OS name mappings + ('posix', 'unix'), + ('nt', 'msvc'), + + ) + +def get_default_compiler(osname=None, platform=None): + """Determine the default compiler to use for the given platform. + + osname should be one of the standard Python OS names (i.e. the + ones returned by os.name) and platform the common value + returned by sys.platform for the platform in question. + + The default values are os.name and sys.platform in case the + parameters are not given. + """ + if osname is None: + osname = os.name + if platform is None: + platform = sys.platform + for pattern, compiler in _default_compilers: + if re.match(pattern, platform) is not None or \ + re.match(pattern, osname) is not None: + return compiler + # Default to Unix compiler + return 'unix' + +# Map compiler types to (module_name, class_name) pairs -- ie. where to +# find the code that implements an interface to this compiler. (The module +# is assumed to be in the 'distutils' package.) +compiler_class = { 'unix': ('unixccompiler', 'UnixCCompiler', + "standard UNIX-style compiler"), + 'msvc': ('_msvccompiler', 'MSVCCompiler', + "Microsoft Visual C++"), + 'cygwin': ('cygwinccompiler', 'CygwinCCompiler', + "Cygwin port of GNU C Compiler for Win32"), + 'mingw32': ('cygwinccompiler', 'Mingw32CCompiler', + "Mingw32 port of GNU C Compiler for Win32"), + 'bcpp': ('bcppcompiler', 'BCPPCompiler', + "Borland C++ Compiler"), + } + +def show_compilers(): + """Print list of available compilers (used by the "--help-compiler" + options to "build", "build_ext", "build_clib"). + """ + # XXX this "knows" that the compiler option it's describing is + # "--compiler", which just happens to be the case for the three + # commands that use it. + from distutils.fancy_getopt import FancyGetopt + compilers = [] + for compiler in compiler_class.keys(): + compilers.append(("compiler="+compiler, None, + compiler_class[compiler][2])) + compilers.sort() + pretty_printer = FancyGetopt(compilers) + pretty_printer.print_help("List of available compilers:") + + +def new_compiler(plat=None, compiler=None, verbose=0, dry_run=0, force=0): + """Generate an instance of some CCompiler subclass for the supplied + platform/compiler combination. 'plat' defaults to 'os.name' + (eg. 'posix', 'nt'), and 'compiler' defaults to the default compiler + for that platform. Currently only 'posix' and 'nt' are supported, and + the default compilers are "traditional Unix interface" (UnixCCompiler + class) and Visual C++ (MSVCCompiler class). Note that it's perfectly + possible to ask for a Unix compiler object under Windows, and a + Microsoft compiler object under Unix -- if you supply a value for + 'compiler', 'plat' is ignored. + """ + if plat is None: + plat = os.name + + try: + if compiler is None: + compiler = get_default_compiler(plat) + + (module_name, class_name, long_description) = compiler_class[compiler] + except KeyError: + msg = "don't know how to compile C/C++ code on platform '%s'" % plat + if compiler is not None: + msg = msg + " with '%s' compiler" % compiler + raise DistutilsPlatformError(msg) + + try: + module_name = "distutils." + module_name + __import__ (module_name) + module = sys.modules[module_name] + klass = vars(module)[class_name] + except ImportError: + raise DistutilsModuleError( + "can't compile C/C++ code: unable to load module '%s'" % \ + module_name) + except KeyError: + raise DistutilsModuleError( + "can't compile C/C++ code: unable to find class '%s' " + "in module '%s'" % (class_name, module_name)) + + # XXX The None is necessary to preserve backwards compatibility + # with classes that expect verbose to be the first positional + # argument. + return klass(None, dry_run, force) + + +def gen_preprocess_options(macros, include_dirs): + """Generate C pre-processor options (-D, -U, -I) as used by at least + two types of compilers: the typical Unix compiler and Visual C++. + 'macros' is the usual thing, a list of 1- or 2-tuples, where (name,) + means undefine (-U) macro 'name', and (name,value) means define (-D) + macro 'name' to 'value'. 'include_dirs' is just a list of directory + names to be added to the header file search path (-I). Returns a list + of command-line options suitable for either Unix compilers or Visual + C++. + """ + # XXX it would be nice (mainly aesthetic, and so we don't generate + # stupid-looking command lines) to go over 'macros' and eliminate + # redundant definitions/undefinitions (ie. ensure that only the + # latest mention of a particular macro winds up on the command + # line). I don't think it's essential, though, since most (all?) + # Unix C compilers only pay attention to the latest -D or -U + # mention of a macro on their command line. Similar situation for + # 'include_dirs'. I'm punting on both for now. Anyways, weeding out + # redundancies like this should probably be the province of + # CCompiler, since the data structures used are inherited from it + # and therefore common to all CCompiler classes. + pp_opts = [] + for macro in macros: + if not (isinstance(macro, tuple) and 1 <= len(macro) <= 2): + raise TypeError( + "bad macro definition '%s': " + "each element of 'macros' list must be a 1- or 2-tuple" + % macro) + + if len(macro) == 1: # undefine this macro + pp_opts.append("-U%s" % macro[0]) + elif len(macro) == 2: + if macro[1] is None: # define with no explicit value + pp_opts.append("-D%s" % macro[0]) + else: + # XXX *don't* need to be clever about quoting the + # macro value here, because we're going to avoid the + # shell at all costs when we spawn the command! + pp_opts.append("-D%s=%s" % macro) + + for dir in include_dirs: + pp_opts.append("-I%s" % dir) + return pp_opts + + +def gen_lib_options (compiler, library_dirs, runtime_library_dirs, libraries): + """Generate linker options for searching library directories and + linking with specific libraries. 'libraries' and 'library_dirs' are, + respectively, lists of library names (not filenames!) and search + directories. Returns a list of command-line options suitable for use + with some compiler (depending on the two format strings passed in). + """ + lib_opts = [] + + for dir in library_dirs: + lib_opts.append(compiler.library_dir_option(dir)) + + for dir in runtime_library_dirs: + opt = compiler.runtime_library_dir_option(dir) + if isinstance(opt, list): + lib_opts = lib_opts + opt + else: + lib_opts.append(opt) + + # XXX it's important that we *not* remove redundant library mentions! + # sometimes you really do have to say "-lfoo -lbar -lfoo" in order to + # resolve all symbols. I just hope we never have to say "-lfoo obj.o + # -lbar" to get things to work -- that's certainly a possibility, but a + # pretty nasty way to arrange your C code. + + for lib in libraries: + (lib_dir, lib_name) = os.path.split(lib) + if lib_dir: + lib_file = compiler.find_library_file([lib_dir], lib_name) + if lib_file: + lib_opts.append(lib_file) + else: + compiler.warn("no library file corresponding to " + "'%s' found (skipping)" % lib) + else: + lib_opts.append(compiler.library_option (lib)) + return lib_opts diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/cmd.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/cmd.py new file mode 100644 index 00000000..dba3191e --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/cmd.py @@ -0,0 +1,403 @@ +"""distutils.cmd + +Provides the Command class, the base class for the command classes +in the distutils.command package. +""" + +import sys, os, re +from distutils.errors import DistutilsOptionError +from distutils import util, dir_util, file_util, archive_util, dep_util +from distutils import log + +class Command: + """Abstract base class for defining command classes, the "worker bees" + of the Distutils. A useful analogy for command classes is to think of + them as subroutines with local variables called "options". The options + are "declared" in 'initialize_options()' and "defined" (given their + final values, aka "finalized") in 'finalize_options()', both of which + must be defined by every command class. The distinction between the + two is necessary because option values might come from the outside + world (command line, config file, ...), and any options dependent on + other options must be computed *after* these outside influences have + been processed -- hence 'finalize_options()'. The "body" of the + subroutine, where it does all its work based on the values of its + options, is the 'run()' method, which must also be implemented by every + command class. + """ + + # 'sub_commands' formalizes the notion of a "family" of commands, + # eg. "install" as the parent with sub-commands "install_lib", + # "install_headers", etc. The parent of a family of commands + # defines 'sub_commands' as a class attribute; it's a list of + # (command_name : string, predicate : unbound_method | string | None) + # tuples, where 'predicate' is a method of the parent command that + # determines whether the corresponding command is applicable in the + # current situation. (Eg. we "install_headers" is only applicable if + # we have any C header files to install.) If 'predicate' is None, + # that command is always applicable. + # + # 'sub_commands' is usually defined at the *end* of a class, because + # predicates can be unbound methods, so they must already have been + # defined. The canonical example is the "install" command. + sub_commands = [] + + + # -- Creation/initialization methods ------------------------------- + + def __init__(self, dist): + """Create and initialize a new Command object. Most importantly, + invokes the 'initialize_options()' method, which is the real + initializer and depends on the actual command being + instantiated. + """ + # late import because of mutual dependence between these classes + from distutils.dist import Distribution + + if not isinstance(dist, Distribution): + raise TypeError("dist must be a Distribution instance") + if self.__class__ is Command: + raise RuntimeError("Command is an abstract class") + + self.distribution = dist + self.initialize_options() + + # Per-command versions of the global flags, so that the user can + # customize Distutils' behaviour command-by-command and let some + # commands fall back on the Distribution's behaviour. None means + # "not defined, check self.distribution's copy", while 0 or 1 mean + # false and true (duh). Note that this means figuring out the real + # value of each flag is a touch complicated -- hence "self._dry_run" + # will be handled by __getattr__, below. + # XXX This needs to be fixed. + self._dry_run = None + + # verbose is largely ignored, but needs to be set for + # backwards compatibility (I think)? + self.verbose = dist.verbose + + # Some commands define a 'self.force' option to ignore file + # timestamps, but methods defined *here* assume that + # 'self.force' exists for all commands. So define it here + # just to be safe. + self.force = None + + # The 'help' flag is just used for command-line parsing, so + # none of that complicated bureaucracy is needed. + self.help = 0 + + # 'finalized' records whether or not 'finalize_options()' has been + # called. 'finalize_options()' itself should not pay attention to + # this flag: it is the business of 'ensure_finalized()', which + # always calls 'finalize_options()', to respect/update it. + self.finalized = 0 + + # XXX A more explicit way to customize dry_run would be better. + def __getattr__(self, attr): + if attr == 'dry_run': + myval = getattr(self, "_" + attr) + if myval is None: + return getattr(self.distribution, attr) + else: + return myval + else: + raise AttributeError(attr) + + def ensure_finalized(self): + if not self.finalized: + self.finalize_options() + self.finalized = 1 + + # Subclasses must define: + # initialize_options() + # provide default values for all options; may be customized by + # setup script, by options from config file(s), or by command-line + # options + # finalize_options() + # decide on the final values for all options; this is called + # after all possible intervention from the outside world + # (command-line, option file, etc.) has been processed + # run() + # run the command: do whatever it is we're here to do, + # controlled by the command's various option values + + def initialize_options(self): + """Set default values for all the options that this command + supports. Note that these defaults may be overridden by other + commands, by the setup script, by config files, or by the + command-line. Thus, this is not the place to code dependencies + between options; generally, 'initialize_options()' implementations + are just a bunch of "self.foo = None" assignments. + + This method must be implemented by all command classes. + """ + raise RuntimeError("abstract method -- subclass %s must override" + % self.__class__) + + def finalize_options(self): + """Set final values for all the options that this command supports. + This is always called as late as possible, ie. after any option + assignments from the command-line or from other commands have been + done. Thus, this is the place to code option dependencies: if + 'foo' depends on 'bar', then it is safe to set 'foo' from 'bar' as + long as 'foo' still has the same value it was assigned in + 'initialize_options()'. + + This method must be implemented by all command classes. + """ + raise RuntimeError("abstract method -- subclass %s must override" + % self.__class__) + + + def dump_options(self, header=None, indent=""): + from distutils.fancy_getopt import longopt_xlate + if header is None: + header = "command options for '%s':" % self.get_command_name() + self.announce(indent + header, level=log.INFO) + indent = indent + " " + for (option, _, _) in self.user_options: + option = option.translate(longopt_xlate) + if option[-1] == "=": + option = option[:-1] + value = getattr(self, option) + self.announce(indent + "%s = %s" % (option, value), + level=log.INFO) + + def run(self): + """A command's raison d'etre: carry out the action it exists to + perform, controlled by the options initialized in + 'initialize_options()', customized by other commands, the setup + script, the command-line, and config files, and finalized in + 'finalize_options()'. All terminal output and filesystem + interaction should be done by 'run()'. + + This method must be implemented by all command classes. + """ + raise RuntimeError("abstract method -- subclass %s must override" + % self.__class__) + + def announce(self, msg, level=1): + """If the current verbosity level is of greater than or equal to + 'level' print 'msg' to stdout. + """ + log.log(level, msg) + + def debug_print(self, msg): + """Print 'msg' to stdout if the global DEBUG (taken from the + DISTUTILS_DEBUG environment variable) flag is true. + """ + from distutils.debug import DEBUG + if DEBUG: + print(msg) + sys.stdout.flush() + + + # -- Option validation methods ------------------------------------- + # (these are very handy in writing the 'finalize_options()' method) + # + # NB. the general philosophy here is to ensure that a particular option + # value meets certain type and value constraints. If not, we try to + # force it into conformance (eg. if we expect a list but have a string, + # split the string on comma and/or whitespace). If we can't force the + # option into conformance, raise DistutilsOptionError. Thus, command + # classes need do nothing more than (eg.) + # self.ensure_string_list('foo') + # and they can be guaranteed that thereafter, self.foo will be + # a list of strings. + + def _ensure_stringlike(self, option, what, default=None): + val = getattr(self, option) + if val is None: + setattr(self, option, default) + return default + elif not isinstance(val, str): + raise DistutilsOptionError("'%s' must be a %s (got `%s`)" + % (option, what, val)) + return val + + def ensure_string(self, option, default=None): + """Ensure that 'option' is a string; if not defined, set it to + 'default'. + """ + self._ensure_stringlike(option, "string", default) + + def ensure_string_list(self, option): + r"""Ensure that 'option' is a list of strings. If 'option' is + currently a string, we split it either on /,\s*/ or /\s+/, so + "foo bar baz", "foo,bar,baz", and "foo, bar baz" all become + ["foo", "bar", "baz"]. + """ + val = getattr(self, option) + if val is None: + return + elif isinstance(val, str): + setattr(self, option, re.split(r',\s*|\s+', val)) + else: + if isinstance(val, list): + ok = all(isinstance(v, str) for v in val) + else: + ok = False + if not ok: + raise DistutilsOptionError( + "'%s' must be a list of strings (got %r)" + % (option, val)) + + def _ensure_tested_string(self, option, tester, what, error_fmt, + default=None): + val = self._ensure_stringlike(option, what, default) + if val is not None and not tester(val): + raise DistutilsOptionError(("error in '%s' option: " + error_fmt) + % (option, val)) + + def ensure_filename(self, option): + """Ensure that 'option' is the name of an existing file.""" + self._ensure_tested_string(option, os.path.isfile, + "filename", + "'%s' does not exist or is not a file") + + def ensure_dirname(self, option): + self._ensure_tested_string(option, os.path.isdir, + "directory name", + "'%s' does not exist or is not a directory") + + + # -- Convenience methods for commands ------------------------------ + + def get_command_name(self): + if hasattr(self, 'command_name'): + return self.command_name + else: + return self.__class__.__name__ + + def set_undefined_options(self, src_cmd, *option_pairs): + """Set the values of any "undefined" options from corresponding + option values in some other command object. "Undefined" here means + "is None", which is the convention used to indicate that an option + has not been changed between 'initialize_options()' and + 'finalize_options()'. Usually called from 'finalize_options()' for + options that depend on some other command rather than another + option of the same command. 'src_cmd' is the other command from + which option values will be taken (a command object will be created + for it if necessary); the remaining arguments are + '(src_option,dst_option)' tuples which mean "take the value of + 'src_option' in the 'src_cmd' command object, and copy it to + 'dst_option' in the current command object". + """ + # Option_pairs: list of (src_option, dst_option) tuples + src_cmd_obj = self.distribution.get_command_obj(src_cmd) + src_cmd_obj.ensure_finalized() + for (src_option, dst_option) in option_pairs: + if getattr(self, dst_option) is None: + setattr(self, dst_option, getattr(src_cmd_obj, src_option)) + + def get_finalized_command(self, command, create=1): + """Wrapper around Distribution's 'get_command_obj()' method: find + (create if necessary and 'create' is true) the command object for + 'command', call its 'ensure_finalized()' method, and return the + finalized command object. + """ + cmd_obj = self.distribution.get_command_obj(command, create) + cmd_obj.ensure_finalized() + return cmd_obj + + # XXX rename to 'get_reinitialized_command()'? (should do the + # same in dist.py, if so) + def reinitialize_command(self, command, reinit_subcommands=0): + return self.distribution.reinitialize_command(command, + reinit_subcommands) + + def run_command(self, command): + """Run some other command: uses the 'run_command()' method of + Distribution, which creates and finalizes the command object if + necessary and then invokes its 'run()' method. + """ + self.distribution.run_command(command) + + def get_sub_commands(self): + """Determine the sub-commands that are relevant in the current + distribution (ie., that need to be run). This is based on the + 'sub_commands' class attribute: each tuple in that list may include + a method that we call to determine if the subcommand needs to be + run for the current distribution. Return a list of command names. + """ + commands = [] + for (cmd_name, method) in self.sub_commands: + if method is None or method(self): + commands.append(cmd_name) + return commands + + + # -- External world manipulation ----------------------------------- + + def warn(self, msg): + log.warn("warning: %s: %s\n", self.get_command_name(), msg) + + def execute(self, func, args, msg=None, level=1): + util.execute(func, args, msg, dry_run=self.dry_run) + + def mkpath(self, name, mode=0o777): + dir_util.mkpath(name, mode, dry_run=self.dry_run) + + def copy_file(self, infile, outfile, preserve_mode=1, preserve_times=1, + link=None, level=1): + """Copy a file respecting verbose, dry-run and force flags. (The + former two default to whatever is in the Distribution object, and + the latter defaults to false for commands that don't define it.)""" + return file_util.copy_file(infile, outfile, preserve_mode, + preserve_times, not self.force, link, + dry_run=self.dry_run) + + def copy_tree(self, infile, outfile, preserve_mode=1, preserve_times=1, + preserve_symlinks=0, level=1): + """Copy an entire directory tree respecting verbose, dry-run, + and force flags. + """ + return dir_util.copy_tree(infile, outfile, preserve_mode, + preserve_times, preserve_symlinks, + not self.force, dry_run=self.dry_run) + + def move_file (self, src, dst, level=1): + """Move a file respecting dry-run flag.""" + return file_util.move_file(src, dst, dry_run=self.dry_run) + + def spawn(self, cmd, search_path=1, level=1): + """Spawn an external command respecting dry-run flag.""" + from distutils.spawn import spawn + spawn(cmd, search_path, dry_run=self.dry_run) + + def make_archive(self, base_name, format, root_dir=None, base_dir=None, + owner=None, group=None): + return archive_util.make_archive(base_name, format, root_dir, base_dir, + dry_run=self.dry_run, + owner=owner, group=group) + + def make_file(self, infiles, outfile, func, args, + exec_msg=None, skip_msg=None, level=1): + """Special case of 'execute()' for operations that process one or + more input files and generate one output file. Works just like + 'execute()', except the operation is skipped and a different + message printed if 'outfile' already exists and is newer than all + files listed in 'infiles'. If the command defined 'self.force', + and it is true, then the command is unconditionally run -- does no + timestamp checks. + """ + if skip_msg is None: + skip_msg = "skipping %s (inputs unchanged)" % outfile + + # Allow 'infiles' to be a single string + if isinstance(infiles, str): + infiles = (infiles,) + elif not isinstance(infiles, (list, tuple)): + raise TypeError( + "'infiles' must be a string, or a list or tuple of strings") + + if exec_msg is None: + exec_msg = "generating %s from %s" % (outfile, ', '.join(infiles)) + + # If 'outfile' must be regenerated (either because it doesn't + # exist, is out-of-date, or the 'force' flag is true) then + # perform the action that presumably regenerates it + if self.force or dep_util.newer_group(infiles, outfile): + self.execute(func, args, exec_msg, level) + # Otherwise, print the "skip" message + else: + log.debug(skip_msg) diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/command/__init__.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/__init__.py new file mode 100644 index 00000000..481eea9f --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/__init__.py @@ -0,0 +1,31 @@ +"""distutils.command + +Package containing implementation of all the standard Distutils +commands.""" + +__all__ = ['build', + 'build_py', + 'build_ext', + 'build_clib', + 'build_scripts', + 'clean', + 'install', + 'install_lib', + 'install_headers', + 'install_scripts', + 'install_data', + 'sdist', + 'register', + 'bdist', + 'bdist_dumb', + 'bdist_rpm', + 'bdist_wininst', + 'check', + 'upload', + # These two are reserved for future use: + #'bdist_sdux', + #'bdist_pkgtool', + # Note: + # bdist_packager is not included because it only provides + # an abstract base class + ] diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/command/bdist.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/bdist.py new file mode 100644 index 00000000..014871d2 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/bdist.py @@ -0,0 +1,143 @@ +"""distutils.command.bdist + +Implements the Distutils 'bdist' command (create a built [binary] +distribution).""" + +import os +from distutils.core import Command +from distutils.errors import * +from distutils.util import get_platform + + +def show_formats(): + """Print list of available formats (arguments to "--format" option). + """ + from distutils.fancy_getopt import FancyGetopt + formats = [] + for format in bdist.format_commands: + formats.append(("formats=" + format, None, + bdist.format_command[format][1])) + pretty_printer = FancyGetopt(formats) + pretty_printer.print_help("List of available distribution formats:") + + +class bdist(Command): + + description = "create a built (binary) distribution" + + user_options = [('bdist-base=', 'b', + "temporary directory for creating built distributions"), + ('plat-name=', 'p', + "platform name to embed in generated filenames " + "(default: %s)" % get_platform()), + ('formats=', None, + "formats for distribution (comma-separated list)"), + ('dist-dir=', 'd', + "directory to put final built distributions in " + "[default: dist]"), + ('skip-build', None, + "skip rebuilding everything (for testing/debugging)"), + ('owner=', 'u', + "Owner name used when creating a tar file" + " [default: current user]"), + ('group=', 'g', + "Group name used when creating a tar file" + " [default: current group]"), + ] + + boolean_options = ['skip-build'] + + help_options = [ + ('help-formats', None, + "lists available distribution formats", show_formats), + ] + + # The following commands do not take a format option from bdist + no_format_option = ('bdist_rpm',) + + # This won't do in reality: will need to distinguish RPM-ish Linux, + # Debian-ish Linux, Solaris, FreeBSD, ..., Windows, Mac OS. + default_format = {'posix': 'gztar', + 'nt': 'zip'} + + # Establish the preferred order (for the --help-formats option). + format_commands = ['rpm', 'gztar', 'bztar', 'xztar', 'ztar', 'tar', + 'wininst', 'zip', 'msi'] + + # And the real information. + format_command = {'rpm': ('bdist_rpm', "RPM distribution"), + 'gztar': ('bdist_dumb', "gzip'ed tar file"), + 'bztar': ('bdist_dumb', "bzip2'ed tar file"), + 'xztar': ('bdist_dumb', "xz'ed tar file"), + 'ztar': ('bdist_dumb', "compressed tar file"), + 'tar': ('bdist_dumb', "tar file"), + 'wininst': ('bdist_wininst', + "Windows executable installer"), + 'zip': ('bdist_dumb', "ZIP file"), + 'msi': ('bdist_msi', "Microsoft Installer") + } + + + def initialize_options(self): + self.bdist_base = None + self.plat_name = None + self.formats = None + self.dist_dir = None + self.skip_build = 0 + self.group = None + self.owner = None + + def finalize_options(self): + # have to finalize 'plat_name' before 'bdist_base' + if self.plat_name is None: + if self.skip_build: + self.plat_name = get_platform() + else: + self.plat_name = self.get_finalized_command('build').plat_name + + # 'bdist_base' -- parent of per-built-distribution-format + # temporary directories (eg. we'll probably have + # "build/bdist./dumb", "build/bdist./rpm", etc.) + if self.bdist_base is None: + build_base = self.get_finalized_command('build').build_base + self.bdist_base = os.path.join(build_base, + 'bdist.' + self.plat_name) + + self.ensure_string_list('formats') + if self.formats is None: + try: + self.formats = [self.default_format[os.name]] + except KeyError: + raise DistutilsPlatformError( + "don't know how to create built distributions " + "on platform %s" % os.name) + + if self.dist_dir is None: + self.dist_dir = "dist" + + def run(self): + # Figure out which sub-commands we need to run. + commands = [] + for format in self.formats: + try: + commands.append(self.format_command[format][0]) + except KeyError: + raise DistutilsOptionError("invalid format '%s'" % format) + + # Reinitialize and run each command. + for i in range(len(self.formats)): + cmd_name = commands[i] + sub_cmd = self.reinitialize_command(cmd_name) + if cmd_name not in self.no_format_option: + sub_cmd.format = self.formats[i] + + # passing the owner and group names for tar archiving + if cmd_name == 'bdist_dumb': + sub_cmd.owner = self.owner + sub_cmd.group = self.group + + # If we're going to need to run this command again, tell it to + # keep its temporary files around so subsequent runs go faster. + if cmd_name in commands[i+1:]: + sub_cmd.keep_temp = 1 + self.run_command(cmd_name) diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/command/bdist_dumb.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/bdist_dumb.py new file mode 100644 index 00000000..f0d6b5b8 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/bdist_dumb.py @@ -0,0 +1,123 @@ +"""distutils.command.bdist_dumb + +Implements the Distutils 'bdist_dumb' command (create a "dumb" built +distribution -- i.e., just an archive to be unpacked under $prefix or +$exec_prefix).""" + +import os +from distutils.core import Command +from distutils.util import get_platform +from distutils.dir_util import remove_tree, ensure_relative +from distutils.errors import * +from distutils.sysconfig import get_python_version +from distutils import log + +class bdist_dumb(Command): + + description = "create a \"dumb\" built distribution" + + user_options = [('bdist-dir=', 'd', + "temporary directory for creating the distribution"), + ('plat-name=', 'p', + "platform name to embed in generated filenames " + "(default: %s)" % get_platform()), + ('format=', 'f', + "archive format to create (tar, gztar, bztar, xztar, " + "ztar, zip)"), + ('keep-temp', 'k', + "keep the pseudo-installation tree around after " + + "creating the distribution archive"), + ('dist-dir=', 'd', + "directory to put final built distributions in"), + ('skip-build', None, + "skip rebuilding everything (for testing/debugging)"), + ('relative', None, + "build the archive using relative paths " + "(default: false)"), + ('owner=', 'u', + "Owner name used when creating a tar file" + " [default: current user]"), + ('group=', 'g', + "Group name used when creating a tar file" + " [default: current group]"), + ] + + boolean_options = ['keep-temp', 'skip-build', 'relative'] + + default_format = { 'posix': 'gztar', + 'nt': 'zip' } + + def initialize_options(self): + self.bdist_dir = None + self.plat_name = None + self.format = None + self.keep_temp = 0 + self.dist_dir = None + self.skip_build = None + self.relative = 0 + self.owner = None + self.group = None + + def finalize_options(self): + if self.bdist_dir is None: + bdist_base = self.get_finalized_command('bdist').bdist_base + self.bdist_dir = os.path.join(bdist_base, 'dumb') + + if self.format is None: + try: + self.format = self.default_format[os.name] + except KeyError: + raise DistutilsPlatformError( + "don't know how to create dumb built distributions " + "on platform %s" % os.name) + + self.set_undefined_options('bdist', + ('dist_dir', 'dist_dir'), + ('plat_name', 'plat_name'), + ('skip_build', 'skip_build')) + + def run(self): + if not self.skip_build: + self.run_command('build') + + install = self.reinitialize_command('install', reinit_subcommands=1) + install.root = self.bdist_dir + install.skip_build = self.skip_build + install.warn_dir = 0 + + log.info("installing to %s", self.bdist_dir) + self.run_command('install') + + # And make an archive relative to the root of the + # pseudo-installation tree. + archive_basename = "%s.%s" % (self.distribution.get_fullname(), + self.plat_name) + + pseudoinstall_root = os.path.join(self.dist_dir, archive_basename) + if not self.relative: + archive_root = self.bdist_dir + else: + if (self.distribution.has_ext_modules() and + (install.install_base != install.install_platbase)): + raise DistutilsPlatformError( + "can't make a dumb built distribution where " + "base and platbase are different (%s, %s)" + % (repr(install.install_base), + repr(install.install_platbase))) + else: + archive_root = os.path.join(self.bdist_dir, + ensure_relative(install.install_base)) + + # Make the archive + filename = self.make_archive(pseudoinstall_root, + self.format, root_dir=archive_root, + owner=self.owner, group=self.group) + if self.distribution.has_ext_modules(): + pyversion = get_python_version() + else: + pyversion = 'any' + self.distribution.dist_files.append(('bdist_dumb', pyversion, + filename)) + + if not self.keep_temp: + remove_tree(self.bdist_dir, dry_run=self.dry_run) diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/command/bdist_msi.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/bdist_msi.py new file mode 100644 index 00000000..0863a188 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/bdist_msi.py @@ -0,0 +1,749 @@ +# Copyright (C) 2005, 2006 Martin von Löwis +# Licensed to PSF under a Contributor Agreement. +# The bdist_wininst command proper +# based on bdist_wininst +""" +Implements the bdist_msi command. +""" + +import os +import sys +import warnings +from distutils.core import Command +from distutils.dir_util import remove_tree +from distutils.sysconfig import get_python_version +from distutils.version import StrictVersion +from distutils.errors import DistutilsOptionError +from distutils.util import get_platform +from distutils import log +import msilib +from msilib import schema, sequence, text +from msilib import Directory, Feature, Dialog, add_data + +class PyDialog(Dialog): + """Dialog class with a fixed layout: controls at the top, then a ruler, + then a list of buttons: back, next, cancel. Optionally a bitmap at the + left.""" + def __init__(self, *args, **kw): + """Dialog(database, name, x, y, w, h, attributes, title, first, + default, cancel, bitmap=true)""" + Dialog.__init__(self, *args) + ruler = self.h - 36 + bmwidth = 152*ruler/328 + #if kw.get("bitmap", True): + # self.bitmap("Bitmap", 0, 0, bmwidth, ruler, "PythonWin") + self.line("BottomLine", 0, ruler, self.w, 0) + + def title(self, title): + "Set the title text of the dialog at the top." + # name, x, y, w, h, flags=Visible|Enabled|Transparent|NoPrefix, + # text, in VerdanaBold10 + self.text("Title", 15, 10, 320, 60, 0x30003, + r"{\VerdanaBold10}%s" % title) + + def back(self, title, next, name = "Back", active = 1): + """Add a back button with a given title, the tab-next button, + its name in the Control table, possibly initially disabled. + + Return the button, so that events can be associated""" + if active: + flags = 3 # Visible|Enabled + else: + flags = 1 # Visible + return self.pushbutton(name, 180, self.h-27 , 56, 17, flags, title, next) + + def cancel(self, title, next, name = "Cancel", active = 1): + """Add a cancel button with a given title, the tab-next button, + its name in the Control table, possibly initially disabled. + + Return the button, so that events can be associated""" + if active: + flags = 3 # Visible|Enabled + else: + flags = 1 # Visible + return self.pushbutton(name, 304, self.h-27, 56, 17, flags, title, next) + + def next(self, title, next, name = "Next", active = 1): + """Add a Next button with a given title, the tab-next button, + its name in the Control table, possibly initially disabled. + + Return the button, so that events can be associated""" + if active: + flags = 3 # Visible|Enabled + else: + flags = 1 # Visible + return self.pushbutton(name, 236, self.h-27, 56, 17, flags, title, next) + + def xbutton(self, name, title, next, xpos): + """Add a button with a given title, the tab-next button, + its name in the Control table, giving its x position; the + y-position is aligned with the other buttons. + + Return the button, so that events can be associated""" + return self.pushbutton(name, int(self.w*xpos - 28), self.h-27, 56, 17, 3, title, next) + +class bdist_msi(Command): + + description = "create a Microsoft Installer (.msi) binary distribution" + + user_options = [('bdist-dir=', None, + "temporary directory for creating the distribution"), + ('plat-name=', 'p', + "platform name to embed in generated filenames " + "(default: %s)" % get_platform()), + ('keep-temp', 'k', + "keep the pseudo-installation tree around after " + + "creating the distribution archive"), + ('target-version=', None, + "require a specific python version" + + " on the target system"), + ('no-target-compile', 'c', + "do not compile .py to .pyc on the target system"), + ('no-target-optimize', 'o', + "do not compile .py to .pyo (optimized) " + "on the target system"), + ('dist-dir=', 'd', + "directory to put final built distributions in"), + ('skip-build', None, + "skip rebuilding everything (for testing/debugging)"), + ('install-script=', None, + "basename of installation script to be run after " + "installation or before deinstallation"), + ('pre-install-script=', None, + "Fully qualified filename of a script to be run before " + "any files are installed. This script need not be in the " + "distribution"), + ] + + boolean_options = ['keep-temp', 'no-target-compile', 'no-target-optimize', + 'skip-build'] + + all_versions = ['2.0', '2.1', '2.2', '2.3', '2.4', + '2.5', '2.6', '2.7', '2.8', '2.9', + '3.0', '3.1', '3.2', '3.3', '3.4', + '3.5', '3.6', '3.7', '3.8', '3.9'] + other_version = 'X' + + def __init__(self, *args, **kw): + super().__init__(*args, **kw) + warnings.warn("bdist_msi command is deprecated since Python 3.9, " + "use bdist_wheel (wheel packages) instead", + DeprecationWarning, 2) + + def initialize_options(self): + self.bdist_dir = None + self.plat_name = None + self.keep_temp = 0 + self.no_target_compile = 0 + self.no_target_optimize = 0 + self.target_version = None + self.dist_dir = None + self.skip_build = None + self.install_script = None + self.pre_install_script = None + self.versions = None + + def finalize_options(self): + self.set_undefined_options('bdist', ('skip_build', 'skip_build')) + + if self.bdist_dir is None: + bdist_base = self.get_finalized_command('bdist').bdist_base + self.bdist_dir = os.path.join(bdist_base, 'msi') + + short_version = get_python_version() + if (not self.target_version) and self.distribution.has_ext_modules(): + self.target_version = short_version + + if self.target_version: + self.versions = [self.target_version] + if not self.skip_build and self.distribution.has_ext_modules()\ + and self.target_version != short_version: + raise DistutilsOptionError( + "target version can only be %s, or the '--skip-build'" + " option must be specified" % (short_version,)) + else: + self.versions = list(self.all_versions) + + self.set_undefined_options('bdist', + ('dist_dir', 'dist_dir'), + ('plat_name', 'plat_name'), + ) + + if self.pre_install_script: + raise DistutilsOptionError( + "the pre-install-script feature is not yet implemented") + + if self.install_script: + for script in self.distribution.scripts: + if self.install_script == os.path.basename(script): + break + else: + raise DistutilsOptionError( + "install_script '%s' not found in scripts" + % self.install_script) + self.install_script_key = None + + def run(self): + if not self.skip_build: + self.run_command('build') + + install = self.reinitialize_command('install', reinit_subcommands=1) + install.prefix = self.bdist_dir + install.skip_build = self.skip_build + install.warn_dir = 0 + + install_lib = self.reinitialize_command('install_lib') + # we do not want to include pyc or pyo files + install_lib.compile = 0 + install_lib.optimize = 0 + + if self.distribution.has_ext_modules(): + # If we are building an installer for a Python version other + # than the one we are currently running, then we need to ensure + # our build_lib reflects the other Python version rather than ours. + # Note that for target_version!=sys.version, we must have skipped the + # build step, so there is no issue with enforcing the build of this + # version. + target_version = self.target_version + if not target_version: + assert self.skip_build, "Should have already checked this" + target_version = '%d.%d' % sys.version_info[:2] + plat_specifier = ".%s-%s" % (self.plat_name, target_version) + build = self.get_finalized_command('build') + build.build_lib = os.path.join(build.build_base, + 'lib' + plat_specifier) + + log.info("installing to %s", self.bdist_dir) + install.ensure_finalized() + + # avoid warning of 'install_lib' about installing + # into a directory not in sys.path + sys.path.insert(0, os.path.join(self.bdist_dir, 'PURELIB')) + + install.run() + + del sys.path[0] + + self.mkpath(self.dist_dir) + fullname = self.distribution.get_fullname() + installer_name = self.get_installer_filename(fullname) + installer_name = os.path.abspath(installer_name) + if os.path.exists(installer_name): os.unlink(installer_name) + + metadata = self.distribution.metadata + author = metadata.author + if not author: + author = metadata.maintainer + if not author: + author = "UNKNOWN" + version = metadata.get_version() + # ProductVersion must be strictly numeric + # XXX need to deal with prerelease versions + sversion = "%d.%d.%d" % StrictVersion(version).version + # Prefix ProductName with Python x.y, so that + # it sorts together with the other Python packages + # in Add-Remove-Programs (APR) + fullname = self.distribution.get_fullname() + if self.target_version: + product_name = "Python %s %s" % (self.target_version, fullname) + else: + product_name = "Python %s" % (fullname) + self.db = msilib.init_database(installer_name, schema, + product_name, msilib.gen_uuid(), + sversion, author) + msilib.add_tables(self.db, sequence) + props = [('DistVersion', version)] + email = metadata.author_email or metadata.maintainer_email + if email: + props.append(("ARPCONTACT", email)) + if metadata.url: + props.append(("ARPURLINFOABOUT", metadata.url)) + if props: + add_data(self.db, 'Property', props) + + self.add_find_python() + self.add_files() + self.add_scripts() + self.add_ui() + self.db.Commit() + + if hasattr(self.distribution, 'dist_files'): + tup = 'bdist_msi', self.target_version or 'any', fullname + self.distribution.dist_files.append(tup) + + if not self.keep_temp: + remove_tree(self.bdist_dir, dry_run=self.dry_run) + + def add_files(self): + db = self.db + cab = msilib.CAB("distfiles") + rootdir = os.path.abspath(self.bdist_dir) + + root = Directory(db, cab, None, rootdir, "TARGETDIR", "SourceDir") + f = Feature(db, "Python", "Python", "Everything", + 0, 1, directory="TARGETDIR") + + items = [(f, root, '')] + for version in self.versions + [self.other_version]: + target = "TARGETDIR" + version + name = default = "Python" + version + desc = "Everything" + if version is self.other_version: + title = "Python from another location" + level = 2 + else: + title = "Python %s from registry" % version + level = 1 + f = Feature(db, name, title, desc, 1, level, directory=target) + dir = Directory(db, cab, root, rootdir, target, default) + items.append((f, dir, version)) + db.Commit() + + seen = {} + for feature, dir, version in items: + todo = [dir] + while todo: + dir = todo.pop() + for file in os.listdir(dir.absolute): + afile = os.path.join(dir.absolute, file) + if os.path.isdir(afile): + short = "%s|%s" % (dir.make_short(file), file) + default = file + version + newdir = Directory(db, cab, dir, file, default, short) + todo.append(newdir) + else: + if not dir.component: + dir.start_component(dir.logical, feature, 0) + if afile not in seen: + key = seen[afile] = dir.add_file(file) + if file==self.install_script: + if self.install_script_key: + raise DistutilsOptionError( + "Multiple files with name %s" % file) + self.install_script_key = '[#%s]' % key + else: + key = seen[afile] + add_data(self.db, "DuplicateFile", + [(key + version, dir.component, key, None, dir.logical)]) + db.Commit() + cab.commit(db) + + def add_find_python(self): + """Adds code to the installer to compute the location of Python. + + Properties PYTHON.MACHINE.X.Y and PYTHON.USER.X.Y will be set from the + registry for each version of Python. + + Properties TARGETDIRX.Y will be set from PYTHON.USER.X.Y if defined, + else from PYTHON.MACHINE.X.Y. + + Properties PYTHONX.Y will be set to TARGETDIRX.Y\\python.exe""" + + start = 402 + for ver in self.versions: + install_path = r"SOFTWARE\Python\PythonCore\%s\InstallPath" % ver + machine_reg = "python.machine." + ver + user_reg = "python.user." + ver + machine_prop = "PYTHON.MACHINE." + ver + user_prop = "PYTHON.USER." + ver + machine_action = "PythonFromMachine" + ver + user_action = "PythonFromUser" + ver + exe_action = "PythonExe" + ver + target_dir_prop = "TARGETDIR" + ver + exe_prop = "PYTHON" + ver + if msilib.Win64: + # type: msidbLocatorTypeRawValue + msidbLocatorType64bit + Type = 2+16 + else: + Type = 2 + add_data(self.db, "RegLocator", + [(machine_reg, 2, install_path, None, Type), + (user_reg, 1, install_path, None, Type)]) + add_data(self.db, "AppSearch", + [(machine_prop, machine_reg), + (user_prop, user_reg)]) + add_data(self.db, "CustomAction", + [(machine_action, 51+256, target_dir_prop, "[" + machine_prop + "]"), + (user_action, 51+256, target_dir_prop, "[" + user_prop + "]"), + (exe_action, 51+256, exe_prop, "[" + target_dir_prop + "]\\python.exe"), + ]) + add_data(self.db, "InstallExecuteSequence", + [(machine_action, machine_prop, start), + (user_action, user_prop, start + 1), + (exe_action, None, start + 2), + ]) + add_data(self.db, "InstallUISequence", + [(machine_action, machine_prop, start), + (user_action, user_prop, start + 1), + (exe_action, None, start + 2), + ]) + add_data(self.db, "Condition", + [("Python" + ver, 0, "NOT TARGETDIR" + ver)]) + start += 4 + assert start < 500 + + def add_scripts(self): + if self.install_script: + start = 6800 + for ver in self.versions + [self.other_version]: + install_action = "install_script." + ver + exe_prop = "PYTHON" + ver + add_data(self.db, "CustomAction", + [(install_action, 50, exe_prop, self.install_script_key)]) + add_data(self.db, "InstallExecuteSequence", + [(install_action, "&Python%s=3" % ver, start)]) + start += 1 + # XXX pre-install scripts are currently refused in finalize_options() + # but if this feature is completed, it will also need to add + # entries for each version as the above code does + if self.pre_install_script: + scriptfn = os.path.join(self.bdist_dir, "preinstall.bat") + with open(scriptfn, "w") as f: + # The batch file will be executed with [PYTHON], so that %1 + # is the path to the Python interpreter; %0 will be the path + # of the batch file. + # rem =""" + # %1 %0 + # exit + # """ + # + f.write('rem ="""\n%1 %0\nexit\n"""\n') + with open(self.pre_install_script) as fin: + f.write(fin.read()) + add_data(self.db, "Binary", + [("PreInstall", msilib.Binary(scriptfn)) + ]) + add_data(self.db, "CustomAction", + [("PreInstall", 2, "PreInstall", None) + ]) + add_data(self.db, "InstallExecuteSequence", + [("PreInstall", "NOT Installed", 450)]) + + + def add_ui(self): + db = self.db + x = y = 50 + w = 370 + h = 300 + title = "[ProductName] Setup" + + # see "Dialog Style Bits" + modal = 3 # visible | modal + modeless = 1 # visible + track_disk_space = 32 + + # UI customization properties + add_data(db, "Property", + # See "DefaultUIFont Property" + [("DefaultUIFont", "DlgFont8"), + # See "ErrorDialog Style Bit" + ("ErrorDialog", "ErrorDlg"), + ("Progress1", "Install"), # modified in maintenance type dlg + ("Progress2", "installs"), + ("MaintenanceForm_Action", "Repair"), + # possible values: ALL, JUSTME + ("WhichUsers", "ALL") + ]) + + # Fonts, see "TextStyle Table" + add_data(db, "TextStyle", + [("DlgFont8", "Tahoma", 9, None, 0), + ("DlgFontBold8", "Tahoma", 8, None, 1), #bold + ("VerdanaBold10", "Verdana", 10, None, 1), + ("VerdanaRed9", "Verdana", 9, 255, 0), + ]) + + # UI Sequences, see "InstallUISequence Table", "Using a Sequence Table" + # Numbers indicate sequence; see sequence.py for how these action integrate + add_data(db, "InstallUISequence", + [("PrepareDlg", "Not Privileged or Windows9x or Installed", 140), + ("WhichUsersDlg", "Privileged and not Windows9x and not Installed", 141), + # In the user interface, assume all-users installation if privileged. + ("SelectFeaturesDlg", "Not Installed", 1230), + # XXX no support for resume installations yet + #("ResumeDlg", "Installed AND (RESUME OR Preselected)", 1240), + ("MaintenanceTypeDlg", "Installed AND NOT RESUME AND NOT Preselected", 1250), + ("ProgressDlg", None, 1280)]) + + add_data(db, 'ActionText', text.ActionText) + add_data(db, 'UIText', text.UIText) + ##################################################################### + # Standard dialogs: FatalError, UserExit, ExitDialog + fatal=PyDialog(db, "FatalError", x, y, w, h, modal, title, + "Finish", "Finish", "Finish") + fatal.title("[ProductName] Installer ended prematurely") + fatal.back("< Back", "Finish", active = 0) + fatal.cancel("Cancel", "Back", active = 0) + fatal.text("Description1", 15, 70, 320, 80, 0x30003, + "[ProductName] setup ended prematurely because of an error. Your system has not been modified. To install this program at a later time, please run the installation again.") + fatal.text("Description2", 15, 155, 320, 20, 0x30003, + "Click the Finish button to exit the Installer.") + c=fatal.next("Finish", "Cancel", name="Finish") + c.event("EndDialog", "Exit") + + user_exit=PyDialog(db, "UserExit", x, y, w, h, modal, title, + "Finish", "Finish", "Finish") + user_exit.title("[ProductName] Installer was interrupted") + user_exit.back("< Back", "Finish", active = 0) + user_exit.cancel("Cancel", "Back", active = 0) + user_exit.text("Description1", 15, 70, 320, 80, 0x30003, + "[ProductName] setup was interrupted. Your system has not been modified. " + "To install this program at a later time, please run the installation again.") + user_exit.text("Description2", 15, 155, 320, 20, 0x30003, + "Click the Finish button to exit the Installer.") + c = user_exit.next("Finish", "Cancel", name="Finish") + c.event("EndDialog", "Exit") + + exit_dialog = PyDialog(db, "ExitDialog", x, y, w, h, modal, title, + "Finish", "Finish", "Finish") + exit_dialog.title("Completing the [ProductName] Installer") + exit_dialog.back("< Back", "Finish", active = 0) + exit_dialog.cancel("Cancel", "Back", active = 0) + exit_dialog.text("Description", 15, 235, 320, 20, 0x30003, + "Click the Finish button to exit the Installer.") + c = exit_dialog.next("Finish", "Cancel", name="Finish") + c.event("EndDialog", "Return") + + ##################################################################### + # Required dialog: FilesInUse, ErrorDlg + inuse = PyDialog(db, "FilesInUse", + x, y, w, h, + 19, # KeepModeless|Modal|Visible + title, + "Retry", "Retry", "Retry", bitmap=False) + inuse.text("Title", 15, 6, 200, 15, 0x30003, + r"{\DlgFontBold8}Files in Use") + inuse.text("Description", 20, 23, 280, 20, 0x30003, + "Some files that need to be updated are currently in use.") + inuse.text("Text", 20, 55, 330, 50, 3, + "The following applications are using files that need to be updated by this setup. Close these applications and then click Retry to continue the installation or Cancel to exit it.") + inuse.control("List", "ListBox", 20, 107, 330, 130, 7, "FileInUseProcess", + None, None, None) + c=inuse.back("Exit", "Ignore", name="Exit") + c.event("EndDialog", "Exit") + c=inuse.next("Ignore", "Retry", name="Ignore") + c.event("EndDialog", "Ignore") + c=inuse.cancel("Retry", "Exit", name="Retry") + c.event("EndDialog","Retry") + + # See "Error Dialog". See "ICE20" for the required names of the controls. + error = Dialog(db, "ErrorDlg", + 50, 10, 330, 101, + 65543, # Error|Minimize|Modal|Visible + title, + "ErrorText", None, None) + error.text("ErrorText", 50,9,280,48,3, "") + #error.control("ErrorIcon", "Icon", 15, 9, 24, 24, 5242881, None, "py.ico", None, None) + error.pushbutton("N",120,72,81,21,3,"No",None).event("EndDialog","ErrorNo") + error.pushbutton("Y",240,72,81,21,3,"Yes",None).event("EndDialog","ErrorYes") + error.pushbutton("A",0,72,81,21,3,"Abort",None).event("EndDialog","ErrorAbort") + error.pushbutton("C",42,72,81,21,3,"Cancel",None).event("EndDialog","ErrorCancel") + error.pushbutton("I",81,72,81,21,3,"Ignore",None).event("EndDialog","ErrorIgnore") + error.pushbutton("O",159,72,81,21,3,"Ok",None).event("EndDialog","ErrorOk") + error.pushbutton("R",198,72,81,21,3,"Retry",None).event("EndDialog","ErrorRetry") + + ##################################################################### + # Global "Query Cancel" dialog + cancel = Dialog(db, "CancelDlg", 50, 10, 260, 85, 3, title, + "No", "No", "No") + cancel.text("Text", 48, 15, 194, 30, 3, + "Are you sure you want to cancel [ProductName] installation?") + #cancel.control("Icon", "Icon", 15, 15, 24, 24, 5242881, None, + # "py.ico", None, None) + c=cancel.pushbutton("Yes", 72, 57, 56, 17, 3, "Yes", "No") + c.event("EndDialog", "Exit") + + c=cancel.pushbutton("No", 132, 57, 56, 17, 3, "No", "Yes") + c.event("EndDialog", "Return") + + ##################################################################### + # Global "Wait for costing" dialog + costing = Dialog(db, "WaitForCostingDlg", 50, 10, 260, 85, modal, title, + "Return", "Return", "Return") + costing.text("Text", 48, 15, 194, 30, 3, + "Please wait while the installer finishes determining your disk space requirements.") + c = costing.pushbutton("Return", 102, 57, 56, 17, 3, "Return", None) + c.event("EndDialog", "Exit") + + ##################################################################### + # Preparation dialog: no user input except cancellation + prep = PyDialog(db, "PrepareDlg", x, y, w, h, modeless, title, + "Cancel", "Cancel", "Cancel") + prep.text("Description", 15, 70, 320, 40, 0x30003, + "Please wait while the Installer prepares to guide you through the installation.") + prep.title("Welcome to the [ProductName] Installer") + c=prep.text("ActionText", 15, 110, 320, 20, 0x30003, "Pondering...") + c.mapping("ActionText", "Text") + c=prep.text("ActionData", 15, 135, 320, 30, 0x30003, None) + c.mapping("ActionData", "Text") + prep.back("Back", None, active=0) + prep.next("Next", None, active=0) + c=prep.cancel("Cancel", None) + c.event("SpawnDialog", "CancelDlg") + + ##################################################################### + # Feature (Python directory) selection + seldlg = PyDialog(db, "SelectFeaturesDlg", x, y, w, h, modal, title, + "Next", "Next", "Cancel") + seldlg.title("Select Python Installations") + + seldlg.text("Hint", 15, 30, 300, 20, 3, + "Select the Python locations where %s should be installed." + % self.distribution.get_fullname()) + + seldlg.back("< Back", None, active=0) + c = seldlg.next("Next >", "Cancel") + order = 1 + c.event("[TARGETDIR]", "[SourceDir]", ordering=order) + for version in self.versions + [self.other_version]: + order += 1 + c.event("[TARGETDIR]", "[TARGETDIR%s]" % version, + "FEATURE_SELECTED AND &Python%s=3" % version, + ordering=order) + c.event("SpawnWaitDialog", "WaitForCostingDlg", ordering=order + 1) + c.event("EndDialog", "Return", ordering=order + 2) + c = seldlg.cancel("Cancel", "Features") + c.event("SpawnDialog", "CancelDlg") + + c = seldlg.control("Features", "SelectionTree", 15, 60, 300, 120, 3, + "FEATURE", None, "PathEdit", None) + c.event("[FEATURE_SELECTED]", "1") + ver = self.other_version + install_other_cond = "FEATURE_SELECTED AND &Python%s=3" % ver + dont_install_other_cond = "FEATURE_SELECTED AND &Python%s<>3" % ver + + c = seldlg.text("Other", 15, 200, 300, 15, 3, + "Provide an alternate Python location") + c.condition("Enable", install_other_cond) + c.condition("Show", install_other_cond) + c.condition("Disable", dont_install_other_cond) + c.condition("Hide", dont_install_other_cond) + + c = seldlg.control("PathEdit", "PathEdit", 15, 215, 300, 16, 1, + "TARGETDIR" + ver, None, "Next", None) + c.condition("Enable", install_other_cond) + c.condition("Show", install_other_cond) + c.condition("Disable", dont_install_other_cond) + c.condition("Hide", dont_install_other_cond) + + ##################################################################### + # Disk cost + cost = PyDialog(db, "DiskCostDlg", x, y, w, h, modal, title, + "OK", "OK", "OK", bitmap=False) + cost.text("Title", 15, 6, 200, 15, 0x30003, + r"{\DlgFontBold8}Disk Space Requirements") + cost.text("Description", 20, 20, 280, 20, 0x30003, + "The disk space required for the installation of the selected features.") + cost.text("Text", 20, 53, 330, 60, 3, + "The highlighted volumes (if any) do not have enough disk space " + "available for the currently selected features. You can either " + "remove some files from the highlighted volumes, or choose to " + "install less features onto local drive(s), or select different " + "destination drive(s).") + cost.control("VolumeList", "VolumeCostList", 20, 100, 330, 150, 393223, + None, "{120}{70}{70}{70}{70}", None, None) + cost.xbutton("OK", "Ok", None, 0.5).event("EndDialog", "Return") + + ##################################################################### + # WhichUsers Dialog. Only available on NT, and for privileged users. + # This must be run before FindRelatedProducts, because that will + # take into account whether the previous installation was per-user + # or per-machine. We currently don't support going back to this + # dialog after "Next" was selected; to support this, we would need to + # find how to reset the ALLUSERS property, and how to re-run + # FindRelatedProducts. + # On Windows9x, the ALLUSERS property is ignored on the command line + # and in the Property table, but installer fails according to the documentation + # if a dialog attempts to set ALLUSERS. + whichusers = PyDialog(db, "WhichUsersDlg", x, y, w, h, modal, title, + "AdminInstall", "Next", "Cancel") + whichusers.title("Select whether to install [ProductName] for all users of this computer.") + # A radio group with two options: allusers, justme + g = whichusers.radiogroup("AdminInstall", 15, 60, 260, 50, 3, + "WhichUsers", "", "Next") + g.add("ALL", 0, 5, 150, 20, "Install for all users") + g.add("JUSTME", 0, 25, 150, 20, "Install just for me") + + whichusers.back("Back", None, active=0) + + c = whichusers.next("Next >", "Cancel") + c.event("[ALLUSERS]", "1", 'WhichUsers="ALL"', 1) + c.event("EndDialog", "Return", ordering = 2) + + c = whichusers.cancel("Cancel", "AdminInstall") + c.event("SpawnDialog", "CancelDlg") + + ##################################################################### + # Installation Progress dialog (modeless) + progress = PyDialog(db, "ProgressDlg", x, y, w, h, modeless, title, + "Cancel", "Cancel", "Cancel", bitmap=False) + progress.text("Title", 20, 15, 200, 15, 0x30003, + r"{\DlgFontBold8}[Progress1] [ProductName]") + progress.text("Text", 35, 65, 300, 30, 3, + "Please wait while the Installer [Progress2] [ProductName]. " + "This may take several minutes.") + progress.text("StatusLabel", 35, 100, 35, 20, 3, "Status:") + + c=progress.text("ActionText", 70, 100, w-70, 20, 3, "Pondering...") + c.mapping("ActionText", "Text") + + #c=progress.text("ActionData", 35, 140, 300, 20, 3, None) + #c.mapping("ActionData", "Text") + + c=progress.control("ProgressBar", "ProgressBar", 35, 120, 300, 10, 65537, + None, "Progress done", None, None) + c.mapping("SetProgress", "Progress") + + progress.back("< Back", "Next", active=False) + progress.next("Next >", "Cancel", active=False) + progress.cancel("Cancel", "Back").event("SpawnDialog", "CancelDlg") + + ################################################################### + # Maintenance type: repair/uninstall + maint = PyDialog(db, "MaintenanceTypeDlg", x, y, w, h, modal, title, + "Next", "Next", "Cancel") + maint.title("Welcome to the [ProductName] Setup Wizard") + maint.text("BodyText", 15, 63, 330, 42, 3, + "Select whether you want to repair or remove [ProductName].") + g=maint.radiogroup("RepairRadioGroup", 15, 108, 330, 60, 3, + "MaintenanceForm_Action", "", "Next") + #g.add("Change", 0, 0, 200, 17, "&Change [ProductName]") + g.add("Repair", 0, 18, 200, 17, "&Repair [ProductName]") + g.add("Remove", 0, 36, 200, 17, "Re&move [ProductName]") + + maint.back("< Back", None, active=False) + c=maint.next("Finish", "Cancel") + # Change installation: Change progress dialog to "Change", then ask + # for feature selection + #c.event("[Progress1]", "Change", 'MaintenanceForm_Action="Change"', 1) + #c.event("[Progress2]", "changes", 'MaintenanceForm_Action="Change"', 2) + + # Reinstall: Change progress dialog to "Repair", then invoke reinstall + # Also set list of reinstalled features to "ALL" + c.event("[REINSTALL]", "ALL", 'MaintenanceForm_Action="Repair"', 5) + c.event("[Progress1]", "Repairing", 'MaintenanceForm_Action="Repair"', 6) + c.event("[Progress2]", "repairs", 'MaintenanceForm_Action="Repair"', 7) + c.event("Reinstall", "ALL", 'MaintenanceForm_Action="Repair"', 8) + + # Uninstall: Change progress to "Remove", then invoke uninstall + # Also set list of removed features to "ALL" + c.event("[REMOVE]", "ALL", 'MaintenanceForm_Action="Remove"', 11) + c.event("[Progress1]", "Removing", 'MaintenanceForm_Action="Remove"', 12) + c.event("[Progress2]", "removes", 'MaintenanceForm_Action="Remove"', 13) + c.event("Remove", "ALL", 'MaintenanceForm_Action="Remove"', 14) + + # Close dialog when maintenance action scheduled + c.event("EndDialog", "Return", 'MaintenanceForm_Action<>"Change"', 20) + #c.event("NewDialog", "SelectFeaturesDlg", 'MaintenanceForm_Action="Change"', 21) + + maint.cancel("Cancel", "RepairRadioGroup").event("SpawnDialog", "CancelDlg") + + def get_installer_filename(self, fullname): + # Factored out to allow overriding in subclasses + if self.target_version: + base_name = "%s.%s-py%s.msi" % (fullname, self.plat_name, + self.target_version) + else: + base_name = "%s.%s.msi" % (fullname, self.plat_name) + installer_name = os.path.join(self.dist_dir, base_name) + return installer_name diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/command/bdist_rpm.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/bdist_rpm.py new file mode 100644 index 00000000..550cbfa1 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/bdist_rpm.py @@ -0,0 +1,579 @@ +"""distutils.command.bdist_rpm + +Implements the Distutils 'bdist_rpm' command (create RPM source and binary +distributions).""" + +import subprocess, sys, os +from distutils.core import Command +from distutils.debug import DEBUG +from distutils.file_util import write_file +from distutils.errors import * +from distutils.sysconfig import get_python_version +from distutils import log + +class bdist_rpm(Command): + + description = "create an RPM distribution" + + user_options = [ + ('bdist-base=', None, + "base directory for creating built distributions"), + ('rpm-base=', None, + "base directory for creating RPMs (defaults to \"rpm\" under " + "--bdist-base; must be specified for RPM 2)"), + ('dist-dir=', 'd', + "directory to put final RPM files in " + "(and .spec files if --spec-only)"), + ('python=', None, + "path to Python interpreter to hard-code in the .spec file " + "(default: \"python\")"), + ('fix-python', None, + "hard-code the exact path to the current Python interpreter in " + "the .spec file"), + ('spec-only', None, + "only regenerate spec file"), + ('source-only', None, + "only generate source RPM"), + ('binary-only', None, + "only generate binary RPM"), + ('use-bzip2', None, + "use bzip2 instead of gzip to create source distribution"), + + # More meta-data: too RPM-specific to put in the setup script, + # but needs to go in the .spec file -- so we make these options + # to "bdist_rpm". The idea is that packagers would put this + # info in setup.cfg, although they are of course free to + # supply it on the command line. + ('distribution-name=', None, + "name of the (Linux) distribution to which this " + "RPM applies (*not* the name of the module distribution!)"), + ('group=', None, + "package classification [default: \"Development/Libraries\"]"), + ('release=', None, + "RPM release number"), + ('serial=', None, + "RPM serial number"), + ('vendor=', None, + "RPM \"vendor\" (eg. \"Joe Blow \") " + "[default: maintainer or author from setup script]"), + ('packager=', None, + "RPM packager (eg. \"Jane Doe \") " + "[default: vendor]"), + ('doc-files=', None, + "list of documentation files (space or comma-separated)"), + ('changelog=', None, + "RPM changelog"), + ('icon=', None, + "name of icon file"), + ('provides=', None, + "capabilities provided by this package"), + ('requires=', None, + "capabilities required by this package"), + ('conflicts=', None, + "capabilities which conflict with this package"), + ('build-requires=', None, + "capabilities required to build this package"), + ('obsoletes=', None, + "capabilities made obsolete by this package"), + ('no-autoreq', None, + "do not automatically calculate dependencies"), + + # Actions to take when building RPM + ('keep-temp', 'k', + "don't clean up RPM build directory"), + ('no-keep-temp', None, + "clean up RPM build directory [default]"), + ('use-rpm-opt-flags', None, + "compile with RPM_OPT_FLAGS when building from source RPM"), + ('no-rpm-opt-flags', None, + "do not pass any RPM CFLAGS to compiler"), + ('rpm3-mode', None, + "RPM 3 compatibility mode (default)"), + ('rpm2-mode', None, + "RPM 2 compatibility mode"), + + # Add the hooks necessary for specifying custom scripts + ('prep-script=', None, + "Specify a script for the PREP phase of RPM building"), + ('build-script=', None, + "Specify a script for the BUILD phase of RPM building"), + + ('pre-install=', None, + "Specify a script for the pre-INSTALL phase of RPM building"), + ('install-script=', None, + "Specify a script for the INSTALL phase of RPM building"), + ('post-install=', None, + "Specify a script for the post-INSTALL phase of RPM building"), + + ('pre-uninstall=', None, + "Specify a script for the pre-UNINSTALL phase of RPM building"), + ('post-uninstall=', None, + "Specify a script for the post-UNINSTALL phase of RPM building"), + + ('clean-script=', None, + "Specify a script for the CLEAN phase of RPM building"), + + ('verify-script=', None, + "Specify a script for the VERIFY phase of the RPM build"), + + # Allow a packager to explicitly force an architecture + ('force-arch=', None, + "Force an architecture onto the RPM build process"), + + ('quiet', 'q', + "Run the INSTALL phase of RPM building in quiet mode"), + ] + + boolean_options = ['keep-temp', 'use-rpm-opt-flags', 'rpm3-mode', + 'no-autoreq', 'quiet'] + + negative_opt = {'no-keep-temp': 'keep-temp', + 'no-rpm-opt-flags': 'use-rpm-opt-flags', + 'rpm2-mode': 'rpm3-mode'} + + + def initialize_options(self): + self.bdist_base = None + self.rpm_base = None + self.dist_dir = None + self.python = None + self.fix_python = None + self.spec_only = None + self.binary_only = None + self.source_only = None + self.use_bzip2 = None + + self.distribution_name = None + self.group = None + self.release = None + self.serial = None + self.vendor = None + self.packager = None + self.doc_files = None + self.changelog = None + self.icon = None + + self.prep_script = None + self.build_script = None + self.install_script = None + self.clean_script = None + self.verify_script = None + self.pre_install = None + self.post_install = None + self.pre_uninstall = None + self.post_uninstall = None + self.prep = None + self.provides = None + self.requires = None + self.conflicts = None + self.build_requires = None + self.obsoletes = None + + self.keep_temp = 0 + self.use_rpm_opt_flags = 1 + self.rpm3_mode = 1 + self.no_autoreq = 0 + + self.force_arch = None + self.quiet = 0 + + def finalize_options(self): + self.set_undefined_options('bdist', ('bdist_base', 'bdist_base')) + if self.rpm_base is None: + if not self.rpm3_mode: + raise DistutilsOptionError( + "you must specify --rpm-base in RPM 2 mode") + self.rpm_base = os.path.join(self.bdist_base, "rpm") + + if self.python is None: + if self.fix_python: + self.python = sys.executable + else: + self.python = "python3" + elif self.fix_python: + raise DistutilsOptionError( + "--python and --fix-python are mutually exclusive options") + + if os.name != 'posix': + raise DistutilsPlatformError("don't know how to create RPM " + "distributions on platform %s" % os.name) + if self.binary_only and self.source_only: + raise DistutilsOptionError( + "cannot supply both '--source-only' and '--binary-only'") + + # don't pass CFLAGS to pure python distributions + if not self.distribution.has_ext_modules(): + self.use_rpm_opt_flags = 0 + + self.set_undefined_options('bdist', ('dist_dir', 'dist_dir')) + self.finalize_package_data() + + def finalize_package_data(self): + self.ensure_string('group', "Development/Libraries") + self.ensure_string('vendor', + "%s <%s>" % (self.distribution.get_contact(), + self.distribution.get_contact_email())) + self.ensure_string('packager') + self.ensure_string_list('doc_files') + if isinstance(self.doc_files, list): + for readme in ('README', 'README.txt'): + if os.path.exists(readme) and readme not in self.doc_files: + self.doc_files.append(readme) + + self.ensure_string('release', "1") + self.ensure_string('serial') # should it be an int? + + self.ensure_string('distribution_name') + + self.ensure_string('changelog') + # Format changelog correctly + self.changelog = self._format_changelog(self.changelog) + + self.ensure_filename('icon') + + self.ensure_filename('prep_script') + self.ensure_filename('build_script') + self.ensure_filename('install_script') + self.ensure_filename('clean_script') + self.ensure_filename('verify_script') + self.ensure_filename('pre_install') + self.ensure_filename('post_install') + self.ensure_filename('pre_uninstall') + self.ensure_filename('post_uninstall') + + # XXX don't forget we punted on summaries and descriptions -- they + # should be handled here eventually! + + # Now *this* is some meta-data that belongs in the setup script... + self.ensure_string_list('provides') + self.ensure_string_list('requires') + self.ensure_string_list('conflicts') + self.ensure_string_list('build_requires') + self.ensure_string_list('obsoletes') + + self.ensure_string('force_arch') + + def run(self): + if DEBUG: + print("before _get_package_data():") + print("vendor =", self.vendor) + print("packager =", self.packager) + print("doc_files =", self.doc_files) + print("changelog =", self.changelog) + + # make directories + if self.spec_only: + spec_dir = self.dist_dir + self.mkpath(spec_dir) + else: + rpm_dir = {} + for d in ('SOURCES', 'SPECS', 'BUILD', 'RPMS', 'SRPMS'): + rpm_dir[d] = os.path.join(self.rpm_base, d) + self.mkpath(rpm_dir[d]) + spec_dir = rpm_dir['SPECS'] + + # Spec file goes into 'dist_dir' if '--spec-only specified', + # build/rpm. otherwise. + spec_path = os.path.join(spec_dir, + "%s.spec" % self.distribution.get_name()) + self.execute(write_file, + (spec_path, + self._make_spec_file()), + "writing '%s'" % spec_path) + + if self.spec_only: # stop if requested + return + + # Make a source distribution and copy to SOURCES directory with + # optional icon. + saved_dist_files = self.distribution.dist_files[:] + sdist = self.reinitialize_command('sdist') + if self.use_bzip2: + sdist.formats = ['bztar'] + else: + sdist.formats = ['gztar'] + self.run_command('sdist') + self.distribution.dist_files = saved_dist_files + + source = sdist.get_archive_files()[0] + source_dir = rpm_dir['SOURCES'] + self.copy_file(source, source_dir) + + if self.icon: + if os.path.exists(self.icon): + self.copy_file(self.icon, source_dir) + else: + raise DistutilsFileError( + "icon file '%s' does not exist" % self.icon) + + # build package + log.info("building RPMs") + rpm_cmd = ['rpmbuild'] + + if self.source_only: # what kind of RPMs? + rpm_cmd.append('-bs') + elif self.binary_only: + rpm_cmd.append('-bb') + else: + rpm_cmd.append('-ba') + rpm_cmd.extend(['--define', '__python %s' % self.python]) + if self.rpm3_mode: + rpm_cmd.extend(['--define', + '_topdir %s' % os.path.abspath(self.rpm_base)]) + if not self.keep_temp: + rpm_cmd.append('--clean') + + if self.quiet: + rpm_cmd.append('--quiet') + + rpm_cmd.append(spec_path) + # Determine the binary rpm names that should be built out of this spec + # file + # Note that some of these may not be really built (if the file + # list is empty) + nvr_string = "%{name}-%{version}-%{release}" + src_rpm = nvr_string + ".src.rpm" + non_src_rpm = "%{arch}/" + nvr_string + ".%{arch}.rpm" + q_cmd = r"rpm -q --qf '%s %s\n' --specfile '%s'" % ( + src_rpm, non_src_rpm, spec_path) + + out = os.popen(q_cmd) + try: + binary_rpms = [] + source_rpm = None + while True: + line = out.readline() + if not line: + break + l = line.strip().split() + assert(len(l) == 2) + binary_rpms.append(l[1]) + # The source rpm is named after the first entry in the spec file + if source_rpm is None: + source_rpm = l[0] + + status = out.close() + if status: + raise DistutilsExecError("Failed to execute: %s" % repr(q_cmd)) + + finally: + out.close() + + self.spawn(rpm_cmd) + + if not self.dry_run: + if self.distribution.has_ext_modules(): + pyversion = get_python_version() + else: + pyversion = 'any' + + if not self.binary_only: + srpm = os.path.join(rpm_dir['SRPMS'], source_rpm) + assert(os.path.exists(srpm)) + self.move_file(srpm, self.dist_dir) + filename = os.path.join(self.dist_dir, source_rpm) + self.distribution.dist_files.append( + ('bdist_rpm', pyversion, filename)) + + if not self.source_only: + for rpm in binary_rpms: + rpm = os.path.join(rpm_dir['RPMS'], rpm) + if os.path.exists(rpm): + self.move_file(rpm, self.dist_dir) + filename = os.path.join(self.dist_dir, + os.path.basename(rpm)) + self.distribution.dist_files.append( + ('bdist_rpm', pyversion, filename)) + + def _dist_path(self, path): + return os.path.join(self.dist_dir, os.path.basename(path)) + + def _make_spec_file(self): + """Generate the text of an RPM spec file and return it as a + list of strings (one per line). + """ + # definitions and headers + spec_file = [ + '%define name ' + self.distribution.get_name(), + '%define version ' + self.distribution.get_version().replace('-','_'), + '%define unmangled_version ' + self.distribution.get_version(), + '%define release ' + self.release.replace('-','_'), + '', + 'Summary: ' + self.distribution.get_description(), + ] + + # Workaround for #14443 which affects some RPM based systems such as + # RHEL6 (and probably derivatives) + vendor_hook = subprocess.getoutput('rpm --eval %{__os_install_post}') + # Generate a potential replacement value for __os_install_post (whilst + # normalizing the whitespace to simplify the test for whether the + # invocation of brp-python-bytecompile passes in __python): + vendor_hook = '\n'.join([' %s \\' % line.strip() + for line in vendor_hook.splitlines()]) + problem = "brp-python-bytecompile \\\n" + fixed = "brp-python-bytecompile %{__python} \\\n" + fixed_hook = vendor_hook.replace(problem, fixed) + if fixed_hook != vendor_hook: + spec_file.append('# Workaround for http://bugs.python.org/issue14443') + spec_file.append('%define __os_install_post ' + fixed_hook + '\n') + + # put locale summaries into spec file + # XXX not supported for now (hard to put a dictionary + # in a config file -- arg!) + #for locale in self.summaries.keys(): + # spec_file.append('Summary(%s): %s' % (locale, + # self.summaries[locale])) + + spec_file.extend([ + 'Name: %{name}', + 'Version: %{version}', + 'Release: %{release}',]) + + # XXX yuck! this filename is available from the "sdist" command, + # but only after it has run: and we create the spec file before + # running "sdist", in case of --spec-only. + if self.use_bzip2: + spec_file.append('Source0: %{name}-%{unmangled_version}.tar.bz2') + else: + spec_file.append('Source0: %{name}-%{unmangled_version}.tar.gz') + + spec_file.extend([ + 'License: ' + self.distribution.get_license(), + 'Group: ' + self.group, + 'BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot', + 'Prefix: %{_prefix}', ]) + + if not self.force_arch: + # noarch if no extension modules + if not self.distribution.has_ext_modules(): + spec_file.append('BuildArch: noarch') + else: + spec_file.append( 'BuildArch: %s' % self.force_arch ) + + for field in ('Vendor', + 'Packager', + 'Provides', + 'Requires', + 'Conflicts', + 'Obsoletes', + ): + val = getattr(self, field.lower()) + if isinstance(val, list): + spec_file.append('%s: %s' % (field, ' '.join(val))) + elif val is not None: + spec_file.append('%s: %s' % (field, val)) + + + if self.distribution.get_url() != 'UNKNOWN': + spec_file.append('Url: ' + self.distribution.get_url()) + + if self.distribution_name: + spec_file.append('Distribution: ' + self.distribution_name) + + if self.build_requires: + spec_file.append('BuildRequires: ' + + ' '.join(self.build_requires)) + + if self.icon: + spec_file.append('Icon: ' + os.path.basename(self.icon)) + + if self.no_autoreq: + spec_file.append('AutoReq: 0') + + spec_file.extend([ + '', + '%description', + self.distribution.get_long_description() + ]) + + # put locale descriptions into spec file + # XXX again, suppressed because config file syntax doesn't + # easily support this ;-( + #for locale in self.descriptions.keys(): + # spec_file.extend([ + # '', + # '%description -l ' + locale, + # self.descriptions[locale], + # ]) + + # rpm scripts + # figure out default build script + def_setup_call = "%s %s" % (self.python,os.path.basename(sys.argv[0])) + def_build = "%s build" % def_setup_call + if self.use_rpm_opt_flags: + def_build = 'env CFLAGS="$RPM_OPT_FLAGS" ' + def_build + + # insert contents of files + + # XXX this is kind of misleading: user-supplied options are files + # that we open and interpolate into the spec file, but the defaults + # are just text that we drop in as-is. Hmmm. + + install_cmd = ('%s install -O1 --root=$RPM_BUILD_ROOT ' + '--record=INSTALLED_FILES') % def_setup_call + + script_options = [ + ('prep', 'prep_script', "%setup -n %{name}-%{unmangled_version}"), + ('build', 'build_script', def_build), + ('install', 'install_script', install_cmd), + ('clean', 'clean_script', "rm -rf $RPM_BUILD_ROOT"), + ('verifyscript', 'verify_script', None), + ('pre', 'pre_install', None), + ('post', 'post_install', None), + ('preun', 'pre_uninstall', None), + ('postun', 'post_uninstall', None), + ] + + for (rpm_opt, attr, default) in script_options: + # Insert contents of file referred to, if no file is referred to + # use 'default' as contents of script + val = getattr(self, attr) + if val or default: + spec_file.extend([ + '', + '%' + rpm_opt,]) + if val: + with open(val) as f: + spec_file.extend(f.read().split('\n')) + else: + spec_file.append(default) + + + # files section + spec_file.extend([ + '', + '%files -f INSTALLED_FILES', + '%defattr(-,root,root)', + ]) + + if self.doc_files: + spec_file.append('%doc ' + ' '.join(self.doc_files)) + + if self.changelog: + spec_file.extend([ + '', + '%changelog',]) + spec_file.extend(self.changelog) + + return spec_file + + def _format_changelog(self, changelog): + """Format the changelog correctly and convert it to a list of strings + """ + if not changelog: + return changelog + new_changelog = [] + for line in changelog.strip().split('\n'): + line = line.strip() + if line[0] == '*': + new_changelog.extend(['', line]) + elif line[0] == '-': + new_changelog.append(line) + else: + new_changelog.append(' ' + line) + + # strip trailing newline inserted by first changelog entry + if not new_changelog[0]: + del new_changelog[0] + + return new_changelog diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/command/bdist_wininst.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/bdist_wininst.py new file mode 100644 index 00000000..0e9ddaa2 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/bdist_wininst.py @@ -0,0 +1,377 @@ +"""distutils.command.bdist_wininst + +Implements the Distutils 'bdist_wininst' command: create a windows installer +exe-program.""" + +import os +import sys +import warnings +from distutils.core import Command +from distutils.util import get_platform +from distutils.dir_util import remove_tree +from distutils.errors import * +from distutils.sysconfig import get_python_version +from distutils import log + +class bdist_wininst(Command): + + description = "create an executable installer for MS Windows" + + user_options = [('bdist-dir=', None, + "temporary directory for creating the distribution"), + ('plat-name=', 'p', + "platform name to embed in generated filenames " + "(default: %s)" % get_platform()), + ('keep-temp', 'k', + "keep the pseudo-installation tree around after " + + "creating the distribution archive"), + ('target-version=', None, + "require a specific python version" + + " on the target system"), + ('no-target-compile', 'c', + "do not compile .py to .pyc on the target system"), + ('no-target-optimize', 'o', + "do not compile .py to .pyo (optimized) " + "on the target system"), + ('dist-dir=', 'd', + "directory to put final built distributions in"), + ('bitmap=', 'b', + "bitmap to use for the installer instead of python-powered logo"), + ('title=', 't', + "title to display on the installer background instead of default"), + ('skip-build', None, + "skip rebuilding everything (for testing/debugging)"), + ('install-script=', None, + "basename of installation script to be run after " + "installation or before deinstallation"), + ('pre-install-script=', None, + "Fully qualified filename of a script to be run before " + "any files are installed. This script need not be in the " + "distribution"), + ('user-access-control=', None, + "specify Vista's UAC handling - 'none'/default=no " + "handling, 'auto'=use UAC if target Python installed for " + "all users, 'force'=always use UAC"), + ] + + boolean_options = ['keep-temp', 'no-target-compile', 'no-target-optimize', + 'skip-build'] + + # bpo-10945: bdist_wininst requires mbcs encoding only available on Windows + _unsupported = (sys.platform != "win32") + + def __init__(self, *args, **kw): + super().__init__(*args, **kw) + warnings.warn("bdist_wininst command is deprecated since Python 3.8, " + "use bdist_wheel (wheel packages) instead", + DeprecationWarning, 2) + + def initialize_options(self): + self.bdist_dir = None + self.plat_name = None + self.keep_temp = 0 + self.no_target_compile = 0 + self.no_target_optimize = 0 + self.target_version = None + self.dist_dir = None + self.bitmap = None + self.title = None + self.skip_build = None + self.install_script = None + self.pre_install_script = None + self.user_access_control = None + + + def finalize_options(self): + self.set_undefined_options('bdist', ('skip_build', 'skip_build')) + + if self.bdist_dir is None: + if self.skip_build and self.plat_name: + # If build is skipped and plat_name is overridden, bdist will + # not see the correct 'plat_name' - so set that up manually. + bdist = self.distribution.get_command_obj('bdist') + bdist.plat_name = self.plat_name + # next the command will be initialized using that name + bdist_base = self.get_finalized_command('bdist').bdist_base + self.bdist_dir = os.path.join(bdist_base, 'wininst') + + if not self.target_version: + self.target_version = "" + + if not self.skip_build and self.distribution.has_ext_modules(): + short_version = get_python_version() + if self.target_version and self.target_version != short_version: + raise DistutilsOptionError( + "target version can only be %s, or the '--skip-build'" \ + " option must be specified" % (short_version,)) + self.target_version = short_version + + self.set_undefined_options('bdist', + ('dist_dir', 'dist_dir'), + ('plat_name', 'plat_name'), + ) + + if self.install_script: + for script in self.distribution.scripts: + if self.install_script == os.path.basename(script): + break + else: + raise DistutilsOptionError( + "install_script '%s' not found in scripts" + % self.install_script) + + def run(self): + if (sys.platform != "win32" and + (self.distribution.has_ext_modules() or + self.distribution.has_c_libraries())): + raise DistutilsPlatformError \ + ("distribution contains extensions and/or C libraries; " + "must be compiled on a Windows 32 platform") + + if not self.skip_build: + self.run_command('build') + + install = self.reinitialize_command('install', reinit_subcommands=1) + install.root = self.bdist_dir + install.skip_build = self.skip_build + install.warn_dir = 0 + install.plat_name = self.plat_name + + install_lib = self.reinitialize_command('install_lib') + # we do not want to include pyc or pyo files + install_lib.compile = 0 + install_lib.optimize = 0 + + if self.distribution.has_ext_modules(): + # If we are building an installer for a Python version other + # than the one we are currently running, then we need to ensure + # our build_lib reflects the other Python version rather than ours. + # Note that for target_version!=sys.version, we must have skipped the + # build step, so there is no issue with enforcing the build of this + # version. + target_version = self.target_version + if not target_version: + assert self.skip_build, "Should have already checked this" + target_version = '%d.%d' % sys.version_info[:2] + plat_specifier = ".%s-%s" % (self.plat_name, target_version) + build = self.get_finalized_command('build') + build.build_lib = os.path.join(build.build_base, + 'lib' + plat_specifier) + + # Use a custom scheme for the zip-file, because we have to decide + # at installation time which scheme to use. + for key in ('purelib', 'platlib', 'headers', 'scripts', 'data'): + value = key.upper() + if key == 'headers': + value = value + '/Include/$dist_name' + setattr(install, + 'install_' + key, + value) + + log.info("installing to %s", self.bdist_dir) + install.ensure_finalized() + + # avoid warning of 'install_lib' about installing + # into a directory not in sys.path + sys.path.insert(0, os.path.join(self.bdist_dir, 'PURELIB')) + + install.run() + + del sys.path[0] + + # And make an archive relative to the root of the + # pseudo-installation tree. + from tempfile import mktemp + archive_basename = mktemp() + fullname = self.distribution.get_fullname() + arcname = self.make_archive(archive_basename, "zip", + root_dir=self.bdist_dir) + # create an exe containing the zip-file + self.create_exe(arcname, fullname, self.bitmap) + if self.distribution.has_ext_modules(): + pyversion = get_python_version() + else: + pyversion = 'any' + self.distribution.dist_files.append(('bdist_wininst', pyversion, + self.get_installer_filename(fullname))) + # remove the zip-file again + log.debug("removing temporary file '%s'", arcname) + os.remove(arcname) + + if not self.keep_temp: + remove_tree(self.bdist_dir, dry_run=self.dry_run) + + def get_inidata(self): + # Return data describing the installation. + lines = [] + metadata = self.distribution.metadata + + # Write the [metadata] section. + lines.append("[metadata]") + + # 'info' will be displayed in the installer's dialog box, + # describing the items to be installed. + info = (metadata.long_description or '') + '\n' + + # Escape newline characters + def escape(s): + return s.replace("\n", "\\n") + + for name in ["author", "author_email", "description", "maintainer", + "maintainer_email", "name", "url", "version"]: + data = getattr(metadata, name, "") + if data: + info = info + ("\n %s: %s" % \ + (name.capitalize(), escape(data))) + lines.append("%s=%s" % (name, escape(data))) + + # The [setup] section contains entries controlling + # the installer runtime. + lines.append("\n[Setup]") + if self.install_script: + lines.append("install_script=%s" % self.install_script) + lines.append("info=%s" % escape(info)) + lines.append("target_compile=%d" % (not self.no_target_compile)) + lines.append("target_optimize=%d" % (not self.no_target_optimize)) + if self.target_version: + lines.append("target_version=%s" % self.target_version) + if self.user_access_control: + lines.append("user_access_control=%s" % self.user_access_control) + + title = self.title or self.distribution.get_fullname() + lines.append("title=%s" % escape(title)) + import time + import distutils + build_info = "Built %s with distutils-%s" % \ + (time.ctime(time.time()), distutils.__version__) + lines.append("build_info=%s" % build_info) + return "\n".join(lines) + + def create_exe(self, arcname, fullname, bitmap=None): + import struct + + self.mkpath(self.dist_dir) + + cfgdata = self.get_inidata() + + installer_name = self.get_installer_filename(fullname) + self.announce("creating %s" % installer_name) + + if bitmap: + with open(bitmap, "rb") as f: + bitmapdata = f.read() + bitmaplen = len(bitmapdata) + else: + bitmaplen = 0 + + with open(installer_name, "wb") as file: + file.write(self.get_exe_bytes()) + if bitmap: + file.write(bitmapdata) + + # Convert cfgdata from unicode to ascii, mbcs encoded + if isinstance(cfgdata, str): + cfgdata = cfgdata.encode("mbcs") + + # Append the pre-install script + cfgdata = cfgdata + b"\0" + if self.pre_install_script: + # We need to normalize newlines, so we open in text mode and + # convert back to bytes. "latin-1" simply avoids any possible + # failures. + with open(self.pre_install_script, "r", + encoding="latin-1") as script: + script_data = script.read().encode("latin-1") + cfgdata = cfgdata + script_data + b"\n\0" + else: + # empty pre-install script + cfgdata = cfgdata + b"\0" + file.write(cfgdata) + + # The 'magic number' 0x1234567B is used to make sure that the + # binary layout of 'cfgdata' is what the wininst.exe binary + # expects. If the layout changes, increment that number, make + # the corresponding changes to the wininst.exe sources, and + # recompile them. + header = struct.pack("' under the base build directory. We only use one of + # them for a given distribution, though -- + if self.build_purelib is None: + self.build_purelib = os.path.join(self.build_base, 'lib') + if self.build_platlib is None: + self.build_platlib = os.path.join(self.build_base, + 'lib' + plat_specifier) + + # 'build_lib' is the actual directory that we will use for this + # particular module distribution -- if user didn't supply it, pick + # one of 'build_purelib' or 'build_platlib'. + if self.build_lib is None: + if self.distribution.has_ext_modules(): + self.build_lib = self.build_platlib + else: + self.build_lib = self.build_purelib + + # 'build_temp' -- temporary directory for compiler turds, + # "build/temp." + if self.build_temp is None: + self.build_temp = os.path.join(self.build_base, + 'temp' + plat_specifier) + if self.build_scripts is None: + self.build_scripts = os.path.join(self.build_base, + 'scripts-%d.%d' % sys.version_info[:2]) + + if self.executable is None and sys.executable: + self.executable = os.path.normpath(sys.executable) + + if isinstance(self.parallel, str): + try: + self.parallel = int(self.parallel) + except ValueError: + raise DistutilsOptionError("parallel should be an integer") + + def run(self): + # Run all relevant sub-commands. This will be some subset of: + # - build_py - pure Python modules + # - build_clib - standalone C libraries + # - build_ext - Python extensions + # - build_scripts - (Python) scripts + for cmd_name in self.get_sub_commands(): + self.run_command(cmd_name) + + + # -- Predicates for the sub-command list --------------------------- + + def has_pure_modules(self): + return self.distribution.has_pure_modules() + + def has_c_libraries(self): + return self.distribution.has_c_libraries() + + def has_ext_modules(self): + return self.distribution.has_ext_modules() + + def has_scripts(self): + return self.distribution.has_scripts() + + + sub_commands = [('build_py', has_pure_modules), + ('build_clib', has_c_libraries), + ('build_ext', has_ext_modules), + ('build_scripts', has_scripts), + ] diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/command/build_clib.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/build_clib.py new file mode 100644 index 00000000..3e20ef23 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/build_clib.py @@ -0,0 +1,209 @@ +"""distutils.command.build_clib + +Implements the Distutils 'build_clib' command, to build a C/C++ library +that is included in the module distribution and needed by an extension +module.""" + + +# XXX this module has *lots* of code ripped-off quite transparently from +# build_ext.py -- not surprisingly really, as the work required to build +# a static library from a collection of C source files is not really all +# that different from what's required to build a shared object file from +# a collection of C source files. Nevertheless, I haven't done the +# necessary refactoring to account for the overlap in code between the +# two modules, mainly because a number of subtle details changed in the +# cut 'n paste. Sigh. + +import os +from distutils.core import Command +from distutils.errors import * +from distutils.sysconfig import customize_compiler +from distutils import log + +def show_compilers(): + from distutils.ccompiler import show_compilers + show_compilers() + + +class build_clib(Command): + + description = "build C/C++ libraries used by Python extensions" + + user_options = [ + ('build-clib=', 'b', + "directory to build C/C++ libraries to"), + ('build-temp=', 't', + "directory to put temporary build by-products"), + ('debug', 'g', + "compile with debugging information"), + ('force', 'f', + "forcibly build everything (ignore file timestamps)"), + ('compiler=', 'c', + "specify the compiler type"), + ] + + boolean_options = ['debug', 'force'] + + help_options = [ + ('help-compiler', None, + "list available compilers", show_compilers), + ] + + def initialize_options(self): + self.build_clib = None + self.build_temp = None + + # List of libraries to build + self.libraries = None + + # Compilation options for all libraries + self.include_dirs = None + self.define = None + self.undef = None + self.debug = None + self.force = 0 + self.compiler = None + + + def finalize_options(self): + # This might be confusing: both build-clib and build-temp default + # to build-temp as defined by the "build" command. This is because + # I think that C libraries are really just temporary build + # by-products, at least from the point of view of building Python + # extensions -- but I want to keep my options open. + self.set_undefined_options('build', + ('build_temp', 'build_clib'), + ('build_temp', 'build_temp'), + ('compiler', 'compiler'), + ('debug', 'debug'), + ('force', 'force')) + + self.libraries = self.distribution.libraries + if self.libraries: + self.check_library_list(self.libraries) + + if self.include_dirs is None: + self.include_dirs = self.distribution.include_dirs or [] + if isinstance(self.include_dirs, str): + self.include_dirs = self.include_dirs.split(os.pathsep) + + # XXX same as for build_ext -- what about 'self.define' and + # 'self.undef' ? + + + def run(self): + if not self.libraries: + return + + # Yech -- this is cut 'n pasted from build_ext.py! + from distutils.ccompiler import new_compiler + self.compiler = new_compiler(compiler=self.compiler, + dry_run=self.dry_run, + force=self.force) + customize_compiler(self.compiler) + + if self.include_dirs is not None: + self.compiler.set_include_dirs(self.include_dirs) + if self.define is not None: + # 'define' option is a list of (name,value) tuples + for (name,value) in self.define: + self.compiler.define_macro(name, value) + if self.undef is not None: + for macro in self.undef: + self.compiler.undefine_macro(macro) + + self.build_libraries(self.libraries) + + + def check_library_list(self, libraries): + """Ensure that the list of libraries is valid. + + `library` is presumably provided as a command option 'libraries'. + This method checks that it is a list of 2-tuples, where the tuples + are (library_name, build_info_dict). + + Raise DistutilsSetupError if the structure is invalid anywhere; + just returns otherwise. + """ + if not isinstance(libraries, list): + raise DistutilsSetupError( + "'libraries' option must be a list of tuples") + + for lib in libraries: + if not isinstance(lib, tuple) and len(lib) != 2: + raise DistutilsSetupError( + "each element of 'libraries' must a 2-tuple") + + name, build_info = lib + + if not isinstance(name, str): + raise DistutilsSetupError( + "first element of each tuple in 'libraries' " + "must be a string (the library name)") + + if '/' in name or (os.sep != '/' and os.sep in name): + raise DistutilsSetupError("bad library name '%s': " + "may not contain directory separators" % lib[0]) + + if not isinstance(build_info, dict): + raise DistutilsSetupError( + "second element of each tuple in 'libraries' " + "must be a dictionary (build info)") + + + def get_library_names(self): + # Assume the library list is valid -- 'check_library_list()' is + # called from 'finalize_options()', so it should be! + if not self.libraries: + return None + + lib_names = [] + for (lib_name, build_info) in self.libraries: + lib_names.append(lib_name) + return lib_names + + + def get_source_files(self): + self.check_library_list(self.libraries) + filenames = [] + for (lib_name, build_info) in self.libraries: + sources = build_info.get('sources') + if sources is None or not isinstance(sources, (list, tuple)): + raise DistutilsSetupError( + "in 'libraries' option (library '%s'), " + "'sources' must be present and must be " + "a list of source filenames" % lib_name) + + filenames.extend(sources) + return filenames + + + def build_libraries(self, libraries): + for (lib_name, build_info) in libraries: + sources = build_info.get('sources') + if sources is None or not isinstance(sources, (list, tuple)): + raise DistutilsSetupError( + "in 'libraries' option (library '%s'), " + "'sources' must be present and must be " + "a list of source filenames" % lib_name) + sources = list(sources) + + log.info("building '%s' library", lib_name) + + # First, compile the source code to object files in the library + # directory. (This should probably change to putting object + # files in a temporary build directory.) + macros = build_info.get('macros') + include_dirs = build_info.get('include_dirs') + objects = self.compiler.compile(sources, + output_dir=self.build_temp, + macros=macros, + include_dirs=include_dirs, + debug=self.debug) + + # Now "link" the object files together into a static library. + # (On Unix at least, this isn't really linking -- it just + # builds an archive. Whatever.) + self.compiler.create_static_lib(objects, lib_name, + output_dir=self.build_clib, + debug=self.debug) diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/command/build_ext.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/build_ext.py new file mode 100644 index 00000000..181671bf --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/build_ext.py @@ -0,0 +1,755 @@ +"""distutils.command.build_ext + +Implements the Distutils 'build_ext' command, for building extension +modules (currently limited to C extensions, should accommodate C++ +extensions ASAP).""" + +import contextlib +import os +import re +import sys +from distutils.core import Command +from distutils.errors import * +from distutils.sysconfig import customize_compiler, get_python_version +from distutils.sysconfig import get_config_h_filename +from distutils.dep_util import newer_group +from distutils.extension import Extension +from distutils.util import get_platform +from distutils import log +from . import py37compat + +from site import USER_BASE + +# An extension name is just a dot-separated list of Python NAMEs (ie. +# the same as a fully-qualified module name). +extension_name_re = re.compile \ + (r'^[a-zA-Z_][a-zA-Z_0-9]*(\.[a-zA-Z_][a-zA-Z_0-9]*)*$') + + +def show_compilers (): + from distutils.ccompiler import show_compilers + show_compilers() + + +class build_ext(Command): + + description = "build C/C++ extensions (compile/link to build directory)" + + # XXX thoughts on how to deal with complex command-line options like + # these, i.e. how to make it so fancy_getopt can suck them off the + # command line and make it look like setup.py defined the appropriate + # lists of tuples of what-have-you. + # - each command needs a callback to process its command-line options + # - Command.__init__() needs access to its share of the whole + # command line (must ultimately come from + # Distribution.parse_command_line()) + # - it then calls the current command class' option-parsing + # callback to deal with weird options like -D, which have to + # parse the option text and churn out some custom data + # structure + # - that data structure (in this case, a list of 2-tuples) + # will then be present in the command object by the time + # we get to finalize_options() (i.e. the constructor + # takes care of both command-line and client options + # in between initialize_options() and finalize_options()) + + sep_by = " (separated by '%s')" % os.pathsep + user_options = [ + ('build-lib=', 'b', + "directory for compiled extension modules"), + ('build-temp=', 't', + "directory for temporary files (build by-products)"), + ('plat-name=', 'p', + "platform name to cross-compile for, if supported " + "(default: %s)" % get_platform()), + ('inplace', 'i', + "ignore build-lib and put compiled extensions into the source " + + "directory alongside your pure Python modules"), + ('include-dirs=', 'I', + "list of directories to search for header files" + sep_by), + ('define=', 'D', + "C preprocessor macros to define"), + ('undef=', 'U', + "C preprocessor macros to undefine"), + ('libraries=', 'l', + "external C libraries to link with"), + ('library-dirs=', 'L', + "directories to search for external C libraries" + sep_by), + ('rpath=', 'R', + "directories to search for shared C libraries at runtime"), + ('link-objects=', 'O', + "extra explicit link objects to include in the link"), + ('debug', 'g', + "compile/link with debugging information"), + ('force', 'f', + "forcibly build everything (ignore file timestamps)"), + ('compiler=', 'c', + "specify the compiler type"), + ('parallel=', 'j', + "number of parallel build jobs"), + ('swig-cpp', None, + "make SWIG create C++ files (default is C)"), + ('swig-opts=', None, + "list of SWIG command line options"), + ('swig=', None, + "path to the SWIG executable"), + ('user', None, + "add user include, library and rpath") + ] + + boolean_options = ['inplace', 'debug', 'force', 'swig-cpp', 'user'] + + help_options = [ + ('help-compiler', None, + "list available compilers", show_compilers), + ] + + def initialize_options(self): + self.extensions = None + self.build_lib = None + self.plat_name = None + self.build_temp = None + self.inplace = 0 + self.package = None + + self.include_dirs = None + self.define = None + self.undef = None + self.libraries = None + self.library_dirs = None + self.rpath = None + self.link_objects = None + self.debug = None + self.force = None + self.compiler = None + self.swig = None + self.swig_cpp = None + self.swig_opts = None + self.user = None + self.parallel = None + + def finalize_options(self): + from distutils import sysconfig + + self.set_undefined_options('build', + ('build_lib', 'build_lib'), + ('build_temp', 'build_temp'), + ('compiler', 'compiler'), + ('debug', 'debug'), + ('force', 'force'), + ('parallel', 'parallel'), + ('plat_name', 'plat_name'), + ) + + if self.package is None: + self.package = self.distribution.ext_package + + self.extensions = self.distribution.ext_modules + + # Make sure Python's include directories (for Python.h, pyconfig.h, + # etc.) are in the include search path. + py_include = sysconfig.get_python_inc() + plat_py_include = sysconfig.get_python_inc(plat_specific=1) + if self.include_dirs is None: + self.include_dirs = self.distribution.include_dirs or [] + if isinstance(self.include_dirs, str): + self.include_dirs = self.include_dirs.split(os.pathsep) + + # If in a virtualenv, add its include directory + # Issue 16116 + if sys.exec_prefix != sys.base_exec_prefix: + self.include_dirs.append(os.path.join(sys.exec_prefix, 'include')) + + # Put the Python "system" include dir at the end, so that + # any local include dirs take precedence. + self.include_dirs.extend(py_include.split(os.path.pathsep)) + if plat_py_include != py_include: + self.include_dirs.extend( + plat_py_include.split(os.path.pathsep)) + + self.ensure_string_list('libraries') + self.ensure_string_list('link_objects') + + # Life is easier if we're not forever checking for None, so + # simplify these options to empty lists if unset + if self.libraries is None: + self.libraries = [] + if self.library_dirs is None: + self.library_dirs = [] + elif isinstance(self.library_dirs, str): + self.library_dirs = self.library_dirs.split(os.pathsep) + + if self.rpath is None: + self.rpath = [] + elif isinstance(self.rpath, str): + self.rpath = self.rpath.split(os.pathsep) + + # for extensions under windows use different directories + # for Release and Debug builds. + # also Python's library directory must be appended to library_dirs + if os.name == 'nt': + # the 'libs' directory is for binary installs - we assume that + # must be the *native* platform. But we don't really support + # cross-compiling via a binary install anyway, so we let it go. + self.library_dirs.append(os.path.join(sys.exec_prefix, 'libs')) + if sys.base_exec_prefix != sys.prefix: # Issue 16116 + self.library_dirs.append(os.path.join(sys.base_exec_prefix, 'libs')) + if self.debug: + self.build_temp = os.path.join(self.build_temp, "Debug") + else: + self.build_temp = os.path.join(self.build_temp, "Release") + + # Append the source distribution include and library directories, + # this allows distutils on windows to work in the source tree + self.include_dirs.append(os.path.dirname(get_config_h_filename())) + self.library_dirs.append(sys.base_exec_prefix) + + # Use the .lib files for the correct architecture + if self.plat_name == 'win32': + suffix = 'win32' + else: + # win-amd64 + suffix = self.plat_name[4:] + new_lib = os.path.join(sys.exec_prefix, 'PCbuild') + if suffix: + new_lib = os.path.join(new_lib, suffix) + self.library_dirs.append(new_lib) + + # For extensions under Cygwin, Python's library directory must be + # appended to library_dirs + if sys.platform[:6] == 'cygwin': + if not sysconfig.python_build: + # building third party extensions + self.library_dirs.append(os.path.join(sys.prefix, "lib", + "python" + get_python_version(), + "config")) + else: + # building python standard extensions + self.library_dirs.append('.') + + # For building extensions with a shared Python library, + # Python's library directory must be appended to library_dirs + # See Issues: #1600860, #4366 + if (sysconfig.get_config_var('Py_ENABLE_SHARED')): + if not sysconfig.python_build: + # building third party extensions + self.library_dirs.append(sysconfig.get_config_var('LIBDIR')) + else: + # building python standard extensions + self.library_dirs.append('.') + + # The argument parsing will result in self.define being a string, but + # it has to be a list of 2-tuples. All the preprocessor symbols + # specified by the 'define' option will be set to '1'. Multiple + # symbols can be separated with commas. + + if self.define: + defines = self.define.split(',') + self.define = [(symbol, '1') for symbol in defines] + + # The option for macros to undefine is also a string from the + # option parsing, but has to be a list. Multiple symbols can also + # be separated with commas here. + if self.undef: + self.undef = self.undef.split(',') + + if self.swig_opts is None: + self.swig_opts = [] + else: + self.swig_opts = self.swig_opts.split(' ') + + # Finally add the user include and library directories if requested + if self.user: + user_include = os.path.join(USER_BASE, "include") + user_lib = os.path.join(USER_BASE, "lib") + if os.path.isdir(user_include): + self.include_dirs.append(user_include) + if os.path.isdir(user_lib): + self.library_dirs.append(user_lib) + self.rpath.append(user_lib) + + if isinstance(self.parallel, str): + try: + self.parallel = int(self.parallel) + except ValueError: + raise DistutilsOptionError("parallel should be an integer") + + def run(self): + from distutils.ccompiler import new_compiler + + # 'self.extensions', as supplied by setup.py, is a list of + # Extension instances. See the documentation for Extension (in + # distutils.extension) for details. + # + # For backwards compatibility with Distutils 0.8.2 and earlier, we + # also allow the 'extensions' list to be a list of tuples: + # (ext_name, build_info) + # where build_info is a dictionary containing everything that + # Extension instances do except the name, with a few things being + # differently named. We convert these 2-tuples to Extension + # instances as needed. + + if not self.extensions: + return + + # If we were asked to build any C/C++ libraries, make sure that the + # directory where we put them is in the library search path for + # linking extensions. + if self.distribution.has_c_libraries(): + build_clib = self.get_finalized_command('build_clib') + self.libraries.extend(build_clib.get_library_names() or []) + self.library_dirs.append(build_clib.build_clib) + + # Setup the CCompiler object that we'll use to do all the + # compiling and linking + self.compiler = new_compiler(compiler=self.compiler, + verbose=self.verbose, + dry_run=self.dry_run, + force=self.force) + customize_compiler(self.compiler) + # If we are cross-compiling, init the compiler now (if we are not + # cross-compiling, init would not hurt, but people may rely on + # late initialization of compiler even if they shouldn't...) + if os.name == 'nt' and self.plat_name != get_platform(): + self.compiler.initialize(self.plat_name) + + # And make sure that any compile/link-related options (which might + # come from the command-line or from the setup script) are set in + # that CCompiler object -- that way, they automatically apply to + # all compiling and linking done here. + if self.include_dirs is not None: + self.compiler.set_include_dirs(self.include_dirs) + if self.define is not None: + # 'define' option is a list of (name,value) tuples + for (name, value) in self.define: + self.compiler.define_macro(name, value) + if self.undef is not None: + for macro in self.undef: + self.compiler.undefine_macro(macro) + if self.libraries is not None: + self.compiler.set_libraries(self.libraries) + if self.library_dirs is not None: + self.compiler.set_library_dirs(self.library_dirs) + if self.rpath is not None: + self.compiler.set_runtime_library_dirs(self.rpath) + if self.link_objects is not None: + self.compiler.set_link_objects(self.link_objects) + + # Now actually compile and link everything. + self.build_extensions() + + def check_extensions_list(self, extensions): + """Ensure that the list of extensions (presumably provided as a + command option 'extensions') is valid, i.e. it is a list of + Extension objects. We also support the old-style list of 2-tuples, + where the tuples are (ext_name, build_info), which are converted to + Extension instances here. + + Raise DistutilsSetupError if the structure is invalid anywhere; + just returns otherwise. + """ + if not isinstance(extensions, list): + raise DistutilsSetupError( + "'ext_modules' option must be a list of Extension instances") + + for i, ext in enumerate(extensions): + if isinstance(ext, Extension): + continue # OK! (assume type-checking done + # by Extension constructor) + + if not isinstance(ext, tuple) or len(ext) != 2: + raise DistutilsSetupError( + "each element of 'ext_modules' option must be an " + "Extension instance or 2-tuple") + + ext_name, build_info = ext + + log.warn("old-style (ext_name, build_info) tuple found in " + "ext_modules for extension '%s' " + "-- please convert to Extension instance", ext_name) + + if not (isinstance(ext_name, str) and + extension_name_re.match(ext_name)): + raise DistutilsSetupError( + "first element of each tuple in 'ext_modules' " + "must be the extension name (a string)") + + if not isinstance(build_info, dict): + raise DistutilsSetupError( + "second element of each tuple in 'ext_modules' " + "must be a dictionary (build info)") + + # OK, the (ext_name, build_info) dict is type-safe: convert it + # to an Extension instance. + ext = Extension(ext_name, build_info['sources']) + + # Easy stuff: one-to-one mapping from dict elements to + # instance attributes. + for key in ('include_dirs', 'library_dirs', 'libraries', + 'extra_objects', 'extra_compile_args', + 'extra_link_args'): + val = build_info.get(key) + if val is not None: + setattr(ext, key, val) + + # Medium-easy stuff: same syntax/semantics, different names. + ext.runtime_library_dirs = build_info.get('rpath') + if 'def_file' in build_info: + log.warn("'def_file' element of build info dict " + "no longer supported") + + # Non-trivial stuff: 'macros' split into 'define_macros' + # and 'undef_macros'. + macros = build_info.get('macros') + if macros: + ext.define_macros = [] + ext.undef_macros = [] + for macro in macros: + if not (isinstance(macro, tuple) and len(macro) in (1, 2)): + raise DistutilsSetupError( + "'macros' element of build info dict " + "must be 1- or 2-tuple") + if len(macro) == 1: + ext.undef_macros.append(macro[0]) + elif len(macro) == 2: + ext.define_macros.append(macro) + + extensions[i] = ext + + def get_source_files(self): + self.check_extensions_list(self.extensions) + filenames = [] + + # Wouldn't it be neat if we knew the names of header files too... + for ext in self.extensions: + filenames.extend(ext.sources) + return filenames + + def get_outputs(self): + # Sanity check the 'extensions' list -- can't assume this is being + # done in the same run as a 'build_extensions()' call (in fact, we + # can probably assume that it *isn't*!). + self.check_extensions_list(self.extensions) + + # And build the list of output (built) filenames. Note that this + # ignores the 'inplace' flag, and assumes everything goes in the + # "build" tree. + outputs = [] + for ext in self.extensions: + outputs.append(self.get_ext_fullpath(ext.name)) + return outputs + + def build_extensions(self): + # First, sanity-check the 'extensions' list + self.check_extensions_list(self.extensions) + if self.parallel: + self._build_extensions_parallel() + else: + self._build_extensions_serial() + + def _build_extensions_parallel(self): + workers = self.parallel + if self.parallel is True: + workers = os.cpu_count() # may return None + try: + from concurrent.futures import ThreadPoolExecutor + except ImportError: + workers = None + + if workers is None: + self._build_extensions_serial() + return + + with ThreadPoolExecutor(max_workers=workers) as executor: + futures = [executor.submit(self.build_extension, ext) + for ext in self.extensions] + for ext, fut in zip(self.extensions, futures): + with self._filter_build_errors(ext): + fut.result() + + def _build_extensions_serial(self): + for ext in self.extensions: + with self._filter_build_errors(ext): + self.build_extension(ext) + + @contextlib.contextmanager + def _filter_build_errors(self, ext): + try: + yield + except (CCompilerError, DistutilsError, CompileError) as e: + if not ext.optional: + raise + self.warn('building extension "%s" failed: %s' % + (ext.name, e)) + + def build_extension(self, ext): + sources = ext.sources + if sources is None or not isinstance(sources, (list, tuple)): + raise DistutilsSetupError( + "in 'ext_modules' option (extension '%s'), " + "'sources' must be present and must be " + "a list of source filenames" % ext.name) + # sort to make the resulting .so file build reproducible + sources = sorted(sources) + + ext_path = self.get_ext_fullpath(ext.name) + depends = sources + ext.depends + if not (self.force or newer_group(depends, ext_path, 'newer')): + log.debug("skipping '%s' extension (up-to-date)", ext.name) + return + else: + log.info("building '%s' extension", ext.name) + + # First, scan the sources for SWIG definition files (.i), run + # SWIG on 'em to create .c files, and modify the sources list + # accordingly. + sources = self.swig_sources(sources, ext) + + # Next, compile the source code to object files. + + # XXX not honouring 'define_macros' or 'undef_macros' -- the + # CCompiler API needs to change to accommodate this, and I + # want to do one thing at a time! + + # Two possible sources for extra compiler arguments: + # - 'extra_compile_args' in Extension object + # - CFLAGS environment variable (not particularly + # elegant, but people seem to expect it and I + # guess it's useful) + # The environment variable should take precedence, and + # any sensible compiler will give precedence to later + # command line args. Hence we combine them in order: + extra_args = ext.extra_compile_args or [] + + macros = ext.define_macros[:] + for undef in ext.undef_macros: + macros.append((undef,)) + + objects = self.compiler.compile(sources, + output_dir=self.build_temp, + macros=macros, + include_dirs=ext.include_dirs, + debug=self.debug, + extra_postargs=extra_args, + depends=ext.depends) + + # XXX outdated variable, kept here in case third-part code + # needs it. + self._built_objects = objects[:] + + # Now link the object files together into a "shared object" -- + # of course, first we have to figure out all the other things + # that go into the mix. + if ext.extra_objects: + objects.extend(ext.extra_objects) + extra_args = ext.extra_link_args or [] + + # Detect target language, if not provided + language = ext.language or self.compiler.detect_language(sources) + + self.compiler.link_shared_object( + objects, ext_path, + libraries=self.get_libraries(ext), + library_dirs=ext.library_dirs, + runtime_library_dirs=ext.runtime_library_dirs, + extra_postargs=extra_args, + export_symbols=self.get_export_symbols(ext), + debug=self.debug, + build_temp=self.build_temp, + target_lang=language) + + def swig_sources(self, sources, extension): + """Walk the list of source files in 'sources', looking for SWIG + interface (.i) files. Run SWIG on all that are found, and + return a modified 'sources' list with SWIG source files replaced + by the generated C (or C++) files. + """ + new_sources = [] + swig_sources = [] + swig_targets = {} + + # XXX this drops generated C/C++ files into the source tree, which + # is fine for developers who want to distribute the generated + # source -- but there should be an option to put SWIG output in + # the temp dir. + + if self.swig_cpp: + log.warn("--swig-cpp is deprecated - use --swig-opts=-c++") + + if self.swig_cpp or ('-c++' in self.swig_opts) or \ + ('-c++' in extension.swig_opts): + target_ext = '.cpp' + else: + target_ext = '.c' + + for source in sources: + (base, ext) = os.path.splitext(source) + if ext == ".i": # SWIG interface file + new_sources.append(base + '_wrap' + target_ext) + swig_sources.append(source) + swig_targets[source] = new_sources[-1] + else: + new_sources.append(source) + + if not swig_sources: + return new_sources + + swig = self.swig or self.find_swig() + swig_cmd = [swig, "-python"] + swig_cmd.extend(self.swig_opts) + if self.swig_cpp: + swig_cmd.append("-c++") + + # Do not override commandline arguments + if not self.swig_opts: + for o in extension.swig_opts: + swig_cmd.append(o) + + for source in swig_sources: + target = swig_targets[source] + log.info("swigging %s to %s", source, target) + self.spawn(swig_cmd + ["-o", target, source]) + + return new_sources + + def find_swig(self): + """Return the name of the SWIG executable. On Unix, this is + just "swig" -- it should be in the PATH. Tries a bit harder on + Windows. + """ + if os.name == "posix": + return "swig" + elif os.name == "nt": + # Look for SWIG in its standard installation directory on + # Windows (or so I presume!). If we find it there, great; + # if not, act like Unix and assume it's in the PATH. + for vers in ("1.3", "1.2", "1.1"): + fn = os.path.join("c:\\swig%s" % vers, "swig.exe") + if os.path.isfile(fn): + return fn + else: + return "swig.exe" + else: + raise DistutilsPlatformError( + "I don't know how to find (much less run) SWIG " + "on platform '%s'" % os.name) + + # -- Name generators ----------------------------------------------- + # (extension names, filenames, whatever) + def get_ext_fullpath(self, ext_name): + """Returns the path of the filename for a given extension. + + The file is located in `build_lib` or directly in the package + (inplace option). + """ + fullname = self.get_ext_fullname(ext_name) + modpath = fullname.split('.') + filename = self.get_ext_filename(modpath[-1]) + + if not self.inplace: + # no further work needed + # returning : + # build_dir/package/path/filename + filename = os.path.join(*modpath[:-1]+[filename]) + return os.path.join(self.build_lib, filename) + + # the inplace option requires to find the package directory + # using the build_py command for that + package = '.'.join(modpath[0:-1]) + build_py = self.get_finalized_command('build_py') + package_dir = os.path.abspath(build_py.get_package_dir(package)) + + # returning + # package_dir/filename + return os.path.join(package_dir, filename) + + def get_ext_fullname(self, ext_name): + """Returns the fullname of a given extension name. + + Adds the `package.` prefix""" + if self.package is None: + return ext_name + else: + return self.package + '.' + ext_name + + def get_ext_filename(self, ext_name): + r"""Convert the name of an extension (eg. "foo.bar") into the name + of the file from which it will be loaded (eg. "foo/bar.so", or + "foo\bar.pyd"). + """ + from distutils.sysconfig import get_config_var + ext_path = ext_name.split('.') + ext_suffix = get_config_var('EXT_SUFFIX') + return os.path.join(*ext_path) + ext_suffix + + def get_export_symbols(self, ext): + """Return the list of symbols that a shared extension has to + export. This either uses 'ext.export_symbols' or, if it's not + provided, "PyInit_" + module_name. Only relevant on Windows, where + the .pyd file (DLL) must export the module "PyInit_" function. + """ + name = ext.name.split('.')[-1] + try: + # Unicode module name support as defined in PEP-489 + # https://www.python.org/dev/peps/pep-0489/#export-hook-name + name.encode('ascii') + except UnicodeEncodeError: + suffix = 'U_' + name.encode('punycode').replace(b'-', b'_').decode('ascii') + else: + suffix = "_" + name + + initfunc_name = "PyInit" + suffix + if initfunc_name not in ext.export_symbols: + ext.export_symbols.append(initfunc_name) + return ext.export_symbols + + def get_libraries(self, ext): + """Return the list of libraries to link against when building a + shared extension. On most platforms, this is just 'ext.libraries'; + on Windows, we add the Python library (eg. python20.dll). + """ + # The python library is always needed on Windows. For MSVC, this + # is redundant, since the library is mentioned in a pragma in + # pyconfig.h that MSVC groks. The other Windows compilers all seem + # to need it mentioned explicitly, though, so that's what we do. + # Append '_d' to the python import library on debug builds. + if sys.platform == "win32": + from distutils._msvccompiler import MSVCCompiler + if not isinstance(self.compiler, MSVCCompiler): + template = "python%d%d" + if self.debug: + template = template + '_d' + pythonlib = (template % + (sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff)) + # don't extend ext.libraries, it may be shared with other + # extensions, it is a reference to the original list + return ext.libraries + [pythonlib] + else: + # On Android only the main executable and LD_PRELOADs are considered + # to be RTLD_GLOBAL, all the dependencies of the main executable + # remain RTLD_LOCAL and so the shared libraries must be linked with + # libpython when python is built with a shared python library (issue + # bpo-21536). + # On Cygwin (and if required, other POSIX-like platforms based on + # Windows like MinGW) it is simply necessary that all symbols in + # shared libraries are resolved at link time. + from distutils.sysconfig import get_config_var + link_libpython = False + if get_config_var('Py_ENABLE_SHARED'): + # A native build on an Android device or on Cygwin + if hasattr(sys, 'getandroidapilevel'): + link_libpython = True + elif sys.platform == 'cygwin': + link_libpython = True + elif '_PYTHON_HOST_PLATFORM' in os.environ: + # We are cross-compiling for one of the relevant platforms + if get_config_var('ANDROID_API_LEVEL') != 0: + link_libpython = True + elif get_config_var('MACHDEP') == 'cygwin': + link_libpython = True + + if link_libpython: + ldversion = get_config_var('LDVERSION') + return ext.libraries + ['python' + ldversion] + + return ext.libraries + py37compat.pythonlib() diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/command/build_py.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/build_py.py new file mode 100644 index 00000000..7ef9bcef --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/build_py.py @@ -0,0 +1,392 @@ +"""distutils.command.build_py + +Implements the Distutils 'build_py' command.""" + +import os +import importlib.util +import sys +import glob + +from distutils.core import Command +from distutils.errors import * +from distutils.util import convert_path +from distutils import log + +class build_py (Command): + + description = "\"build\" pure Python modules (copy to build directory)" + + user_options = [ + ('build-lib=', 'd', "directory to \"build\" (copy) to"), + ('compile', 'c', "compile .py to .pyc"), + ('no-compile', None, "don't compile .py files [default]"), + ('optimize=', 'O', + "also compile with optimization: -O1 for \"python -O\", " + "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"), + ('force', 'f', "forcibly build everything (ignore file timestamps)"), + ] + + boolean_options = ['compile', 'force'] + negative_opt = {'no-compile' : 'compile'} + + def initialize_options(self): + self.build_lib = None + self.py_modules = None + self.package = None + self.package_data = None + self.package_dir = None + self.compile = 0 + self.optimize = 0 + self.force = None + + def finalize_options(self): + self.set_undefined_options('build', + ('build_lib', 'build_lib'), + ('force', 'force')) + + # Get the distribution options that are aliases for build_py + # options -- list of packages and list of modules. + self.packages = self.distribution.packages + self.py_modules = self.distribution.py_modules + self.package_data = self.distribution.package_data + self.package_dir = {} + if self.distribution.package_dir: + for name, path in self.distribution.package_dir.items(): + self.package_dir[name] = convert_path(path) + self.data_files = self.get_data_files() + + # Ick, copied straight from install_lib.py (fancy_getopt needs a + # type system! Hell, *everything* needs a type system!!!) + if not isinstance(self.optimize, int): + try: + self.optimize = int(self.optimize) + assert 0 <= self.optimize <= 2 + except (ValueError, AssertionError): + raise DistutilsOptionError("optimize must be 0, 1, or 2") + + def run(self): + # XXX copy_file by default preserves atime and mtime. IMHO this is + # the right thing to do, but perhaps it should be an option -- in + # particular, a site administrator might want installed files to + # reflect the time of installation rather than the last + # modification time before the installed release. + + # XXX copy_file by default preserves mode, which appears to be the + # wrong thing to do: if a file is read-only in the working + # directory, we want it to be installed read/write so that the next + # installation of the same module distribution can overwrite it + # without problems. (This might be a Unix-specific issue.) Thus + # we turn off 'preserve_mode' when copying to the build directory, + # since the build directory is supposed to be exactly what the + # installation will look like (ie. we preserve mode when + # installing). + + # Two options control which modules will be installed: 'packages' + # and 'py_modules'. The former lets us work with whole packages, not + # specifying individual modules at all; the latter is for + # specifying modules one-at-a-time. + + if self.py_modules: + self.build_modules() + if self.packages: + self.build_packages() + self.build_package_data() + + self.byte_compile(self.get_outputs(include_bytecode=0)) + + def get_data_files(self): + """Generate list of '(package,src_dir,build_dir,filenames)' tuples""" + data = [] + if not self.packages: + return data + for package in self.packages: + # Locate package source directory + src_dir = self.get_package_dir(package) + + # Compute package build directory + build_dir = os.path.join(*([self.build_lib] + package.split('.'))) + + # Length of path to strip from found files + plen = 0 + if src_dir: + plen = len(src_dir)+1 + + # Strip directory from globbed filenames + filenames = [ + file[plen:] for file in self.find_data_files(package, src_dir) + ] + data.append((package, src_dir, build_dir, filenames)) + return data + + def find_data_files(self, package, src_dir): + """Return filenames for package's data files in 'src_dir'""" + globs = (self.package_data.get('', []) + + self.package_data.get(package, [])) + files = [] + for pattern in globs: + # Each pattern has to be converted to a platform-specific path + filelist = glob.glob(os.path.join(glob.escape(src_dir), convert_path(pattern))) + # Files that match more than one pattern are only added once + files.extend([fn for fn in filelist if fn not in files + and os.path.isfile(fn)]) + return files + + def build_package_data(self): + """Copy data files into build directory""" + lastdir = None + for package, src_dir, build_dir, filenames in self.data_files: + for filename in filenames: + target = os.path.join(build_dir, filename) + self.mkpath(os.path.dirname(target)) + self.copy_file(os.path.join(src_dir, filename), target, + preserve_mode=False) + + def get_package_dir(self, package): + """Return the directory, relative to the top of the source + distribution, where package 'package' should be found + (at least according to the 'package_dir' option, if any).""" + path = package.split('.') + + if not self.package_dir: + if path: + return os.path.join(*path) + else: + return '' + else: + tail = [] + while path: + try: + pdir = self.package_dir['.'.join(path)] + except KeyError: + tail.insert(0, path[-1]) + del path[-1] + else: + tail.insert(0, pdir) + return os.path.join(*tail) + else: + # Oops, got all the way through 'path' without finding a + # match in package_dir. If package_dir defines a directory + # for the root (nameless) package, then fallback on it; + # otherwise, we might as well have not consulted + # package_dir at all, as we just use the directory implied + # by 'tail' (which should be the same as the original value + # of 'path' at this point). + pdir = self.package_dir.get('') + if pdir is not None: + tail.insert(0, pdir) + + if tail: + return os.path.join(*tail) + else: + return '' + + def check_package(self, package, package_dir): + # Empty dir name means current directory, which we can probably + # assume exists. Also, os.path.exists and isdir don't know about + # my "empty string means current dir" convention, so we have to + # circumvent them. + if package_dir != "": + if not os.path.exists(package_dir): + raise DistutilsFileError( + "package directory '%s' does not exist" % package_dir) + if not os.path.isdir(package_dir): + raise DistutilsFileError( + "supposed package directory '%s' exists, " + "but is not a directory" % package_dir) + + # Require __init__.py for all but the "root package" + if package: + init_py = os.path.join(package_dir, "__init__.py") + if os.path.isfile(init_py): + return init_py + else: + log.warn(("package init file '%s' not found " + + "(or not a regular file)"), init_py) + + # Either not in a package at all (__init__.py not expected), or + # __init__.py doesn't exist -- so don't return the filename. + return None + + def check_module(self, module, module_file): + if not os.path.isfile(module_file): + log.warn("file %s (for module %s) not found", module_file, module) + return False + else: + return True + + def find_package_modules(self, package, package_dir): + self.check_package(package, package_dir) + module_files = glob.glob(os.path.join(glob.escape(package_dir), "*.py")) + modules = [] + setup_script = os.path.abspath(self.distribution.script_name) + + for f in module_files: + abs_f = os.path.abspath(f) + if abs_f != setup_script: + module = os.path.splitext(os.path.basename(f))[0] + modules.append((package, module, f)) + else: + self.debug_print("excluding %s" % setup_script) + return modules + + def find_modules(self): + """Finds individually-specified Python modules, ie. those listed by + module name in 'self.py_modules'. Returns a list of tuples (package, + module_base, filename): 'package' is a tuple of the path through + package-space to the module; 'module_base' is the bare (no + packages, no dots) module name, and 'filename' is the path to the + ".py" file (relative to the distribution root) that implements the + module. + """ + # Map package names to tuples of useful info about the package: + # (package_dir, checked) + # package_dir - the directory where we'll find source files for + # this package + # checked - true if we have checked that the package directory + # is valid (exists, contains __init__.py, ... ?) + packages = {} + + # List of (package, module, filename) tuples to return + modules = [] + + # We treat modules-in-packages almost the same as toplevel modules, + # just the "package" for a toplevel is empty (either an empty + # string or empty list, depending on context). Differences: + # - don't check for __init__.py in directory for empty package + for module in self.py_modules: + path = module.split('.') + package = '.'.join(path[0:-1]) + module_base = path[-1] + + try: + (package_dir, checked) = packages[package] + except KeyError: + package_dir = self.get_package_dir(package) + checked = 0 + + if not checked: + init_py = self.check_package(package, package_dir) + packages[package] = (package_dir, 1) + if init_py: + modules.append((package, "__init__", init_py)) + + # XXX perhaps we should also check for just .pyc files + # (so greedy closed-source bastards can distribute Python + # modules too) + module_file = os.path.join(package_dir, module_base + ".py") + if not self.check_module(module, module_file): + continue + + modules.append((package, module_base, module_file)) + + return modules + + def find_all_modules(self): + """Compute the list of all modules that will be built, whether + they are specified one-module-at-a-time ('self.py_modules') or + by whole packages ('self.packages'). Return a list of tuples + (package, module, module_file), just like 'find_modules()' and + 'find_package_modules()' do.""" + modules = [] + if self.py_modules: + modules.extend(self.find_modules()) + if self.packages: + for package in self.packages: + package_dir = self.get_package_dir(package) + m = self.find_package_modules(package, package_dir) + modules.extend(m) + return modules + + def get_source_files(self): + return [module[-1] for module in self.find_all_modules()] + + def get_module_outfile(self, build_dir, package, module): + outfile_path = [build_dir] + list(package) + [module + ".py"] + return os.path.join(*outfile_path) + + def get_outputs(self, include_bytecode=1): + modules = self.find_all_modules() + outputs = [] + for (package, module, module_file) in modules: + package = package.split('.') + filename = self.get_module_outfile(self.build_lib, package, module) + outputs.append(filename) + if include_bytecode: + if self.compile: + outputs.append(importlib.util.cache_from_source( + filename, optimization='')) + if self.optimize > 0: + outputs.append(importlib.util.cache_from_source( + filename, optimization=self.optimize)) + + outputs += [ + os.path.join(build_dir, filename) + for package, src_dir, build_dir, filenames in self.data_files + for filename in filenames + ] + + return outputs + + def build_module(self, module, module_file, package): + if isinstance(package, str): + package = package.split('.') + elif not isinstance(package, (list, tuple)): + raise TypeError( + "'package' must be a string (dot-separated), list, or tuple") + + # Now put the module source file into the "build" area -- this is + # easy, we just copy it somewhere under self.build_lib (the build + # directory for Python source). + outfile = self.get_module_outfile(self.build_lib, package, module) + dir = os.path.dirname(outfile) + self.mkpath(dir) + return self.copy_file(module_file, outfile, preserve_mode=0) + + def build_modules(self): + modules = self.find_modules() + for (package, module, module_file) in modules: + # Now "build" the module -- ie. copy the source file to + # self.build_lib (the build directory for Python source). + # (Actually, it gets copied to the directory for this package + # under self.build_lib.) + self.build_module(module, module_file, package) + + def build_packages(self): + for package in self.packages: + # Get list of (package, module, module_file) tuples based on + # scanning the package directory. 'package' is only included + # in the tuple so that 'find_modules()' and + # 'find_package_tuples()' have a consistent interface; it's + # ignored here (apart from a sanity check). Also, 'module' is + # the *unqualified* module name (ie. no dots, no package -- we + # already know its package!), and 'module_file' is the path to + # the .py file, relative to the current directory + # (ie. including 'package_dir'). + package_dir = self.get_package_dir(package) + modules = self.find_package_modules(package, package_dir) + + # Now loop over the modules we found, "building" each one (just + # copy it to self.build_lib). + for (package_, module, module_file) in modules: + assert package == package_ + self.build_module(module, module_file, package) + + def byte_compile(self, files): + if sys.dont_write_bytecode: + self.warn('byte-compiling is disabled, skipping.') + return + + from distutils.util import byte_compile + prefix = self.build_lib + if prefix[-1] != os.sep: + prefix = prefix + os.sep + + # XXX this code is essentially the same as the 'byte_compile() + # method of the "install_lib" command, except for the determination + # of the 'prefix' string. Hmmm. + if self.compile: + byte_compile(files, optimize=0, + force=self.force, prefix=prefix, dry_run=self.dry_run) + if self.optimize > 0: + byte_compile(files, optimize=self.optimize, + force=self.force, prefix=prefix, dry_run=self.dry_run) diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/command/build_scripts.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/build_scripts.py new file mode 100644 index 00000000..e3312cf0 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/build_scripts.py @@ -0,0 +1,152 @@ +"""distutils.command.build_scripts + +Implements the Distutils 'build_scripts' command.""" + +import os, re +from stat import ST_MODE +from distutils import sysconfig +from distutils.core import Command +from distutils.dep_util import newer +from distutils.util import convert_path +from distutils import log +import tokenize + +# check if Python is called on the first line with this expression +first_line_re = re.compile(b'^#!.*python[0-9.]*([ \t].*)?$') + +class build_scripts(Command): + + description = "\"build\" scripts (copy and fixup #! line)" + + user_options = [ + ('build-dir=', 'd', "directory to \"build\" (copy) to"), + ('force', 'f', "forcibly build everything (ignore file timestamps"), + ('executable=', 'e', "specify final destination interpreter path"), + ] + + boolean_options = ['force'] + + + def initialize_options(self): + self.build_dir = None + self.scripts = None + self.force = None + self.executable = None + self.outfiles = None + + def finalize_options(self): + self.set_undefined_options('build', + ('build_scripts', 'build_dir'), + ('force', 'force'), + ('executable', 'executable')) + self.scripts = self.distribution.scripts + + def get_source_files(self): + return self.scripts + + def run(self): + if not self.scripts: + return + self.copy_scripts() + + + def copy_scripts(self): + r"""Copy each script listed in 'self.scripts'; if it's marked as a + Python script in the Unix way (first line matches 'first_line_re', + ie. starts with "\#!" and contains "python"), then adjust the first + line to refer to the current Python interpreter as we copy. + """ + self.mkpath(self.build_dir) + outfiles = [] + updated_files = [] + for script in self.scripts: + adjust = False + script = convert_path(script) + outfile = os.path.join(self.build_dir, os.path.basename(script)) + outfiles.append(outfile) + + if not self.force and not newer(script, outfile): + log.debug("not copying %s (up-to-date)", script) + continue + + # Always open the file, but ignore failures in dry-run mode -- + # that way, we'll get accurate feedback if we can read the + # script. + try: + f = open(script, "rb") + except OSError: + if not self.dry_run: + raise + f = None + else: + encoding, lines = tokenize.detect_encoding(f.readline) + f.seek(0) + first_line = f.readline() + if not first_line: + self.warn("%s is an empty file (skipping)" % script) + continue + + match = first_line_re.match(first_line) + if match: + adjust = True + post_interp = match.group(1) or b'' + + if adjust: + log.info("copying and adjusting %s -> %s", script, + self.build_dir) + updated_files.append(outfile) + if not self.dry_run: + if not sysconfig.python_build: + executable = self.executable + else: + executable = os.path.join( + sysconfig.get_config_var("BINDIR"), + "python%s%s" % (sysconfig.get_config_var("VERSION"), + sysconfig.get_config_var("EXE"))) + executable = os.fsencode(executable) + shebang = b"#!" + executable + post_interp + b"\n" + # Python parser starts to read a script using UTF-8 until + # it gets a #coding:xxx cookie. The shebang has to be the + # first line of a file, the #coding:xxx cookie cannot be + # written before. So the shebang has to be decodable from + # UTF-8. + try: + shebang.decode('utf-8') + except UnicodeDecodeError: + raise ValueError( + "The shebang ({!r}) is not decodable " + "from utf-8".format(shebang)) + # If the script is encoded to a custom encoding (use a + # #coding:xxx cookie), the shebang has to be decodable from + # the script encoding too. + try: + shebang.decode(encoding) + except UnicodeDecodeError: + raise ValueError( + "The shebang ({!r}) is not decodable " + "from the script encoding ({})" + .format(shebang, encoding)) + with open(outfile, "wb") as outf: + outf.write(shebang) + outf.writelines(f.readlines()) + if f: + f.close() + else: + if f: + f.close() + updated_files.append(outfile) + self.copy_file(script, outfile) + + if os.name == 'posix': + for file in outfiles: + if self.dry_run: + log.info("changing mode of %s", file) + else: + oldmode = os.stat(file)[ST_MODE] & 0o7777 + newmode = (oldmode | 0o555) & 0o7777 + if newmode != oldmode: + log.info("changing mode of %s from %o to %o", + file, oldmode, newmode) + os.chmod(file, newmode) + # XXX should we modify self.outfiles? + return outfiles, updated_files diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/command/check.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/check.py new file mode 100644 index 00000000..ada25006 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/check.py @@ -0,0 +1,148 @@ +"""distutils.command.check + +Implements the Distutils 'check' command. +""" +from distutils.core import Command +from distutils.errors import DistutilsSetupError + +try: + # docutils is installed + from docutils.utils import Reporter + from docutils.parsers.rst import Parser + from docutils import frontend + from docutils import nodes + + class SilentReporter(Reporter): + + def __init__(self, source, report_level, halt_level, stream=None, + debug=0, encoding='ascii', error_handler='replace'): + self.messages = [] + Reporter.__init__(self, source, report_level, halt_level, stream, + debug, encoding, error_handler) + + def system_message(self, level, message, *children, **kwargs): + self.messages.append((level, message, children, kwargs)) + return nodes.system_message(message, level=level, + type=self.levels[level], + *children, **kwargs) + + HAS_DOCUTILS = True +except Exception: + # Catch all exceptions because exceptions besides ImportError probably + # indicate that docutils is not ported to Py3k. + HAS_DOCUTILS = False + +class check(Command): + """This command checks the meta-data of the package. + """ + description = ("perform some checks on the package") + user_options = [('metadata', 'm', 'Verify meta-data'), + ('restructuredtext', 'r', + ('Checks if long string meta-data syntax ' + 'are reStructuredText-compliant')), + ('strict', 's', + 'Will exit with an error if a check fails')] + + boolean_options = ['metadata', 'restructuredtext', 'strict'] + + def initialize_options(self): + """Sets default values for options.""" + self.restructuredtext = 0 + self.metadata = 1 + self.strict = 0 + self._warnings = 0 + + def finalize_options(self): + pass + + def warn(self, msg): + """Counts the number of warnings that occurs.""" + self._warnings += 1 + return Command.warn(self, msg) + + def run(self): + """Runs the command.""" + # perform the various tests + if self.metadata: + self.check_metadata() + if self.restructuredtext: + if HAS_DOCUTILS: + self.check_restructuredtext() + elif self.strict: + raise DistutilsSetupError('The docutils package is needed.') + + # let's raise an error in strict mode, if we have at least + # one warning + if self.strict and self._warnings > 0: + raise DistutilsSetupError('Please correct your package.') + + def check_metadata(self): + """Ensures that all required elements of meta-data are supplied. + + Required fields: + name, version, URL + + Recommended fields: + (author and author_email) or (maintainer and maintainer_email)) + + Warns if any are missing. + """ + metadata = self.distribution.metadata + + missing = [] + for attr in ('name', 'version', 'url'): + if not (hasattr(metadata, attr) and getattr(metadata, attr)): + missing.append(attr) + + if missing: + self.warn("missing required meta-data: %s" % ', '.join(missing)) + if metadata.author: + if not metadata.author_email: + self.warn("missing meta-data: if 'author' supplied, " + + "'author_email' should be supplied too") + elif metadata.maintainer: + if not metadata.maintainer_email: + self.warn("missing meta-data: if 'maintainer' supplied, " + + "'maintainer_email' should be supplied too") + else: + self.warn("missing meta-data: either (author and author_email) " + + "or (maintainer and maintainer_email) " + + "should be supplied") + + def check_restructuredtext(self): + """Checks if the long string fields are reST-compliant.""" + data = self.distribution.get_long_description() + for warning in self._check_rst_data(data): + line = warning[-1].get('line') + if line is None: + warning = warning[1] + else: + warning = '%s (line %s)' % (warning[1], line) + self.warn(warning) + + def _check_rst_data(self, data): + """Returns warnings when the provided data doesn't compile.""" + # the include and csv_table directives need this to be a path + source_path = self.distribution.script_name or 'setup.py' + parser = Parser() + settings = frontend.OptionParser(components=(Parser,)).get_default_values() + settings.tab_width = 4 + settings.pep_references = None + settings.rfc_references = None + reporter = SilentReporter(source_path, + settings.report_level, + settings.halt_level, + stream=settings.warning_stream, + debug=settings.debug, + encoding=settings.error_encoding, + error_handler=settings.error_encoding_error_handler) + + document = nodes.document(settings, reporter, source=source_path) + document.note_source(source_path, -1) + try: + parser.parse(data, document) + except AttributeError as e: + reporter.messages.append( + (-1, 'Could not finish the parsing: %s.' % e, '', {})) + + return reporter.messages diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/command/clean.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/clean.py new file mode 100644 index 00000000..0cb27016 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/clean.py @@ -0,0 +1,76 @@ +"""distutils.command.clean + +Implements the Distutils 'clean' command.""" + +# contributed by Bastian Kleineidam , added 2000-03-18 + +import os +from distutils.core import Command +from distutils.dir_util import remove_tree +from distutils import log + +class clean(Command): + + description = "clean up temporary files from 'build' command" + user_options = [ + ('build-base=', 'b', + "base build directory (default: 'build.build-base')"), + ('build-lib=', None, + "build directory for all modules (default: 'build.build-lib')"), + ('build-temp=', 't', + "temporary build directory (default: 'build.build-temp')"), + ('build-scripts=', None, + "build directory for scripts (default: 'build.build-scripts')"), + ('bdist-base=', None, + "temporary directory for built distributions"), + ('all', 'a', + "remove all build output, not just temporary by-products") + ] + + boolean_options = ['all'] + + def initialize_options(self): + self.build_base = None + self.build_lib = None + self.build_temp = None + self.build_scripts = None + self.bdist_base = None + self.all = None + + def finalize_options(self): + self.set_undefined_options('build', + ('build_base', 'build_base'), + ('build_lib', 'build_lib'), + ('build_scripts', 'build_scripts'), + ('build_temp', 'build_temp')) + self.set_undefined_options('bdist', + ('bdist_base', 'bdist_base')) + + def run(self): + # remove the build/temp. directory (unless it's already + # gone) + if os.path.exists(self.build_temp): + remove_tree(self.build_temp, dry_run=self.dry_run) + else: + log.debug("'%s' does not exist -- can't clean it", + self.build_temp) + + if self.all: + # remove build directories + for directory in (self.build_lib, + self.bdist_base, + self.build_scripts): + if os.path.exists(directory): + remove_tree(directory, dry_run=self.dry_run) + else: + log.warn("'%s' does not exist -- can't clean it", + directory) + + # just for the heck of it, try to remove the base build directory: + # we might have emptied it right now, but if not we don't care + if not self.dry_run: + try: + os.rmdir(self.build_base) + log.info("removing '%s'", self.build_base) + except OSError: + pass diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/command/config.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/config.py new file mode 100644 index 00000000..aeda408e --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/config.py @@ -0,0 +1,344 @@ +"""distutils.command.config + +Implements the Distutils 'config' command, a (mostly) empty command class +that exists mainly to be sub-classed by specific module distributions and +applications. The idea is that while every "config" command is different, +at least they're all named the same, and users always see "config" in the +list of standard commands. Also, this is a good place to put common +configure-like tasks: "try to compile this C code", or "figure out where +this header file lives". +""" + +import os, re + +from distutils.core import Command +from distutils.errors import DistutilsExecError +from distutils.sysconfig import customize_compiler +from distutils import log + +LANG_EXT = {"c": ".c", "c++": ".cxx"} + +class config(Command): + + description = "prepare to build" + + user_options = [ + ('compiler=', None, + "specify the compiler type"), + ('cc=', None, + "specify the compiler executable"), + ('include-dirs=', 'I', + "list of directories to search for header files"), + ('define=', 'D', + "C preprocessor macros to define"), + ('undef=', 'U', + "C preprocessor macros to undefine"), + ('libraries=', 'l', + "external C libraries to link with"), + ('library-dirs=', 'L', + "directories to search for external C libraries"), + + ('noisy', None, + "show every action (compile, link, run, ...) taken"), + ('dump-source', None, + "dump generated source files before attempting to compile them"), + ] + + + # The three standard command methods: since the "config" command + # does nothing by default, these are empty. + + def initialize_options(self): + self.compiler = None + self.cc = None + self.include_dirs = None + self.libraries = None + self.library_dirs = None + + # maximal output for now + self.noisy = 1 + self.dump_source = 1 + + # list of temporary files generated along-the-way that we have + # to clean at some point + self.temp_files = [] + + def finalize_options(self): + if self.include_dirs is None: + self.include_dirs = self.distribution.include_dirs or [] + elif isinstance(self.include_dirs, str): + self.include_dirs = self.include_dirs.split(os.pathsep) + + if self.libraries is None: + self.libraries = [] + elif isinstance(self.libraries, str): + self.libraries = [self.libraries] + + if self.library_dirs is None: + self.library_dirs = [] + elif isinstance(self.library_dirs, str): + self.library_dirs = self.library_dirs.split(os.pathsep) + + def run(self): + pass + + # Utility methods for actual "config" commands. The interfaces are + # loosely based on Autoconf macros of similar names. Sub-classes + # may use these freely. + + def _check_compiler(self): + """Check that 'self.compiler' really is a CCompiler object; + if not, make it one. + """ + # We do this late, and only on-demand, because this is an expensive + # import. + from distutils.ccompiler import CCompiler, new_compiler + if not isinstance(self.compiler, CCompiler): + self.compiler = new_compiler(compiler=self.compiler, + dry_run=self.dry_run, force=1) + customize_compiler(self.compiler) + if self.include_dirs: + self.compiler.set_include_dirs(self.include_dirs) + if self.libraries: + self.compiler.set_libraries(self.libraries) + if self.library_dirs: + self.compiler.set_library_dirs(self.library_dirs) + + def _gen_temp_sourcefile(self, body, headers, lang): + filename = "_configtest" + LANG_EXT[lang] + with open(filename, "w") as file: + if headers: + for header in headers: + file.write("#include <%s>\n" % header) + file.write("\n") + file.write(body) + if body[-1] != "\n": + file.write("\n") + return filename + + def _preprocess(self, body, headers, include_dirs, lang): + src = self._gen_temp_sourcefile(body, headers, lang) + out = "_configtest.i" + self.temp_files.extend([src, out]) + self.compiler.preprocess(src, out, include_dirs=include_dirs) + return (src, out) + + def _compile(self, body, headers, include_dirs, lang): + src = self._gen_temp_sourcefile(body, headers, lang) + if self.dump_source: + dump_file(src, "compiling '%s':" % src) + (obj,) = self.compiler.object_filenames([src]) + self.temp_files.extend([src, obj]) + self.compiler.compile([src], include_dirs=include_dirs) + return (src, obj) + + def _link(self, body, headers, include_dirs, libraries, library_dirs, + lang): + (src, obj) = self._compile(body, headers, include_dirs, lang) + prog = os.path.splitext(os.path.basename(src))[0] + self.compiler.link_executable([obj], prog, + libraries=libraries, + library_dirs=library_dirs, + target_lang=lang) + + if self.compiler.exe_extension is not None: + prog = prog + self.compiler.exe_extension + self.temp_files.append(prog) + + return (src, obj, prog) + + def _clean(self, *filenames): + if not filenames: + filenames = self.temp_files + self.temp_files = [] + log.info("removing: %s", ' '.join(filenames)) + for filename in filenames: + try: + os.remove(filename) + except OSError: + pass + + + # XXX these ignore the dry-run flag: what to do, what to do? even if + # you want a dry-run build, you still need some sort of configuration + # info. My inclination is to make it up to the real config command to + # consult 'dry_run', and assume a default (minimal) configuration if + # true. The problem with trying to do it here is that you'd have to + # return either true or false from all the 'try' methods, neither of + # which is correct. + + # XXX need access to the header search path and maybe default macros. + + def try_cpp(self, body=None, headers=None, include_dirs=None, lang="c"): + """Construct a source file from 'body' (a string containing lines + of C/C++ code) and 'headers' (a list of header files to include) + and run it through the preprocessor. Return true if the + preprocessor succeeded, false if there were any errors. + ('body' probably isn't of much use, but what the heck.) + """ + from distutils.ccompiler import CompileError + self._check_compiler() + ok = True + try: + self._preprocess(body, headers, include_dirs, lang) + except CompileError: + ok = False + + self._clean() + return ok + + def search_cpp(self, pattern, body=None, headers=None, include_dirs=None, + lang="c"): + """Construct a source file (just like 'try_cpp()'), run it through + the preprocessor, and return true if any line of the output matches + 'pattern'. 'pattern' should either be a compiled regex object or a + string containing a regex. If both 'body' and 'headers' are None, + preprocesses an empty file -- which can be useful to determine the + symbols the preprocessor and compiler set by default. + """ + self._check_compiler() + src, out = self._preprocess(body, headers, include_dirs, lang) + + if isinstance(pattern, str): + pattern = re.compile(pattern) + + with open(out) as file: + match = False + while True: + line = file.readline() + if line == '': + break + if pattern.search(line): + match = True + break + + self._clean() + return match + + def try_compile(self, body, headers=None, include_dirs=None, lang="c"): + """Try to compile a source file built from 'body' and 'headers'. + Return true on success, false otherwise. + """ + from distutils.ccompiler import CompileError + self._check_compiler() + try: + self._compile(body, headers, include_dirs, lang) + ok = True + except CompileError: + ok = False + + log.info(ok and "success!" or "failure.") + self._clean() + return ok + + def try_link(self, body, headers=None, include_dirs=None, libraries=None, + library_dirs=None, lang="c"): + """Try to compile and link a source file, built from 'body' and + 'headers', to executable form. Return true on success, false + otherwise. + """ + from distutils.ccompiler import CompileError, LinkError + self._check_compiler() + try: + self._link(body, headers, include_dirs, + libraries, library_dirs, lang) + ok = True + except (CompileError, LinkError): + ok = False + + log.info(ok and "success!" or "failure.") + self._clean() + return ok + + def try_run(self, body, headers=None, include_dirs=None, libraries=None, + library_dirs=None, lang="c"): + """Try to compile, link to an executable, and run a program + built from 'body' and 'headers'. Return true on success, false + otherwise. + """ + from distutils.ccompiler import CompileError, LinkError + self._check_compiler() + try: + src, obj, exe = self._link(body, headers, include_dirs, + libraries, library_dirs, lang) + self.spawn([exe]) + ok = True + except (CompileError, LinkError, DistutilsExecError): + ok = False + + log.info(ok and "success!" or "failure.") + self._clean() + return ok + + + # -- High-level methods -------------------------------------------- + # (these are the ones that are actually likely to be useful + # when implementing a real-world config command!) + + def check_func(self, func, headers=None, include_dirs=None, + libraries=None, library_dirs=None, decl=0, call=0): + """Determine if function 'func' is available by constructing a + source file that refers to 'func', and compiles and links it. + If everything succeeds, returns true; otherwise returns false. + + The constructed source file starts out by including the header + files listed in 'headers'. If 'decl' is true, it then declares + 'func' (as "int func()"); you probably shouldn't supply 'headers' + and set 'decl' true in the same call, or you might get errors about + a conflicting declarations for 'func'. Finally, the constructed + 'main()' function either references 'func' or (if 'call' is true) + calls it. 'libraries' and 'library_dirs' are used when + linking. + """ + self._check_compiler() + body = [] + if decl: + body.append("int %s ();" % func) + body.append("int main () {") + if call: + body.append(" %s();" % func) + else: + body.append(" %s;" % func) + body.append("}") + body = "\n".join(body) + "\n" + + return self.try_link(body, headers, include_dirs, + libraries, library_dirs) + + def check_lib(self, library, library_dirs=None, headers=None, + include_dirs=None, other_libraries=[]): + """Determine if 'library' is available to be linked against, + without actually checking that any particular symbols are provided + by it. 'headers' will be used in constructing the source file to + be compiled, but the only effect of this is to check if all the + header files listed are available. Any libraries listed in + 'other_libraries' will be included in the link, in case 'library' + has symbols that depend on other libraries. + """ + self._check_compiler() + return self.try_link("int main (void) { }", headers, include_dirs, + [library] + other_libraries, library_dirs) + + def check_header(self, header, include_dirs=None, library_dirs=None, + lang="c"): + """Determine if the system header file named by 'header_file' + exists and can be found by the preprocessor; return true if so, + false otherwise. + """ + return self.try_cpp(body="/* No body */", headers=[header], + include_dirs=include_dirs) + +def dump_file(filename, head=None): + """Dumps a file content into log.info. + + If head is not None, will be dumped before the file content. + """ + if head is None: + log.info('%s', filename) + else: + log.info(head) + file = open(filename) + try: + log.info(file.read()) + finally: + file.close() diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/command/install.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/install.py new file mode 100644 index 00000000..18b352fa --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/install.py @@ -0,0 +1,721 @@ +"""distutils.command.install + +Implements the Distutils 'install' command.""" + +import sys +import os +import contextlib +import sysconfig +import itertools + +from distutils import log +from distutils.core import Command +from distutils.debug import DEBUG +from distutils.sysconfig import get_config_vars +from distutils.errors import DistutilsPlatformError +from distutils.file_util import write_file +from distutils.util import convert_path, subst_vars, change_root +from distutils.util import get_platform +from distutils.errors import DistutilsOptionError + +from site import USER_BASE +from site import USER_SITE +HAS_USER_SITE = True + +WINDOWS_SCHEME = { + 'purelib': '{base}/Lib/site-packages', + 'platlib': '{base}/Lib/site-packages', + 'headers': '{base}/Include/{dist_name}', + 'scripts': '{base}/Scripts', + 'data' : '{base}', +} + +INSTALL_SCHEMES = { + 'posix_prefix': { + 'purelib': '{base}/lib/{implementation_lower}{py_version_short}/site-packages', + 'platlib': '{platbase}/{platlibdir}/{implementation_lower}{py_version_short}/site-packages', + 'headers': '{base}/include/{implementation_lower}{py_version_short}{abiflags}/{dist_name}', + 'scripts': '{base}/bin', + 'data' : '{base}', + }, + 'posix_home': { + 'purelib': '{base}/lib/{implementation_lower}', + 'platlib': '{base}/{platlibdir}/{implementation_lower}', + 'headers': '{base}/include/{implementation_lower}/{dist_name}', + 'scripts': '{base}/bin', + 'data' : '{base}', + }, + 'nt': WINDOWS_SCHEME, + 'pypy': { + 'purelib': '{base}/site-packages', + 'platlib': '{base}/site-packages', + 'headers': '{base}/include/{dist_name}', + 'scripts': '{base}/bin', + 'data' : '{base}', + }, + 'pypy_nt': { + 'purelib': '{base}/site-packages', + 'platlib': '{base}/site-packages', + 'headers': '{base}/include/{dist_name}', + 'scripts': '{base}/Scripts', + 'data' : '{base}', + }, + } + +# user site schemes +if HAS_USER_SITE: + INSTALL_SCHEMES['nt_user'] = { + 'purelib': '{usersite}', + 'platlib': '{usersite}', + 'headers': '{userbase}/{implementation}{py_version_nodot}/Include/{dist_name}', + 'scripts': '{userbase}/{implementation}{py_version_nodot}/Scripts', + 'data' : '{userbase}', + } + + INSTALL_SCHEMES['posix_user'] = { + 'purelib': '{usersite}', + 'platlib': '{usersite}', + 'headers': + '{userbase}/include/{implementation_lower}{py_version_short}{abiflags}/{dist_name}', + 'scripts': '{userbase}/bin', + 'data' : '{userbase}', + } + +# The keys to an installation scheme; if any new types of files are to be +# installed, be sure to add an entry to every installation scheme above, +# and to SCHEME_KEYS here. +SCHEME_KEYS = ('purelib', 'platlib', 'headers', 'scripts', 'data') + + +def _load_sysconfig_schemes(): + with contextlib.suppress(AttributeError): + return { + scheme: sysconfig.get_paths(scheme, expand=False) + for scheme in sysconfig.get_scheme_names() + } + + +def _load_schemes(): + """ + Extend default schemes with schemes from sysconfig. + """ + + sysconfig_schemes = _load_sysconfig_schemes() or {} + + return { + scheme: { + **INSTALL_SCHEMES.get(scheme, {}), + **sysconfig_schemes.get(scheme, {}), + } + for scheme in set(itertools.chain(INSTALL_SCHEMES, sysconfig_schemes)) + } + + +def _get_implementation(): + if hasattr(sys, 'pypy_version_info'): + return 'PyPy' + else: + return 'Python' + + +class install(Command): + + description = "install everything from build directory" + + user_options = [ + # Select installation scheme and set base director(y|ies) + ('prefix=', None, + "installation prefix"), + ('exec-prefix=', None, + "(Unix only) prefix for platform-specific files"), + ('home=', None, + "(Unix only) home directory to install under"), + + # Or, just set the base director(y|ies) + ('install-base=', None, + "base installation directory (instead of --prefix or --home)"), + ('install-platbase=', None, + "base installation directory for platform-specific files " + + "(instead of --exec-prefix or --home)"), + ('root=', None, + "install everything relative to this alternate root directory"), + + # Or, explicitly set the installation scheme + ('install-purelib=', None, + "installation directory for pure Python module distributions"), + ('install-platlib=', None, + "installation directory for non-pure module distributions"), + ('install-lib=', None, + "installation directory for all module distributions " + + "(overrides --install-purelib and --install-platlib)"), + + ('install-headers=', None, + "installation directory for C/C++ headers"), + ('install-scripts=', None, + "installation directory for Python scripts"), + ('install-data=', None, + "installation directory for data files"), + + # Byte-compilation options -- see install_lib.py for details, as + # these are duplicated from there (but only install_lib does + # anything with them). + ('compile', 'c', "compile .py to .pyc [default]"), + ('no-compile', None, "don't compile .py files"), + ('optimize=', 'O', + "also compile with optimization: -O1 for \"python -O\", " + "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"), + + # Miscellaneous control options + ('force', 'f', + "force installation (overwrite any existing files)"), + ('skip-build', None, + "skip rebuilding everything (for testing/debugging)"), + + # Where to install documentation (eventually!) + #('doc-format=', None, "format of documentation to generate"), + #('install-man=', None, "directory for Unix man pages"), + #('install-html=', None, "directory for HTML documentation"), + #('install-info=', None, "directory for GNU info files"), + + ('record=', None, + "filename in which to record list of installed files"), + ] + + boolean_options = ['compile', 'force', 'skip-build'] + + if HAS_USER_SITE: + user_options.append(('user', None, + "install in user site-package '%s'" % USER_SITE)) + boolean_options.append('user') + + negative_opt = {'no-compile' : 'compile'} + + + def initialize_options(self): + """Initializes options.""" + # High-level options: these select both an installation base + # and scheme. + self.prefix = None + self.exec_prefix = None + self.home = None + self.user = 0 + + # These select only the installation base; it's up to the user to + # specify the installation scheme (currently, that means supplying + # the --install-{platlib,purelib,scripts,data} options). + self.install_base = None + self.install_platbase = None + self.root = None + + # These options are the actual installation directories; if not + # supplied by the user, they are filled in using the installation + # scheme implied by prefix/exec-prefix/home and the contents of + # that installation scheme. + self.install_purelib = None # for pure module distributions + self.install_platlib = None # non-pure (dists w/ extensions) + self.install_headers = None # for C/C++ headers + self.install_lib = None # set to either purelib or platlib + self.install_scripts = None + self.install_data = None + self.install_userbase = USER_BASE + self.install_usersite = USER_SITE + + self.compile = None + self.optimize = None + + # Deprecated + # These two are for putting non-packagized distributions into their + # own directory and creating a .pth file if it makes sense. + # 'extra_path' comes from the setup file; 'install_path_file' can + # be turned off if it makes no sense to install a .pth file. (But + # better to install it uselessly than to guess wrong and not + # install it when it's necessary and would be used!) Currently, + # 'install_path_file' is always true unless some outsider meddles + # with it. + self.extra_path = None + self.install_path_file = 1 + + # 'force' forces installation, even if target files are not + # out-of-date. 'skip_build' skips running the "build" command, + # handy if you know it's not necessary. 'warn_dir' (which is *not* + # a user option, it's just there so the bdist_* commands can turn + # it off) determines whether we warn about installing to a + # directory not in sys.path. + self.force = 0 + self.skip_build = 0 + self.warn_dir = 1 + + # These are only here as a conduit from the 'build' command to the + # 'install_*' commands that do the real work. ('build_base' isn't + # actually used anywhere, but it might be useful in future.) They + # are not user options, because if the user told the install + # command where the build directory is, that wouldn't affect the + # build command. + self.build_base = None + self.build_lib = None + + # Not defined yet because we don't know anything about + # documentation yet. + #self.install_man = None + #self.install_html = None + #self.install_info = None + + self.record = None + + + # -- Option finalizing methods ------------------------------------- + # (This is rather more involved than for most commands, + # because this is where the policy for installing third- + # party Python modules on various platforms given a wide + # array of user input is decided. Yes, it's quite complex!) + + def finalize_options(self): + """Finalizes options.""" + # This method (and its helpers, like 'finalize_unix()', + # 'finalize_other()', and 'select_scheme()') is where the default + # installation directories for modules, extension modules, and + # anything else we care to install from a Python module + # distribution. Thus, this code makes a pretty important policy + # statement about how third-party stuff is added to a Python + # installation! Note that the actual work of installation is done + # by the relatively simple 'install_*' commands; they just take + # their orders from the installation directory options determined + # here. + + # Check for errors/inconsistencies in the options; first, stuff + # that's wrong on any platform. + + if ((self.prefix or self.exec_prefix or self.home) and + (self.install_base or self.install_platbase)): + raise DistutilsOptionError( + "must supply either prefix/exec-prefix/home or " + + "install-base/install-platbase -- not both") + + if self.home and (self.prefix or self.exec_prefix): + raise DistutilsOptionError( + "must supply either home or prefix/exec-prefix -- not both") + + if self.user and (self.prefix or self.exec_prefix or self.home or + self.install_base or self.install_platbase): + raise DistutilsOptionError("can't combine user with prefix, " + "exec_prefix/home, or install_(plat)base") + + # Next, stuff that's wrong (or dubious) only on certain platforms. + if os.name != "posix": + if self.exec_prefix: + self.warn("exec-prefix option ignored on this platform") + self.exec_prefix = None + + # Now the interesting logic -- so interesting that we farm it out + # to other methods. The goal of these methods is to set the final + # values for the install_{lib,scripts,data,...} options, using as + # input a heady brew of prefix, exec_prefix, home, install_base, + # install_platbase, user-supplied versions of + # install_{purelib,platlib,lib,scripts,data,...}, and the + # install schemes. Phew! + + self.dump_dirs("pre-finalize_{unix,other}") + + if os.name == 'posix': + self.finalize_unix() + else: + self.finalize_other() + + self.dump_dirs("post-finalize_{unix,other}()") + + # Expand configuration variables, tilde, etc. in self.install_base + # and self.install_platbase -- that way, we can use $base or + # $platbase in the other installation directories and not worry + # about needing recursive variable expansion (shudder). + + py_version = sys.version.split()[0] + (prefix, exec_prefix) = get_config_vars('prefix', 'exec_prefix') + try: + abiflags = sys.abiflags + except AttributeError: + # sys.abiflags may not be defined on all platforms. + abiflags = '' + self.config_vars = {'dist_name': self.distribution.get_name(), + 'dist_version': self.distribution.get_version(), + 'dist_fullname': self.distribution.get_fullname(), + 'py_version': py_version, + 'py_version_short': '%d.%d' % sys.version_info[:2], + 'py_version_nodot': '%d%d' % sys.version_info[:2], + 'sys_prefix': prefix, + 'prefix': prefix, + 'sys_exec_prefix': exec_prefix, + 'exec_prefix': exec_prefix, + 'abiflags': abiflags, + 'platlibdir': getattr(sys, 'platlibdir', 'lib'), + 'implementation_lower': _get_implementation().lower(), + 'implementation': _get_implementation(), + } + + if HAS_USER_SITE: + self.config_vars['userbase'] = self.install_userbase + self.config_vars['usersite'] = self.install_usersite + + self.expand_basedirs() + + self.dump_dirs("post-expand_basedirs()") + + # Now define config vars for the base directories so we can expand + # everything else. + self.config_vars['base'] = self.install_base + self.config_vars['platbase'] = self.install_platbase + self.config_vars['installed_base'] = ( + sysconfig.get_config_vars()['installed_base']) + + if DEBUG: + from pprint import pprint + print("config vars:") + pprint(self.config_vars) + + # Expand "~" and configuration variables in the installation + # directories. + self.expand_dirs() + + self.dump_dirs("post-expand_dirs()") + + # Create directories in the home dir: + if self.user: + self.create_home_path() + + # Pick the actual directory to install all modules to: either + # install_purelib or install_platlib, depending on whether this + # module distribution is pure or not. Of course, if the user + # already specified install_lib, use their selection. + if self.install_lib is None: + if self.distribution.has_ext_modules(): # has extensions: non-pure + self.install_lib = self.install_platlib + else: + self.install_lib = self.install_purelib + + + # Convert directories from Unix /-separated syntax to the local + # convention. + self.convert_paths('lib', 'purelib', 'platlib', + 'scripts', 'data', 'headers', + 'userbase', 'usersite') + + # Deprecated + # Well, we're not actually fully completely finalized yet: we still + # have to deal with 'extra_path', which is the hack for allowing + # non-packagized module distributions (hello, Numerical Python!) to + # get their own directories. + self.handle_extra_path() + self.install_libbase = self.install_lib # needed for .pth file + self.install_lib = os.path.join(self.install_lib, self.extra_dirs) + + # If a new root directory was supplied, make all the installation + # dirs relative to it. + if self.root is not None: + self.change_roots('libbase', 'lib', 'purelib', 'platlib', + 'scripts', 'data', 'headers') + + self.dump_dirs("after prepending root") + + # Find out the build directories, ie. where to install from. + self.set_undefined_options('build', + ('build_base', 'build_base'), + ('build_lib', 'build_lib')) + + # Punt on doc directories for now -- after all, we're punting on + # documentation completely! + + def dump_dirs(self, msg): + """Dumps the list of user options.""" + if not DEBUG: + return + from distutils.fancy_getopt import longopt_xlate + log.debug(msg + ":") + for opt in self.user_options: + opt_name = opt[0] + if opt_name[-1] == "=": + opt_name = opt_name[0:-1] + if opt_name in self.negative_opt: + opt_name = self.negative_opt[opt_name] + opt_name = opt_name.translate(longopt_xlate) + val = not getattr(self, opt_name) + else: + opt_name = opt_name.translate(longopt_xlate) + val = getattr(self, opt_name) + log.debug(" %s: %s", opt_name, val) + + def finalize_unix(self): + """Finalizes options for posix platforms.""" + if self.install_base is not None or self.install_platbase is not None: + if ((self.install_lib is None and + self.install_purelib is None and + self.install_platlib is None) or + self.install_headers is None or + self.install_scripts is None or + self.install_data is None): + raise DistutilsOptionError( + "install-base or install-platbase supplied, but " + "installation scheme is incomplete") + return + + if self.user: + if self.install_userbase is None: + raise DistutilsPlatformError( + "User base directory is not specified") + self.install_base = self.install_platbase = self.install_userbase + self.select_scheme("posix_user") + elif self.home is not None: + self.install_base = self.install_platbase = self.home + self.select_scheme("posix_home") + else: + if self.prefix is None: + if self.exec_prefix is not None: + raise DistutilsOptionError( + "must not supply exec-prefix without prefix") + + # Allow Fedora to add components to the prefix + _prefix_addition = getattr(sysconfig, '_prefix_addition', "") + + self.prefix = ( + os.path.normpath(sys.prefix) + _prefix_addition) + self.exec_prefix = ( + os.path.normpath(sys.exec_prefix) + _prefix_addition) + + else: + if self.exec_prefix is None: + self.exec_prefix = self.prefix + + self.install_base = self.prefix + self.install_platbase = self.exec_prefix + self.select_scheme("posix_prefix") + + def finalize_other(self): + """Finalizes options for non-posix platforms""" + if self.user: + if self.install_userbase is None: + raise DistutilsPlatformError( + "User base directory is not specified") + self.install_base = self.install_platbase = self.install_userbase + self.select_scheme(os.name + "_user") + elif self.home is not None: + self.install_base = self.install_platbase = self.home + self.select_scheme("posix_home") + else: + if self.prefix is None: + self.prefix = os.path.normpath(sys.prefix) + + self.install_base = self.install_platbase = self.prefix + try: + self.select_scheme(os.name) + except KeyError: + raise DistutilsPlatformError( + "I don't know how to install stuff on '%s'" % os.name) + + def select_scheme(self, name): + """Sets the install directories by applying the install schemes.""" + # it's the caller's problem if they supply a bad name! + if (hasattr(sys, 'pypy_version_info') and + sys.version_info < (3, 8) and + not name.endswith(('_user', '_home'))): + if os.name == 'nt': + name = 'pypy_nt' + else: + name = 'pypy' + scheme = _load_schemes()[name] + for key in SCHEME_KEYS: + attrname = 'install_' + key + if getattr(self, attrname) is None: + setattr(self, attrname, scheme[key]) + + def _expand_attrs(self, attrs): + for attr in attrs: + val = getattr(self, attr) + if val is not None: + if os.name == 'posix' or os.name == 'nt': + val = os.path.expanduser(val) + val = subst_vars(val, self.config_vars) + setattr(self, attr, val) + + def expand_basedirs(self): + """Calls `os.path.expanduser` on install_base, install_platbase and + root.""" + self._expand_attrs(['install_base', 'install_platbase', 'root']) + + def expand_dirs(self): + """Calls `os.path.expanduser` on install dirs.""" + self._expand_attrs(['install_purelib', 'install_platlib', + 'install_lib', 'install_headers', + 'install_scripts', 'install_data',]) + + def convert_paths(self, *names): + """Call `convert_path` over `names`.""" + for name in names: + attr = "install_" + name + setattr(self, attr, convert_path(getattr(self, attr))) + + def handle_extra_path(self): + """Set `path_file` and `extra_dirs` using `extra_path`.""" + if self.extra_path is None: + self.extra_path = self.distribution.extra_path + + if self.extra_path is not None: + log.warn( + "Distribution option extra_path is deprecated. " + "See issue27919 for details." + ) + if isinstance(self.extra_path, str): + self.extra_path = self.extra_path.split(',') + + if len(self.extra_path) == 1: + path_file = extra_dirs = self.extra_path[0] + elif len(self.extra_path) == 2: + path_file, extra_dirs = self.extra_path + else: + raise DistutilsOptionError( + "'extra_path' option must be a list, tuple, or " + "comma-separated string with 1 or 2 elements") + + # convert to local form in case Unix notation used (as it + # should be in setup scripts) + extra_dirs = convert_path(extra_dirs) + else: + path_file = None + extra_dirs = '' + + # XXX should we warn if path_file and not extra_dirs? (in which + # case the path file would be harmless but pointless) + self.path_file = path_file + self.extra_dirs = extra_dirs + + def change_roots(self, *names): + """Change the install directories pointed by name using root.""" + for name in names: + attr = "install_" + name + setattr(self, attr, change_root(self.root, getattr(self, attr))) + + def create_home_path(self): + """Create directories under ~.""" + if not self.user: + return + home = convert_path(os.path.expanduser("~")) + for name, path in self.config_vars.items(): + if path.startswith(home) and not os.path.isdir(path): + self.debug_print("os.makedirs('%s', 0o700)" % path) + os.makedirs(path, 0o700) + + # -- Command execution methods ------------------------------------- + + def run(self): + """Runs the command.""" + # Obviously have to build before we can install + if not self.skip_build: + self.run_command('build') + # If we built for any other platform, we can't install. + build_plat = self.distribution.get_command_obj('build').plat_name + # check warn_dir - it is a clue that the 'install' is happening + # internally, and not to sys.path, so we don't check the platform + # matches what we are running. + if self.warn_dir and build_plat != get_platform(): + raise DistutilsPlatformError("Can't install when " + "cross-compiling") + + # Run all sub-commands (at least those that need to be run) + for cmd_name in self.get_sub_commands(): + self.run_command(cmd_name) + + if self.path_file: + self.create_path_file() + + # write list of installed files, if requested. + if self.record: + outputs = self.get_outputs() + if self.root: # strip any package prefix + root_len = len(self.root) + for counter in range(len(outputs)): + outputs[counter] = outputs[counter][root_len:] + self.execute(write_file, + (self.record, outputs), + "writing list of installed files to '%s'" % + self.record) + + sys_path = map(os.path.normpath, sys.path) + sys_path = map(os.path.normcase, sys_path) + install_lib = os.path.normcase(os.path.normpath(self.install_lib)) + if (self.warn_dir and + not (self.path_file and self.install_path_file) and + install_lib not in sys_path): + log.debug(("modules installed to '%s', which is not in " + "Python's module search path (sys.path) -- " + "you'll have to change the search path yourself"), + self.install_lib) + + def create_path_file(self): + """Creates the .pth file""" + filename = os.path.join(self.install_libbase, + self.path_file + ".pth") + if self.install_path_file: + self.execute(write_file, + (filename, [self.extra_dirs]), + "creating %s" % filename) + else: + self.warn("path file '%s' not created" % filename) + + + # -- Reporting methods --------------------------------------------- + + def get_outputs(self): + """Assembles the outputs of all the sub-commands.""" + outputs = [] + for cmd_name in self.get_sub_commands(): + cmd = self.get_finalized_command(cmd_name) + # Add the contents of cmd.get_outputs(), ensuring + # that outputs doesn't contain duplicate entries + for filename in cmd.get_outputs(): + if filename not in outputs: + outputs.append(filename) + + if self.path_file and self.install_path_file: + outputs.append(os.path.join(self.install_libbase, + self.path_file + ".pth")) + + return outputs + + def get_inputs(self): + """Returns the inputs of all the sub-commands""" + # XXX gee, this looks familiar ;-( + inputs = [] + for cmd_name in self.get_sub_commands(): + cmd = self.get_finalized_command(cmd_name) + inputs.extend(cmd.get_inputs()) + + return inputs + + # -- Predicates for sub-command list ------------------------------- + + def has_lib(self): + """Returns true if the current distribution has any Python + modules to install.""" + return (self.distribution.has_pure_modules() or + self.distribution.has_ext_modules()) + + def has_headers(self): + """Returns true if the current distribution has any headers to + install.""" + return self.distribution.has_headers() + + def has_scripts(self): + """Returns true if the current distribution has any scripts to. + install.""" + return self.distribution.has_scripts() + + def has_data(self): + """Returns true if the current distribution has any data to. + install.""" + return self.distribution.has_data_files() + + # 'sub_commands': a list of commands this command might have to run to + # get its work done. See cmd.py for more info. + sub_commands = [('install_lib', has_lib), + ('install_headers', has_headers), + ('install_scripts', has_scripts), + ('install_data', has_data), + ('install_egg_info', lambda self:True), + ] diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/command/install_data.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/install_data.py new file mode 100644 index 00000000..947cd76a --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/install_data.py @@ -0,0 +1,79 @@ +"""distutils.command.install_data + +Implements the Distutils 'install_data' command, for installing +platform-independent data files.""" + +# contributed by Bastian Kleineidam + +import os +from distutils.core import Command +from distutils.util import change_root, convert_path + +class install_data(Command): + + description = "install data files" + + user_options = [ + ('install-dir=', 'd', + "base directory for installing data files " + "(default: installation base dir)"), + ('root=', None, + "install everything relative to this alternate root directory"), + ('force', 'f', "force installation (overwrite existing files)"), + ] + + boolean_options = ['force'] + + def initialize_options(self): + self.install_dir = None + self.outfiles = [] + self.root = None + self.force = 0 + self.data_files = self.distribution.data_files + self.warn_dir = 1 + + def finalize_options(self): + self.set_undefined_options('install', + ('install_data', 'install_dir'), + ('root', 'root'), + ('force', 'force'), + ) + + def run(self): + self.mkpath(self.install_dir) + for f in self.data_files: + if isinstance(f, str): + # it's a simple file, so copy it + f = convert_path(f) + if self.warn_dir: + self.warn("setup script did not provide a directory for " + "'%s' -- installing right in '%s'" % + (f, self.install_dir)) + (out, _) = self.copy_file(f, self.install_dir) + self.outfiles.append(out) + else: + # it's a tuple with path to install to and a list of files + dir = convert_path(f[0]) + if not os.path.isabs(dir): + dir = os.path.join(self.install_dir, dir) + elif self.root: + dir = change_root(self.root, dir) + self.mkpath(dir) + + if f[1] == []: + # If there are no files listed, the user must be + # trying to create an empty directory, so add the + # directory to the list of output files. + self.outfiles.append(dir) + else: + # Copy files, adding them to the list of output files. + for data in f[1]: + data = convert_path(data) + (out, _) = self.copy_file(data, dir) + self.outfiles.append(out) + + def get_inputs(self): + return self.data_files or [] + + def get_outputs(self): + return self.outfiles diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/command/install_egg_info.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/install_egg_info.py new file mode 100644 index 00000000..adc0323f --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/install_egg_info.py @@ -0,0 +1,84 @@ +"""distutils.command.install_egg_info + +Implements the Distutils 'install_egg_info' command, for installing +a package's PKG-INFO metadata.""" + + +from distutils.cmd import Command +from distutils import log, dir_util +import os, sys, re + +class install_egg_info(Command): + """Install an .egg-info file for the package""" + + description = "Install package's PKG-INFO metadata as an .egg-info file" + user_options = [ + ('install-dir=', 'd', "directory to install to"), + ] + + def initialize_options(self): + self.install_dir = None + + @property + def basename(self): + """ + Allow basename to be overridden by child class. + Ref pypa/distutils#2. + """ + return "%s-%s-py%d.%d.egg-info" % ( + to_filename(safe_name(self.distribution.get_name())), + to_filename(safe_version(self.distribution.get_version())), + *sys.version_info[:2] + ) + + def finalize_options(self): + self.set_undefined_options('install_lib',('install_dir','install_dir')) + self.target = os.path.join(self.install_dir, self.basename) + self.outputs = [self.target] + + def run(self): + target = self.target + if os.path.isdir(target) and not os.path.islink(target): + dir_util.remove_tree(target, dry_run=self.dry_run) + elif os.path.exists(target): + self.execute(os.unlink,(self.target,),"Removing "+target) + elif not os.path.isdir(self.install_dir): + self.execute(os.makedirs, (self.install_dir,), + "Creating "+self.install_dir) + log.info("Writing %s", target) + if not self.dry_run: + with open(target, 'w', encoding='UTF-8') as f: + self.distribution.metadata.write_pkg_file(f) + + def get_outputs(self): + return self.outputs + + +# The following routines are taken from setuptools' pkg_resources module and +# can be replaced by importing them from pkg_resources once it is included +# in the stdlib. + +def safe_name(name): + """Convert an arbitrary string to a standard distribution name + + Any runs of non-alphanumeric/. characters are replaced with a single '-'. + """ + return re.sub('[^A-Za-z0-9.]+', '-', name) + + +def safe_version(version): + """Convert an arbitrary string to a standard version string + + Spaces become dots, and all other non-alphanumeric characters become + dashes, with runs of multiple dashes condensed to a single dash. + """ + version = version.replace(' ','.') + return re.sub('[^A-Za-z0-9.]+', '-', version) + + +def to_filename(name): + """Convert a project or version name to its filename-escaped form + + Any '-' characters are currently replaced with '_'. + """ + return name.replace('-','_') diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/command/install_headers.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/install_headers.py new file mode 100644 index 00000000..9bb0b18d --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/install_headers.py @@ -0,0 +1,47 @@ +"""distutils.command.install_headers + +Implements the Distutils 'install_headers' command, to install C/C++ header +files to the Python include directory.""" + +from distutils.core import Command + + +# XXX force is never used +class install_headers(Command): + + description = "install C/C++ header files" + + user_options = [('install-dir=', 'd', + "directory to install header files to"), + ('force', 'f', + "force installation (overwrite existing files)"), + ] + + boolean_options = ['force'] + + def initialize_options(self): + self.install_dir = None + self.force = 0 + self.outfiles = [] + + def finalize_options(self): + self.set_undefined_options('install', + ('install_headers', 'install_dir'), + ('force', 'force')) + + + def run(self): + headers = self.distribution.headers + if not headers: + return + + self.mkpath(self.install_dir) + for header in headers: + (out, _) = self.copy_file(header, self.install_dir) + self.outfiles.append(out) + + def get_inputs(self): + return self.distribution.headers or [] + + def get_outputs(self): + return self.outfiles diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/command/install_lib.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/install_lib.py new file mode 100644 index 00000000..6154cf09 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/install_lib.py @@ -0,0 +1,217 @@ +"""distutils.command.install_lib + +Implements the Distutils 'install_lib' command +(install all Python modules).""" + +import os +import importlib.util +import sys + +from distutils.core import Command +from distutils.errors import DistutilsOptionError + + +# Extension for Python source files. +PYTHON_SOURCE_EXTENSION = ".py" + +class install_lib(Command): + + description = "install all Python modules (extensions and pure Python)" + + # The byte-compilation options are a tad confusing. Here are the + # possible scenarios: + # 1) no compilation at all (--no-compile --no-optimize) + # 2) compile .pyc only (--compile --no-optimize; default) + # 3) compile .pyc and "opt-1" .pyc (--compile --optimize) + # 4) compile "opt-1" .pyc only (--no-compile --optimize) + # 5) compile .pyc and "opt-2" .pyc (--compile --optimize-more) + # 6) compile "opt-2" .pyc only (--no-compile --optimize-more) + # + # The UI for this is two options, 'compile' and 'optimize'. + # 'compile' is strictly boolean, and only decides whether to + # generate .pyc files. 'optimize' is three-way (0, 1, or 2), and + # decides both whether to generate .pyc files and what level of + # optimization to use. + + user_options = [ + ('install-dir=', 'd', "directory to install to"), + ('build-dir=','b', "build directory (where to install from)"), + ('force', 'f', "force installation (overwrite existing files)"), + ('compile', 'c', "compile .py to .pyc [default]"), + ('no-compile', None, "don't compile .py files"), + ('optimize=', 'O', + "also compile with optimization: -O1 for \"python -O\", " + "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"), + ('skip-build', None, "skip the build steps"), + ] + + boolean_options = ['force', 'compile', 'skip-build'] + negative_opt = {'no-compile' : 'compile'} + + def initialize_options(self): + # let the 'install' command dictate our installation directory + self.install_dir = None + self.build_dir = None + self.force = 0 + self.compile = None + self.optimize = None + self.skip_build = None + + def finalize_options(self): + # Get all the information we need to install pure Python modules + # from the umbrella 'install' command -- build (source) directory, + # install (target) directory, and whether to compile .py files. + self.set_undefined_options('install', + ('build_lib', 'build_dir'), + ('install_lib', 'install_dir'), + ('force', 'force'), + ('compile', 'compile'), + ('optimize', 'optimize'), + ('skip_build', 'skip_build'), + ) + + if self.compile is None: + self.compile = True + if self.optimize is None: + self.optimize = False + + if not isinstance(self.optimize, int): + try: + self.optimize = int(self.optimize) + if self.optimize not in (0, 1, 2): + raise AssertionError + except (ValueError, AssertionError): + raise DistutilsOptionError("optimize must be 0, 1, or 2") + + def run(self): + # Make sure we have built everything we need first + self.build() + + # Install everything: simply dump the entire contents of the build + # directory to the installation directory (that's the beauty of + # having a build directory!) + outfiles = self.install() + + # (Optionally) compile .py to .pyc + if outfiles is not None and self.distribution.has_pure_modules(): + self.byte_compile(outfiles) + + # -- Top-level worker functions ------------------------------------ + # (called from 'run()') + + def build(self): + if not self.skip_build: + if self.distribution.has_pure_modules(): + self.run_command('build_py') + if self.distribution.has_ext_modules(): + self.run_command('build_ext') + + def install(self): + if os.path.isdir(self.build_dir): + outfiles = self.copy_tree(self.build_dir, self.install_dir) + else: + self.warn("'%s' does not exist -- no Python modules to install" % + self.build_dir) + return + return outfiles + + def byte_compile(self, files): + if sys.dont_write_bytecode: + self.warn('byte-compiling is disabled, skipping.') + return + + from distutils.util import byte_compile + + # Get the "--root" directory supplied to the "install" command, + # and use it as a prefix to strip off the purported filename + # encoded in bytecode files. This is far from complete, but it + # should at least generate usable bytecode in RPM distributions. + install_root = self.get_finalized_command('install').root + + if self.compile: + byte_compile(files, optimize=0, + force=self.force, prefix=install_root, + dry_run=self.dry_run) + if self.optimize > 0: + byte_compile(files, optimize=self.optimize, + force=self.force, prefix=install_root, + verbose=self.verbose, dry_run=self.dry_run) + + + # -- Utility methods ----------------------------------------------- + + def _mutate_outputs(self, has_any, build_cmd, cmd_option, output_dir): + if not has_any: + return [] + + build_cmd = self.get_finalized_command(build_cmd) + build_files = build_cmd.get_outputs() + build_dir = getattr(build_cmd, cmd_option) + + prefix_len = len(build_dir) + len(os.sep) + outputs = [] + for file in build_files: + outputs.append(os.path.join(output_dir, file[prefix_len:])) + + return outputs + + def _bytecode_filenames(self, py_filenames): + bytecode_files = [] + for py_file in py_filenames: + # Since build_py handles package data installation, the + # list of outputs can contain more than just .py files. + # Make sure we only report bytecode for the .py files. + ext = os.path.splitext(os.path.normcase(py_file))[1] + if ext != PYTHON_SOURCE_EXTENSION: + continue + if self.compile: + bytecode_files.append(importlib.util.cache_from_source( + py_file, optimization='')) + if self.optimize > 0: + bytecode_files.append(importlib.util.cache_from_source( + py_file, optimization=self.optimize)) + + return bytecode_files + + + # -- External interface -------------------------------------------- + # (called by outsiders) + + def get_outputs(self): + """Return the list of files that would be installed if this command + were actually run. Not affected by the "dry-run" flag or whether + modules have actually been built yet. + """ + pure_outputs = \ + self._mutate_outputs(self.distribution.has_pure_modules(), + 'build_py', 'build_lib', + self.install_dir) + if self.compile: + bytecode_outputs = self._bytecode_filenames(pure_outputs) + else: + bytecode_outputs = [] + + ext_outputs = \ + self._mutate_outputs(self.distribution.has_ext_modules(), + 'build_ext', 'build_lib', + self.install_dir) + + return pure_outputs + bytecode_outputs + ext_outputs + + def get_inputs(self): + """Get the list of files that are input to this command, ie. the + files that get installed as they are named in the build tree. + The files in this list correspond one-to-one to the output + filenames returned by 'get_outputs()'. + """ + inputs = [] + + if self.distribution.has_pure_modules(): + build_py = self.get_finalized_command('build_py') + inputs.extend(build_py.get_outputs()) + + if self.distribution.has_ext_modules(): + build_ext = self.get_finalized_command('build_ext') + inputs.extend(build_ext.get_outputs()) + + return inputs diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/command/install_scripts.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/install_scripts.py new file mode 100644 index 00000000..31a1130e --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/install_scripts.py @@ -0,0 +1,60 @@ +"""distutils.command.install_scripts + +Implements the Distutils 'install_scripts' command, for installing +Python scripts.""" + +# contributed by Bastian Kleineidam + +import os +from distutils.core import Command +from distutils import log +from stat import ST_MODE + + +class install_scripts(Command): + + description = "install scripts (Python or otherwise)" + + user_options = [ + ('install-dir=', 'd', "directory to install scripts to"), + ('build-dir=','b', "build directory (where to install from)"), + ('force', 'f', "force installation (overwrite existing files)"), + ('skip-build', None, "skip the build steps"), + ] + + boolean_options = ['force', 'skip-build'] + + def initialize_options(self): + self.install_dir = None + self.force = 0 + self.build_dir = None + self.skip_build = None + + def finalize_options(self): + self.set_undefined_options('build', ('build_scripts', 'build_dir')) + self.set_undefined_options('install', + ('install_scripts', 'install_dir'), + ('force', 'force'), + ('skip_build', 'skip_build'), + ) + + def run(self): + if not self.skip_build: + self.run_command('build_scripts') + self.outfiles = self.copy_tree(self.build_dir, self.install_dir) + if os.name == 'posix': + # Set the executable bits (owner, group, and world) on + # all the scripts we just installed. + for file in self.get_outputs(): + if self.dry_run: + log.info("changing mode of %s", file) + else: + mode = ((os.stat(file)[ST_MODE]) | 0o555) & 0o7777 + log.info("changing mode of %s to %o", file, mode) + os.chmod(file, mode) + + def get_inputs(self): + return self.distribution.scripts or [] + + def get_outputs(self): + return self.outfiles or [] diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/command/py37compat.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/py37compat.py new file mode 100644 index 00000000..754715a5 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/py37compat.py @@ -0,0 +1,30 @@ +import sys + + +def _pythonlib_compat(): + """ + On Python 3.7 and earlier, distutils would include the Python + library. See pypa/distutils#9. + """ + from distutils import sysconfig + if not sysconfig.get_config_var('Py_ENABLED_SHARED'): + return + + yield 'python{}.{}{}'.format( + sys.hexversion >> 24, + (sys.hexversion >> 16) & 0xff, + sysconfig.get_config_var('ABIFLAGS'), + ) + + +def compose(f1, f2): + return lambda *args, **kwargs: f1(f2(*args, **kwargs)) + + +pythonlib = ( + compose(list, _pythonlib_compat) + if sys.version_info < (3, 8) + and sys.platform != 'darwin' + and sys.platform[:3] != 'aix' + else list +) diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/command/register.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/register.py new file mode 100644 index 00000000..0fac94e9 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/register.py @@ -0,0 +1,304 @@ +"""distutils.command.register + +Implements the Distutils 'register' command (register with the repository). +""" + +# created 2002/10/21, Richard Jones + +import getpass +import io +import urllib.parse, urllib.request +from warnings import warn + +from distutils.core import PyPIRCCommand +from distutils.errors import * +from distutils import log + +class register(PyPIRCCommand): + + description = ("register the distribution with the Python package index") + user_options = PyPIRCCommand.user_options + [ + ('list-classifiers', None, + 'list the valid Trove classifiers'), + ('strict', None , + 'Will stop the registering if the meta-data are not fully compliant') + ] + boolean_options = PyPIRCCommand.boolean_options + [ + 'verify', 'list-classifiers', 'strict'] + + sub_commands = [('check', lambda self: True)] + + def initialize_options(self): + PyPIRCCommand.initialize_options(self) + self.list_classifiers = 0 + self.strict = 0 + + def finalize_options(self): + PyPIRCCommand.finalize_options(self) + # setting options for the `check` subcommand + check_options = {'strict': ('register', self.strict), + 'restructuredtext': ('register', 1)} + self.distribution.command_options['check'] = check_options + + def run(self): + self.finalize_options() + self._set_config() + + # Run sub commands + for cmd_name in self.get_sub_commands(): + self.run_command(cmd_name) + + if self.dry_run: + self.verify_metadata() + elif self.list_classifiers: + self.classifiers() + else: + self.send_metadata() + + def check_metadata(self): + """Deprecated API.""" + warn("distutils.command.register.check_metadata is deprecated, \ + use the check command instead", PendingDeprecationWarning) + check = self.distribution.get_command_obj('check') + check.ensure_finalized() + check.strict = self.strict + check.restructuredtext = 1 + check.run() + + def _set_config(self): + ''' Reads the configuration file and set attributes. + ''' + config = self._read_pypirc() + if config != {}: + self.username = config['username'] + self.password = config['password'] + self.repository = config['repository'] + self.realm = config['realm'] + self.has_config = True + else: + if self.repository not in ('pypi', self.DEFAULT_REPOSITORY): + raise ValueError('%s not found in .pypirc' % self.repository) + if self.repository == 'pypi': + self.repository = self.DEFAULT_REPOSITORY + self.has_config = False + + def classifiers(self): + ''' Fetch the list of classifiers from the server. + ''' + url = self.repository+'?:action=list_classifiers' + response = urllib.request.urlopen(url) + log.info(self._read_pypi_response(response)) + + def verify_metadata(self): + ''' Send the metadata to the package index server to be checked. + ''' + # send the info to the server and report the result + (code, result) = self.post_to_server(self.build_post_data('verify')) + log.info('Server response (%s): %s', code, result) + + def send_metadata(self): + ''' Send the metadata to the package index server. + + Well, do the following: + 1. figure who the user is, and then + 2. send the data as a Basic auth'ed POST. + + First we try to read the username/password from $HOME/.pypirc, + which is a ConfigParser-formatted file with a section + [distutils] containing username and password entries (both + in clear text). Eg: + + [distutils] + index-servers = + pypi + + [pypi] + username: fred + password: sekrit + + Otherwise, to figure who the user is, we offer the user three + choices: + + 1. use existing login, + 2. register as a new user, or + 3. set the password to a random string and email the user. + + ''' + # see if we can short-cut and get the username/password from the + # config + if self.has_config: + choice = '1' + username = self.username + password = self.password + else: + choice = 'x' + username = password = '' + + # get the user's login info + choices = '1 2 3 4'.split() + while choice not in choices: + self.announce('''\ +We need to know who you are, so please choose either: + 1. use your existing login, + 2. register as a new user, + 3. have the server generate a new password for you (and email it to you), or + 4. quit +Your selection [default 1]: ''', log.INFO) + choice = input() + if not choice: + choice = '1' + elif choice not in choices: + print('Please choose one of the four options!') + + if choice == '1': + # get the username and password + while not username: + username = input('Username: ') + while not password: + password = getpass.getpass('Password: ') + + # set up the authentication + auth = urllib.request.HTTPPasswordMgr() + host = urllib.parse.urlparse(self.repository)[1] + auth.add_password(self.realm, host, username, password) + # send the info to the server and report the result + code, result = self.post_to_server(self.build_post_data('submit'), + auth) + self.announce('Server response (%s): %s' % (code, result), + log.INFO) + + # possibly save the login + if code == 200: + if self.has_config: + # sharing the password in the distribution instance + # so the upload command can reuse it + self.distribution.password = password + else: + self.announce(('I can store your PyPI login so future ' + 'submissions will be faster.'), log.INFO) + self.announce('(the login will be stored in %s)' % \ + self._get_rc_file(), log.INFO) + choice = 'X' + while choice.lower() not in 'yn': + choice = input('Save your login (y/N)?') + if not choice: + choice = 'n' + if choice.lower() == 'y': + self._store_pypirc(username, password) + + elif choice == '2': + data = {':action': 'user'} + data['name'] = data['password'] = data['email'] = '' + data['confirm'] = None + while not data['name']: + data['name'] = input('Username: ') + while data['password'] != data['confirm']: + while not data['password']: + data['password'] = getpass.getpass('Password: ') + while not data['confirm']: + data['confirm'] = getpass.getpass(' Confirm: ') + if data['password'] != data['confirm']: + data['password'] = '' + data['confirm'] = None + print("Password and confirm don't match!") + while not data['email']: + data['email'] = input(' EMail: ') + code, result = self.post_to_server(data) + if code != 200: + log.info('Server response (%s): %s', code, result) + else: + log.info('You will receive an email shortly.') + log.info(('Follow the instructions in it to ' + 'complete registration.')) + elif choice == '3': + data = {':action': 'password_reset'} + data['email'] = '' + while not data['email']: + data['email'] = input('Your email address: ') + code, result = self.post_to_server(data) + log.info('Server response (%s): %s', code, result) + + def build_post_data(self, action): + # figure the data to send - the metadata plus some additional + # information used by the package server + meta = self.distribution.metadata + data = { + ':action': action, + 'metadata_version' : '1.0', + 'name': meta.get_name(), + 'version': meta.get_version(), + 'summary': meta.get_description(), + 'home_page': meta.get_url(), + 'author': meta.get_contact(), + 'author_email': meta.get_contact_email(), + 'license': meta.get_licence(), + 'description': meta.get_long_description(), + 'keywords': meta.get_keywords(), + 'platform': meta.get_platforms(), + 'classifiers': meta.get_classifiers(), + 'download_url': meta.get_download_url(), + # PEP 314 + 'provides': meta.get_provides(), + 'requires': meta.get_requires(), + 'obsoletes': meta.get_obsoletes(), + } + if data['provides'] or data['requires'] or data['obsoletes']: + data['metadata_version'] = '1.1' + return data + + def post_to_server(self, data, auth=None): + ''' Post a query to the server, and return a string response. + ''' + if 'name' in data: + self.announce('Registering %s to %s' % (data['name'], + self.repository), + log.INFO) + # Build up the MIME payload for the urllib2 POST data + boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' + sep_boundary = '\n--' + boundary + end_boundary = sep_boundary + '--' + body = io.StringIO() + for key, value in data.items(): + # handle multiple entries for the same name + if type(value) not in (type([]), type( () )): + value = [value] + for value in value: + value = str(value) + body.write(sep_boundary) + body.write('\nContent-Disposition: form-data; name="%s"'%key) + body.write("\n\n") + body.write(value) + if value and value[-1] == '\r': + body.write('\n') # write an extra newline (lurve Macs) + body.write(end_boundary) + body.write("\n") + body = body.getvalue().encode("utf-8") + + # build the Request + headers = { + 'Content-type': 'multipart/form-data; boundary=%s; charset=utf-8'%boundary, + 'Content-length': str(len(body)) + } + req = urllib.request.Request(self.repository, body, headers) + + # handle HTTP and include the Basic Auth handler + opener = urllib.request.build_opener( + urllib.request.HTTPBasicAuthHandler(password_mgr=auth) + ) + data = '' + try: + result = opener.open(req) + except urllib.error.HTTPError as e: + if self.show_response: + data = e.fp.read() + result = e.code, e.msg + except urllib.error.URLError as e: + result = 500, str(e) + else: + if self.show_response: + data = self._read_pypi_response(result) + result = 200, 'OK' + if self.show_response: + msg = '\n'.join(('-' * 75, data, '-' * 75)) + self.announce(msg, log.INFO) + return result diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/command/sdist.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/sdist.py new file mode 100644 index 00000000..b4996fcb --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/sdist.py @@ -0,0 +1,494 @@ +"""distutils.command.sdist + +Implements the Distutils 'sdist' command (create a source distribution).""" + +import os +import sys +from glob import glob +from warnings import warn + +from distutils.core import Command +from distutils import dir_util +from distutils import file_util +from distutils import archive_util +from distutils.text_file import TextFile +from distutils.filelist import FileList +from distutils import log +from distutils.util import convert_path +from distutils.errors import DistutilsTemplateError, DistutilsOptionError + + +def show_formats(): + """Print all possible values for the 'formats' option (used by + the "--help-formats" command-line option). + """ + from distutils.fancy_getopt import FancyGetopt + from distutils.archive_util import ARCHIVE_FORMATS + formats = [] + for format in ARCHIVE_FORMATS.keys(): + formats.append(("formats=" + format, None, + ARCHIVE_FORMATS[format][2])) + formats.sort() + FancyGetopt(formats).print_help( + "List of available source distribution formats:") + + +class sdist(Command): + + description = "create a source distribution (tarball, zip file, etc.)" + + def checking_metadata(self): + """Callable used for the check sub-command. + + Placed here so user_options can view it""" + return self.metadata_check + + user_options = [ + ('template=', 't', + "name of manifest template file [default: MANIFEST.in]"), + ('manifest=', 'm', + "name of manifest file [default: MANIFEST]"), + ('use-defaults', None, + "include the default file set in the manifest " + "[default; disable with --no-defaults]"), + ('no-defaults', None, + "don't include the default file set"), + ('prune', None, + "specifically exclude files/directories that should not be " + "distributed (build tree, RCS/CVS dirs, etc.) " + "[default; disable with --no-prune]"), + ('no-prune', None, + "don't automatically exclude anything"), + ('manifest-only', 'o', + "just regenerate the manifest and then stop " + "(implies --force-manifest)"), + ('force-manifest', 'f', + "forcibly regenerate the manifest and carry on as usual. " + "Deprecated: now the manifest is always regenerated."), + ('formats=', None, + "formats for source distribution (comma-separated list)"), + ('keep-temp', 'k', + "keep the distribution tree around after creating " + + "archive file(s)"), + ('dist-dir=', 'd', + "directory to put the source distribution archive(s) in " + "[default: dist]"), + ('metadata-check', None, + "Ensure that all required elements of meta-data " + "are supplied. Warn if any missing. [default]"), + ('owner=', 'u', + "Owner name used when creating a tar file [default: current user]"), + ('group=', 'g', + "Group name used when creating a tar file [default: current group]"), + ] + + boolean_options = ['use-defaults', 'prune', + 'manifest-only', 'force-manifest', + 'keep-temp', 'metadata-check'] + + help_options = [ + ('help-formats', None, + "list available distribution formats", show_formats), + ] + + negative_opt = {'no-defaults': 'use-defaults', + 'no-prune': 'prune' } + + sub_commands = [('check', checking_metadata)] + + READMES = ('README', 'README.txt', 'README.rst') + + def initialize_options(self): + # 'template' and 'manifest' are, respectively, the names of + # the manifest template and manifest file. + self.template = None + self.manifest = None + + # 'use_defaults': if true, we will include the default file set + # in the manifest + self.use_defaults = 1 + self.prune = 1 + + self.manifest_only = 0 + self.force_manifest = 0 + + self.formats = ['gztar'] + self.keep_temp = 0 + self.dist_dir = None + + self.archive_files = None + self.metadata_check = 1 + self.owner = None + self.group = None + + def finalize_options(self): + if self.manifest is None: + self.manifest = "MANIFEST" + if self.template is None: + self.template = "MANIFEST.in" + + self.ensure_string_list('formats') + + bad_format = archive_util.check_archive_formats(self.formats) + if bad_format: + raise DistutilsOptionError( + "unknown archive format '%s'" % bad_format) + + if self.dist_dir is None: + self.dist_dir = "dist" + + def run(self): + # 'filelist' contains the list of files that will make up the + # manifest + self.filelist = FileList() + + # Run sub commands + for cmd_name in self.get_sub_commands(): + self.run_command(cmd_name) + + # Do whatever it takes to get the list of files to process + # (process the manifest template, read an existing manifest, + # whatever). File list is accumulated in 'self.filelist'. + self.get_file_list() + + # If user just wanted us to regenerate the manifest, stop now. + if self.manifest_only: + return + + # Otherwise, go ahead and create the source distribution tarball, + # or zipfile, or whatever. + self.make_distribution() + + def check_metadata(self): + """Deprecated API.""" + warn("distutils.command.sdist.check_metadata is deprecated, \ + use the check command instead", PendingDeprecationWarning) + check = self.distribution.get_command_obj('check') + check.ensure_finalized() + check.run() + + def get_file_list(self): + """Figure out the list of files to include in the source + distribution, and put it in 'self.filelist'. This might involve + reading the manifest template (and writing the manifest), or just + reading the manifest, or just using the default file set -- it all + depends on the user's options. + """ + # new behavior when using a template: + # the file list is recalculated every time because + # even if MANIFEST.in or setup.py are not changed + # the user might have added some files in the tree that + # need to be included. + # + # This makes --force the default and only behavior with templates. + template_exists = os.path.isfile(self.template) + if not template_exists and self._manifest_is_not_generated(): + self.read_manifest() + self.filelist.sort() + self.filelist.remove_duplicates() + return + + if not template_exists: + self.warn(("manifest template '%s' does not exist " + + "(using default file list)") % + self.template) + self.filelist.findall() + + if self.use_defaults: + self.add_defaults() + + if template_exists: + self.read_template() + + if self.prune: + self.prune_file_list() + + self.filelist.sort() + self.filelist.remove_duplicates() + self.write_manifest() + + def add_defaults(self): + """Add all the default files to self.filelist: + - README or README.txt + - setup.py + - test/test*.py + - all pure Python modules mentioned in setup script + - all files pointed by package_data (build_py) + - all files defined in data_files. + - all files defined as scripts. + - all C sources listed as part of extensions or C libraries + in the setup script (doesn't catch C headers!) + Warns if (README or README.txt) or setup.py are missing; everything + else is optional. + """ + self._add_defaults_standards() + self._add_defaults_optional() + self._add_defaults_python() + self._add_defaults_data_files() + self._add_defaults_ext() + self._add_defaults_c_libs() + self._add_defaults_scripts() + + @staticmethod + def _cs_path_exists(fspath): + """ + Case-sensitive path existence check + + >>> sdist._cs_path_exists(__file__) + True + >>> sdist._cs_path_exists(__file__.upper()) + False + """ + if not os.path.exists(fspath): + return False + # make absolute so we always have a directory + abspath = os.path.abspath(fspath) + directory, filename = os.path.split(abspath) + return filename in os.listdir(directory) + + def _add_defaults_standards(self): + standards = [self.READMES, self.distribution.script_name] + for fn in standards: + if isinstance(fn, tuple): + alts = fn + got_it = False + for fn in alts: + if self._cs_path_exists(fn): + got_it = True + self.filelist.append(fn) + break + + if not got_it: + self.warn("standard file not found: should have one of " + + ', '.join(alts)) + else: + if self._cs_path_exists(fn): + self.filelist.append(fn) + else: + self.warn("standard file '%s' not found" % fn) + + def _add_defaults_optional(self): + optional = ['test/test*.py', 'setup.cfg'] + for pattern in optional: + files = filter(os.path.isfile, glob(pattern)) + self.filelist.extend(files) + + def _add_defaults_python(self): + # build_py is used to get: + # - python modules + # - files defined in package_data + build_py = self.get_finalized_command('build_py') + + # getting python files + if self.distribution.has_pure_modules(): + self.filelist.extend(build_py.get_source_files()) + + # getting package_data files + # (computed in build_py.data_files by build_py.finalize_options) + for pkg, src_dir, build_dir, filenames in build_py.data_files: + for filename in filenames: + self.filelist.append(os.path.join(src_dir, filename)) + + def _add_defaults_data_files(self): + # getting distribution.data_files + if self.distribution.has_data_files(): + for item in self.distribution.data_files: + if isinstance(item, str): + # plain file + item = convert_path(item) + if os.path.isfile(item): + self.filelist.append(item) + else: + # a (dirname, filenames) tuple + dirname, filenames = item + for f in filenames: + f = convert_path(f) + if os.path.isfile(f): + self.filelist.append(f) + + def _add_defaults_ext(self): + if self.distribution.has_ext_modules(): + build_ext = self.get_finalized_command('build_ext') + self.filelist.extend(build_ext.get_source_files()) + + def _add_defaults_c_libs(self): + if self.distribution.has_c_libraries(): + build_clib = self.get_finalized_command('build_clib') + self.filelist.extend(build_clib.get_source_files()) + + def _add_defaults_scripts(self): + if self.distribution.has_scripts(): + build_scripts = self.get_finalized_command('build_scripts') + self.filelist.extend(build_scripts.get_source_files()) + + def read_template(self): + """Read and parse manifest template file named by self.template. + + (usually "MANIFEST.in") The parsing and processing is done by + 'self.filelist', which updates itself accordingly. + """ + log.info("reading manifest template '%s'", self.template) + template = TextFile(self.template, strip_comments=1, skip_blanks=1, + join_lines=1, lstrip_ws=1, rstrip_ws=1, + collapse_join=1) + + try: + while True: + line = template.readline() + if line is None: # end of file + break + + try: + self.filelist.process_template_line(line) + # the call above can raise a DistutilsTemplateError for + # malformed lines, or a ValueError from the lower-level + # convert_path function + except (DistutilsTemplateError, ValueError) as msg: + self.warn("%s, line %d: %s" % (template.filename, + template.current_line, + msg)) + finally: + template.close() + + def prune_file_list(self): + """Prune off branches that might slip into the file list as created + by 'read_template()', but really don't belong there: + * the build tree (typically "build") + * the release tree itself (only an issue if we ran "sdist" + previously with --keep-temp, or it aborted) + * any RCS, CVS, .svn, .hg, .git, .bzr, _darcs directories + """ + build = self.get_finalized_command('build') + base_dir = self.distribution.get_fullname() + + self.filelist.exclude_pattern(None, prefix=build.build_base) + self.filelist.exclude_pattern(None, prefix=base_dir) + + if sys.platform == 'win32': + seps = r'/|\\' + else: + seps = '/' + + vcs_dirs = ['RCS', 'CVS', r'\.svn', r'\.hg', r'\.git', r'\.bzr', + '_darcs'] + vcs_ptrn = r'(^|%s)(%s)(%s).*' % (seps, '|'.join(vcs_dirs), seps) + self.filelist.exclude_pattern(vcs_ptrn, is_regex=1) + + def write_manifest(self): + """Write the file list in 'self.filelist' (presumably as filled in + by 'add_defaults()' and 'read_template()') to the manifest file + named by 'self.manifest'. + """ + if self._manifest_is_not_generated(): + log.info("not writing to manually maintained " + "manifest file '%s'" % self.manifest) + return + + content = self.filelist.files[:] + content.insert(0, '# file GENERATED by distutils, do NOT edit') + self.execute(file_util.write_file, (self.manifest, content), + "writing manifest file '%s'" % self.manifest) + + def _manifest_is_not_generated(self): + # check for special comment used in 3.1.3 and higher + if not os.path.isfile(self.manifest): + return False + + fp = open(self.manifest) + try: + first_line = fp.readline() + finally: + fp.close() + return first_line != '# file GENERATED by distutils, do NOT edit\n' + + def read_manifest(self): + """Read the manifest file (named by 'self.manifest') and use it to + fill in 'self.filelist', the list of files to include in the source + distribution. + """ + log.info("reading manifest file '%s'", self.manifest) + with open(self.manifest) as manifest: + for line in manifest: + # ignore comments and blank lines + line = line.strip() + if line.startswith('#') or not line: + continue + self.filelist.append(line) + + def make_release_tree(self, base_dir, files): + """Create the directory tree that will become the source + distribution archive. All directories implied by the filenames in + 'files' are created under 'base_dir', and then we hard link or copy + (if hard linking is unavailable) those files into place. + Essentially, this duplicates the developer's source tree, but in a + directory named after the distribution, containing only the files + to be distributed. + """ + # Create all the directories under 'base_dir' necessary to + # put 'files' there; the 'mkpath()' is just so we don't die + # if the manifest happens to be empty. + self.mkpath(base_dir) + dir_util.create_tree(base_dir, files, dry_run=self.dry_run) + + # And walk over the list of files, either making a hard link (if + # os.link exists) to each one that doesn't already exist in its + # corresponding location under 'base_dir', or copying each file + # that's out-of-date in 'base_dir'. (Usually, all files will be + # out-of-date, because by default we blow away 'base_dir' when + # we're done making the distribution archives.) + + if hasattr(os, 'link'): # can make hard links on this system + link = 'hard' + msg = "making hard links in %s..." % base_dir + else: # nope, have to copy + link = None + msg = "copying files to %s..." % base_dir + + if not files: + log.warn("no files to distribute -- empty manifest?") + else: + log.info(msg) + for file in files: + if not os.path.isfile(file): + log.warn("'%s' not a regular file -- skipping", file) + else: + dest = os.path.join(base_dir, file) + self.copy_file(file, dest, link=link) + + self.distribution.metadata.write_pkg_info(base_dir) + + def make_distribution(self): + """Create the source distribution(s). First, we create the release + tree with 'make_release_tree()'; then, we create all required + archive files (according to 'self.formats') from the release tree. + Finally, we clean up by blowing away the release tree (unless + 'self.keep_temp' is true). The list of archive files created is + stored so it can be retrieved later by 'get_archive_files()'. + """ + # Don't warn about missing meta-data here -- should be (and is!) + # done elsewhere. + base_dir = self.distribution.get_fullname() + base_name = os.path.join(self.dist_dir, base_dir) + + self.make_release_tree(base_dir, self.filelist.files) + archive_files = [] # remember names of files we create + # tar archive must be created last to avoid overwrite and remove + if 'tar' in self.formats: + self.formats.append(self.formats.pop(self.formats.index('tar'))) + + for fmt in self.formats: + file = self.make_archive(base_name, fmt, base_dir=base_dir, + owner=self.owner, group=self.group) + archive_files.append(file) + self.distribution.dist_files.append(('sdist', '', file)) + + self.archive_files = archive_files + + if not self.keep_temp: + dir_util.remove_tree(base_dir, dry_run=self.dry_run) + + def get_archive_files(self): + """Return the list of archive files created when the command + was run, or None if the command hasn't run yet. + """ + return self.archive_files diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/command/upload.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/upload.py new file mode 100644 index 00000000..95e9fda1 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/command/upload.py @@ -0,0 +1,214 @@ +""" +distutils.command.upload + +Implements the Distutils 'upload' subcommand (upload package to a package +index). +""" + +import os +import io +import hashlib +from base64 import standard_b64encode +from urllib.request import urlopen, Request, HTTPError +from urllib.parse import urlparse +from distutils.errors import DistutilsError, DistutilsOptionError +from distutils.core import PyPIRCCommand +from distutils.spawn import spawn +from distutils import log + + +# PyPI Warehouse supports MD5, SHA256, and Blake2 (blake2-256) +# https://bugs.python.org/issue40698 +_FILE_CONTENT_DIGESTS = { + "md5_digest": getattr(hashlib, "md5", None), + "sha256_digest": getattr(hashlib, "sha256", None), + "blake2_256_digest": getattr(hashlib, "blake2b", None), +} + + +class upload(PyPIRCCommand): + + description = "upload binary package to PyPI" + + user_options = PyPIRCCommand.user_options + [ + ('sign', 's', + 'sign files to upload using gpg'), + ('identity=', 'i', 'GPG identity used to sign files'), + ] + + boolean_options = PyPIRCCommand.boolean_options + ['sign'] + + def initialize_options(self): + PyPIRCCommand.initialize_options(self) + self.username = '' + self.password = '' + self.show_response = 0 + self.sign = False + self.identity = None + + def finalize_options(self): + PyPIRCCommand.finalize_options(self) + if self.identity and not self.sign: + raise DistutilsOptionError( + "Must use --sign for --identity to have meaning" + ) + config = self._read_pypirc() + if config != {}: + self.username = config['username'] + self.password = config['password'] + self.repository = config['repository'] + self.realm = config['realm'] + + # getting the password from the distribution + # if previously set by the register command + if not self.password and self.distribution.password: + self.password = self.distribution.password + + def run(self): + if not self.distribution.dist_files: + msg = ("Must create and upload files in one command " + "(e.g. setup.py sdist upload)") + raise DistutilsOptionError(msg) + for command, pyversion, filename in self.distribution.dist_files: + self.upload_file(command, pyversion, filename) + + def upload_file(self, command, pyversion, filename): + # Makes sure the repository URL is compliant + schema, netloc, url, params, query, fragments = \ + urlparse(self.repository) + if params or query or fragments: + raise AssertionError("Incompatible url %s" % self.repository) + + if schema not in ('http', 'https'): + raise AssertionError("unsupported schema " + schema) + + # Sign if requested + if self.sign: + gpg_args = ["gpg", "--detach-sign", "-a", filename] + if self.identity: + gpg_args[2:2] = ["--local-user", self.identity] + spawn(gpg_args, + dry_run=self.dry_run) + + # Fill in the data - send all the meta-data in case we need to + # register a new release + f = open(filename,'rb') + try: + content = f.read() + finally: + f.close() + + meta = self.distribution.metadata + data = { + # action + ':action': 'file_upload', + 'protocol_version': '1', + + # identify release + 'name': meta.get_name(), + 'version': meta.get_version(), + + # file content + 'content': (os.path.basename(filename),content), + 'filetype': command, + 'pyversion': pyversion, + + # additional meta-data + 'metadata_version': '1.0', + 'summary': meta.get_description(), + 'home_page': meta.get_url(), + 'author': meta.get_contact(), + 'author_email': meta.get_contact_email(), + 'license': meta.get_licence(), + 'description': meta.get_long_description(), + 'keywords': meta.get_keywords(), + 'platform': meta.get_platforms(), + 'classifiers': meta.get_classifiers(), + 'download_url': meta.get_download_url(), + # PEP 314 + 'provides': meta.get_provides(), + 'requires': meta.get_requires(), + 'obsoletes': meta.get_obsoletes(), + } + + data['comment'] = '' + + # file content digests + for digest_name, digest_cons in _FILE_CONTENT_DIGESTS.items(): + if digest_cons is None: + continue + try: + data[digest_name] = digest_cons(content).hexdigest() + except ValueError: + # hash digest not available or blocked by security policy + pass + + if self.sign: + with open(filename + ".asc", "rb") as f: + data['gpg_signature'] = (os.path.basename(filename) + ".asc", + f.read()) + + # set up the authentication + user_pass = (self.username + ":" + self.password).encode('ascii') + # The exact encoding of the authentication string is debated. + # Anyway PyPI only accepts ascii for both username or password. + auth = "Basic " + standard_b64encode(user_pass).decode('ascii') + + # Build up the MIME payload for the POST data + boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' + sep_boundary = b'\r\n--' + boundary.encode('ascii') + end_boundary = sep_boundary + b'--\r\n' + body = io.BytesIO() + for key, value in data.items(): + title = '\r\nContent-Disposition: form-data; name="%s"' % key + # handle multiple entries for the same name + if not isinstance(value, list): + value = [value] + for value in value: + if type(value) is tuple: + title += '; filename="%s"' % value[0] + value = value[1] + else: + value = str(value).encode('utf-8') + body.write(sep_boundary) + body.write(title.encode('utf-8')) + body.write(b"\r\n\r\n") + body.write(value) + body.write(end_boundary) + body = body.getvalue() + + msg = "Submitting %s to %s" % (filename, self.repository) + self.announce(msg, log.INFO) + + # build the Request + headers = { + 'Content-type': 'multipart/form-data; boundary=%s' % boundary, + 'Content-length': str(len(body)), + 'Authorization': auth, + } + + request = Request(self.repository, data=body, + headers=headers) + # send the data + try: + result = urlopen(request) + status = result.getcode() + reason = result.msg + except HTTPError as e: + status = e.code + reason = e.msg + except OSError as e: + self.announce(str(e), log.ERROR) + raise + + if status == 200: + self.announce('Server response (%s): %s' % (status, reason), + log.INFO) + if self.show_response: + text = self._read_pypi_response(result) + msg = '\n'.join(('-' * 75, text, '-' * 75)) + self.announce(msg, log.INFO) + else: + msg = 'Upload failed (%s): %s' % (status, reason) + self.announce(msg, log.ERROR) + raise DistutilsError(msg) diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/config.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/config.py new file mode 100644 index 00000000..2171abd6 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/config.py @@ -0,0 +1,130 @@ +"""distutils.pypirc + +Provides the PyPIRCCommand class, the base class for the command classes +that uses .pypirc in the distutils.command package. +""" +import os +from configparser import RawConfigParser + +from distutils.cmd import Command + +DEFAULT_PYPIRC = """\ +[distutils] +index-servers = + pypi + +[pypi] +username:%s +password:%s +""" + +class PyPIRCCommand(Command): + """Base command that knows how to handle the .pypirc file + """ + DEFAULT_REPOSITORY = 'https://upload.pypi.org/legacy/' + DEFAULT_REALM = 'pypi' + repository = None + realm = None + + user_options = [ + ('repository=', 'r', + "url of repository [default: %s]" % \ + DEFAULT_REPOSITORY), + ('show-response', None, + 'display full response text from server')] + + boolean_options = ['show-response'] + + def _get_rc_file(self): + """Returns rc file path.""" + return os.path.join(os.path.expanduser('~'), '.pypirc') + + def _store_pypirc(self, username, password): + """Creates a default .pypirc file.""" + rc = self._get_rc_file() + with os.fdopen(os.open(rc, os.O_CREAT | os.O_WRONLY, 0o600), 'w') as f: + f.write(DEFAULT_PYPIRC % (username, password)) + + def _read_pypirc(self): + """Reads the .pypirc file.""" + rc = self._get_rc_file() + if os.path.exists(rc): + self.announce('Using PyPI login from %s' % rc) + repository = self.repository or self.DEFAULT_REPOSITORY + + config = RawConfigParser() + config.read(rc) + sections = config.sections() + if 'distutils' in sections: + # let's get the list of servers + index_servers = config.get('distutils', 'index-servers') + _servers = [server.strip() for server in + index_servers.split('\n') + if server.strip() != ''] + if _servers == []: + # nothing set, let's try to get the default pypi + if 'pypi' in sections: + _servers = ['pypi'] + else: + # the file is not properly defined, returning + # an empty dict + return {} + for server in _servers: + current = {'server': server} + current['username'] = config.get(server, 'username') + + # optional params + for key, default in (('repository', + self.DEFAULT_REPOSITORY), + ('realm', self.DEFAULT_REALM), + ('password', None)): + if config.has_option(server, key): + current[key] = config.get(server, key) + else: + current[key] = default + + # work around people having "repository" for the "pypi" + # section of their config set to the HTTP (rather than + # HTTPS) URL + if (server == 'pypi' and + repository in (self.DEFAULT_REPOSITORY, 'pypi')): + current['repository'] = self.DEFAULT_REPOSITORY + return current + + if (current['server'] == repository or + current['repository'] == repository): + return current + elif 'server-login' in sections: + # old format + server = 'server-login' + if config.has_option(server, 'repository'): + repository = config.get(server, 'repository') + else: + repository = self.DEFAULT_REPOSITORY + return {'username': config.get(server, 'username'), + 'password': config.get(server, 'password'), + 'repository': repository, + 'server': server, + 'realm': self.DEFAULT_REALM} + + return {} + + def _read_pypi_response(self, response): + """Read and decode a PyPI HTTP response.""" + import cgi + content_type = response.getheader('content-type', 'text/plain') + encoding = cgi.parse_header(content_type)[1].get('charset', 'ascii') + return response.read().decode(encoding) + + def initialize_options(self): + """Initialize options.""" + self.repository = None + self.realm = None + self.show_response = 0 + + def finalize_options(self): + """Finalizes options.""" + if self.repository is None: + self.repository = self.DEFAULT_REPOSITORY + if self.realm is None: + self.realm = self.DEFAULT_REALM diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/core.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/core.py new file mode 100644 index 00000000..f43888ea --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/core.py @@ -0,0 +1,249 @@ +"""distutils.core + +The only module that needs to be imported to use the Distutils; provides +the 'setup' function (which is to be called from the setup script). Also +indirectly provides the Distribution and Command classes, although they are +really defined in distutils.dist and distutils.cmd. +""" + +import os +import sys +import tokenize + +from distutils.debug import DEBUG +from distutils.errors import * + +# Mainly import these so setup scripts can "from distutils.core import" them. +from distutils.dist import Distribution +from distutils.cmd import Command +from distutils.config import PyPIRCCommand +from distutils.extension import Extension + +# This is a barebones help message generated displayed when the user +# runs the setup script with no arguments at all. More useful help +# is generated with various --help options: global help, list commands, +# and per-command help. +USAGE = """\ +usage: %(script)s [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...] + or: %(script)s --help [cmd1 cmd2 ...] + or: %(script)s --help-commands + or: %(script)s cmd --help +""" + +def gen_usage (script_name): + script = os.path.basename(script_name) + return USAGE % vars() + + +# Some mild magic to control the behaviour of 'setup()' from 'run_setup()'. +_setup_stop_after = None +_setup_distribution = None + +# Legal keyword arguments for the setup() function +setup_keywords = ('distclass', 'script_name', 'script_args', 'options', + 'name', 'version', 'author', 'author_email', + 'maintainer', 'maintainer_email', 'url', 'license', + 'description', 'long_description', 'keywords', + 'platforms', 'classifiers', 'download_url', + 'requires', 'provides', 'obsoletes', + ) + +# Legal keyword arguments for the Extension constructor +extension_keywords = ('name', 'sources', 'include_dirs', + 'define_macros', 'undef_macros', + 'library_dirs', 'libraries', 'runtime_library_dirs', + 'extra_objects', 'extra_compile_args', 'extra_link_args', + 'swig_opts', 'export_symbols', 'depends', 'language') + +def setup (**attrs): + """The gateway to the Distutils: do everything your setup script needs + to do, in a highly flexible and user-driven way. Briefly: create a + Distribution instance; find and parse config files; parse the command + line; run each Distutils command found there, customized by the options + supplied to 'setup()' (as keyword arguments), in config files, and on + the command line. + + The Distribution instance might be an instance of a class supplied via + the 'distclass' keyword argument to 'setup'; if no such class is + supplied, then the Distribution class (in dist.py) is instantiated. + All other arguments to 'setup' (except for 'cmdclass') are used to set + attributes of the Distribution instance. + + The 'cmdclass' argument, if supplied, is a dictionary mapping command + names to command classes. Each command encountered on the command line + will be turned into a command class, which is in turn instantiated; any + class found in 'cmdclass' is used in place of the default, which is + (for command 'foo_bar') class 'foo_bar' in module + 'distutils.command.foo_bar'. The command class must provide a + 'user_options' attribute which is a list of option specifiers for + 'distutils.fancy_getopt'. Any command-line options between the current + and the next command are used to set attributes of the current command + object. + + When the entire command-line has been successfully parsed, calls the + 'run()' method on each command object in turn. This method will be + driven entirely by the Distribution object (which each command object + has a reference to, thanks to its constructor), and the + command-specific options that became attributes of each command + object. + """ + + global _setup_stop_after, _setup_distribution + + # Determine the distribution class -- either caller-supplied or + # our Distribution (see below). + klass = attrs.get('distclass') + if klass: + del attrs['distclass'] + else: + klass = Distribution + + if 'script_name' not in attrs: + attrs['script_name'] = os.path.basename(sys.argv[0]) + if 'script_args' not in attrs: + attrs['script_args'] = sys.argv[1:] + + # Create the Distribution instance, using the remaining arguments + # (ie. everything except distclass) to initialize it + try: + _setup_distribution = dist = klass(attrs) + except DistutilsSetupError as msg: + if 'name' not in attrs: + raise SystemExit("error in setup command: %s" % msg) + else: + raise SystemExit("error in %s setup command: %s" % \ + (attrs['name'], msg)) + + if _setup_stop_after == "init": + return dist + + # Find and parse the config file(s): they will override options from + # the setup script, but be overridden by the command line. + dist.parse_config_files() + + if DEBUG: + print("options (after parsing config files):") + dist.dump_option_dicts() + + if _setup_stop_after == "config": + return dist + + # Parse the command line and override config files; any + # command-line errors are the end user's fault, so turn them into + # SystemExit to suppress tracebacks. + try: + ok = dist.parse_command_line() + except DistutilsArgError as msg: + raise SystemExit(gen_usage(dist.script_name) + "\nerror: %s" % msg) + + if DEBUG: + print("options (after parsing command line):") + dist.dump_option_dicts() + + if _setup_stop_after == "commandline": + return dist + + # And finally, run all the commands found on the command line. + if ok: + return run_commands(dist) + + return dist + +# setup () + + +def run_commands (dist): + """Given a Distribution object run all the commands, + raising ``SystemExit`` errors in the case of failure. + + This function assumes that either ``sys.argv`` or ``dist.script_args`` + is already set accordingly. + """ + try: + dist.run_commands() + except KeyboardInterrupt: + raise SystemExit("interrupted") + except OSError as exc: + if DEBUG: + sys.stderr.write("error: %s\n" % (exc,)) + raise + else: + raise SystemExit("error: %s" % (exc,)) + + except (DistutilsError, + CCompilerError) as msg: + if DEBUG: + raise + else: + raise SystemExit("error: " + str(msg)) + + return dist + + +def run_setup (script_name, script_args=None, stop_after="run"): + """Run a setup script in a somewhat controlled environment, and + return the Distribution instance that drives things. This is useful + if you need to find out the distribution meta-data (passed as + keyword args from 'script' to 'setup()', or the contents of the + config files or command-line. + + 'script_name' is a file that will be read and run with 'exec()'; + 'sys.argv[0]' will be replaced with 'script' for the duration of the + call. 'script_args' is a list of strings; if supplied, + 'sys.argv[1:]' will be replaced by 'script_args' for the duration of + the call. + + 'stop_after' tells 'setup()' when to stop processing; possible + values: + init + stop after the Distribution instance has been created and + populated with the keyword arguments to 'setup()' + config + stop after config files have been parsed (and their data + stored in the Distribution instance) + commandline + stop after the command-line ('sys.argv[1:]' or 'script_args') + have been parsed (and the data stored in the Distribution) + run [default] + stop after all commands have been run (the same as if 'setup()' + had been called in the usual way + + Returns the Distribution instance, which provides all information + used to drive the Distutils. + """ + if stop_after not in ('init', 'config', 'commandline', 'run'): + raise ValueError("invalid value for 'stop_after': %r" % (stop_after,)) + + global _setup_stop_after, _setup_distribution + _setup_stop_after = stop_after + + save_argv = sys.argv.copy() + g = {'__file__': script_name, '__name__': '__main__'} + try: + try: + sys.argv[0] = script_name + if script_args is not None: + sys.argv[1:] = script_args + # tokenize.open supports automatic encoding detection + with tokenize.open(script_name) as f: + code = f.read().replace(r'\r\n', r'\n') + exec(code, g) + finally: + sys.argv = save_argv + _setup_stop_after = None + except SystemExit: + # Hmm, should we do something if exiting with a non-zero code + # (ie. error)? + pass + + if _setup_distribution is None: + raise RuntimeError(("'distutils.core.setup()' was never called -- " + "perhaps '%s' is not a Distutils setup script?") % \ + script_name) + + # I wonder if the setup script's namespace -- g and l -- would be of + # any interest to callers? + #print "_setup_distribution:", _setup_distribution + return _setup_distribution + +# run_setup () diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/cygwinccompiler.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/cygwinccompiler.py new file mode 100644 index 00000000..ad6cc44b --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/cygwinccompiler.py @@ -0,0 +1,425 @@ +"""distutils.cygwinccompiler + +Provides the CygwinCCompiler class, a subclass of UnixCCompiler that +handles the Cygwin port of the GNU C compiler to Windows. It also contains +the Mingw32CCompiler class which handles the mingw32 port of GCC (same as +cygwin in no-cygwin mode). +""" + +# problems: +# +# * if you use a msvc compiled python version (1.5.2) +# 1. you have to insert a __GNUC__ section in its config.h +# 2. you have to generate an import library for its dll +# - create a def-file for python??.dll +# - create an import library using +# dlltool --dllname python15.dll --def python15.def \ +# --output-lib libpython15.a +# +# see also http://starship.python.net/crew/kernr/mingw32/Notes.html +# +# * We put export_symbols in a def-file, and don't use +# --export-all-symbols because it doesn't worked reliable in some +# tested configurations. And because other windows compilers also +# need their symbols specified this no serious problem. +# +# tested configurations: +# +# * cygwin gcc 2.91.57/ld 2.9.4/dllwrap 0.2.4 works +# (after patching python's config.h and for C++ some other include files) +# see also http://starship.python.net/crew/kernr/mingw32/Notes.html +# * mingw32 gcc 2.95.2/ld 2.9.4/dllwrap 0.2.4 works +# (ld doesn't support -shared, so we use dllwrap) +# * cygwin gcc 2.95.2/ld 2.10.90/dllwrap 2.10.90 works now +# - its dllwrap doesn't work, there is a bug in binutils 2.10.90 +# see also http://sources.redhat.com/ml/cygwin/2000-06/msg01274.html +# - using gcc -mdll instead dllwrap doesn't work without -static because +# it tries to link against dlls instead their import libraries. (If +# it finds the dll first.) +# By specifying -static we force ld to link against the import libraries, +# this is windows standard and there are normally not the necessary symbols +# in the dlls. +# *** only the version of June 2000 shows these problems +# * cygwin gcc 3.2/ld 2.13.90 works +# (ld supports -shared) +# * mingw gcc 3.2/ld 2.13 works +# (ld supports -shared) +# * llvm-mingw with Clang 11 works +# (lld supports -shared) + +import os +import sys +import copy +from subprocess import Popen, PIPE, check_output +import re + +import distutils.version +from distutils.unixccompiler import UnixCCompiler +from distutils.file_util import write_file +from distutils.errors import (DistutilsExecError, CCompilerError, + CompileError, UnknownFileError) +from distutils.version import LooseVersion +from distutils.spawn import find_executable + +def get_msvcr(): + """Include the appropriate MSVC runtime library if Python was built + with MSVC 7.0 or later. + """ + msc_pos = sys.version.find('MSC v.') + if msc_pos != -1: + msc_ver = sys.version[msc_pos+6:msc_pos+10] + if msc_ver == '1300': + # MSVC 7.0 + return ['msvcr70'] + elif msc_ver == '1310': + # MSVC 7.1 + return ['msvcr71'] + elif msc_ver == '1400': + # VS2005 / MSVC 8.0 + return ['msvcr80'] + elif msc_ver == '1500': + # VS2008 / MSVC 9.0 + return ['msvcr90'] + elif msc_ver == '1600': + # VS2010 / MSVC 10.0 + return ['msvcr100'] + elif msc_ver == '1700': + # VS2012 / MSVC 11.0 + return ['msvcr110'] + elif msc_ver == '1800': + # VS2013 / MSVC 12.0 + return ['msvcr120'] + elif 1900 <= int(msc_ver) < 2000: + # VS2015 / MSVC 14.0 + return ['ucrt', 'vcruntime140'] + else: + raise ValueError("Unknown MS Compiler version %s " % msc_ver) + + +class CygwinCCompiler(UnixCCompiler): + """ Handles the Cygwin port of the GNU C compiler to Windows. + """ + compiler_type = 'cygwin' + obj_extension = ".o" + static_lib_extension = ".a" + shared_lib_extension = ".dll" + static_lib_format = "lib%s%s" + shared_lib_format = "%s%s" + exe_extension = ".exe" + + def __init__(self, verbose=0, dry_run=0, force=0): + + UnixCCompiler.__init__(self, verbose, dry_run, force) + + status, details = check_config_h() + self.debug_print("Python's GCC status: %s (details: %s)" % + (status, details)) + if status is not CONFIG_H_OK: + self.warn( + "Python's pyconfig.h doesn't seem to support your compiler. " + "Reason: %s. " + "Compiling may fail because of undefined preprocessor macros." + % details) + + self.cc = os.environ.get('CC', 'gcc') + self.cxx = os.environ.get('CXX', 'g++') + + if ('gcc' in self.cc): # Start gcc workaround + self.gcc_version, self.ld_version, self.dllwrap_version = \ + get_versions() + self.debug_print(self.compiler_type + ": gcc %s, ld %s, dllwrap %s\n" % + (self.gcc_version, + self.ld_version, + self.dllwrap_version) ) + + # ld_version >= "2.10.90" and < "2.13" should also be able to use + # gcc -mdll instead of dllwrap + # Older dllwraps had own version numbers, newer ones use the + # same as the rest of binutils ( also ld ) + # dllwrap 2.10.90 is buggy + if self.ld_version >= "2.10.90": + self.linker_dll = self.cc + else: + self.linker_dll = "dllwrap" + + # ld_version >= "2.13" support -shared so use it instead of + # -mdll -static + if self.ld_version >= "2.13": + shared_option = "-shared" + else: + shared_option = "-mdll -static" + else: # Assume linker is up to date + self.linker_dll = self.cc + shared_option = "-shared" + + self.set_executables(compiler='%s -mcygwin -O -Wall' % self.cc, + compiler_so='%s -mcygwin -mdll -O -Wall' % self.cc, + compiler_cxx='%s -mcygwin -O -Wall' % self.cxx, + linker_exe='%s -mcygwin' % self.cc, + linker_so=('%s -mcygwin %s' % + (self.linker_dll, shared_option))) + + # cygwin and mingw32 need different sets of libraries + if ('gcc' in self.cc and self.gcc_version == "2.91.57"): + # cygwin shouldn't need msvcrt, but without the dlls will crash + # (gcc version 2.91.57) -- perhaps something about initialization + self.dll_libraries=["msvcrt"] + self.warn( + "Consider upgrading to a newer version of gcc") + else: + # Include the appropriate MSVC runtime library if Python was built + # with MSVC 7.0 or later. + self.dll_libraries = get_msvcr() + + def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts): + """Compiles the source by spawning GCC and windres if needed.""" + if ext == '.rc' or ext == '.res': + # gcc needs '.res' and '.rc' compiled to object files !!! + try: + self.spawn(["windres", "-i", src, "-o", obj]) + except DistutilsExecError as msg: + raise CompileError(msg) + else: # for other files use the C-compiler + try: + self.spawn(self.compiler_so + cc_args + [src, '-o', obj] + + extra_postargs) + except DistutilsExecError as msg: + raise CompileError(msg) + + def link(self, target_desc, objects, output_filename, output_dir=None, + libraries=None, library_dirs=None, runtime_library_dirs=None, + export_symbols=None, debug=0, extra_preargs=None, + extra_postargs=None, build_temp=None, target_lang=None): + """Link the objects.""" + # use separate copies, so we can modify the lists + extra_preargs = copy.copy(extra_preargs or []) + libraries = copy.copy(libraries or []) + objects = copy.copy(objects or []) + + # Additional libraries + libraries.extend(self.dll_libraries) + + # handle export symbols by creating a def-file + # with executables this only works with gcc/ld as linker + if ((export_symbols is not None) and + (target_desc != self.EXECUTABLE or self.linker_dll == "gcc")): + # (The linker doesn't do anything if output is up-to-date. + # So it would probably better to check if we really need this, + # but for this we had to insert some unchanged parts of + # UnixCCompiler, and this is not what we want.) + + # we want to put some files in the same directory as the + # object files are, build_temp doesn't help much + # where are the object files + temp_dir = os.path.dirname(objects[0]) + # name of dll to give the helper files the same base name + (dll_name, dll_extension) = os.path.splitext( + os.path.basename(output_filename)) + + # generate the filenames for these files + def_file = os.path.join(temp_dir, dll_name + ".def") + lib_file = os.path.join(temp_dir, 'lib' + dll_name + ".a") + + # Generate .def file + contents = [ + "LIBRARY %s" % os.path.basename(output_filename), + "EXPORTS"] + for sym in export_symbols: + contents.append(sym) + self.execute(write_file, (def_file, contents), + "writing %s" % def_file) + + # next add options for def-file and to creating import libraries + + # dllwrap uses different options than gcc/ld + if self.linker_dll == "dllwrap": + extra_preargs.extend(["--output-lib", lib_file]) + # for dllwrap we have to use a special option + extra_preargs.extend(["--def", def_file]) + # we use gcc/ld here and can be sure ld is >= 2.9.10 + else: + # doesn't work: bfd_close build\...\libfoo.a: Invalid operation + #extra_preargs.extend(["-Wl,--out-implib,%s" % lib_file]) + # for gcc/ld the def-file is specified as any object files + objects.append(def_file) + + #end: if ((export_symbols is not None) and + # (target_desc != self.EXECUTABLE or self.linker_dll == "gcc")): + + # who wants symbols and a many times larger output file + # should explicitly switch the debug mode on + # otherwise we let dllwrap/ld strip the output file + # (On my machine: 10KiB < stripped_file < ??100KiB + # unstripped_file = stripped_file + XXX KiB + # ( XXX=254 for a typical python extension)) + if not debug: + extra_preargs.append("-s") + + UnixCCompiler.link(self, target_desc, objects, output_filename, + output_dir, libraries, library_dirs, + runtime_library_dirs, + None, # export_symbols, we do this in our def-file + debug, extra_preargs, extra_postargs, build_temp, + target_lang) + + # -- Miscellaneous methods ----------------------------------------- + + def object_filenames(self, source_filenames, strip_dir=0, output_dir=''): + """Adds supports for rc and res files.""" + if output_dir is None: + output_dir = '' + obj_names = [] + for src_name in source_filenames: + # use normcase to make sure '.rc' is really '.rc' and not '.RC' + base, ext = os.path.splitext(os.path.normcase(src_name)) + if ext not in (self.src_extensions + ['.rc','.res']): + raise UnknownFileError("unknown file type '%s' (from '%s')" % \ + (ext, src_name)) + if strip_dir: + base = os.path.basename (base) + if ext in ('.res', '.rc'): + # these need to be compiled to object files + obj_names.append (os.path.join(output_dir, + base + ext + self.obj_extension)) + else: + obj_names.append (os.path.join(output_dir, + base + self.obj_extension)) + return obj_names + +# the same as cygwin plus some additional parameters +class Mingw32CCompiler(CygwinCCompiler): + """ Handles the Mingw32 port of the GNU C compiler to Windows. + """ + compiler_type = 'mingw32' + + def __init__(self, verbose=0, dry_run=0, force=0): + + CygwinCCompiler.__init__ (self, verbose, dry_run, force) + + # ld_version >= "2.13" support -shared so use it instead of + # -mdll -static + if ('gcc' in self.cc and self.ld_version < "2.13"): + shared_option = "-mdll -static" + else: + shared_option = "-shared" + + # A real mingw32 doesn't need to specify a different entry point, + # but cygwin 2.91.57 in no-cygwin-mode needs it. + if ('gcc' in self.cc and self.gcc_version <= "2.91.57"): + entry_point = '--entry _DllMain@12' + else: + entry_point = '' + + if is_cygwincc(self.cc): + raise CCompilerError( + 'Cygwin gcc cannot be used with --compiler=mingw32') + + self.set_executables(compiler='%s -O -Wall' % self.cc, + compiler_so='%s -mdll -O -Wall' % self.cc, + compiler_cxx='%s -O -Wall' % self.cxx, + linker_exe='%s' % self.cc, + linker_so='%s %s %s' + % (self.linker_dll, shared_option, + entry_point)) + # Maybe we should also append -mthreads, but then the finished + # dlls need another dll (mingwm10.dll see Mingw32 docs) + # (-mthreads: Support thread-safe exception handling on `Mingw32') + + # no additional libraries needed + self.dll_libraries=[] + + # Include the appropriate MSVC runtime library if Python was built + # with MSVC 7.0 or later. + self.dll_libraries = get_msvcr() + +# Because these compilers aren't configured in Python's pyconfig.h file by +# default, we should at least warn the user if he is using an unmodified +# version. + +CONFIG_H_OK = "ok" +CONFIG_H_NOTOK = "not ok" +CONFIG_H_UNCERTAIN = "uncertain" + +def check_config_h(): + """Check if the current Python installation appears amenable to building + extensions with GCC. + + Returns a tuple (status, details), where 'status' is one of the following + constants: + + - CONFIG_H_OK: all is well, go ahead and compile + - CONFIG_H_NOTOK: doesn't look good + - CONFIG_H_UNCERTAIN: not sure -- unable to read pyconfig.h + + 'details' is a human-readable string explaining the situation. + + Note there are two ways to conclude "OK": either 'sys.version' contains + the string "GCC" (implying that this Python was built with GCC), or the + installed "pyconfig.h" contains the string "__GNUC__". + """ + + # XXX since this function also checks sys.version, it's not strictly a + # "pyconfig.h" check -- should probably be renamed... + + from distutils import sysconfig + + # if sys.version contains GCC then python was compiled with GCC, and the + # pyconfig.h file should be OK + if "GCC" in sys.version: + return CONFIG_H_OK, "sys.version mentions 'GCC'" + + # Clang would also work + if "Clang" in sys.version: + return CONFIG_H_OK, "sys.version mentions 'Clang'" + + # let's see if __GNUC__ is mentioned in python.h + fn = sysconfig.get_config_h_filename() + try: + config_h = open(fn) + try: + if "__GNUC__" in config_h.read(): + return CONFIG_H_OK, "'%s' mentions '__GNUC__'" % fn + else: + return CONFIG_H_NOTOK, "'%s' does not mention '__GNUC__'" % fn + finally: + config_h.close() + except OSError as exc: + return (CONFIG_H_UNCERTAIN, + "couldn't read '%s': %s" % (fn, exc.strerror)) + +RE_VERSION = re.compile(br'(\d+\.\d+(\.\d+)*)') + +def _find_exe_version(cmd): + """Find the version of an executable by running `cmd` in the shell. + + If the command is not found, or the output does not match + `RE_VERSION`, returns None. + """ + executable = cmd.split()[0] + if find_executable(executable) is None: + return None + out = Popen(cmd, shell=True, stdout=PIPE).stdout + try: + out_string = out.read() + finally: + out.close() + result = RE_VERSION.search(out_string) + if result is None: + return None + # LooseVersion works with strings; decode + ver_str = result.group(1).decode() + with distutils.version.suppress_known_deprecation(): + return LooseVersion(ver_str) + +def get_versions(): + """ Try to find out the versions of gcc, ld and dllwrap. + + If not possible it returns None for it. + """ + commands = ['gcc -dumpversion', 'ld -v', 'dllwrap --version'] + return tuple([_find_exe_version(cmd) for cmd in commands]) + +def is_cygwincc(cc): + '''Try to determine if the compiler that would be used is from cygwin.''' + out_string = check_output([cc, '-dumpmachine']) + return out_string.strip().endswith(b'cygwin') diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/debug.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/debug.py new file mode 100644 index 00000000..daf1660f --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/debug.py @@ -0,0 +1,5 @@ +import os + +# If DISTUTILS_DEBUG is anything other than the empty string, we run in +# debug mode. +DEBUG = os.environ.get('DISTUTILS_DEBUG') diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/dep_util.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/dep_util.py new file mode 100644 index 00000000..d74f5e4e --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/dep_util.py @@ -0,0 +1,92 @@ +"""distutils.dep_util + +Utility functions for simple, timestamp-based dependency of files +and groups of files; also, function based entirely on such +timestamp dependency analysis.""" + +import os +from distutils.errors import DistutilsFileError + + +def newer (source, target): + """Return true if 'source' exists and is more recently modified than + 'target', or if 'source' exists and 'target' doesn't. Return false if + both exist and 'target' is the same age or younger than 'source'. + Raise DistutilsFileError if 'source' does not exist. + """ + if not os.path.exists(source): + raise DistutilsFileError("file '%s' does not exist" % + os.path.abspath(source)) + if not os.path.exists(target): + return 1 + + from stat import ST_MTIME + mtime1 = os.stat(source)[ST_MTIME] + mtime2 = os.stat(target)[ST_MTIME] + + return mtime1 > mtime2 + +# newer () + + +def newer_pairwise (sources, targets): + """Walk two filename lists in parallel, testing if each source is newer + than its corresponding target. Return a pair of lists (sources, + targets) where source is newer than target, according to the semantics + of 'newer()'. + """ + if len(sources) != len(targets): + raise ValueError("'sources' and 'targets' must be same length") + + # build a pair of lists (sources, targets) where source is newer + n_sources = [] + n_targets = [] + for i in range(len(sources)): + if newer(sources[i], targets[i]): + n_sources.append(sources[i]) + n_targets.append(targets[i]) + + return (n_sources, n_targets) + +# newer_pairwise () + + +def newer_group (sources, target, missing='error'): + """Return true if 'target' is out-of-date with respect to any file + listed in 'sources'. In other words, if 'target' exists and is newer + than every file in 'sources', return false; otherwise return true. + 'missing' controls what we do when a source file is missing; the + default ("error") is to blow up with an OSError from inside 'stat()'; + if it is "ignore", we silently drop any missing source files; if it is + "newer", any missing source files make us assume that 'target' is + out-of-date (this is handy in "dry-run" mode: it'll make you pretend to + carry out commands that wouldn't work because inputs are missing, but + that doesn't matter because you're not actually going to run the + commands). + """ + # If the target doesn't even exist, then it's definitely out-of-date. + if not os.path.exists(target): + return 1 + + # Otherwise we have to find out the hard way: if *any* source file + # is more recent than 'target', then 'target' is out-of-date and + # we can immediately return true. If we fall through to the end + # of the loop, then 'target' is up-to-date and we return false. + from stat import ST_MTIME + target_mtime = os.stat(target)[ST_MTIME] + for source in sources: + if not os.path.exists(source): + if missing == 'error': # blow up when we stat() the file + pass + elif missing == 'ignore': # missing source dropped from + continue # target's dependency list + elif missing == 'newer': # missing source means target is + return 1 # out-of-date + + source_mtime = os.stat(source)[ST_MTIME] + if source_mtime > target_mtime: + return 1 + else: + return 0 + +# newer_group () diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/dir_util.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/dir_util.py new file mode 100644 index 00000000..d5cd8e3e --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/dir_util.py @@ -0,0 +1,210 @@ +"""distutils.dir_util + +Utility functions for manipulating directories and directory trees.""" + +import os +import errno +from distutils.errors import DistutilsFileError, DistutilsInternalError +from distutils import log + +# cache for by mkpath() -- in addition to cheapening redundant calls, +# eliminates redundant "creating /foo/bar/baz" messages in dry-run mode +_path_created = {} + +# I don't use os.makedirs because a) it's new to Python 1.5.2, and +# b) it blows up if the directory already exists (I want to silently +# succeed in that case). +def mkpath(name, mode=0o777, verbose=1, dry_run=0): + """Create a directory and any missing ancestor directories. + + If the directory already exists (or if 'name' is the empty string, which + means the current directory, which of course exists), then do nothing. + Raise DistutilsFileError if unable to create some directory along the way + (eg. some sub-path exists, but is a file rather than a directory). + If 'verbose' is true, print a one-line summary of each mkdir to stdout. + Return the list of directories actually created. + """ + + global _path_created + + # Detect a common bug -- name is None + if not isinstance(name, str): + raise DistutilsInternalError( + "mkpath: 'name' must be a string (got %r)" % (name,)) + + # XXX what's the better way to handle verbosity? print as we create + # each directory in the path (the current behaviour), or only announce + # the creation of the whole path? (quite easy to do the latter since + # we're not using a recursive algorithm) + + name = os.path.normpath(name) + created_dirs = [] + if os.path.isdir(name) or name == '': + return created_dirs + if _path_created.get(os.path.abspath(name)): + return created_dirs + + (head, tail) = os.path.split(name) + tails = [tail] # stack of lone dirs to create + + while head and tail and not os.path.isdir(head): + (head, tail) = os.path.split(head) + tails.insert(0, tail) # push next higher dir onto stack + + # now 'head' contains the deepest directory that already exists + # (that is, the child of 'head' in 'name' is the highest directory + # that does *not* exist) + for d in tails: + #print "head = %s, d = %s: " % (head, d), + head = os.path.join(head, d) + abs_head = os.path.abspath(head) + + if _path_created.get(abs_head): + continue + + if verbose >= 1: + log.info("creating %s", head) + + if not dry_run: + try: + os.mkdir(head, mode) + except OSError as exc: + if not (exc.errno == errno.EEXIST and os.path.isdir(head)): + raise DistutilsFileError( + "could not create '%s': %s" % (head, exc.args[-1])) + created_dirs.append(head) + + _path_created[abs_head] = 1 + return created_dirs + +def create_tree(base_dir, files, mode=0o777, verbose=1, dry_run=0): + """Create all the empty directories under 'base_dir' needed to put 'files' + there. + + 'base_dir' is just the name of a directory which doesn't necessarily + exist yet; 'files' is a list of filenames to be interpreted relative to + 'base_dir'. 'base_dir' + the directory portion of every file in 'files' + will be created if it doesn't already exist. 'mode', 'verbose' and + 'dry_run' flags are as for 'mkpath()'. + """ + # First get the list of directories to create + need_dir = set() + for file in files: + need_dir.add(os.path.join(base_dir, os.path.dirname(file))) + + # Now create them + for dir in sorted(need_dir): + mkpath(dir, mode, verbose=verbose, dry_run=dry_run) + +def copy_tree(src, dst, preserve_mode=1, preserve_times=1, + preserve_symlinks=0, update=0, verbose=1, dry_run=0): + """Copy an entire directory tree 'src' to a new location 'dst'. + + Both 'src' and 'dst' must be directory names. If 'src' is not a + directory, raise DistutilsFileError. If 'dst' does not exist, it is + created with 'mkpath()'. The end result of the copy is that every + file in 'src' is copied to 'dst', and directories under 'src' are + recursively copied to 'dst'. Return the list of files that were + copied or might have been copied, using their output name. The + return value is unaffected by 'update' or 'dry_run': it is simply + the list of all files under 'src', with the names changed to be + under 'dst'. + + 'preserve_mode' and 'preserve_times' are the same as for + 'copy_file'; note that they only apply to regular files, not to + directories. If 'preserve_symlinks' is true, symlinks will be + copied as symlinks (on platforms that support them!); otherwise + (the default), the destination of the symlink will be copied. + 'update' and 'verbose' are the same as for 'copy_file'. + """ + from distutils.file_util import copy_file + + if not dry_run and not os.path.isdir(src): + raise DistutilsFileError( + "cannot copy tree '%s': not a directory" % src) + try: + names = os.listdir(src) + except OSError as e: + if dry_run: + names = [] + else: + raise DistutilsFileError( + "error listing files in '%s': %s" % (src, e.strerror)) + + if not dry_run: + mkpath(dst, verbose=verbose) + + outputs = [] + + for n in names: + src_name = os.path.join(src, n) + dst_name = os.path.join(dst, n) + + if n.startswith('.nfs'): + # skip NFS rename files + continue + + if preserve_symlinks and os.path.islink(src_name): + link_dest = os.readlink(src_name) + if verbose >= 1: + log.info("linking %s -> %s", dst_name, link_dest) + if not dry_run: + os.symlink(link_dest, dst_name) + outputs.append(dst_name) + + elif os.path.isdir(src_name): + outputs.extend( + copy_tree(src_name, dst_name, preserve_mode, + preserve_times, preserve_symlinks, update, + verbose=verbose, dry_run=dry_run)) + else: + copy_file(src_name, dst_name, preserve_mode, + preserve_times, update, verbose=verbose, + dry_run=dry_run) + outputs.append(dst_name) + + return outputs + +def _build_cmdtuple(path, cmdtuples): + """Helper for remove_tree().""" + for f in os.listdir(path): + real_f = os.path.join(path,f) + if os.path.isdir(real_f) and not os.path.islink(real_f): + _build_cmdtuple(real_f, cmdtuples) + else: + cmdtuples.append((os.remove, real_f)) + cmdtuples.append((os.rmdir, path)) + +def remove_tree(directory, verbose=1, dry_run=0): + """Recursively remove an entire directory tree. + + Any errors are ignored (apart from being reported to stdout if 'verbose' + is true). + """ + global _path_created + + if verbose >= 1: + log.info("removing '%s' (and everything under it)", directory) + if dry_run: + return + cmdtuples = [] + _build_cmdtuple(directory, cmdtuples) + for cmd in cmdtuples: + try: + cmd[0](cmd[1]) + # remove dir from cache if it's already there + abspath = os.path.abspath(cmd[1]) + if abspath in _path_created: + del _path_created[abspath] + except OSError as exc: + log.warn("error removing %s: %s", directory, exc) + +def ensure_relative(path): + """Take the full path 'path', and make it a relative path. + + This is useful to make 'path' the second argument to os.path.join(). + """ + drive, path = os.path.splitdrive(path) + if path[0:1] == os.sep: + path = drive + path[1:] + return path diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/dist.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/dist.py new file mode 100644 index 00000000..37db4d6c --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/dist.py @@ -0,0 +1,1257 @@ +"""distutils.dist + +Provides the Distribution class, which represents the module distribution +being built/installed/distributed. +""" + +import sys +import os +import re +from email import message_from_file + +try: + import warnings +except ImportError: + warnings = None + +from distutils.errors import * +from distutils.fancy_getopt import FancyGetopt, translate_longopt +from distutils.util import check_environ, strtobool, rfc822_escape +from distutils import log +from distutils.debug import DEBUG + +# Regex to define acceptable Distutils command names. This is not *quite* +# the same as a Python NAME -- I don't allow leading underscores. The fact +# that they're very similar is no coincidence; the default naming scheme is +# to look for a Python module named after the command. +command_re = re.compile(r'^[a-zA-Z]([a-zA-Z0-9_]*)$') + + +def _ensure_list(value, fieldname): + if isinstance(value, str): + # a string containing comma separated values is okay. It will + # be converted to a list by Distribution.finalize_options(). + pass + elif not isinstance(value, list): + # passing a tuple or an iterator perhaps, warn and convert + typename = type(value).__name__ + msg = "Warning: '{fieldname}' should be a list, got type '{typename}'" + msg = msg.format(**locals()) + log.log(log.WARN, msg) + value = list(value) + return value + + +class Distribution: + """The core of the Distutils. Most of the work hiding behind 'setup' + is really done within a Distribution instance, which farms the work out + to the Distutils commands specified on the command line. + + Setup scripts will almost never instantiate Distribution directly, + unless the 'setup()' function is totally inadequate to their needs. + However, it is conceivable that a setup script might wish to subclass + Distribution for some specialized purpose, and then pass the subclass + to 'setup()' as the 'distclass' keyword argument. If so, it is + necessary to respect the expectations that 'setup' has of Distribution. + See the code for 'setup()', in core.py, for details. + """ + + # 'global_options' describes the command-line options that may be + # supplied to the setup script prior to any actual commands. + # Eg. "./setup.py -n" or "./setup.py --quiet" both take advantage of + # these global options. This list should be kept to a bare minimum, + # since every global option is also valid as a command option -- and we + # don't want to pollute the commands with too many options that they + # have minimal control over. + # The fourth entry for verbose means that it can be repeated. + global_options = [ + ('verbose', 'v', "run verbosely (default)", 1), + ('quiet', 'q', "run quietly (turns verbosity off)"), + ('dry-run', 'n', "don't actually do anything"), + ('help', 'h', "show detailed help message"), + ('no-user-cfg', None, + 'ignore pydistutils.cfg in your home directory'), + ] + + # 'common_usage' is a short (2-3 line) string describing the common + # usage of the setup script. + common_usage = """\ +Common commands: (see '--help-commands' for more) + + setup.py build will build the package underneath 'build/' + setup.py install will install the package +""" + + # options that are not propagated to the commands + display_options = [ + ('help-commands', None, + "list all available commands"), + ('name', None, + "print package name"), + ('version', 'V', + "print package version"), + ('fullname', None, + "print -"), + ('author', None, + "print the author's name"), + ('author-email', None, + "print the author's email address"), + ('maintainer', None, + "print the maintainer's name"), + ('maintainer-email', None, + "print the maintainer's email address"), + ('contact', None, + "print the maintainer's name if known, else the author's"), + ('contact-email', None, + "print the maintainer's email address if known, else the author's"), + ('url', None, + "print the URL for this package"), + ('license', None, + "print the license of the package"), + ('licence', None, + "alias for --license"), + ('description', None, + "print the package description"), + ('long-description', None, + "print the long package description"), + ('platforms', None, + "print the list of platforms"), + ('classifiers', None, + "print the list of classifiers"), + ('keywords', None, + "print the list of keywords"), + ('provides', None, + "print the list of packages/modules provided"), + ('requires', None, + "print the list of packages/modules required"), + ('obsoletes', None, + "print the list of packages/modules made obsolete") + ] + display_option_names = [translate_longopt(x[0]) for x in display_options] + + # negative options are options that exclude other options + negative_opt = {'quiet': 'verbose'} + + # -- Creation/initialization methods ------------------------------- + + def __init__(self, attrs=None): + """Construct a new Distribution instance: initialize all the + attributes of a Distribution, and then use 'attrs' (a dictionary + mapping attribute names to values) to assign some of those + attributes their "real" values. (Any attributes not mentioned in + 'attrs' will be assigned to some null value: 0, None, an empty list + or dictionary, etc.) Most importantly, initialize the + 'command_obj' attribute to the empty dictionary; this will be + filled in with real command objects by 'parse_command_line()'. + """ + + # Default values for our command-line options + self.verbose = 1 + self.dry_run = 0 + self.help = 0 + for attr in self.display_option_names: + setattr(self, attr, 0) + + # Store the distribution meta-data (name, version, author, and so + # forth) in a separate object -- we're getting to have enough + # information here (and enough command-line options) that it's + # worth it. Also delegate 'get_XXX()' methods to the 'metadata' + # object in a sneaky and underhanded (but efficient!) way. + self.metadata = DistributionMetadata() + for basename in self.metadata._METHOD_BASENAMES: + method_name = "get_" + basename + setattr(self, method_name, getattr(self.metadata, method_name)) + + # 'cmdclass' maps command names to class objects, so we + # can 1) quickly figure out which class to instantiate when + # we need to create a new command object, and 2) have a way + # for the setup script to override command classes + self.cmdclass = {} + + # 'command_packages' is a list of packages in which commands + # are searched for. The factory for command 'foo' is expected + # to be named 'foo' in the module 'foo' in one of the packages + # named here. This list is searched from the left; an error + # is raised if no named package provides the command being + # searched for. (Always access using get_command_packages().) + self.command_packages = None + + # 'script_name' and 'script_args' are usually set to sys.argv[0] + # and sys.argv[1:], but they can be overridden when the caller is + # not necessarily a setup script run from the command-line. + self.script_name = None + self.script_args = None + + # 'command_options' is where we store command options between + # parsing them (from config files, the command-line, etc.) and when + # they are actually needed -- ie. when the command in question is + # instantiated. It is a dictionary of dictionaries of 2-tuples: + # command_options = { command_name : { option : (source, value) } } + self.command_options = {} + + # 'dist_files' is the list of (command, pyversion, file) that + # have been created by any dist commands run so far. This is + # filled regardless of whether the run is dry or not. pyversion + # gives sysconfig.get_python_version() if the dist file is + # specific to a Python version, 'any' if it is good for all + # Python versions on the target platform, and '' for a source + # file. pyversion should not be used to specify minimum or + # maximum required Python versions; use the metainfo for that + # instead. + self.dist_files = [] + + # These options are really the business of various commands, rather + # than of the Distribution itself. We provide aliases for them in + # Distribution as a convenience to the developer. + self.packages = None + self.package_data = {} + self.package_dir = None + self.py_modules = None + self.libraries = None + self.headers = None + self.ext_modules = None + self.ext_package = None + self.include_dirs = None + self.extra_path = None + self.scripts = None + self.data_files = None + self.password = '' + + # And now initialize bookkeeping stuff that can't be supplied by + # the caller at all. 'command_obj' maps command names to + # Command instances -- that's how we enforce that every command + # class is a singleton. + self.command_obj = {} + + # 'have_run' maps command names to boolean values; it keeps track + # of whether we have actually run a particular command, to make it + # cheap to "run" a command whenever we think we might need to -- if + # it's already been done, no need for expensive filesystem + # operations, we just check the 'have_run' dictionary and carry on. + # It's only safe to query 'have_run' for a command class that has + # been instantiated -- a false value will be inserted when the + # command object is created, and replaced with a true value when + # the command is successfully run. Thus it's probably best to use + # '.get()' rather than a straight lookup. + self.have_run = {} + + # Now we'll use the attrs dictionary (ultimately, keyword args from + # the setup script) to possibly override any or all of these + # distribution options. + + if attrs: + # Pull out the set of command options and work on them + # specifically. Note that this order guarantees that aliased + # command options will override any supplied redundantly + # through the general options dictionary. + options = attrs.get('options') + if options is not None: + del attrs['options'] + for (command, cmd_options) in options.items(): + opt_dict = self.get_option_dict(command) + for (opt, val) in cmd_options.items(): + opt_dict[opt] = ("setup script", val) + + if 'licence' in attrs: + attrs['license'] = attrs['licence'] + del attrs['licence'] + msg = "'licence' distribution option is deprecated; use 'license'" + if warnings is not None: + warnings.warn(msg) + else: + sys.stderr.write(msg + "\n") + + # Now work on the rest of the attributes. Any attribute that's + # not already defined is invalid! + for (key, val) in attrs.items(): + if hasattr(self.metadata, "set_" + key): + getattr(self.metadata, "set_" + key)(val) + elif hasattr(self.metadata, key): + setattr(self.metadata, key, val) + elif hasattr(self, key): + setattr(self, key, val) + else: + msg = "Unknown distribution option: %s" % repr(key) + warnings.warn(msg) + + # no-user-cfg is handled before other command line args + # because other args override the config files, and this + # one is needed before we can load the config files. + # If attrs['script_args'] wasn't passed, assume false. + # + # This also make sure we just look at the global options + self.want_user_cfg = True + + if self.script_args is not None: + for arg in self.script_args: + if not arg.startswith('-'): + break + if arg == '--no-user-cfg': + self.want_user_cfg = False + break + + self.finalize_options() + + def get_option_dict(self, command): + """Get the option dictionary for a given command. If that + command's option dictionary hasn't been created yet, then create it + and return the new dictionary; otherwise, return the existing + option dictionary. + """ + dict = self.command_options.get(command) + if dict is None: + dict = self.command_options[command] = {} + return dict + + def dump_option_dicts(self, header=None, commands=None, indent=""): + from pprint import pformat + + if commands is None: # dump all command option dicts + commands = sorted(self.command_options.keys()) + + if header is not None: + self.announce(indent + header) + indent = indent + " " + + if not commands: + self.announce(indent + "no commands known yet") + return + + for cmd_name in commands: + opt_dict = self.command_options.get(cmd_name) + if opt_dict is None: + self.announce(indent + + "no option dict for '%s' command" % cmd_name) + else: + self.announce(indent + + "option dict for '%s' command:" % cmd_name) + out = pformat(opt_dict) + for line in out.split('\n'): + self.announce(indent + " " + line) + + # -- Config file finding/parsing methods --------------------------- + + def find_config_files(self): + """Find as many configuration files as should be processed for this + platform, and return a list of filenames in the order in which they + should be parsed. The filenames returned are guaranteed to exist + (modulo nasty race conditions). + + There are three possible config files: distutils.cfg in the + Distutils installation directory (ie. where the top-level + Distutils __inst__.py file lives), a file in the user's home + directory named .pydistutils.cfg on Unix and pydistutils.cfg + on Windows/Mac; and setup.cfg in the current directory. + + The file in the user's home directory can be disabled with the + --no-user-cfg option. + """ + files = [] + check_environ() + + # Where to look for the system-wide Distutils config file + sys_dir = os.path.dirname(sys.modules['distutils'].__file__) + + # Look for the system config file + sys_file = os.path.join(sys_dir, "distutils.cfg") + if os.path.isfile(sys_file): + files.append(sys_file) + + # What to call the per-user config file + if os.name == 'posix': + user_filename = ".pydistutils.cfg" + else: + user_filename = "pydistutils.cfg" + + # And look for the user config file + if self.want_user_cfg: + user_file = os.path.join(os.path.expanduser('~'), user_filename) + if os.path.isfile(user_file): + files.append(user_file) + + # All platforms support local setup.cfg + local_file = "setup.cfg" + if os.path.isfile(local_file): + files.append(local_file) + + if DEBUG: + self.announce("using config files: %s" % ', '.join(files)) + + return files + + def parse_config_files(self, filenames=None): + from configparser import ConfigParser + + # Ignore install directory options if we have a venv + if sys.prefix != sys.base_prefix: + ignore_options = [ + 'install-base', 'install-platbase', 'install-lib', + 'install-platlib', 'install-purelib', 'install-headers', + 'install-scripts', 'install-data', 'prefix', 'exec-prefix', + 'home', 'user', 'root'] + else: + ignore_options = [] + + ignore_options = frozenset(ignore_options) + + if filenames is None: + filenames = self.find_config_files() + + if DEBUG: + self.announce("Distribution.parse_config_files():") + + parser = ConfigParser() + for filename in filenames: + if DEBUG: + self.announce(" reading %s" % filename) + parser.read(filename) + for section in parser.sections(): + options = parser.options(section) + opt_dict = self.get_option_dict(section) + + for opt in options: + if opt != '__name__' and opt not in ignore_options: + val = parser.get(section,opt) + opt = opt.replace('-', '_') + opt_dict[opt] = (filename, val) + + # Make the ConfigParser forget everything (so we retain + # the original filenames that options come from) + parser.__init__() + + # If there was a "global" section in the config file, use it + # to set Distribution options. + + if 'global' in self.command_options: + for (opt, (src, val)) in self.command_options['global'].items(): + alias = self.negative_opt.get(opt) + try: + if alias: + setattr(self, alias, not strtobool(val)) + elif opt in ('verbose', 'dry_run'): # ugh! + setattr(self, opt, strtobool(val)) + else: + setattr(self, opt, val) + except ValueError as msg: + raise DistutilsOptionError(msg) + + # -- Command-line parsing methods ---------------------------------- + + def parse_command_line(self): + """Parse the setup script's command line, taken from the + 'script_args' instance attribute (which defaults to 'sys.argv[1:]' + -- see 'setup()' in core.py). This list is first processed for + "global options" -- options that set attributes of the Distribution + instance. Then, it is alternately scanned for Distutils commands + and options for that command. Each new command terminates the + options for the previous command. The allowed options for a + command are determined by the 'user_options' attribute of the + command class -- thus, we have to be able to load command classes + in order to parse the command line. Any error in that 'options' + attribute raises DistutilsGetoptError; any error on the + command-line raises DistutilsArgError. If no Distutils commands + were found on the command line, raises DistutilsArgError. Return + true if command-line was successfully parsed and we should carry + on with executing commands; false if no errors but we shouldn't + execute commands (currently, this only happens if user asks for + help). + """ + # + # We now have enough information to show the Macintosh dialog + # that allows the user to interactively specify the "command line". + # + toplevel_options = self._get_toplevel_options() + + # We have to parse the command line a bit at a time -- global + # options, then the first command, then its options, and so on -- + # because each command will be handled by a different class, and + # the options that are valid for a particular class aren't known + # until we have loaded the command class, which doesn't happen + # until we know what the command is. + + self.commands = [] + parser = FancyGetopt(toplevel_options + self.display_options) + parser.set_negative_aliases(self.negative_opt) + parser.set_aliases({'licence': 'license'}) + args = parser.getopt(args=self.script_args, object=self) + option_order = parser.get_option_order() + log.set_verbosity(self.verbose) + + # for display options we return immediately + if self.handle_display_options(option_order): + return + while args: + args = self._parse_command_opts(parser, args) + if args is None: # user asked for help (and got it) + return + + # Handle the cases of --help as a "global" option, ie. + # "setup.py --help" and "setup.py --help command ...". For the + # former, we show global options (--verbose, --dry-run, etc.) + # and display-only options (--name, --version, etc.); for the + # latter, we omit the display-only options and show help for + # each command listed on the command line. + if self.help: + self._show_help(parser, + display_options=len(self.commands) == 0, + commands=self.commands) + return + + # Oops, no commands found -- an end-user error + if not self.commands: + raise DistutilsArgError("no commands supplied") + + # All is well: return true + return True + + def _get_toplevel_options(self): + """Return the non-display options recognized at the top level. + + This includes options that are recognized *only* at the top + level as well as options recognized for commands. + """ + return self.global_options + [ + ("command-packages=", None, + "list of packages that provide distutils commands"), + ] + + def _parse_command_opts(self, parser, args): + """Parse the command-line options for a single command. + 'parser' must be a FancyGetopt instance; 'args' must be the list + of arguments, starting with the current command (whose options + we are about to parse). Returns a new version of 'args' with + the next command at the front of the list; will be the empty + list if there are no more commands on the command line. Returns + None if the user asked for help on this command. + """ + # late import because of mutual dependence between these modules + from distutils.cmd import Command + + # Pull the current command from the head of the command line + command = args[0] + if not command_re.match(command): + raise SystemExit("invalid command name '%s'" % command) + self.commands.append(command) + + # Dig up the command class that implements this command, so we + # 1) know that it's a valid command, and 2) know which options + # it takes. + try: + cmd_class = self.get_command_class(command) + except DistutilsModuleError as msg: + raise DistutilsArgError(msg) + + # Require that the command class be derived from Command -- want + # to be sure that the basic "command" interface is implemented. + if not issubclass(cmd_class, Command): + raise DistutilsClassError( + "command class %s must subclass Command" % cmd_class) + + # Also make sure that the command object provides a list of its + # known options. + if not (hasattr(cmd_class, 'user_options') and + isinstance(cmd_class.user_options, list)): + msg = ("command class %s must provide " + "'user_options' attribute (a list of tuples)") + raise DistutilsClassError(msg % cmd_class) + + # If the command class has a list of negative alias options, + # merge it in with the global negative aliases. + negative_opt = self.negative_opt + if hasattr(cmd_class, 'negative_opt'): + negative_opt = negative_opt.copy() + negative_opt.update(cmd_class.negative_opt) + + # Check for help_options in command class. They have a different + # format (tuple of four) so we need to preprocess them here. + if (hasattr(cmd_class, 'help_options') and + isinstance(cmd_class.help_options, list)): + help_options = fix_help_options(cmd_class.help_options) + else: + help_options = [] + + # All commands support the global options too, just by adding + # in 'global_options'. + parser.set_option_table(self.global_options + + cmd_class.user_options + + help_options) + parser.set_negative_aliases(negative_opt) + (args, opts) = parser.getopt(args[1:]) + if hasattr(opts, 'help') and opts.help: + self._show_help(parser, display_options=0, commands=[cmd_class]) + return + + if (hasattr(cmd_class, 'help_options') and + isinstance(cmd_class.help_options, list)): + help_option_found=0 + for (help_option, short, desc, func) in cmd_class.help_options: + if hasattr(opts, parser.get_attr_name(help_option)): + help_option_found=1 + if callable(func): + func() + else: + raise DistutilsClassError( + "invalid help function %r for help option '%s': " + "must be a callable object (function, etc.)" + % (func, help_option)) + + if help_option_found: + return + + # Put the options from the command-line into their official + # holding pen, the 'command_options' dictionary. + opt_dict = self.get_option_dict(command) + for (name, value) in vars(opts).items(): + opt_dict[name] = ("command line", value) + + return args + + def finalize_options(self): + """Set final values for all the options on the Distribution + instance, analogous to the .finalize_options() method of Command + objects. + """ + for attr in ('keywords', 'platforms'): + value = getattr(self.metadata, attr) + if value is None: + continue + if isinstance(value, str): + value = [elm.strip() for elm in value.split(',')] + setattr(self.metadata, attr, value) + + def _show_help(self, parser, global_options=1, display_options=1, + commands=[]): + """Show help for the setup script command-line in the form of + several lists of command-line options. 'parser' should be a + FancyGetopt instance; do not expect it to be returned in the + same state, as its option table will be reset to make it + generate the correct help text. + + If 'global_options' is true, lists the global options: + --verbose, --dry-run, etc. If 'display_options' is true, lists + the "display-only" options: --name, --version, etc. Finally, + lists per-command help for every command name or command class + in 'commands'. + """ + # late import because of mutual dependence between these modules + from distutils.core import gen_usage + from distutils.cmd import Command + + if global_options: + if display_options: + options = self._get_toplevel_options() + else: + options = self.global_options + parser.set_option_table(options) + parser.print_help(self.common_usage + "\nGlobal options:") + print('') + + if display_options: + parser.set_option_table(self.display_options) + parser.print_help( + "Information display options (just display " + + "information, ignore any commands)") + print('') + + for command in self.commands: + if isinstance(command, type) and issubclass(command, Command): + klass = command + else: + klass = self.get_command_class(command) + if (hasattr(klass, 'help_options') and + isinstance(klass.help_options, list)): + parser.set_option_table(klass.user_options + + fix_help_options(klass.help_options)) + else: + parser.set_option_table(klass.user_options) + parser.print_help("Options for '%s' command:" % klass.__name__) + print('') + + print(gen_usage(self.script_name)) + + def handle_display_options(self, option_order): + """If there were any non-global "display-only" options + (--help-commands or the metadata display options) on the command + line, display the requested info and return true; else return + false. + """ + from distutils.core import gen_usage + + # User just wants a list of commands -- we'll print it out and stop + # processing now (ie. if they ran "setup --help-commands foo bar", + # we ignore "foo bar"). + if self.help_commands: + self.print_commands() + print('') + print(gen_usage(self.script_name)) + return 1 + + # If user supplied any of the "display metadata" options, then + # display that metadata in the order in which the user supplied the + # metadata options. + any_display_options = 0 + is_display_option = {} + for option in self.display_options: + is_display_option[option[0]] = 1 + + for (opt, val) in option_order: + if val and is_display_option.get(opt): + opt = translate_longopt(opt) + value = getattr(self.metadata, "get_"+opt)() + if opt in ['keywords', 'platforms']: + print(','.join(value)) + elif opt in ('classifiers', 'provides', 'requires', + 'obsoletes'): + print('\n'.join(value)) + else: + print(value) + any_display_options = 1 + + return any_display_options + + def print_command_list(self, commands, header, max_length): + """Print a subset of the list of all commands -- used by + 'print_commands()'. + """ + print(header + ":") + + for cmd in commands: + klass = self.cmdclass.get(cmd) + if not klass: + klass = self.get_command_class(cmd) + try: + description = klass.description + except AttributeError: + description = "(no description available)" + + print(" %-*s %s" % (max_length, cmd, description)) + + def print_commands(self): + """Print out a help message listing all available commands with a + description of each. The list is divided into "standard commands" + (listed in distutils.command.__all__) and "extra commands" + (mentioned in self.cmdclass, but not a standard command). The + descriptions come from the command class attribute + 'description'. + """ + import distutils.command + std_commands = distutils.command.__all__ + is_std = {} + for cmd in std_commands: + is_std[cmd] = 1 + + extra_commands = [] + for cmd in self.cmdclass.keys(): + if not is_std.get(cmd): + extra_commands.append(cmd) + + max_length = 0 + for cmd in (std_commands + extra_commands): + if len(cmd) > max_length: + max_length = len(cmd) + + self.print_command_list(std_commands, + "Standard commands", + max_length) + if extra_commands: + print() + self.print_command_list(extra_commands, + "Extra commands", + max_length) + + def get_command_list(self): + """Get a list of (command, description) tuples. + The list is divided into "standard commands" (listed in + distutils.command.__all__) and "extra commands" (mentioned in + self.cmdclass, but not a standard command). The descriptions come + from the command class attribute 'description'. + """ + # Currently this is only used on Mac OS, for the Mac-only GUI + # Distutils interface (by Jack Jansen) + import distutils.command + std_commands = distutils.command.__all__ + is_std = {} + for cmd in std_commands: + is_std[cmd] = 1 + + extra_commands = [] + for cmd in self.cmdclass.keys(): + if not is_std.get(cmd): + extra_commands.append(cmd) + + rv = [] + for cmd in (std_commands + extra_commands): + klass = self.cmdclass.get(cmd) + if not klass: + klass = self.get_command_class(cmd) + try: + description = klass.description + except AttributeError: + description = "(no description available)" + rv.append((cmd, description)) + return rv + + # -- Command class/object methods ---------------------------------- + + def get_command_packages(self): + """Return a list of packages from which commands are loaded.""" + pkgs = self.command_packages + if not isinstance(pkgs, list): + if pkgs is None: + pkgs = '' + pkgs = [pkg.strip() for pkg in pkgs.split(',') if pkg != ''] + if "distutils.command" not in pkgs: + pkgs.insert(0, "distutils.command") + self.command_packages = pkgs + return pkgs + + def get_command_class(self, command): + """Return the class that implements the Distutils command named by + 'command'. First we check the 'cmdclass' dictionary; if the + command is mentioned there, we fetch the class object from the + dictionary and return it. Otherwise we load the command module + ("distutils.command." + command) and fetch the command class from + the module. The loaded class is also stored in 'cmdclass' + to speed future calls to 'get_command_class()'. + + Raises DistutilsModuleError if the expected module could not be + found, or if that module does not define the expected class. + """ + klass = self.cmdclass.get(command) + if klass: + return klass + + for pkgname in self.get_command_packages(): + module_name = "%s.%s" % (pkgname, command) + klass_name = command + + try: + __import__(module_name) + module = sys.modules[module_name] + except ImportError: + continue + + try: + klass = getattr(module, klass_name) + except AttributeError: + raise DistutilsModuleError( + "invalid command '%s' (no class '%s' in module '%s')" + % (command, klass_name, module_name)) + + self.cmdclass[command] = klass + return klass + + raise DistutilsModuleError("invalid command '%s'" % command) + + def get_command_obj(self, command, create=1): + """Return the command object for 'command'. Normally this object + is cached on a previous call to 'get_command_obj()'; if no command + object for 'command' is in the cache, then we either create and + return it (if 'create' is true) or return None. + """ + cmd_obj = self.command_obj.get(command) + if not cmd_obj and create: + if DEBUG: + self.announce("Distribution.get_command_obj(): " + "creating '%s' command object" % command) + + klass = self.get_command_class(command) + cmd_obj = self.command_obj[command] = klass(self) + self.have_run[command] = 0 + + # Set any options that were supplied in config files + # or on the command line. (NB. support for error + # reporting is lame here: any errors aren't reported + # until 'finalize_options()' is called, which means + # we won't report the source of the error.) + options = self.command_options.get(command) + if options: + self._set_command_options(cmd_obj, options) + + return cmd_obj + + def _set_command_options(self, command_obj, option_dict=None): + """Set the options for 'command_obj' from 'option_dict'. Basically + this means copying elements of a dictionary ('option_dict') to + attributes of an instance ('command'). + + 'command_obj' must be a Command instance. If 'option_dict' is not + supplied, uses the standard option dictionary for this command + (from 'self.command_options'). + """ + command_name = command_obj.get_command_name() + if option_dict is None: + option_dict = self.get_option_dict(command_name) + + if DEBUG: + self.announce(" setting options for '%s' command:" % command_name) + for (option, (source, value)) in option_dict.items(): + if DEBUG: + self.announce(" %s = %s (from %s)" % (option, value, + source)) + try: + bool_opts = [translate_longopt(o) + for o in command_obj.boolean_options] + except AttributeError: + bool_opts = [] + try: + neg_opt = command_obj.negative_opt + except AttributeError: + neg_opt = {} + + try: + is_string = isinstance(value, str) + if option in neg_opt and is_string: + setattr(command_obj, neg_opt[option], not strtobool(value)) + elif option in bool_opts and is_string: + setattr(command_obj, option, strtobool(value)) + elif hasattr(command_obj, option): + setattr(command_obj, option, value) + else: + raise DistutilsOptionError( + "error in %s: command '%s' has no such option '%s'" + % (source, command_name, option)) + except ValueError as msg: + raise DistutilsOptionError(msg) + + def reinitialize_command(self, command, reinit_subcommands=0): + """Reinitializes a command to the state it was in when first + returned by 'get_command_obj()': ie., initialized but not yet + finalized. This provides the opportunity to sneak option + values in programmatically, overriding or supplementing + user-supplied values from the config files and command line. + You'll have to re-finalize the command object (by calling + 'finalize_options()' or 'ensure_finalized()') before using it for + real. + + 'command' should be a command name (string) or command object. If + 'reinit_subcommands' is true, also reinitializes the command's + sub-commands, as declared by the 'sub_commands' class attribute (if + it has one). See the "install" command for an example. Only + reinitializes the sub-commands that actually matter, ie. those + whose test predicates return true. + + Returns the reinitialized command object. + """ + from distutils.cmd import Command + if not isinstance(command, Command): + command_name = command + command = self.get_command_obj(command_name) + else: + command_name = command.get_command_name() + + if not command.finalized: + return command + command.initialize_options() + command.finalized = 0 + self.have_run[command_name] = 0 + self._set_command_options(command) + + if reinit_subcommands: + for sub in command.get_sub_commands(): + self.reinitialize_command(sub, reinit_subcommands) + + return command + + # -- Methods that operate on the Distribution ---------------------- + + def announce(self, msg, level=log.INFO): + log.log(level, msg) + + def run_commands(self): + """Run each command that was seen on the setup script command line. + Uses the list of commands found and cache of command objects + created by 'get_command_obj()'. + """ + for cmd in self.commands: + self.run_command(cmd) + + # -- Methods that operate on its Commands -------------------------- + + def run_command(self, command): + """Do whatever it takes to run a command (including nothing at all, + if the command has already been run). Specifically: if we have + already created and run the command named by 'command', return + silently without doing anything. If the command named by 'command' + doesn't even have a command object yet, create one. Then invoke + 'run()' on that command object (or an existing one). + """ + # Already been here, done that? then return silently. + if self.have_run.get(command): + return + + log.info("running %s", command) + cmd_obj = self.get_command_obj(command) + cmd_obj.ensure_finalized() + cmd_obj.run() + self.have_run[command] = 1 + + # -- Distribution query methods ------------------------------------ + + def has_pure_modules(self): + return len(self.packages or self.py_modules or []) > 0 + + def has_ext_modules(self): + return self.ext_modules and len(self.ext_modules) > 0 + + def has_c_libraries(self): + return self.libraries and len(self.libraries) > 0 + + def has_modules(self): + return self.has_pure_modules() or self.has_ext_modules() + + def has_headers(self): + return self.headers and len(self.headers) > 0 + + def has_scripts(self): + return self.scripts and len(self.scripts) > 0 + + def has_data_files(self): + return self.data_files and len(self.data_files) > 0 + + def is_pure(self): + return (self.has_pure_modules() and + not self.has_ext_modules() and + not self.has_c_libraries()) + + # -- Metadata query methods ---------------------------------------- + + # If you're looking for 'get_name()', 'get_version()', and so forth, + # they are defined in a sneaky way: the constructor binds self.get_XXX + # to self.metadata.get_XXX. The actual code is in the + # DistributionMetadata class, below. + +class DistributionMetadata: + """Dummy class to hold the distribution meta-data: name, version, + author, and so forth. + """ + + _METHOD_BASENAMES = ("name", "version", "author", "author_email", + "maintainer", "maintainer_email", "url", + "license", "description", "long_description", + "keywords", "platforms", "fullname", "contact", + "contact_email", "classifiers", "download_url", + # PEP 314 + "provides", "requires", "obsoletes", + ) + + def __init__(self, path=None): + if path is not None: + self.read_pkg_file(open(path)) + else: + self.name = None + self.version = None + self.author = None + self.author_email = None + self.maintainer = None + self.maintainer_email = None + self.url = None + self.license = None + self.description = None + self.long_description = None + self.keywords = None + self.platforms = None + self.classifiers = None + self.download_url = None + # PEP 314 + self.provides = None + self.requires = None + self.obsoletes = None + + def read_pkg_file(self, file): + """Reads the metadata values from a file object.""" + msg = message_from_file(file) + + def _read_field(name): + value = msg[name] + if value == 'UNKNOWN': + return None + return value + + def _read_list(name): + values = msg.get_all(name, None) + if values == []: + return None + return values + + metadata_version = msg['metadata-version'] + self.name = _read_field('name') + self.version = _read_field('version') + self.description = _read_field('summary') + # we are filling author only. + self.author = _read_field('author') + self.maintainer = None + self.author_email = _read_field('author-email') + self.maintainer_email = None + self.url = _read_field('home-page') + self.license = _read_field('license') + + if 'download-url' in msg: + self.download_url = _read_field('download-url') + else: + self.download_url = None + + self.long_description = _read_field('description') + self.description = _read_field('summary') + + if 'keywords' in msg: + self.keywords = _read_field('keywords').split(',') + + self.platforms = _read_list('platform') + self.classifiers = _read_list('classifier') + + # PEP 314 - these fields only exist in 1.1 + if metadata_version == '1.1': + self.requires = _read_list('requires') + self.provides = _read_list('provides') + self.obsoletes = _read_list('obsoletes') + else: + self.requires = None + self.provides = None + self.obsoletes = None + + def write_pkg_info(self, base_dir): + """Write the PKG-INFO file into the release tree. + """ + with open(os.path.join(base_dir, 'PKG-INFO'), 'w', + encoding='UTF-8') as pkg_info: + self.write_pkg_file(pkg_info) + + def write_pkg_file(self, file): + """Write the PKG-INFO format data to a file object. + """ + version = '1.0' + if (self.provides or self.requires or self.obsoletes or + self.classifiers or self.download_url): + version = '1.1' + + file.write('Metadata-Version: %s\n' % version) + file.write('Name: %s\n' % self.get_name()) + file.write('Version: %s\n' % self.get_version()) + file.write('Summary: %s\n' % self.get_description()) + file.write('Home-page: %s\n' % self.get_url()) + file.write('Author: %s\n' % self.get_contact()) + file.write('Author-email: %s\n' % self.get_contact_email()) + file.write('License: %s\n' % self.get_license()) + if self.download_url: + file.write('Download-URL: %s\n' % self.download_url) + + long_desc = rfc822_escape(self.get_long_description()) + file.write('Description: %s\n' % long_desc) + + keywords = ','.join(self.get_keywords()) + if keywords: + file.write('Keywords: %s\n' % keywords) + + self._write_list(file, 'Platform', self.get_platforms()) + self._write_list(file, 'Classifier', self.get_classifiers()) + + # PEP 314 + self._write_list(file, 'Requires', self.get_requires()) + self._write_list(file, 'Provides', self.get_provides()) + self._write_list(file, 'Obsoletes', self.get_obsoletes()) + + def _write_list(self, file, name, values): + for value in values: + file.write('%s: %s\n' % (name, value)) + + # -- Metadata query methods ---------------------------------------- + + def get_name(self): + return self.name or "UNKNOWN" + + def get_version(self): + return self.version or "0.0.0" + + def get_fullname(self): + return "%s-%s" % (self.get_name(), self.get_version()) + + def get_author(self): + return self.author or "UNKNOWN" + + def get_author_email(self): + return self.author_email or "UNKNOWN" + + def get_maintainer(self): + return self.maintainer or "UNKNOWN" + + def get_maintainer_email(self): + return self.maintainer_email or "UNKNOWN" + + def get_contact(self): + return self.maintainer or self.author or "UNKNOWN" + + def get_contact_email(self): + return self.maintainer_email or self.author_email or "UNKNOWN" + + def get_url(self): + return self.url or "UNKNOWN" + + def get_license(self): + return self.license or "UNKNOWN" + get_licence = get_license + + def get_description(self): + return self.description or "UNKNOWN" + + def get_long_description(self): + return self.long_description or "UNKNOWN" + + def get_keywords(self): + return self.keywords or [] + + def set_keywords(self, value): + self.keywords = _ensure_list(value, 'keywords') + + def get_platforms(self): + return self.platforms or ["UNKNOWN"] + + def set_platforms(self, value): + self.platforms = _ensure_list(value, 'platforms') + + def get_classifiers(self): + return self.classifiers or [] + + def set_classifiers(self, value): + self.classifiers = _ensure_list(value, 'classifiers') + + def get_download_url(self): + return self.download_url or "UNKNOWN" + + # PEP 314 + def get_requires(self): + return self.requires or [] + + def set_requires(self, value): + import distutils.versionpredicate + for v in value: + distutils.versionpredicate.VersionPredicate(v) + self.requires = list(value) + + def get_provides(self): + return self.provides or [] + + def set_provides(self, value): + value = [v.strip() for v in value] + for v in value: + import distutils.versionpredicate + distutils.versionpredicate.split_provision(v) + self.provides = value + + def get_obsoletes(self): + return self.obsoletes or [] + + def set_obsoletes(self, value): + import distutils.versionpredicate + for v in value: + distutils.versionpredicate.VersionPredicate(v) + self.obsoletes = list(value) + +def fix_help_options(options): + """Convert a 4-tuple 'help_options' list as found in various command + classes to the 3-tuple form required by FancyGetopt. + """ + new_options = [] + for help_tuple in options: + new_options.append(help_tuple[0:3]) + return new_options diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/errors.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/errors.py new file mode 100644 index 00000000..8b93059e --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/errors.py @@ -0,0 +1,97 @@ +"""distutils.errors + +Provides exceptions used by the Distutils modules. Note that Distutils +modules may raise standard exceptions; in particular, SystemExit is +usually raised for errors that are obviously the end-user's fault +(eg. bad command-line arguments). + +This module is safe to use in "from ... import *" mode; it only exports +symbols whose names start with "Distutils" and end with "Error".""" + +class DistutilsError (Exception): + """The root of all Distutils evil.""" + pass + +class DistutilsModuleError (DistutilsError): + """Unable to load an expected module, or to find an expected class + within some module (in particular, command modules and classes).""" + pass + +class DistutilsClassError (DistutilsError): + """Some command class (or possibly distribution class, if anyone + feels a need to subclass Distribution) is found not to be holding + up its end of the bargain, ie. implementing some part of the + "command "interface.""" + pass + +class DistutilsGetoptError (DistutilsError): + """The option table provided to 'fancy_getopt()' is bogus.""" + pass + +class DistutilsArgError (DistutilsError): + """Raised by fancy_getopt in response to getopt.error -- ie. an + error in the command line usage.""" + pass + +class DistutilsFileError (DistutilsError): + """Any problems in the filesystem: expected file not found, etc. + Typically this is for problems that we detect before OSError + could be raised.""" + pass + +class DistutilsOptionError (DistutilsError): + """Syntactic/semantic errors in command options, such as use of + mutually conflicting options, or inconsistent options, + badly-spelled values, etc. No distinction is made between option + values originating in the setup script, the command line, config + files, or what-have-you -- but if we *know* something originated in + the setup script, we'll raise DistutilsSetupError instead.""" + pass + +class DistutilsSetupError (DistutilsError): + """For errors that can be definitely blamed on the setup script, + such as invalid keyword arguments to 'setup()'.""" + pass + +class DistutilsPlatformError (DistutilsError): + """We don't know how to do something on the current platform (but + we do know how to do it on some platform) -- eg. trying to compile + C files on a platform not supported by a CCompiler subclass.""" + pass + +class DistutilsExecError (DistutilsError): + """Any problems executing an external program (such as the C + compiler, when compiling C files).""" + pass + +class DistutilsInternalError (DistutilsError): + """Internal inconsistencies or impossibilities (obviously, this + should never be seen if the code is working!).""" + pass + +class DistutilsTemplateError (DistutilsError): + """Syntax error in a file list template.""" + +class DistutilsByteCompileError(DistutilsError): + """Byte compile error.""" + +# Exception classes used by the CCompiler implementation classes +class CCompilerError (Exception): + """Some compile/link operation failed.""" + +class PreprocessError (CCompilerError): + """Failure to preprocess one or more C/C++ files.""" + +class CompileError (CCompilerError): + """Failure to compile one or more C/C++ source files.""" + +class LibError (CCompilerError): + """Failure to create a static library from one or more C/C++ object + files.""" + +class LinkError (CCompilerError): + """Failure to link one or more C/C++ object files into an executable + or shared library file.""" + +class UnknownFileError (CCompilerError): + """Attempt to process an unknown file type.""" diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/extension.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/extension.py new file mode 100644 index 00000000..c507da36 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/extension.py @@ -0,0 +1,240 @@ +"""distutils.extension + +Provides the Extension class, used to describe C/C++ extension +modules in setup scripts.""" + +import os +import warnings + +# This class is really only used by the "build_ext" command, so it might +# make sense to put it in distutils.command.build_ext. However, that +# module is already big enough, and I want to make this class a bit more +# complex to simplify some common cases ("foo" module in "foo.c") and do +# better error-checking ("foo.c" actually exists). +# +# Also, putting this in build_ext.py means every setup script would have to +# import that large-ish module (indirectly, through distutils.core) in +# order to do anything. + +class Extension: + """Just a collection of attributes that describes an extension + module and everything needed to build it (hopefully in a portable + way, but there are hooks that let you be as unportable as you need). + + Instance attributes: + name : string + the full name of the extension, including any packages -- ie. + *not* a filename or pathname, but Python dotted name + sources : [string] + list of source filenames, relative to the distribution root + (where the setup script lives), in Unix form (slash-separated) + for portability. Source files may be C, C++, SWIG (.i), + platform-specific resource files, or whatever else is recognized + by the "build_ext" command as source for a Python extension. + include_dirs : [string] + list of directories to search for C/C++ header files (in Unix + form for portability) + define_macros : [(name : string, value : string|None)] + list of macros to define; each macro is defined using a 2-tuple, + where 'value' is either the string to define it to or None to + define it without a particular value (equivalent of "#define + FOO" in source or -DFOO on Unix C compiler command line) + undef_macros : [string] + list of macros to undefine explicitly + library_dirs : [string] + list of directories to search for C/C++ libraries at link time + libraries : [string] + list of library names (not filenames or paths) to link against + runtime_library_dirs : [string] + list of directories to search for C/C++ libraries at run time + (for shared extensions, this is when the extension is loaded) + extra_objects : [string] + list of extra files to link with (eg. object files not implied + by 'sources', static library that must be explicitly specified, + binary resource files, etc.) + extra_compile_args : [string] + any extra platform- and compiler-specific information to use + when compiling the source files in 'sources'. For platforms and + compilers where "command line" makes sense, this is typically a + list of command-line arguments, but for other platforms it could + be anything. + extra_link_args : [string] + any extra platform- and compiler-specific information to use + when linking object files together to create the extension (or + to create a new static Python interpreter). Similar + interpretation as for 'extra_compile_args'. + export_symbols : [string] + list of symbols to be exported from a shared extension. Not + used on all platforms, and not generally necessary for Python + extensions, which typically export exactly one symbol: "init" + + extension_name. + swig_opts : [string] + any extra options to pass to SWIG if a source file has the .i + extension. + depends : [string] + list of files that the extension depends on + language : string + extension language (i.e. "c", "c++", "objc"). Will be detected + from the source extensions if not provided. + optional : boolean + specifies that a build failure in the extension should not abort the + build process, but simply not install the failing extension. + """ + + # When adding arguments to this constructor, be sure to update + # setup_keywords in core.py. + def __init__(self, name, sources, + include_dirs=None, + define_macros=None, + undef_macros=None, + library_dirs=None, + libraries=None, + runtime_library_dirs=None, + extra_objects=None, + extra_compile_args=None, + extra_link_args=None, + export_symbols=None, + swig_opts = None, + depends=None, + language=None, + optional=None, + **kw # To catch unknown keywords + ): + if not isinstance(name, str): + raise AssertionError("'name' must be a string") + if not (isinstance(sources, list) and + all(isinstance(v, str) for v in sources)): + raise AssertionError("'sources' must be a list of strings") + + self.name = name + self.sources = sources + self.include_dirs = include_dirs or [] + self.define_macros = define_macros or [] + self.undef_macros = undef_macros or [] + self.library_dirs = library_dirs or [] + self.libraries = libraries or [] + self.runtime_library_dirs = runtime_library_dirs or [] + self.extra_objects = extra_objects or [] + self.extra_compile_args = extra_compile_args or [] + self.extra_link_args = extra_link_args or [] + self.export_symbols = export_symbols or [] + self.swig_opts = swig_opts or [] + self.depends = depends or [] + self.language = language + self.optional = optional + + # If there are unknown keyword options, warn about them + if len(kw) > 0: + options = [repr(option) for option in kw] + options = ', '.join(sorted(options)) + msg = "Unknown Extension options: %s" % options + warnings.warn(msg) + + def __repr__(self): + return '<%s.%s(%r) at %#x>' % ( + self.__class__.__module__, + self.__class__.__qualname__, + self.name, + id(self)) + + +def read_setup_file(filename): + """Reads a Setup file and returns Extension instances.""" + from distutils.sysconfig import (parse_makefile, expand_makefile_vars, + _variable_rx) + + from distutils.text_file import TextFile + from distutils.util import split_quoted + + # First pass over the file to gather "VAR = VALUE" assignments. + vars = parse_makefile(filename) + + # Second pass to gobble up the real content: lines of the form + # ... [ ...] [ ...] [ ...] + file = TextFile(filename, + strip_comments=1, skip_blanks=1, join_lines=1, + lstrip_ws=1, rstrip_ws=1) + try: + extensions = [] + + while True: + line = file.readline() + if line is None: # eof + break + if _variable_rx.match(line): # VAR=VALUE, handled in first pass + continue + + if line[0] == line[-1] == "*": + file.warn("'%s' lines not handled yet" % line) + continue + + line = expand_makefile_vars(line, vars) + words = split_quoted(line) + + # NB. this parses a slightly different syntax than the old + # makesetup script: here, there must be exactly one extension per + # line, and it must be the first word of the line. I have no idea + # why the old syntax supported multiple extensions per line, as + # they all wind up being the same. + + module = words[0] + ext = Extension(module, []) + append_next_word = None + + for word in words[1:]: + if append_next_word is not None: + append_next_word.append(word) + append_next_word = None + continue + + suffix = os.path.splitext(word)[1] + switch = word[0:2] ; value = word[2:] + + if suffix in (".c", ".cc", ".cpp", ".cxx", ".c++", ".m", ".mm"): + # hmm, should we do something about C vs. C++ sources? + # or leave it up to the CCompiler implementation to + # worry about? + ext.sources.append(word) + elif switch == "-I": + ext.include_dirs.append(value) + elif switch == "-D": + equals = value.find("=") + if equals == -1: # bare "-DFOO" -- no value + ext.define_macros.append((value, None)) + else: # "-DFOO=blah" + ext.define_macros.append((value[0:equals], + value[equals+2:])) + elif switch == "-U": + ext.undef_macros.append(value) + elif switch == "-C": # only here 'cause makesetup has it! + ext.extra_compile_args.append(word) + elif switch == "-l": + ext.libraries.append(value) + elif switch == "-L": + ext.library_dirs.append(value) + elif switch == "-R": + ext.runtime_library_dirs.append(value) + elif word == "-rpath": + append_next_word = ext.runtime_library_dirs + elif word == "-Xlinker": + append_next_word = ext.extra_link_args + elif word == "-Xcompiler": + append_next_word = ext.extra_compile_args + elif switch == "-u": + ext.extra_link_args.append(word) + if not value: + append_next_word = ext.extra_link_args + elif suffix in (".a", ".so", ".sl", ".o", ".dylib"): + # NB. a really faithful emulation of makesetup would + # append a .o file to extra_objects only if it + # had a slash in it; otherwise, it would s/.o/.c/ + # and append it to sources. Hmmmm. + ext.extra_objects.append(word) + else: + file.warn("unrecognized argument '%s'" % word) + + extensions.append(ext) + finally: + file.close() + + return extensions diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/fancy_getopt.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/fancy_getopt.py new file mode 100644 index 00000000..7d170dd2 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/fancy_getopt.py @@ -0,0 +1,457 @@ +"""distutils.fancy_getopt + +Wrapper around the standard getopt module that provides the following +additional features: + * short and long options are tied together + * options have help strings, so fancy_getopt could potentially + create a complete usage summary + * options set attributes of a passed-in object +""" + +import sys, string, re +import getopt +from distutils.errors import * + +# Much like command_re in distutils.core, this is close to but not quite +# the same as a Python NAME -- except, in the spirit of most GNU +# utilities, we use '-' in place of '_'. (The spirit of LISP lives on!) +# The similarities to NAME are again not a coincidence... +longopt_pat = r'[a-zA-Z](?:[a-zA-Z0-9-]*)' +longopt_re = re.compile(r'^%s$' % longopt_pat) + +# For recognizing "negative alias" options, eg. "quiet=!verbose" +neg_alias_re = re.compile("^(%s)=!(%s)$" % (longopt_pat, longopt_pat)) + +# This is used to translate long options to legitimate Python identifiers +# (for use as attributes of some object). +longopt_xlate = str.maketrans('-', '_') + +class FancyGetopt: + """Wrapper around the standard 'getopt()' module that provides some + handy extra functionality: + * short and long options are tied together + * options have help strings, and help text can be assembled + from them + * options set attributes of a passed-in object + * boolean options can have "negative aliases" -- eg. if + --quiet is the "negative alias" of --verbose, then "--quiet" + on the command line sets 'verbose' to false + """ + + def __init__(self, option_table=None): + # The option table is (currently) a list of tuples. The + # tuples may have 3 or four values: + # (long_option, short_option, help_string [, repeatable]) + # if an option takes an argument, its long_option should have '=' + # appended; short_option should just be a single character, no ':' + # in any case. If a long_option doesn't have a corresponding + # short_option, short_option should be None. All option tuples + # must have long options. + self.option_table = option_table + + # 'option_index' maps long option names to entries in the option + # table (ie. those 3-tuples). + self.option_index = {} + if self.option_table: + self._build_index() + + # 'alias' records (duh) alias options; {'foo': 'bar'} means + # --foo is an alias for --bar + self.alias = {} + + # 'negative_alias' keeps track of options that are the boolean + # opposite of some other option + self.negative_alias = {} + + # These keep track of the information in the option table. We + # don't actually populate these structures until we're ready to + # parse the command-line, since the 'option_table' passed in here + # isn't necessarily the final word. + self.short_opts = [] + self.long_opts = [] + self.short2long = {} + self.attr_name = {} + self.takes_arg = {} + + # And 'option_order' is filled up in 'getopt()'; it records the + # original order of options (and their values) on the command-line, + # but expands short options, converts aliases, etc. + self.option_order = [] + + def _build_index(self): + self.option_index.clear() + for option in self.option_table: + self.option_index[option[0]] = option + + def set_option_table(self, option_table): + self.option_table = option_table + self._build_index() + + def add_option(self, long_option, short_option=None, help_string=None): + if long_option in self.option_index: + raise DistutilsGetoptError( + "option conflict: already an option '%s'" % long_option) + else: + option = (long_option, short_option, help_string) + self.option_table.append(option) + self.option_index[long_option] = option + + def has_option(self, long_option): + """Return true if the option table for this parser has an + option with long name 'long_option'.""" + return long_option in self.option_index + + def get_attr_name(self, long_option): + """Translate long option name 'long_option' to the form it + has as an attribute of some object: ie., translate hyphens + to underscores.""" + return long_option.translate(longopt_xlate) + + def _check_alias_dict(self, aliases, what): + assert isinstance(aliases, dict) + for (alias, opt) in aliases.items(): + if alias not in self.option_index: + raise DistutilsGetoptError(("invalid %s '%s': " + "option '%s' not defined") % (what, alias, alias)) + if opt not in self.option_index: + raise DistutilsGetoptError(("invalid %s '%s': " + "aliased option '%s' not defined") % (what, alias, opt)) + + def set_aliases(self, alias): + """Set the aliases for this option parser.""" + self._check_alias_dict(alias, "alias") + self.alias = alias + + def set_negative_aliases(self, negative_alias): + """Set the negative aliases for this option parser. + 'negative_alias' should be a dictionary mapping option names to + option names, both the key and value must already be defined + in the option table.""" + self._check_alias_dict(negative_alias, "negative alias") + self.negative_alias = negative_alias + + def _grok_option_table(self): + """Populate the various data structures that keep tabs on the + option table. Called by 'getopt()' before it can do anything + worthwhile. + """ + self.long_opts = [] + self.short_opts = [] + self.short2long.clear() + self.repeat = {} + + for option in self.option_table: + if len(option) == 3: + long, short, help = option + repeat = 0 + elif len(option) == 4: + long, short, help, repeat = option + else: + # the option table is part of the code, so simply + # assert that it is correct + raise ValueError("invalid option tuple: %r" % (option,)) + + # Type- and value-check the option names + if not isinstance(long, str) or len(long) < 2: + raise DistutilsGetoptError(("invalid long option '%s': " + "must be a string of length >= 2") % long) + + if (not ((short is None) or + (isinstance(short, str) and len(short) == 1))): + raise DistutilsGetoptError("invalid short option '%s': " + "must a single character or None" % short) + + self.repeat[long] = repeat + self.long_opts.append(long) + + if long[-1] == '=': # option takes an argument? + if short: short = short + ':' + long = long[0:-1] + self.takes_arg[long] = 1 + else: + # Is option is a "negative alias" for some other option (eg. + # "quiet" == "!verbose")? + alias_to = self.negative_alias.get(long) + if alias_to is not None: + if self.takes_arg[alias_to]: + raise DistutilsGetoptError( + "invalid negative alias '%s': " + "aliased option '%s' takes a value" + % (long, alias_to)) + + self.long_opts[-1] = long # XXX redundant?! + self.takes_arg[long] = 0 + + # If this is an alias option, make sure its "takes arg" flag is + # the same as the option it's aliased to. + alias_to = self.alias.get(long) + if alias_to is not None: + if self.takes_arg[long] != self.takes_arg[alias_to]: + raise DistutilsGetoptError( + "invalid alias '%s': inconsistent with " + "aliased option '%s' (one of them takes a value, " + "the other doesn't" + % (long, alias_to)) + + # Now enforce some bondage on the long option name, so we can + # later translate it to an attribute name on some object. Have + # to do this a bit late to make sure we've removed any trailing + # '='. + if not longopt_re.match(long): + raise DistutilsGetoptError( + "invalid long option name '%s' " + "(must be letters, numbers, hyphens only" % long) + + self.attr_name[long] = self.get_attr_name(long) + if short: + self.short_opts.append(short) + self.short2long[short[0]] = long + + def getopt(self, args=None, object=None): + """Parse command-line options in args. Store as attributes on object. + + If 'args' is None or not supplied, uses 'sys.argv[1:]'. If + 'object' is None or not supplied, creates a new OptionDummy + object, stores option values there, and returns a tuple (args, + object). If 'object' is supplied, it is modified in place and + 'getopt()' just returns 'args'; in both cases, the returned + 'args' is a modified copy of the passed-in 'args' list, which + is left untouched. + """ + if args is None: + args = sys.argv[1:] + if object is None: + object = OptionDummy() + created_object = True + else: + created_object = False + + self._grok_option_table() + + short_opts = ' '.join(self.short_opts) + try: + opts, args = getopt.getopt(args, short_opts, self.long_opts) + except getopt.error as msg: + raise DistutilsArgError(msg) + + for opt, val in opts: + if len(opt) == 2 and opt[0] == '-': # it's a short option + opt = self.short2long[opt[1]] + else: + assert len(opt) > 2 and opt[:2] == '--' + opt = opt[2:] + + alias = self.alias.get(opt) + if alias: + opt = alias + + if not self.takes_arg[opt]: # boolean option? + assert val == '', "boolean option can't have value" + alias = self.negative_alias.get(opt) + if alias: + opt = alias + val = 0 + else: + val = 1 + + attr = self.attr_name[opt] + # The only repeating option at the moment is 'verbose'. + # It has a negative option -q quiet, which should set verbose = 0. + if val and self.repeat.get(attr) is not None: + val = getattr(object, attr, 0) + 1 + setattr(object, attr, val) + self.option_order.append((opt, val)) + + # for opts + if created_object: + return args, object + else: + return args + + def get_option_order(self): + """Returns the list of (option, value) tuples processed by the + previous run of 'getopt()'. Raises RuntimeError if + 'getopt()' hasn't been called yet. + """ + if self.option_order is None: + raise RuntimeError("'getopt()' hasn't been called yet") + else: + return self.option_order + + def generate_help(self, header=None): + """Generate help text (a list of strings, one per suggested line of + output) from the option table for this FancyGetopt object. + """ + # Blithely assume the option table is good: probably wouldn't call + # 'generate_help()' unless you've already called 'getopt()'. + + # First pass: determine maximum length of long option names + max_opt = 0 + for option in self.option_table: + long = option[0] + short = option[1] + l = len(long) + if long[-1] == '=': + l = l - 1 + if short is not None: + l = l + 5 # " (-x)" where short == 'x' + if l > max_opt: + max_opt = l + + opt_width = max_opt + 2 + 2 + 2 # room for indent + dashes + gutter + + # Typical help block looks like this: + # --foo controls foonabulation + # Help block for longest option looks like this: + # --flimflam set the flim-flam level + # and with wrapped text: + # --flimflam set the flim-flam level (must be between + # 0 and 100, except on Tuesdays) + # Options with short names will have the short name shown (but + # it doesn't contribute to max_opt): + # --foo (-f) controls foonabulation + # If adding the short option would make the left column too wide, + # we push the explanation off to the next line + # --flimflam (-l) + # set the flim-flam level + # Important parameters: + # - 2 spaces before option block start lines + # - 2 dashes for each long option name + # - min. 2 spaces between option and explanation (gutter) + # - 5 characters (incl. space) for short option name + + # Now generate lines of help text. (If 80 columns were good enough + # for Jesus, then 78 columns are good enough for me!) + line_width = 78 + text_width = line_width - opt_width + big_indent = ' ' * opt_width + if header: + lines = [header] + else: + lines = ['Option summary:'] + + for option in self.option_table: + long, short, help = option[:3] + text = wrap_text(help, text_width) + if long[-1] == '=': + long = long[0:-1] + + # Case 1: no short option at all (makes life easy) + if short is None: + if text: + lines.append(" --%-*s %s" % (max_opt, long, text[0])) + else: + lines.append(" --%-*s " % (max_opt, long)) + + # Case 2: we have a short option, so we have to include it + # just after the long option + else: + opt_names = "%s (-%s)" % (long, short) + if text: + lines.append(" --%-*s %s" % + (max_opt, opt_names, text[0])) + else: + lines.append(" --%-*s" % opt_names) + + for l in text[1:]: + lines.append(big_indent + l) + return lines + + def print_help(self, header=None, file=None): + if file is None: + file = sys.stdout + for line in self.generate_help(header): + file.write(line + "\n") + + +def fancy_getopt(options, negative_opt, object, args): + parser = FancyGetopt(options) + parser.set_negative_aliases(negative_opt) + return parser.getopt(args, object) + + +WS_TRANS = {ord(_wschar) : ' ' for _wschar in string.whitespace} + +def wrap_text(text, width): + """wrap_text(text : string, width : int) -> [string] + + Split 'text' into multiple lines of no more than 'width' characters + each, and return the list of strings that results. + """ + if text is None: + return [] + if len(text) <= width: + return [text] + + text = text.expandtabs() + text = text.translate(WS_TRANS) + chunks = re.split(r'( +|-+)', text) + chunks = [ch for ch in chunks if ch] # ' - ' results in empty strings + lines = [] + + while chunks: + cur_line = [] # list of chunks (to-be-joined) + cur_len = 0 # length of current line + + while chunks: + l = len(chunks[0]) + if cur_len + l <= width: # can squeeze (at least) this chunk in + cur_line.append(chunks[0]) + del chunks[0] + cur_len = cur_len + l + else: # this line is full + # drop last chunk if all space + if cur_line and cur_line[-1][0] == ' ': + del cur_line[-1] + break + + if chunks: # any chunks left to process? + # if the current line is still empty, then we had a single + # chunk that's too big too fit on a line -- so we break + # down and break it up at the line width + if cur_len == 0: + cur_line.append(chunks[0][0:width]) + chunks[0] = chunks[0][width:] + + # all-whitespace chunks at the end of a line can be discarded + # (and we know from the re.split above that if a chunk has + # *any* whitespace, it is *all* whitespace) + if chunks[0][0] == ' ': + del chunks[0] + + # and store this line in the list-of-all-lines -- as a single + # string, of course! + lines.append(''.join(cur_line)) + + return lines + + +def translate_longopt(opt): + """Convert a long option name to a valid Python identifier by + changing "-" to "_". + """ + return opt.translate(longopt_xlate) + + +class OptionDummy: + """Dummy class just used as a place to hold command-line option + values as instance attributes.""" + + def __init__(self, options=[]): + """Create a new OptionDummy instance. The attributes listed in + 'options' will be initialized to None.""" + for opt in options: + setattr(self, opt, None) + + +if __name__ == "__main__": + text = """\ +Tra-la-la, supercalifragilisticexpialidocious. +How *do* you spell that odd word, anyways? +(Someone ask Mary -- she'll know [or she'll +say, "How should I know?"].)""" + + for w in (10, 20, 30, 40): + print("width: %d" % w) + print("\n".join(wrap_text(text, w))) + print() diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/file_util.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/file_util.py new file mode 100644 index 00000000..b3fee35a --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/file_util.py @@ -0,0 +1,238 @@ +"""distutils.file_util + +Utility functions for operating on single files. +""" + +import os +from distutils.errors import DistutilsFileError +from distutils import log + +# for generating verbose output in 'copy_file()' +_copy_action = { None: 'copying', + 'hard': 'hard linking', + 'sym': 'symbolically linking' } + + +def _copy_file_contents(src, dst, buffer_size=16*1024): + """Copy the file 'src' to 'dst'; both must be filenames. Any error + opening either file, reading from 'src', or writing to 'dst', raises + DistutilsFileError. Data is read/written in chunks of 'buffer_size' + bytes (default 16k). No attempt is made to handle anything apart from + regular files. + """ + # Stolen from shutil module in the standard library, but with + # custom error-handling added. + fsrc = None + fdst = None + try: + try: + fsrc = open(src, 'rb') + except OSError as e: + raise DistutilsFileError("could not open '%s': %s" % (src, e.strerror)) + + if os.path.exists(dst): + try: + os.unlink(dst) + except OSError as e: + raise DistutilsFileError( + "could not delete '%s': %s" % (dst, e.strerror)) + + try: + fdst = open(dst, 'wb') + except OSError as e: + raise DistutilsFileError( + "could not create '%s': %s" % (dst, e.strerror)) + + while True: + try: + buf = fsrc.read(buffer_size) + except OSError as e: + raise DistutilsFileError( + "could not read from '%s': %s" % (src, e.strerror)) + + if not buf: + break + + try: + fdst.write(buf) + except OSError as e: + raise DistutilsFileError( + "could not write to '%s': %s" % (dst, e.strerror)) + finally: + if fdst: + fdst.close() + if fsrc: + fsrc.close() + +def copy_file(src, dst, preserve_mode=1, preserve_times=1, update=0, + link=None, verbose=1, dry_run=0): + """Copy a file 'src' to 'dst'. If 'dst' is a directory, then 'src' is + copied there with the same name; otherwise, it must be a filename. (If + the file exists, it will be ruthlessly clobbered.) If 'preserve_mode' + is true (the default), the file's mode (type and permission bits, or + whatever is analogous on the current platform) is copied. If + 'preserve_times' is true (the default), the last-modified and + last-access times are copied as well. If 'update' is true, 'src' will + only be copied if 'dst' does not exist, or if 'dst' does exist but is + older than 'src'. + + 'link' allows you to make hard links (os.link) or symbolic links + (os.symlink) instead of copying: set it to "hard" or "sym"; if it is + None (the default), files are copied. Don't set 'link' on systems that + don't support it: 'copy_file()' doesn't check if hard or symbolic + linking is available. If hardlink fails, falls back to + _copy_file_contents(). + + Under Mac OS, uses the native file copy function in macostools; on + other systems, uses '_copy_file_contents()' to copy file contents. + + Return a tuple (dest_name, copied): 'dest_name' is the actual name of + the output file, and 'copied' is true if the file was copied (or would + have been copied, if 'dry_run' true). + """ + # XXX if the destination file already exists, we clobber it if + # copying, but blow up if linking. Hmmm. And I don't know what + # macostools.copyfile() does. Should definitely be consistent, and + # should probably blow up if destination exists and we would be + # changing it (ie. it's not already a hard/soft link to src OR + # (not update) and (src newer than dst). + + from distutils.dep_util import newer + from stat import ST_ATIME, ST_MTIME, ST_MODE, S_IMODE + + if not os.path.isfile(src): + raise DistutilsFileError( + "can't copy '%s': doesn't exist or not a regular file" % src) + + if os.path.isdir(dst): + dir = dst + dst = os.path.join(dst, os.path.basename(src)) + else: + dir = os.path.dirname(dst) + + if update and not newer(src, dst): + if verbose >= 1: + log.debug("not copying %s (output up-to-date)", src) + return (dst, 0) + + try: + action = _copy_action[link] + except KeyError: + raise ValueError("invalid value '%s' for 'link' argument" % link) + + if verbose >= 1: + if os.path.basename(dst) == os.path.basename(src): + log.info("%s %s -> %s", action, src, dir) + else: + log.info("%s %s -> %s", action, src, dst) + + if dry_run: + return (dst, 1) + + # If linking (hard or symbolic), use the appropriate system call + # (Unix only, of course, but that's the caller's responsibility) + elif link == 'hard': + if not (os.path.exists(dst) and os.path.samefile(src, dst)): + try: + os.link(src, dst) + return (dst, 1) + except OSError: + # If hard linking fails, fall back on copying file + # (some special filesystems don't support hard linking + # even under Unix, see issue #8876). + pass + elif link == 'sym': + if not (os.path.exists(dst) and os.path.samefile(src, dst)): + os.symlink(src, dst) + return (dst, 1) + + # Otherwise (non-Mac, not linking), copy the file contents and + # (optionally) copy the times and mode. + _copy_file_contents(src, dst) + if preserve_mode or preserve_times: + st = os.stat(src) + + # According to David Ascher , utime() should be done + # before chmod() (at least under NT). + if preserve_times: + os.utime(dst, (st[ST_ATIME], st[ST_MTIME])) + if preserve_mode: + os.chmod(dst, S_IMODE(st[ST_MODE])) + + return (dst, 1) + + +# XXX I suspect this is Unix-specific -- need porting help! +def move_file (src, dst, + verbose=1, + dry_run=0): + + """Move a file 'src' to 'dst'. If 'dst' is a directory, the file will + be moved into it with the same name; otherwise, 'src' is just renamed + to 'dst'. Return the new full name of the file. + + Handles cross-device moves on Unix using 'copy_file()'. What about + other systems??? + """ + from os.path import exists, isfile, isdir, basename, dirname + import errno + + if verbose >= 1: + log.info("moving %s -> %s", src, dst) + + if dry_run: + return dst + + if not isfile(src): + raise DistutilsFileError("can't move '%s': not a regular file" % src) + + if isdir(dst): + dst = os.path.join(dst, basename(src)) + elif exists(dst): + raise DistutilsFileError( + "can't move '%s': destination '%s' already exists" % + (src, dst)) + + if not isdir(dirname(dst)): + raise DistutilsFileError( + "can't move '%s': destination '%s' not a valid path" % + (src, dst)) + + copy_it = False + try: + os.rename(src, dst) + except OSError as e: + (num, msg) = e.args + if num == errno.EXDEV: + copy_it = True + else: + raise DistutilsFileError( + "couldn't move '%s' to '%s': %s" % (src, dst, msg)) + + if copy_it: + copy_file(src, dst, verbose=verbose) + try: + os.unlink(src) + except OSError as e: + (num, msg) = e.args + try: + os.unlink(dst) + except OSError: + pass + raise DistutilsFileError( + "couldn't move '%s' to '%s' by copy/delete: " + "delete '%s' failed: %s" + % (src, dst, src, msg)) + return dst + + +def write_file (filename, contents): + """Create a file with the specified name and write 'contents' (a + sequence of strings without line terminators) to it. + """ + f = open(filename, "w") + try: + for line in contents: + f.write(line + "\n") + finally: + f.close() diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/filelist.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/filelist.py new file mode 100644 index 00000000..82a77384 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/filelist.py @@ -0,0 +1,355 @@ +"""distutils.filelist + +Provides the FileList class, used for poking about the filesystem +and building lists of files. +""" + +import os +import re +import fnmatch +import functools + +from distutils.util import convert_path +from distutils.errors import DistutilsTemplateError, DistutilsInternalError +from distutils import log + + +class FileList: + """A list of files built by on exploring the filesystem and filtered by + applying various patterns to what we find there. + + Instance attributes: + dir + directory from which files will be taken -- only used if + 'allfiles' not supplied to constructor + files + list of filenames currently being built/filtered/manipulated + allfiles + complete list of files under consideration (ie. without any + filtering applied) + """ + + def __init__(self, warn=None, debug_print=None): + # ignore argument to FileList, but keep them for backwards + # compatibility + self.allfiles = None + self.files = [] + + def set_allfiles(self, allfiles): + self.allfiles = allfiles + + def findall(self, dir=os.curdir): + self.allfiles = findall(dir) + + def debug_print(self, msg): + """Print 'msg' to stdout if the global DEBUG (taken from the + DISTUTILS_DEBUG environment variable) flag is true. + """ + from distutils.debug import DEBUG + if DEBUG: + print(msg) + + # Collection methods + + def append(self, item): + self.files.append(item) + + def extend(self, items): + self.files.extend(items) + + def sort(self): + # Not a strict lexical sort! + sortable_files = sorted(map(os.path.split, self.files)) + self.files = [] + for sort_tuple in sortable_files: + self.files.append(os.path.join(*sort_tuple)) + + # Other miscellaneous utility methods + + def remove_duplicates(self): + # Assumes list has been sorted! + for i in range(len(self.files) - 1, 0, -1): + if self.files[i] == self.files[i - 1]: + del self.files[i] + + # "File template" methods + + def _parse_template_line(self, line): + words = line.split() + action = words[0] + + patterns = dir = dir_pattern = None + + if action in ('include', 'exclude', + 'global-include', 'global-exclude'): + if len(words) < 2: + raise DistutilsTemplateError( + "'%s' expects ..." % action) + patterns = [convert_path(w) for w in words[1:]] + elif action in ('recursive-include', 'recursive-exclude'): + if len(words) < 3: + raise DistutilsTemplateError( + "'%s' expects ..." % action) + dir = convert_path(words[1]) + patterns = [convert_path(w) for w in words[2:]] + elif action in ('graft', 'prune'): + if len(words) != 2: + raise DistutilsTemplateError( + "'%s' expects a single " % action) + dir_pattern = convert_path(words[1]) + else: + raise DistutilsTemplateError("unknown action '%s'" % action) + + return (action, patterns, dir, dir_pattern) + + def process_template_line(self, line): + # Parse the line: split it up, make sure the right number of words + # is there, and return the relevant words. 'action' is always + # defined: it's the first word of the line. Which of the other + # three are defined depends on the action; it'll be either + # patterns, (dir and patterns), or (dir_pattern). + (action, patterns, dir, dir_pattern) = self._parse_template_line(line) + + # OK, now we know that the action is valid and we have the + # right number of words on the line for that action -- so we + # can proceed with minimal error-checking. + if action == 'include': + self.debug_print("include " + ' '.join(patterns)) + for pattern in patterns: + if not self.include_pattern(pattern, anchor=1): + log.warn("warning: no files found matching '%s'", + pattern) + + elif action == 'exclude': + self.debug_print("exclude " + ' '.join(patterns)) + for pattern in patterns: + if not self.exclude_pattern(pattern, anchor=1): + log.warn(("warning: no previously-included files " + "found matching '%s'"), pattern) + + elif action == 'global-include': + self.debug_print("global-include " + ' '.join(patterns)) + for pattern in patterns: + if not self.include_pattern(pattern, anchor=0): + log.warn(("warning: no files found matching '%s' " + "anywhere in distribution"), pattern) + + elif action == 'global-exclude': + self.debug_print("global-exclude " + ' '.join(patterns)) + for pattern in patterns: + if not self.exclude_pattern(pattern, anchor=0): + log.warn(("warning: no previously-included files matching " + "'%s' found anywhere in distribution"), + pattern) + + elif action == 'recursive-include': + self.debug_print("recursive-include %s %s" % + (dir, ' '.join(patterns))) + for pattern in patterns: + if not self.include_pattern(pattern, prefix=dir): + msg = ( + "warning: no files found matching '%s' " + "under directory '%s'" + ) + log.warn(msg, pattern, dir) + + elif action == 'recursive-exclude': + self.debug_print("recursive-exclude %s %s" % + (dir, ' '.join(patterns))) + for pattern in patterns: + if not self.exclude_pattern(pattern, prefix=dir): + log.warn(("warning: no previously-included files matching " + "'%s' found under directory '%s'"), + pattern, dir) + + elif action == 'graft': + self.debug_print("graft " + dir_pattern) + if not self.include_pattern(None, prefix=dir_pattern): + log.warn("warning: no directories found matching '%s'", + dir_pattern) + + elif action == 'prune': + self.debug_print("prune " + dir_pattern) + if not self.exclude_pattern(None, prefix=dir_pattern): + log.warn(("no previously-included directories found " + "matching '%s'"), dir_pattern) + else: + raise DistutilsInternalError( + "this cannot happen: invalid action '%s'" % action) + + # Filtering/selection methods + + def include_pattern(self, pattern, anchor=1, prefix=None, is_regex=0): + """Select strings (presumably filenames) from 'self.files' that + match 'pattern', a Unix-style wildcard (glob) pattern. Patterns + are not quite the same as implemented by the 'fnmatch' module: '*' + and '?' match non-special characters, where "special" is platform- + dependent: slash on Unix; colon, slash, and backslash on + DOS/Windows; and colon on Mac OS. + + If 'anchor' is true (the default), then the pattern match is more + stringent: "*.py" will match "foo.py" but not "foo/bar.py". If + 'anchor' is false, both of these will match. + + If 'prefix' is supplied, then only filenames starting with 'prefix' + (itself a pattern) and ending with 'pattern', with anything in between + them, will match. 'anchor' is ignored in this case. + + If 'is_regex' is true, 'anchor' and 'prefix' are ignored, and + 'pattern' is assumed to be either a string containing a regex or a + regex object -- no translation is done, the regex is just compiled + and used as-is. + + Selected strings will be added to self.files. + + Return True if files are found, False otherwise. + """ + # XXX docstring lying about what the special chars are? + files_found = False + pattern_re = translate_pattern(pattern, anchor, prefix, is_regex) + self.debug_print("include_pattern: applying regex r'%s'" % + pattern_re.pattern) + + # delayed loading of allfiles list + if self.allfiles is None: + self.findall() + + for name in self.allfiles: + if pattern_re.search(name): + self.debug_print(" adding " + name) + self.files.append(name) + files_found = True + return files_found + + def exclude_pattern( + self, pattern, anchor=1, prefix=None, is_regex=0): + """Remove strings (presumably filenames) from 'files' that match + 'pattern'. Other parameters are the same as for + 'include_pattern()', above. + The list 'self.files' is modified in place. + Return True if files are found, False otherwise. + """ + files_found = False + pattern_re = translate_pattern(pattern, anchor, prefix, is_regex) + self.debug_print("exclude_pattern: applying regex r'%s'" % + pattern_re.pattern) + for i in range(len(self.files)-1, -1, -1): + if pattern_re.search(self.files[i]): + self.debug_print(" removing " + self.files[i]) + del self.files[i] + files_found = True + return files_found + + +# Utility functions + +def _find_all_simple(path): + """ + Find all files under 'path' + """ + all_unique = _UniqueDirs.filter(os.walk(path, followlinks=True)) + results = ( + os.path.join(base, file) + for base, dirs, files in all_unique + for file in files + ) + return filter(os.path.isfile, results) + + +class _UniqueDirs(set): + """ + Exclude previously-seen dirs from walk results, + avoiding infinite recursion. + Ref https://bugs.python.org/issue44497. + """ + def __call__(self, walk_item): + """ + Given an item from an os.walk result, determine + if the item represents a unique dir for this instance + and if not, prevent further traversal. + """ + base, dirs, files = walk_item + stat = os.stat(base) + candidate = stat.st_dev, stat.st_ino + found = candidate in self + if found: + del dirs[:] + self.add(candidate) + return not found + + @classmethod + def filter(cls, items): + return filter(cls(), items) + + +def findall(dir=os.curdir): + """ + Find all files under 'dir' and return the list of full filenames. + Unless dir is '.', return full filenames with dir prepended. + """ + files = _find_all_simple(dir) + if dir == os.curdir: + make_rel = functools.partial(os.path.relpath, start=dir) + files = map(make_rel, files) + return list(files) + + +def glob_to_re(pattern): + """Translate a shell-like glob pattern to a regular expression; return + a string containing the regex. Differs from 'fnmatch.translate()' in + that '*' does not match "special characters" (which are + platform-specific). + """ + pattern_re = fnmatch.translate(pattern) + + # '?' and '*' in the glob pattern become '.' and '.*' in the RE, which + # IMHO is wrong -- '?' and '*' aren't supposed to match slash in Unix, + # and by extension they shouldn't match such "special characters" under + # any OS. So change all non-escaped dots in the RE to match any + # character except the special characters (currently: just os.sep). + sep = os.sep + if os.sep == '\\': + # we're using a regex to manipulate a regex, so we need + # to escape the backslash twice + sep = r'\\\\' + escaped = r'\1[^%s]' % sep + pattern_re = re.sub(r'((?= self.threshold: + if args: + msg = msg % args + if level in (WARN, ERROR, FATAL): + stream = sys.stderr + else: + stream = sys.stdout + try: + stream.write('%s\n' % msg) + except UnicodeEncodeError: + # emulate backslashreplace error handler + encoding = stream.encoding + msg = msg.encode(encoding, "backslashreplace").decode(encoding) + stream.write('%s\n' % msg) + stream.flush() + + def log(self, level, msg, *args): + self._log(level, msg, args) + + def debug(self, msg, *args): + self._log(DEBUG, msg, args) + + def info(self, msg, *args): + self._log(INFO, msg, args) + + def warn(self, msg, *args): + self._log(WARN, msg, args) + + def error(self, msg, *args): + self._log(ERROR, msg, args) + + def fatal(self, msg, *args): + self._log(FATAL, msg, args) + +_global_log = Log() +log = _global_log.log +debug = _global_log.debug +info = _global_log.info +warn = _global_log.warn +error = _global_log.error +fatal = _global_log.fatal + +def set_threshold(level): + # return the old threshold for use from tests + old = _global_log.threshold + _global_log.threshold = level + return old + +def set_verbosity(v): + if v <= 0: + set_threshold(WARN) + elif v == 1: + set_threshold(INFO) + elif v >= 2: + set_threshold(DEBUG) diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/msvc9compiler.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/msvc9compiler.py new file mode 100644 index 00000000..a1b3b02f --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/msvc9compiler.py @@ -0,0 +1,788 @@ +"""distutils.msvc9compiler + +Contains MSVCCompiler, an implementation of the abstract CCompiler class +for the Microsoft Visual Studio 2008. + +The module is compatible with VS 2005 and VS 2008. You can find legacy support +for older versions of VS in distutils.msvccompiler. +""" + +# Written by Perry Stoll +# hacked by Robin Becker and Thomas Heller to do a better job of +# finding DevStudio (through the registry) +# ported to VS2005 and VS 2008 by Christian Heimes + +import os +import subprocess +import sys +import re + +from distutils.errors import DistutilsExecError, DistutilsPlatformError, \ + CompileError, LibError, LinkError +from distutils.ccompiler import CCompiler, gen_lib_options +from distutils import log +from distutils.util import get_platform + +import winreg + +RegOpenKeyEx = winreg.OpenKeyEx +RegEnumKey = winreg.EnumKey +RegEnumValue = winreg.EnumValue +RegError = winreg.error + +HKEYS = (winreg.HKEY_USERS, + winreg.HKEY_CURRENT_USER, + winreg.HKEY_LOCAL_MACHINE, + winreg.HKEY_CLASSES_ROOT) + +NATIVE_WIN64 = (sys.platform == 'win32' and sys.maxsize > 2**32) +if NATIVE_WIN64: + # Visual C++ is a 32-bit application, so we need to look in + # the corresponding registry branch, if we're running a + # 64-bit Python on Win64 + VS_BASE = r"Software\Wow6432Node\Microsoft\VisualStudio\%0.1f" + WINSDK_BASE = r"Software\Wow6432Node\Microsoft\Microsoft SDKs\Windows" + NET_BASE = r"Software\Wow6432Node\Microsoft\.NETFramework" +else: + VS_BASE = r"Software\Microsoft\VisualStudio\%0.1f" + WINSDK_BASE = r"Software\Microsoft\Microsoft SDKs\Windows" + NET_BASE = r"Software\Microsoft\.NETFramework" + +# A map keyed by get_platform() return values to values accepted by +# 'vcvarsall.bat'. Note a cross-compile may combine these (eg, 'x86_amd64' is +# the param to cross-compile on x86 targeting amd64.) +PLAT_TO_VCVARS = { + 'win32' : 'x86', + 'win-amd64' : 'amd64', +} + +class Reg: + """Helper class to read values from the registry + """ + + def get_value(cls, path, key): + for base in HKEYS: + d = cls.read_values(base, path) + if d and key in d: + return d[key] + raise KeyError(key) + get_value = classmethod(get_value) + + def read_keys(cls, base, key): + """Return list of registry keys.""" + try: + handle = RegOpenKeyEx(base, key) + except RegError: + return None + L = [] + i = 0 + while True: + try: + k = RegEnumKey(handle, i) + except RegError: + break + L.append(k) + i += 1 + return L + read_keys = classmethod(read_keys) + + def read_values(cls, base, key): + """Return dict of registry keys and values. + + All names are converted to lowercase. + """ + try: + handle = RegOpenKeyEx(base, key) + except RegError: + return None + d = {} + i = 0 + while True: + try: + name, value, type = RegEnumValue(handle, i) + except RegError: + break + name = name.lower() + d[cls.convert_mbcs(name)] = cls.convert_mbcs(value) + i += 1 + return d + read_values = classmethod(read_values) + + def convert_mbcs(s): + dec = getattr(s, "decode", None) + if dec is not None: + try: + s = dec("mbcs") + except UnicodeError: + pass + return s + convert_mbcs = staticmethod(convert_mbcs) + +class MacroExpander: + + def __init__(self, version): + self.macros = {} + self.vsbase = VS_BASE % version + self.load_macros(version) + + def set_macro(self, macro, path, key): + self.macros["$(%s)" % macro] = Reg.get_value(path, key) + + def load_macros(self, version): + self.set_macro("VCInstallDir", self.vsbase + r"\Setup\VC", "productdir") + self.set_macro("VSInstallDir", self.vsbase + r"\Setup\VS", "productdir") + self.set_macro("FrameworkDir", NET_BASE, "installroot") + try: + if version >= 8.0: + self.set_macro("FrameworkSDKDir", NET_BASE, + "sdkinstallrootv2.0") + else: + raise KeyError("sdkinstallrootv2.0") + except KeyError: + raise DistutilsPlatformError( + """Python was built with Visual Studio 2008; +extensions must be built with a compiler than can generate compatible binaries. +Visual Studio 2008 was not found on this system. If you have Cygwin installed, +you can try compiling with MingW32, by passing "-c mingw32" to setup.py.""") + + if version >= 9.0: + self.set_macro("FrameworkVersion", self.vsbase, "clr version") + self.set_macro("WindowsSdkDir", WINSDK_BASE, "currentinstallfolder") + else: + p = r"Software\Microsoft\NET Framework Setup\Product" + for base in HKEYS: + try: + h = RegOpenKeyEx(base, p) + except RegError: + continue + key = RegEnumKey(h, 0) + d = Reg.get_value(base, r"%s\%s" % (p, key)) + self.macros["$(FrameworkVersion)"] = d["version"] + + def sub(self, s): + for k, v in self.macros.items(): + s = s.replace(k, v) + return s + +def get_build_version(): + """Return the version of MSVC that was used to build Python. + + For Python 2.3 and up, the version number is included in + sys.version. For earlier versions, assume the compiler is MSVC 6. + """ + prefix = "MSC v." + i = sys.version.find(prefix) + if i == -1: + return 6 + i = i + len(prefix) + s, rest = sys.version[i:].split(" ", 1) + majorVersion = int(s[:-2]) - 6 + if majorVersion >= 13: + # v13 was skipped and should be v14 + majorVersion += 1 + minorVersion = int(s[2:3]) / 10.0 + # I don't think paths are affected by minor version in version 6 + if majorVersion == 6: + minorVersion = 0 + if majorVersion >= 6: + return majorVersion + minorVersion + # else we don't know what version of the compiler this is + return None + +def normalize_and_reduce_paths(paths): + """Return a list of normalized paths with duplicates removed. + + The current order of paths is maintained. + """ + # Paths are normalized so things like: /a and /a/ aren't both preserved. + reduced_paths = [] + for p in paths: + np = os.path.normpath(p) + # XXX(nnorwitz): O(n**2), if reduced_paths gets long perhaps use a set. + if np not in reduced_paths: + reduced_paths.append(np) + return reduced_paths + +def removeDuplicates(variable): + """Remove duplicate values of an environment variable. + """ + oldList = variable.split(os.pathsep) + newList = [] + for i in oldList: + if i not in newList: + newList.append(i) + newVariable = os.pathsep.join(newList) + return newVariable + +def find_vcvarsall(version): + """Find the vcvarsall.bat file + + At first it tries to find the productdir of VS 2008 in the registry. If + that fails it falls back to the VS90COMNTOOLS env var. + """ + vsbase = VS_BASE % version + try: + productdir = Reg.get_value(r"%s\Setup\VC" % vsbase, + "productdir") + except KeyError: + log.debug("Unable to find productdir in registry") + productdir = None + + if not productdir or not os.path.isdir(productdir): + toolskey = "VS%0.f0COMNTOOLS" % version + toolsdir = os.environ.get(toolskey, None) + + if toolsdir and os.path.isdir(toolsdir): + productdir = os.path.join(toolsdir, os.pardir, os.pardir, "VC") + productdir = os.path.abspath(productdir) + if not os.path.isdir(productdir): + log.debug("%s is not a valid directory" % productdir) + return None + else: + log.debug("Env var %s is not set or invalid" % toolskey) + if not productdir: + log.debug("No productdir found") + return None + vcvarsall = os.path.join(productdir, "vcvarsall.bat") + if os.path.isfile(vcvarsall): + return vcvarsall + log.debug("Unable to find vcvarsall.bat") + return None + +def query_vcvarsall(version, arch="x86"): + """Launch vcvarsall.bat and read the settings from its environment + """ + vcvarsall = find_vcvarsall(version) + interesting = {"include", "lib", "libpath", "path"} + result = {} + + if vcvarsall is None: + raise DistutilsPlatformError("Unable to find vcvarsall.bat") + log.debug("Calling 'vcvarsall.bat %s' (version=%s)", arch, version) + popen = subprocess.Popen('"%s" %s & set' % (vcvarsall, arch), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + try: + stdout, stderr = popen.communicate() + if popen.wait() != 0: + raise DistutilsPlatformError(stderr.decode("mbcs")) + + stdout = stdout.decode("mbcs") + for line in stdout.split("\n"): + line = Reg.convert_mbcs(line) + if '=' not in line: + continue + line = line.strip() + key, value = line.split('=', 1) + key = key.lower() + if key in interesting: + if value.endswith(os.pathsep): + value = value[:-1] + result[key] = removeDuplicates(value) + + finally: + popen.stdout.close() + popen.stderr.close() + + if len(result) != len(interesting): + raise ValueError(str(list(result.keys()))) + + return result + +# More globals +VERSION = get_build_version() +if VERSION < 8.0: + raise DistutilsPlatformError("VC %0.1f is not supported by this module" % VERSION) +# MACROS = MacroExpander(VERSION) + +class MSVCCompiler(CCompiler) : + """Concrete class that implements an interface to Microsoft Visual C++, + as defined by the CCompiler abstract class.""" + + compiler_type = 'msvc' + + # Just set this so CCompiler's constructor doesn't barf. We currently + # don't use the 'set_executables()' bureaucracy provided by CCompiler, + # as it really isn't necessary for this sort of single-compiler class. + # Would be nice to have a consistent interface with UnixCCompiler, + # though, so it's worth thinking about. + executables = {} + + # Private class data (need to distinguish C from C++ source for compiler) + _c_extensions = ['.c'] + _cpp_extensions = ['.cc', '.cpp', '.cxx'] + _rc_extensions = ['.rc'] + _mc_extensions = ['.mc'] + + # Needed for the filename generation methods provided by the + # base class, CCompiler. + src_extensions = (_c_extensions + _cpp_extensions + + _rc_extensions + _mc_extensions) + res_extension = '.res' + obj_extension = '.obj' + static_lib_extension = '.lib' + shared_lib_extension = '.dll' + static_lib_format = shared_lib_format = '%s%s' + exe_extension = '.exe' + + def __init__(self, verbose=0, dry_run=0, force=0): + CCompiler.__init__ (self, verbose, dry_run, force) + self.__version = VERSION + self.__root = r"Software\Microsoft\VisualStudio" + # self.__macros = MACROS + self.__paths = [] + # target platform (.plat_name is consistent with 'bdist') + self.plat_name = None + self.__arch = None # deprecated name + self.initialized = False + + def initialize(self, plat_name=None): + # multi-init means we would need to check platform same each time... + assert not self.initialized, "don't init multiple times" + if plat_name is None: + plat_name = get_platform() + # sanity check for platforms to prevent obscure errors later. + ok_plats = 'win32', 'win-amd64' + if plat_name not in ok_plats: + raise DistutilsPlatformError("--plat-name must be one of %s" % + (ok_plats,)) + + if "DISTUTILS_USE_SDK" in os.environ and "MSSdk" in os.environ and self.find_exe("cl.exe"): + # Assume that the SDK set up everything alright; don't try to be + # smarter + self.cc = "cl.exe" + self.linker = "link.exe" + self.lib = "lib.exe" + self.rc = "rc.exe" + self.mc = "mc.exe" + else: + # On x86, 'vcvars32.bat amd64' creates an env that doesn't work; + # to cross compile, you use 'x86_amd64'. + # On AMD64, 'vcvars32.bat amd64' is a native build env; to cross + # compile use 'x86' (ie, it runs the x86 compiler directly) + if plat_name == get_platform() or plat_name == 'win32': + # native build or cross-compile to win32 + plat_spec = PLAT_TO_VCVARS[plat_name] + else: + # cross compile from win32 -> some 64bit + plat_spec = PLAT_TO_VCVARS[get_platform()] + '_' + \ + PLAT_TO_VCVARS[plat_name] + + vc_env = query_vcvarsall(VERSION, plat_spec) + + self.__paths = vc_env['path'].split(os.pathsep) + os.environ['lib'] = vc_env['lib'] + os.environ['include'] = vc_env['include'] + + if len(self.__paths) == 0: + raise DistutilsPlatformError("Python was built with %s, " + "and extensions need to be built with the same " + "version of the compiler, but it isn't installed." + % self.__product) + + self.cc = self.find_exe("cl.exe") + self.linker = self.find_exe("link.exe") + self.lib = self.find_exe("lib.exe") + self.rc = self.find_exe("rc.exe") # resource compiler + self.mc = self.find_exe("mc.exe") # message compiler + #self.set_path_env_var('lib') + #self.set_path_env_var('include') + + # extend the MSVC path with the current path + try: + for p in os.environ['path'].split(';'): + self.__paths.append(p) + except KeyError: + pass + self.__paths = normalize_and_reduce_paths(self.__paths) + os.environ['path'] = ";".join(self.__paths) + + self.preprocess_options = None + if self.__arch == "x86": + self.compile_options = [ '/nologo', '/O2', '/MD', '/W3', + '/DNDEBUG'] + self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', + '/Z7', '/D_DEBUG'] + else: + # Win64 + self.compile_options = [ '/nologo', '/O2', '/MD', '/W3', '/GS-' , + '/DNDEBUG'] + self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', '/GS-', + '/Z7', '/D_DEBUG'] + + self.ldflags_shared = ['/DLL', '/nologo', '/INCREMENTAL:NO'] + if self.__version >= 7: + self.ldflags_shared_debug = [ + '/DLL', '/nologo', '/INCREMENTAL:no', '/DEBUG' + ] + self.ldflags_static = [ '/nologo'] + + self.initialized = True + + # -- Worker methods ------------------------------------------------ + + def object_filenames(self, + source_filenames, + strip_dir=0, + output_dir=''): + # Copied from ccompiler.py, extended to return .res as 'object'-file + # for .rc input file + if output_dir is None: output_dir = '' + obj_names = [] + for src_name in source_filenames: + (base, ext) = os.path.splitext (src_name) + base = os.path.splitdrive(base)[1] # Chop off the drive + base = base[os.path.isabs(base):] # If abs, chop off leading / + if ext not in self.src_extensions: + # Better to raise an exception instead of silently continuing + # and later complain about sources and targets having + # different lengths + raise CompileError ("Don't know how to compile %s" % src_name) + if strip_dir: + base = os.path.basename (base) + if ext in self._rc_extensions: + obj_names.append (os.path.join (output_dir, + base + self.res_extension)) + elif ext in self._mc_extensions: + obj_names.append (os.path.join (output_dir, + base + self.res_extension)) + else: + obj_names.append (os.path.join (output_dir, + base + self.obj_extension)) + return obj_names + + + def compile(self, sources, + output_dir=None, macros=None, include_dirs=None, debug=0, + extra_preargs=None, extra_postargs=None, depends=None): + + if not self.initialized: + self.initialize() + compile_info = self._setup_compile(output_dir, macros, include_dirs, + sources, depends, extra_postargs) + macros, objects, extra_postargs, pp_opts, build = compile_info + + compile_opts = extra_preargs or [] + compile_opts.append ('/c') + if debug: + compile_opts.extend(self.compile_options_debug) + else: + compile_opts.extend(self.compile_options) + + for obj in objects: + try: + src, ext = build[obj] + except KeyError: + continue + if debug: + # pass the full pathname to MSVC in debug mode, + # this allows the debugger to find the source file + # without asking the user to browse for it + src = os.path.abspath(src) + + if ext in self._c_extensions: + input_opt = "/Tc" + src + elif ext in self._cpp_extensions: + input_opt = "/Tp" + src + elif ext in self._rc_extensions: + # compile .RC to .RES file + input_opt = src + output_opt = "/fo" + obj + try: + self.spawn([self.rc] + pp_opts + + [output_opt] + [input_opt]) + except DistutilsExecError as msg: + raise CompileError(msg) + continue + elif ext in self._mc_extensions: + # Compile .MC to .RC file to .RES file. + # * '-h dir' specifies the directory for the + # generated include file + # * '-r dir' specifies the target directory of the + # generated RC file and the binary message resource + # it includes + # + # For now (since there are no options to change this), + # we use the source-directory for the include file and + # the build directory for the RC file and message + # resources. This works at least for win32all. + h_dir = os.path.dirname(src) + rc_dir = os.path.dirname(obj) + try: + # first compile .MC to .RC and .H file + self.spawn([self.mc] + + ['-h', h_dir, '-r', rc_dir] + [src]) + base, _ = os.path.splitext (os.path.basename (src)) + rc_file = os.path.join (rc_dir, base + '.rc') + # then compile .RC to .RES file + self.spawn([self.rc] + + ["/fo" + obj] + [rc_file]) + + except DistutilsExecError as msg: + raise CompileError(msg) + continue + else: + # how to handle this file? + raise CompileError("Don't know how to compile %s to %s" + % (src, obj)) + + output_opt = "/Fo" + obj + try: + self.spawn([self.cc] + compile_opts + pp_opts + + [input_opt, output_opt] + + extra_postargs) + except DistutilsExecError as msg: + raise CompileError(msg) + + return objects + + + def create_static_lib(self, + objects, + output_libname, + output_dir=None, + debug=0, + target_lang=None): + + if not self.initialized: + self.initialize() + (objects, output_dir) = self._fix_object_args(objects, output_dir) + output_filename = self.library_filename(output_libname, + output_dir=output_dir) + + if self._need_link(objects, output_filename): + lib_args = objects + ['/OUT:' + output_filename] + if debug: + pass # XXX what goes here? + try: + self.spawn([self.lib] + lib_args) + except DistutilsExecError as msg: + raise LibError(msg) + else: + log.debug("skipping %s (up-to-date)", output_filename) + + + def link(self, + target_desc, + objects, + output_filename, + output_dir=None, + libraries=None, + library_dirs=None, + runtime_library_dirs=None, + export_symbols=None, + debug=0, + extra_preargs=None, + extra_postargs=None, + build_temp=None, + target_lang=None): + + if not self.initialized: + self.initialize() + (objects, output_dir) = self._fix_object_args(objects, output_dir) + fixed_args = self._fix_lib_args(libraries, library_dirs, + runtime_library_dirs) + (libraries, library_dirs, runtime_library_dirs) = fixed_args + + if runtime_library_dirs: + self.warn ("I don't know what to do with 'runtime_library_dirs': " + + str (runtime_library_dirs)) + + lib_opts = gen_lib_options(self, + library_dirs, runtime_library_dirs, + libraries) + if output_dir is not None: + output_filename = os.path.join(output_dir, output_filename) + + if self._need_link(objects, output_filename): + if target_desc == CCompiler.EXECUTABLE: + if debug: + ldflags = self.ldflags_shared_debug[1:] + else: + ldflags = self.ldflags_shared[1:] + else: + if debug: + ldflags = self.ldflags_shared_debug + else: + ldflags = self.ldflags_shared + + export_opts = [] + for sym in (export_symbols or []): + export_opts.append("/EXPORT:" + sym) + + ld_args = (ldflags + lib_opts + export_opts + + objects + ['/OUT:' + output_filename]) + + # The MSVC linker generates .lib and .exp files, which cannot be + # suppressed by any linker switches. The .lib files may even be + # needed! Make sure they are generated in the temporary build + # directory. Since they have different names for debug and release + # builds, they can go into the same directory. + build_temp = os.path.dirname(objects[0]) + if export_symbols is not None: + (dll_name, dll_ext) = os.path.splitext( + os.path.basename(output_filename)) + implib_file = os.path.join( + build_temp, + self.library_filename(dll_name)) + ld_args.append ('/IMPLIB:' + implib_file) + + self.manifest_setup_ldargs(output_filename, build_temp, ld_args) + + if extra_preargs: + ld_args[:0] = extra_preargs + if extra_postargs: + ld_args.extend(extra_postargs) + + self.mkpath(os.path.dirname(output_filename)) + try: + self.spawn([self.linker] + ld_args) + except DistutilsExecError as msg: + raise LinkError(msg) + + # embed the manifest + # XXX - this is somewhat fragile - if mt.exe fails, distutils + # will still consider the DLL up-to-date, but it will not have a + # manifest. Maybe we should link to a temp file? OTOH, that + # implies a build environment error that shouldn't go undetected. + mfinfo = self.manifest_get_embed_info(target_desc, ld_args) + if mfinfo is not None: + mffilename, mfid = mfinfo + out_arg = '-outputresource:%s;%s' % (output_filename, mfid) + try: + self.spawn(['mt.exe', '-nologo', '-manifest', + mffilename, out_arg]) + except DistutilsExecError as msg: + raise LinkError(msg) + else: + log.debug("skipping %s (up-to-date)", output_filename) + + def manifest_setup_ldargs(self, output_filename, build_temp, ld_args): + # If we need a manifest at all, an embedded manifest is recommended. + # See MSDN article titled + # "How to: Embed a Manifest Inside a C/C++ Application" + # (currently at http://msdn2.microsoft.com/en-us/library/ms235591(VS.80).aspx) + # Ask the linker to generate the manifest in the temp dir, so + # we can check it, and possibly embed it, later. + temp_manifest = os.path.join( + build_temp, + os.path.basename(output_filename) + ".manifest") + ld_args.append('/MANIFESTFILE:' + temp_manifest) + + def manifest_get_embed_info(self, target_desc, ld_args): + # If a manifest should be embedded, return a tuple of + # (manifest_filename, resource_id). Returns None if no manifest + # should be embedded. See http://bugs.python.org/issue7833 for why + # we want to avoid any manifest for extension modules if we can) + for arg in ld_args: + if arg.startswith("/MANIFESTFILE:"): + temp_manifest = arg.split(":", 1)[1] + break + else: + # no /MANIFESTFILE so nothing to do. + return None + if target_desc == CCompiler.EXECUTABLE: + # by default, executables always get the manifest with the + # CRT referenced. + mfid = 1 + else: + # Extension modules try and avoid any manifest if possible. + mfid = 2 + temp_manifest = self._remove_visual_c_ref(temp_manifest) + if temp_manifest is None: + return None + return temp_manifest, mfid + + def _remove_visual_c_ref(self, manifest_file): + try: + # Remove references to the Visual C runtime, so they will + # fall through to the Visual C dependency of Python.exe. + # This way, when installed for a restricted user (e.g. + # runtimes are not in WinSxS folder, but in Python's own + # folder), the runtimes do not need to be in every folder + # with .pyd's. + # Returns either the filename of the modified manifest or + # None if no manifest should be embedded. + manifest_f = open(manifest_file) + try: + manifest_buf = manifest_f.read() + finally: + manifest_f.close() + pattern = re.compile( + r"""|)""", + re.DOTALL) + manifest_buf = re.sub(pattern, "", manifest_buf) + pattern = r"\s*" + manifest_buf = re.sub(pattern, "", manifest_buf) + # Now see if any other assemblies are referenced - if not, we + # don't want a manifest embedded. + pattern = re.compile( + r"""|)""", re.DOTALL) + if re.search(pattern, manifest_buf) is None: + return None + + manifest_f = open(manifest_file, 'w') + try: + manifest_f.write(manifest_buf) + return manifest_file + finally: + manifest_f.close() + except OSError: + pass + + # -- Miscellaneous methods ----------------------------------------- + # These are all used by the 'gen_lib_options() function, in + # ccompiler.py. + + def library_dir_option(self, dir): + return "/LIBPATH:" + dir + + def runtime_library_dir_option(self, dir): + raise DistutilsPlatformError( + "don't know how to set runtime library search path for MSVC++") + + def library_option(self, lib): + return self.library_filename(lib) + + + def find_library_file(self, dirs, lib, debug=0): + # Prefer a debugging library if found (and requested), but deal + # with it if we don't have one. + if debug: + try_names = [lib + "_d", lib] + else: + try_names = [lib] + for dir in dirs: + for name in try_names: + libfile = os.path.join(dir, self.library_filename (name)) + if os.path.exists(libfile): + return libfile + else: + # Oops, didn't find it in *any* of 'dirs' + return None + + # Helper methods for using the MSVC registry settings + + def find_exe(self, exe): + """Return path to an MSVC executable program. + + Tries to find the program in several places: first, one of the + MSVC program search paths from the registry; next, the directories + in the PATH environment variable. If any of those work, return an + absolute path that is known to exist. If none of them work, just + return the original program name, 'exe'. + """ + for p in self.__paths: + fn = os.path.join(os.path.abspath(p), exe) + if os.path.isfile(fn): + return fn + + # didn't find it; try existing path + for p in os.environ['Path'].split(';'): + fn = os.path.join(os.path.abspath(p),exe) + if os.path.isfile(fn): + return fn + + return exe diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/msvccompiler.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/msvccompiler.py new file mode 100644 index 00000000..2d447b85 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/msvccompiler.py @@ -0,0 +1,643 @@ +"""distutils.msvccompiler + +Contains MSVCCompiler, an implementation of the abstract CCompiler class +for the Microsoft Visual Studio. +""" + +# Written by Perry Stoll +# hacked by Robin Becker and Thomas Heller to do a better job of +# finding DevStudio (through the registry) + +import sys, os +from distutils.errors import \ + DistutilsExecError, DistutilsPlatformError, \ + CompileError, LibError, LinkError +from distutils.ccompiler import \ + CCompiler, gen_lib_options +from distutils import log + +_can_read_reg = False +try: + import winreg + + _can_read_reg = True + hkey_mod = winreg + + RegOpenKeyEx = winreg.OpenKeyEx + RegEnumKey = winreg.EnumKey + RegEnumValue = winreg.EnumValue + RegError = winreg.error + +except ImportError: + try: + import win32api + import win32con + _can_read_reg = True + hkey_mod = win32con + + RegOpenKeyEx = win32api.RegOpenKeyEx + RegEnumKey = win32api.RegEnumKey + RegEnumValue = win32api.RegEnumValue + RegError = win32api.error + except ImportError: + log.info("Warning: Can't read registry to find the " + "necessary compiler setting\n" + "Make sure that Python modules winreg, " + "win32api or win32con are installed.") + pass + +if _can_read_reg: + HKEYS = (hkey_mod.HKEY_USERS, + hkey_mod.HKEY_CURRENT_USER, + hkey_mod.HKEY_LOCAL_MACHINE, + hkey_mod.HKEY_CLASSES_ROOT) + +def read_keys(base, key): + """Return list of registry keys.""" + try: + handle = RegOpenKeyEx(base, key) + except RegError: + return None + L = [] + i = 0 + while True: + try: + k = RegEnumKey(handle, i) + except RegError: + break + L.append(k) + i += 1 + return L + +def read_values(base, key): + """Return dict of registry keys and values. + + All names are converted to lowercase. + """ + try: + handle = RegOpenKeyEx(base, key) + except RegError: + return None + d = {} + i = 0 + while True: + try: + name, value, type = RegEnumValue(handle, i) + except RegError: + break + name = name.lower() + d[convert_mbcs(name)] = convert_mbcs(value) + i += 1 + return d + +def convert_mbcs(s): + dec = getattr(s, "decode", None) + if dec is not None: + try: + s = dec("mbcs") + except UnicodeError: + pass + return s + +class MacroExpander: + def __init__(self, version): + self.macros = {} + self.load_macros(version) + + def set_macro(self, macro, path, key): + for base in HKEYS: + d = read_values(base, path) + if d: + self.macros["$(%s)" % macro] = d[key] + break + + def load_macros(self, version): + vsbase = r"Software\Microsoft\VisualStudio\%0.1f" % version + self.set_macro("VCInstallDir", vsbase + r"\Setup\VC", "productdir") + self.set_macro("VSInstallDir", vsbase + r"\Setup\VS", "productdir") + net = r"Software\Microsoft\.NETFramework" + self.set_macro("FrameworkDir", net, "installroot") + try: + if version > 7.0: + self.set_macro("FrameworkSDKDir", net, "sdkinstallrootv1.1") + else: + self.set_macro("FrameworkSDKDir", net, "sdkinstallroot") + except KeyError as exc: # + raise DistutilsPlatformError( + """Python was built with Visual Studio 2003; +extensions must be built with a compiler than can generate compatible binaries. +Visual Studio 2003 was not found on this system. If you have Cygwin installed, +you can try compiling with MingW32, by passing "-c mingw32" to setup.py.""") + + p = r"Software\Microsoft\NET Framework Setup\Product" + for base in HKEYS: + try: + h = RegOpenKeyEx(base, p) + except RegError: + continue + key = RegEnumKey(h, 0) + d = read_values(base, r"%s\%s" % (p, key)) + self.macros["$(FrameworkVersion)"] = d["version"] + + def sub(self, s): + for k, v in self.macros.items(): + s = s.replace(k, v) + return s + +def get_build_version(): + """Return the version of MSVC that was used to build Python. + + For Python 2.3 and up, the version number is included in + sys.version. For earlier versions, assume the compiler is MSVC 6. + """ + prefix = "MSC v." + i = sys.version.find(prefix) + if i == -1: + return 6 + i = i + len(prefix) + s, rest = sys.version[i:].split(" ", 1) + majorVersion = int(s[:-2]) - 6 + if majorVersion >= 13: + # v13 was skipped and should be v14 + majorVersion += 1 + minorVersion = int(s[2:3]) / 10.0 + # I don't think paths are affected by minor version in version 6 + if majorVersion == 6: + minorVersion = 0 + if majorVersion >= 6: + return majorVersion + minorVersion + # else we don't know what version of the compiler this is + return None + +def get_build_architecture(): + """Return the processor architecture. + + Possible results are "Intel" or "AMD64". + """ + + prefix = " bit (" + i = sys.version.find(prefix) + if i == -1: + return "Intel" + j = sys.version.find(")", i) + return sys.version[i+len(prefix):j] + +def normalize_and_reduce_paths(paths): + """Return a list of normalized paths with duplicates removed. + + The current order of paths is maintained. + """ + # Paths are normalized so things like: /a and /a/ aren't both preserved. + reduced_paths = [] + for p in paths: + np = os.path.normpath(p) + # XXX(nnorwitz): O(n**2), if reduced_paths gets long perhaps use a set. + if np not in reduced_paths: + reduced_paths.append(np) + return reduced_paths + + +class MSVCCompiler(CCompiler) : + """Concrete class that implements an interface to Microsoft Visual C++, + as defined by the CCompiler abstract class.""" + + compiler_type = 'msvc' + + # Just set this so CCompiler's constructor doesn't barf. We currently + # don't use the 'set_executables()' bureaucracy provided by CCompiler, + # as it really isn't necessary for this sort of single-compiler class. + # Would be nice to have a consistent interface with UnixCCompiler, + # though, so it's worth thinking about. + executables = {} + + # Private class data (need to distinguish C from C++ source for compiler) + _c_extensions = ['.c'] + _cpp_extensions = ['.cc', '.cpp', '.cxx'] + _rc_extensions = ['.rc'] + _mc_extensions = ['.mc'] + + # Needed for the filename generation methods provided by the + # base class, CCompiler. + src_extensions = (_c_extensions + _cpp_extensions + + _rc_extensions + _mc_extensions) + res_extension = '.res' + obj_extension = '.obj' + static_lib_extension = '.lib' + shared_lib_extension = '.dll' + static_lib_format = shared_lib_format = '%s%s' + exe_extension = '.exe' + + def __init__(self, verbose=0, dry_run=0, force=0): + CCompiler.__init__ (self, verbose, dry_run, force) + self.__version = get_build_version() + self.__arch = get_build_architecture() + if self.__arch == "Intel": + # x86 + if self.__version >= 7: + self.__root = r"Software\Microsoft\VisualStudio" + self.__macros = MacroExpander(self.__version) + else: + self.__root = r"Software\Microsoft\Devstudio" + self.__product = "Visual Studio version %s" % self.__version + else: + # Win64. Assume this was built with the platform SDK + self.__product = "Microsoft SDK compiler %s" % (self.__version + 6) + + self.initialized = False + + def initialize(self): + self.__paths = [] + if "DISTUTILS_USE_SDK" in os.environ and "MSSdk" in os.environ and self.find_exe("cl.exe"): + # Assume that the SDK set up everything alright; don't try to be + # smarter + self.cc = "cl.exe" + self.linker = "link.exe" + self.lib = "lib.exe" + self.rc = "rc.exe" + self.mc = "mc.exe" + else: + self.__paths = self.get_msvc_paths("path") + + if len(self.__paths) == 0: + raise DistutilsPlatformError("Python was built with %s, " + "and extensions need to be built with the same " + "version of the compiler, but it isn't installed." + % self.__product) + + self.cc = self.find_exe("cl.exe") + self.linker = self.find_exe("link.exe") + self.lib = self.find_exe("lib.exe") + self.rc = self.find_exe("rc.exe") # resource compiler + self.mc = self.find_exe("mc.exe") # message compiler + self.set_path_env_var('lib') + self.set_path_env_var('include') + + # extend the MSVC path with the current path + try: + for p in os.environ['path'].split(';'): + self.__paths.append(p) + except KeyError: + pass + self.__paths = normalize_and_reduce_paths(self.__paths) + os.environ['path'] = ";".join(self.__paths) + + self.preprocess_options = None + if self.__arch == "Intel": + self.compile_options = [ '/nologo', '/O2', '/MD', '/W3', '/GX' , + '/DNDEBUG'] + self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', '/GX', + '/Z7', '/D_DEBUG'] + else: + # Win64 + self.compile_options = [ '/nologo', '/O2', '/MD', '/W3', '/GS-' , + '/DNDEBUG'] + self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', '/GS-', + '/Z7', '/D_DEBUG'] + + self.ldflags_shared = ['/DLL', '/nologo', '/INCREMENTAL:NO'] + if self.__version >= 7: + self.ldflags_shared_debug = [ + '/DLL', '/nologo', '/INCREMENTAL:no', '/DEBUG' + ] + else: + self.ldflags_shared_debug = [ + '/DLL', '/nologo', '/INCREMENTAL:no', '/pdb:None', '/DEBUG' + ] + self.ldflags_static = [ '/nologo'] + + self.initialized = True + + # -- Worker methods ------------------------------------------------ + + def object_filenames(self, + source_filenames, + strip_dir=0, + output_dir=''): + # Copied from ccompiler.py, extended to return .res as 'object'-file + # for .rc input file + if output_dir is None: output_dir = '' + obj_names = [] + for src_name in source_filenames: + (base, ext) = os.path.splitext (src_name) + base = os.path.splitdrive(base)[1] # Chop off the drive + base = base[os.path.isabs(base):] # If abs, chop off leading / + if ext not in self.src_extensions: + # Better to raise an exception instead of silently continuing + # and later complain about sources and targets having + # different lengths + raise CompileError ("Don't know how to compile %s" % src_name) + if strip_dir: + base = os.path.basename (base) + if ext in self._rc_extensions: + obj_names.append (os.path.join (output_dir, + base + self.res_extension)) + elif ext in self._mc_extensions: + obj_names.append (os.path.join (output_dir, + base + self.res_extension)) + else: + obj_names.append (os.path.join (output_dir, + base + self.obj_extension)) + return obj_names + + + def compile(self, sources, + output_dir=None, macros=None, include_dirs=None, debug=0, + extra_preargs=None, extra_postargs=None, depends=None): + + if not self.initialized: + self.initialize() + compile_info = self._setup_compile(output_dir, macros, include_dirs, + sources, depends, extra_postargs) + macros, objects, extra_postargs, pp_opts, build = compile_info + + compile_opts = extra_preargs or [] + compile_opts.append ('/c') + if debug: + compile_opts.extend(self.compile_options_debug) + else: + compile_opts.extend(self.compile_options) + + for obj in objects: + try: + src, ext = build[obj] + except KeyError: + continue + if debug: + # pass the full pathname to MSVC in debug mode, + # this allows the debugger to find the source file + # without asking the user to browse for it + src = os.path.abspath(src) + + if ext in self._c_extensions: + input_opt = "/Tc" + src + elif ext in self._cpp_extensions: + input_opt = "/Tp" + src + elif ext in self._rc_extensions: + # compile .RC to .RES file + input_opt = src + output_opt = "/fo" + obj + try: + self.spawn([self.rc] + pp_opts + + [output_opt] + [input_opt]) + except DistutilsExecError as msg: + raise CompileError(msg) + continue + elif ext in self._mc_extensions: + # Compile .MC to .RC file to .RES file. + # * '-h dir' specifies the directory for the + # generated include file + # * '-r dir' specifies the target directory of the + # generated RC file and the binary message resource + # it includes + # + # For now (since there are no options to change this), + # we use the source-directory for the include file and + # the build directory for the RC file and message + # resources. This works at least for win32all. + h_dir = os.path.dirname(src) + rc_dir = os.path.dirname(obj) + try: + # first compile .MC to .RC and .H file + self.spawn([self.mc] + + ['-h', h_dir, '-r', rc_dir] + [src]) + base, _ = os.path.splitext (os.path.basename (src)) + rc_file = os.path.join (rc_dir, base + '.rc') + # then compile .RC to .RES file + self.spawn([self.rc] + + ["/fo" + obj] + [rc_file]) + + except DistutilsExecError as msg: + raise CompileError(msg) + continue + else: + # how to handle this file? + raise CompileError("Don't know how to compile %s to %s" + % (src, obj)) + + output_opt = "/Fo" + obj + try: + self.spawn([self.cc] + compile_opts + pp_opts + + [input_opt, output_opt] + + extra_postargs) + except DistutilsExecError as msg: + raise CompileError(msg) + + return objects + + + def create_static_lib(self, + objects, + output_libname, + output_dir=None, + debug=0, + target_lang=None): + + if not self.initialized: + self.initialize() + (objects, output_dir) = self._fix_object_args(objects, output_dir) + output_filename = self.library_filename(output_libname, + output_dir=output_dir) + + if self._need_link(objects, output_filename): + lib_args = objects + ['/OUT:' + output_filename] + if debug: + pass # XXX what goes here? + try: + self.spawn([self.lib] + lib_args) + except DistutilsExecError as msg: + raise LibError(msg) + else: + log.debug("skipping %s (up-to-date)", output_filename) + + + def link(self, + target_desc, + objects, + output_filename, + output_dir=None, + libraries=None, + library_dirs=None, + runtime_library_dirs=None, + export_symbols=None, + debug=0, + extra_preargs=None, + extra_postargs=None, + build_temp=None, + target_lang=None): + + if not self.initialized: + self.initialize() + (objects, output_dir) = self._fix_object_args(objects, output_dir) + fixed_args = self._fix_lib_args(libraries, library_dirs, + runtime_library_dirs) + (libraries, library_dirs, runtime_library_dirs) = fixed_args + + if runtime_library_dirs: + self.warn ("I don't know what to do with 'runtime_library_dirs': " + + str (runtime_library_dirs)) + + lib_opts = gen_lib_options(self, + library_dirs, runtime_library_dirs, + libraries) + if output_dir is not None: + output_filename = os.path.join(output_dir, output_filename) + + if self._need_link(objects, output_filename): + if target_desc == CCompiler.EXECUTABLE: + if debug: + ldflags = self.ldflags_shared_debug[1:] + else: + ldflags = self.ldflags_shared[1:] + else: + if debug: + ldflags = self.ldflags_shared_debug + else: + ldflags = self.ldflags_shared + + export_opts = [] + for sym in (export_symbols or []): + export_opts.append("/EXPORT:" + sym) + + ld_args = (ldflags + lib_opts + export_opts + + objects + ['/OUT:' + output_filename]) + + # The MSVC linker generates .lib and .exp files, which cannot be + # suppressed by any linker switches. The .lib files may even be + # needed! Make sure they are generated in the temporary build + # directory. Since they have different names for debug and release + # builds, they can go into the same directory. + if export_symbols is not None: + (dll_name, dll_ext) = os.path.splitext( + os.path.basename(output_filename)) + implib_file = os.path.join( + os.path.dirname(objects[0]), + self.library_filename(dll_name)) + ld_args.append ('/IMPLIB:' + implib_file) + + if extra_preargs: + ld_args[:0] = extra_preargs + if extra_postargs: + ld_args.extend(extra_postargs) + + self.mkpath(os.path.dirname(output_filename)) + try: + self.spawn([self.linker] + ld_args) + except DistutilsExecError as msg: + raise LinkError(msg) + + else: + log.debug("skipping %s (up-to-date)", output_filename) + + + # -- Miscellaneous methods ----------------------------------------- + # These are all used by the 'gen_lib_options() function, in + # ccompiler.py. + + def library_dir_option(self, dir): + return "/LIBPATH:" + dir + + def runtime_library_dir_option(self, dir): + raise DistutilsPlatformError( + "don't know how to set runtime library search path for MSVC++") + + def library_option(self, lib): + return self.library_filename(lib) + + + def find_library_file(self, dirs, lib, debug=0): + # Prefer a debugging library if found (and requested), but deal + # with it if we don't have one. + if debug: + try_names = [lib + "_d", lib] + else: + try_names = [lib] + for dir in dirs: + for name in try_names: + libfile = os.path.join(dir, self.library_filename (name)) + if os.path.exists(libfile): + return libfile + else: + # Oops, didn't find it in *any* of 'dirs' + return None + + # Helper methods for using the MSVC registry settings + + def find_exe(self, exe): + """Return path to an MSVC executable program. + + Tries to find the program in several places: first, one of the + MSVC program search paths from the registry; next, the directories + in the PATH environment variable. If any of those work, return an + absolute path that is known to exist. If none of them work, just + return the original program name, 'exe'. + """ + for p in self.__paths: + fn = os.path.join(os.path.abspath(p), exe) + if os.path.isfile(fn): + return fn + + # didn't find it; try existing path + for p in os.environ['Path'].split(';'): + fn = os.path.join(os.path.abspath(p),exe) + if os.path.isfile(fn): + return fn + + return exe + + def get_msvc_paths(self, path, platform='x86'): + """Get a list of devstudio directories (include, lib or path). + + Return a list of strings. The list will be empty if unable to + access the registry or appropriate registry keys not found. + """ + if not _can_read_reg: + return [] + + path = path + " dirs" + if self.__version >= 7: + key = (r"%s\%0.1f\VC\VC_OBJECTS_PLATFORM_INFO\Win32\Directories" + % (self.__root, self.__version)) + else: + key = (r"%s\6.0\Build System\Components\Platforms" + r"\Win32 (%s)\Directories" % (self.__root, platform)) + + for base in HKEYS: + d = read_values(base, key) + if d: + if self.__version >= 7: + return self.__macros.sub(d[path]).split(";") + else: + return d[path].split(";") + # MSVC 6 seems to create the registry entries we need only when + # the GUI is run. + if self.__version == 6: + for base in HKEYS: + if read_values(base, r"%s\6.0" % self.__root) is not None: + self.warn("It seems you have Visual Studio 6 installed, " + "but the expected registry settings are not present.\n" + "You must at least run the Visual Studio GUI once " + "so that these entries are created.") + break + return [] + + def set_path_env_var(self, name): + """Set environment variable 'name' to an MSVC path type value. + + This is equivalent to a SET command prior to execution of spawned + commands. + """ + + if name == "lib": + p = self.get_msvc_paths("library") + else: + p = self.get_msvc_paths(name) + if p: + os.environ[name] = ';'.join(p) + + +if get_build_version() >= 8.0: + log.debug("Importing new compiler from distutils.msvc9compiler") + OldMSVCCompiler = MSVCCompiler + from distutils.msvc9compiler import MSVCCompiler + # get_build_architecture not really relevant now we support cross-compile + from distutils.msvc9compiler import MacroExpander diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/py35compat.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/py35compat.py new file mode 100644 index 00000000..79b2e7f3 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/py35compat.py @@ -0,0 +1,19 @@ +import sys +import subprocess + + +def __optim_args_from_interpreter_flags(): + """Return a list of command-line arguments reproducing the current + optimization settings in sys.flags.""" + args = [] + value = sys.flags.optimize + if value > 0: + args.append("-" + "O" * value) + return args + + +_optim_args_from_interpreter_flags = getattr( + subprocess, + "_optim_args_from_interpreter_flags", + __optim_args_from_interpreter_flags, +) diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/py38compat.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/py38compat.py new file mode 100644 index 00000000..7dbe8cef --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/py38compat.py @@ -0,0 +1,7 @@ +def aix_platform(osname, version, release): + try: + import _aix_support + return _aix_support.aix_platform() + except ImportError: + pass + return "%s-%s.%s" % (osname, version, release) diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/spawn.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/spawn.py new file mode 100644 index 00000000..6e1c89f1 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/spawn.py @@ -0,0 +1,106 @@ +"""distutils.spawn + +Provides the 'spawn()' function, a front-end to various platform- +specific functions for launching another program in a sub-process. +Also provides the 'find_executable()' to search the path for a given +executable name. +""" + +import sys +import os +import subprocess + +from distutils.errors import DistutilsPlatformError, DistutilsExecError +from distutils.debug import DEBUG +from distutils import log + + +def spawn(cmd, search_path=1, verbose=0, dry_run=0, env=None): + """Run another program, specified as a command list 'cmd', in a new process. + + 'cmd' is just the argument list for the new process, ie. + cmd[0] is the program to run and cmd[1:] are the rest of its arguments. + There is no way to run a program with a name different from that of its + executable. + + If 'search_path' is true (the default), the system's executable + search path will be used to find the program; otherwise, cmd[0] + must be the exact path to the executable. If 'dry_run' is true, + the command will not actually be run. + + Raise DistutilsExecError if running the program fails in any way; just + return on success. + """ + # cmd is documented as a list, but just in case some code passes a tuple + # in, protect our %-formatting code against horrible death + cmd = list(cmd) + + log.info(subprocess.list2cmdline(cmd)) + if dry_run: + return + + if search_path: + executable = find_executable(cmd[0]) + if executable is not None: + cmd[0] = executable + + env = env if env is not None else dict(os.environ) + + if sys.platform == 'darwin': + from distutils.util import MACOSX_VERSION_VAR, get_macosx_target_ver + macosx_target_ver = get_macosx_target_ver() + if macosx_target_ver: + env[MACOSX_VERSION_VAR] = macosx_target_ver + + try: + proc = subprocess.Popen(cmd, env=env) + proc.wait() + exitcode = proc.returncode + except OSError as exc: + if not DEBUG: + cmd = cmd[0] + raise DistutilsExecError( + "command %r failed: %s" % (cmd, exc.args[-1])) from exc + + if exitcode: + if not DEBUG: + cmd = cmd[0] + raise DistutilsExecError( + "command %r failed with exit code %s" % (cmd, exitcode)) + + +def find_executable(executable, path=None): + """Tries to find 'executable' in the directories listed in 'path'. + + A string listing directories separated by 'os.pathsep'; defaults to + os.environ['PATH']. Returns the complete filename or None if not found. + """ + _, ext = os.path.splitext(executable) + if (sys.platform == 'win32') and (ext != '.exe'): + executable = executable + '.exe' + + if os.path.isfile(executable): + return executable + + if path is None: + path = os.environ.get('PATH', None) + if path is None: + try: + path = os.confstr("CS_PATH") + except (AttributeError, ValueError): + # os.confstr() or CS_PATH is not available + path = os.defpath + # bpo-35755: Don't use os.defpath if the PATH environment variable is + # set to an empty string + + # PATH='' doesn't match, whereas PATH=':' looks in the current directory + if not path: + return None + + paths = path.split(os.pathsep) + for p in paths: + f = os.path.join(p, executable) + if os.path.isfile(f): + # the file exists, we have a shot at spawn working + return f + return None diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/sysconfig.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/sysconfig.py new file mode 100644 index 00000000..d36d94f7 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/sysconfig.py @@ -0,0 +1,601 @@ +"""Provide access to Python's configuration information. The specific +configuration variables available depend heavily on the platform and +configuration. The values may be retrieved using +get_config_var(name), and the list of variables is available via +get_config_vars().keys(). Additional convenience functions are also +available. + +Written by: Fred L. Drake, Jr. +Email: +""" + +import _imp +import os +import re +import sys + +from .errors import DistutilsPlatformError + +IS_PYPY = '__pypy__' in sys.builtin_module_names + +# These are needed in a couple of spots, so just compute them once. +PREFIX = os.path.normpath(sys.prefix) +EXEC_PREFIX = os.path.normpath(sys.exec_prefix) +BASE_PREFIX = os.path.normpath(sys.base_prefix) +BASE_EXEC_PREFIX = os.path.normpath(sys.base_exec_prefix) + +# Path to the base directory of the project. On Windows the binary may +# live in project/PCbuild/win32 or project/PCbuild/amd64. +# set for cross builds +if "_PYTHON_PROJECT_BASE" in os.environ: + project_base = os.path.abspath(os.environ["_PYTHON_PROJECT_BASE"]) +else: + if sys.executable: + project_base = os.path.dirname(os.path.abspath(sys.executable)) + else: + # sys.executable can be empty if argv[0] has been changed and Python is + # unable to retrieve the real program name + project_base = os.getcwd() + + +# python_build: (Boolean) if true, we're either building Python or +# building an extension with an un-installed Python, so we use +# different (hard-wired) directories. +def _is_python_source_dir(d): + for fn in ("Setup", "Setup.local"): + if os.path.isfile(os.path.join(d, "Modules", fn)): + return True + return False + +_sys_home = getattr(sys, '_home', None) + +if os.name == 'nt': + def _fix_pcbuild(d): + if d and os.path.normcase(d).startswith( + os.path.normcase(os.path.join(PREFIX, "PCbuild"))): + return PREFIX + return d + project_base = _fix_pcbuild(project_base) + _sys_home = _fix_pcbuild(_sys_home) + +def _python_build(): + if _sys_home: + return _is_python_source_dir(_sys_home) + return _is_python_source_dir(project_base) + +python_build = _python_build() + + +# Calculate the build qualifier flags if they are defined. Adding the flags +# to the include and lib directories only makes sense for an installation, not +# an in-source build. +build_flags = '' +try: + if not python_build: + build_flags = sys.abiflags +except AttributeError: + # It's not a configure-based build, so the sys module doesn't have + # this attribute, which is fine. + pass + +def get_python_version(): + """Return a string containing the major and minor Python version, + leaving off the patchlevel. Sample return values could be '1.5' + or '2.2'. + """ + return '%d.%d' % sys.version_info[:2] + + +def get_python_inc(plat_specific=0, prefix=None): + """Return the directory containing installed Python header files. + + If 'plat_specific' is false (the default), this is the path to the + non-platform-specific header files, i.e. Python.h and so on; + otherwise, this is the path to platform-specific header files + (namely pyconfig.h). + + If 'prefix' is supplied, use it instead of sys.base_prefix or + sys.base_exec_prefix -- i.e., ignore 'plat_specific'. + """ + if prefix is None: + prefix = plat_specific and BASE_EXEC_PREFIX or BASE_PREFIX + if os.name == "posix": + if IS_PYPY and sys.version_info < (3, 8): + return os.path.join(prefix, 'include') + if python_build: + # Assume the executable is in the build directory. The + # pyconfig.h file should be in the same directory. Since + # the build directory may not be the source directory, we + # must use "srcdir" from the makefile to find the "Include" + # directory. + if plat_specific: + return _sys_home or project_base + else: + incdir = os.path.join(get_config_var('srcdir'), 'Include') + return os.path.normpath(incdir) + implementation = 'pypy' if IS_PYPY else 'python' + python_dir = implementation + get_python_version() + build_flags + return os.path.join(prefix, "include", python_dir) + elif os.name == "nt": + if python_build: + # Include both the include and PC dir to ensure we can find + # pyconfig.h + return (os.path.join(prefix, "include") + os.path.pathsep + + os.path.join(prefix, "PC")) + return os.path.join(prefix, "include") + else: + raise DistutilsPlatformError( + "I don't know where Python installs its C header files " + "on platform '%s'" % os.name) + + +# allow this behavior to be monkey-patched. Ref pypa/distutils#2. +def _posix_lib(standard_lib, libpython, early_prefix, prefix): + if standard_lib: + return libpython + else: + return os.path.join(libpython, "site-packages") + + +def get_python_lib(plat_specific=0, standard_lib=0, prefix=None): + """Return the directory containing the Python library (standard or + site additions). + + If 'plat_specific' is true, return the directory containing + platform-specific modules, i.e. any module from a non-pure-Python + module distribution; otherwise, return the platform-shared library + directory. If 'standard_lib' is true, return the directory + containing standard Python library modules; otherwise, return the + directory for site-specific modules. + + If 'prefix' is supplied, use it instead of sys.base_prefix or + sys.base_exec_prefix -- i.e., ignore 'plat_specific'. + """ + + if IS_PYPY and sys.version_info < (3, 8): + # PyPy-specific schema + if prefix is None: + prefix = PREFIX + if standard_lib: + return os.path.join(prefix, "lib-python", sys.version[0]) + return os.path.join(prefix, 'site-packages') + + early_prefix = prefix + + if prefix is None: + if standard_lib: + prefix = plat_specific and BASE_EXEC_PREFIX or BASE_PREFIX + else: + prefix = plat_specific and EXEC_PREFIX or PREFIX + + if os.name == "posix": + if plat_specific or standard_lib: + # Platform-specific modules (any module from a non-pure-Python + # module distribution) or standard Python library modules. + libdir = getattr(sys, "platlibdir", "lib") + else: + # Pure Python + libdir = "lib" + implementation = 'pypy' if IS_PYPY else 'python' + libpython = os.path.join(prefix, libdir, + implementation + get_python_version()) + return _posix_lib(standard_lib, libpython, early_prefix, prefix) + elif os.name == "nt": + if standard_lib: + return os.path.join(prefix, "Lib") + else: + return os.path.join(prefix, "Lib", "site-packages") + else: + raise DistutilsPlatformError( + "I don't know where Python installs its library " + "on platform '%s'" % os.name) + + + +def customize_compiler(compiler): + """Do any platform-specific customization of a CCompiler instance. + + Mainly needed on Unix, so we can plug in the information that + varies across Unices and is stored in Python's Makefile. + """ + if compiler.compiler_type == "unix": + if sys.platform == "darwin": + # Perform first-time customization of compiler-related + # config vars on OS X now that we know we need a compiler. + # This is primarily to support Pythons from binary + # installers. The kind and paths to build tools on + # the user system may vary significantly from the system + # that Python itself was built on. Also the user OS + # version and build tools may not support the same set + # of CPU architectures for universal builds. + global _config_vars + # Use get_config_var() to ensure _config_vars is initialized. + if not get_config_var('CUSTOMIZED_OSX_COMPILER'): + import _osx_support + _osx_support.customize_compiler(_config_vars) + _config_vars['CUSTOMIZED_OSX_COMPILER'] = 'True' + + (cc, cxx, cflags, ccshared, ldshared, shlib_suffix, ar, ar_flags) = \ + get_config_vars('CC', 'CXX', 'CFLAGS', + 'CCSHARED', 'LDSHARED', 'SHLIB_SUFFIX', 'AR', 'ARFLAGS') + + if 'CC' in os.environ: + newcc = os.environ['CC'] + if('LDSHARED' not in os.environ + and ldshared.startswith(cc)): + # If CC is overridden, use that as the default + # command for LDSHARED as well + ldshared = newcc + ldshared[len(cc):] + cc = newcc + if 'CXX' in os.environ: + cxx = os.environ['CXX'] + if 'LDSHARED' in os.environ: + ldshared = os.environ['LDSHARED'] + if 'CPP' in os.environ: + cpp = os.environ['CPP'] + else: + cpp = cc + " -E" # not always + if 'LDFLAGS' in os.environ: + ldshared = ldshared + ' ' + os.environ['LDFLAGS'] + if 'CFLAGS' in os.environ: + cflags = cflags + ' ' + os.environ['CFLAGS'] + ldshared = ldshared + ' ' + os.environ['CFLAGS'] + if 'CPPFLAGS' in os.environ: + cpp = cpp + ' ' + os.environ['CPPFLAGS'] + cflags = cflags + ' ' + os.environ['CPPFLAGS'] + ldshared = ldshared + ' ' + os.environ['CPPFLAGS'] + if 'AR' in os.environ: + ar = os.environ['AR'] + if 'ARFLAGS' in os.environ: + archiver = ar + ' ' + os.environ['ARFLAGS'] + else: + archiver = ar + ' ' + ar_flags + + cc_cmd = cc + ' ' + cflags + compiler.set_executables( + preprocessor=cpp, + compiler=cc_cmd, + compiler_so=cc_cmd + ' ' + ccshared, + compiler_cxx=cxx, + linker_so=ldshared, + linker_exe=cc, + archiver=archiver) + + if 'RANLIB' in os.environ and compiler.executables.get('ranlib', None): + compiler.set_executables(ranlib=os.environ['RANLIB']) + + compiler.shared_lib_extension = shlib_suffix + + +def get_config_h_filename(): + """Return full pathname of installed pyconfig.h file.""" + if python_build: + if os.name == "nt": + inc_dir = os.path.join(_sys_home or project_base, "PC") + else: + inc_dir = _sys_home or project_base + else: + inc_dir = get_python_inc(plat_specific=1) + + return os.path.join(inc_dir, 'pyconfig.h') + + +# Allow this value to be patched by pkgsrc. Ref pypa/distutils#16. +_makefile_tmpl = 'config-{python_ver}{build_flags}{multiarch}' + + +def get_makefile_filename(): + """Return full pathname of installed Makefile from the Python build.""" + if python_build: + return os.path.join(_sys_home or project_base, "Makefile") + lib_dir = get_python_lib(plat_specific=0, standard_lib=1) + multiarch = ( + '-%s' % sys.implementation._multiarch + if hasattr(sys.implementation, '_multiarch') else '' + ) + config_file = _makefile_tmpl.format( + python_ver=get_python_version(), + build_flags=build_flags, + multiarch=multiarch, + ) + return os.path.join(lib_dir, config_file, 'Makefile') + + +def parse_config_h(fp, g=None): + """Parse a config.h-style file. + + A dictionary containing name/value pairs is returned. If an + optional dictionary is passed in as the second argument, it is + used instead of a new dictionary. + """ + if g is None: + g = {} + define_rx = re.compile("#define ([A-Z][A-Za-z0-9_]+) (.*)\n") + undef_rx = re.compile("/[*] #undef ([A-Z][A-Za-z0-9_]+) [*]/\n") + # + while True: + line = fp.readline() + if not line: + break + m = define_rx.match(line) + if m: + n, v = m.group(1, 2) + try: v = int(v) + except ValueError: pass + g[n] = v + else: + m = undef_rx.match(line) + if m: + g[m.group(1)] = 0 + return g + + +# Regexes needed for parsing Makefile (and similar syntaxes, +# like old-style Setup files). +_variable_rx = re.compile(r"([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)") +_findvar1_rx = re.compile(r"\$\(([A-Za-z][A-Za-z0-9_]*)\)") +_findvar2_rx = re.compile(r"\${([A-Za-z][A-Za-z0-9_]*)}") + +def parse_makefile(fn, g=None): + """Parse a Makefile-style file. + + A dictionary containing name/value pairs is returned. If an + optional dictionary is passed in as the second argument, it is + used instead of a new dictionary. + """ + from distutils.text_file import TextFile + fp = TextFile(fn, strip_comments=1, skip_blanks=1, join_lines=1, errors="surrogateescape") + + if g is None: + g = {} + done = {} + notdone = {} + + while True: + line = fp.readline() + if line is None: # eof + break + m = _variable_rx.match(line) + if m: + n, v = m.group(1, 2) + v = v.strip() + # `$$' is a literal `$' in make + tmpv = v.replace('$$', '') + + if "$" in tmpv: + notdone[n] = v + else: + try: + v = int(v) + except ValueError: + # insert literal `$' + done[n] = v.replace('$$', '$') + else: + done[n] = v + + # Variables with a 'PY_' prefix in the makefile. These need to + # be made available without that prefix through sysconfig. + # Special care is needed to ensure that variable expansion works, even + # if the expansion uses the name without a prefix. + renamed_variables = ('CFLAGS', 'LDFLAGS', 'CPPFLAGS') + + # do variable interpolation here + while notdone: + for name in list(notdone): + value = notdone[name] + m = _findvar1_rx.search(value) or _findvar2_rx.search(value) + if m: + n = m.group(1) + found = True + if n in done: + item = str(done[n]) + elif n in notdone: + # get it on a subsequent round + found = False + elif n in os.environ: + # do it like make: fall back to environment + item = os.environ[n] + + elif n in renamed_variables: + if name.startswith('PY_') and name[3:] in renamed_variables: + item = "" + + elif 'PY_' + n in notdone: + found = False + + else: + item = str(done['PY_' + n]) + else: + done[n] = item = "" + if found: + after = value[m.end():] + value = value[:m.start()] + item + after + if "$" in after: + notdone[name] = value + else: + try: value = int(value) + except ValueError: + done[name] = value.strip() + else: + done[name] = value + del notdone[name] + + if name.startswith('PY_') \ + and name[3:] in renamed_variables: + + name = name[3:] + if name not in done: + done[name] = value + else: + # bogus variable reference; just drop it since we can't deal + del notdone[name] + + fp.close() + + # strip spurious spaces + for k, v in done.items(): + if isinstance(v, str): + done[k] = v.strip() + + # save the results in the global dictionary + g.update(done) + return g + + +def expand_makefile_vars(s, vars): + """Expand Makefile-style variables -- "${foo}" or "$(foo)" -- in + 'string' according to 'vars' (a dictionary mapping variable names to + values). Variables not present in 'vars' are silently expanded to the + empty string. The variable values in 'vars' should not contain further + variable expansions; if 'vars' is the output of 'parse_makefile()', + you're fine. Returns a variable-expanded version of 's'. + """ + + # This algorithm does multiple expansion, so if vars['foo'] contains + # "${bar}", it will expand ${foo} to ${bar}, and then expand + # ${bar}... and so forth. This is fine as long as 'vars' comes from + # 'parse_makefile()', which takes care of such expansions eagerly, + # according to make's variable expansion semantics. + + while True: + m = _findvar1_rx.search(s) or _findvar2_rx.search(s) + if m: + (beg, end) = m.span() + s = s[0:beg] + vars.get(m.group(1)) + s[end:] + else: + break + return s + + +_config_vars = None + + +_sysconfig_name_tmpl = '_sysconfigdata_{abi}_{platform}_{multiarch}' + + +def _init_posix(): + """Initialize the module as appropriate for POSIX systems.""" + # _sysconfigdata is generated at build time, see the sysconfig module + name = os.environ.get( + '_PYTHON_SYSCONFIGDATA_NAME', + _sysconfig_name_tmpl.format( + abi=sys.abiflags, + platform=sys.platform, + multiarch=getattr(sys.implementation, '_multiarch', ''), + ), + ) + try: + _temp = __import__(name, globals(), locals(), ['build_time_vars'], 0) + except ImportError: + # Python 3.5 and pypy 7.3.1 + _temp = __import__( + '_sysconfigdata', globals(), locals(), ['build_time_vars'], 0) + build_time_vars = _temp.build_time_vars + global _config_vars + _config_vars = {} + _config_vars.update(build_time_vars) + + +def _init_nt(): + """Initialize the module as appropriate for NT""" + g = {} + # set basic install directories + g['LIBDEST'] = get_python_lib(plat_specific=0, standard_lib=1) + g['BINLIBDEST'] = get_python_lib(plat_specific=1, standard_lib=1) + + # XXX hmmm.. a normal install puts include files here + g['INCLUDEPY'] = get_python_inc(plat_specific=0) + + g['EXT_SUFFIX'] = _imp.extension_suffixes()[0] + g['EXE'] = ".exe" + g['VERSION'] = get_python_version().replace(".", "") + g['BINDIR'] = os.path.dirname(os.path.abspath(sys.executable)) + + global _config_vars + _config_vars = g + + +def get_config_vars(*args): + """With no arguments, return a dictionary of all configuration + variables relevant for the current platform. Generally this includes + everything needed to build extensions and install both pure modules and + extensions. On Unix, this means every variable defined in Python's + installed Makefile; on Windows it's a much smaller set. + + With arguments, return a list of values that result from looking up + each argument in the configuration variable dictionary. + """ + global _config_vars + if _config_vars is None: + func = globals().get("_init_" + os.name) + if func: + func() + else: + _config_vars = {} + + # Normalized versions of prefix and exec_prefix are handy to have; + # in fact, these are the standard versions used most places in the + # Distutils. + _config_vars['prefix'] = PREFIX + _config_vars['exec_prefix'] = EXEC_PREFIX + + if not IS_PYPY: + # For backward compatibility, see issue19555 + SO = _config_vars.get('EXT_SUFFIX') + if SO is not None: + _config_vars['SO'] = SO + + # Always convert srcdir to an absolute path + srcdir = _config_vars.get('srcdir', project_base) + if os.name == 'posix': + if python_build: + # If srcdir is a relative path (typically '.' or '..') + # then it should be interpreted relative to the directory + # containing Makefile. + base = os.path.dirname(get_makefile_filename()) + srcdir = os.path.join(base, srcdir) + else: + # srcdir is not meaningful since the installation is + # spread about the filesystem. We choose the + # directory containing the Makefile since we know it + # exists. + srcdir = os.path.dirname(get_makefile_filename()) + _config_vars['srcdir'] = os.path.abspath(os.path.normpath(srcdir)) + + # Convert srcdir into an absolute path if it appears necessary. + # Normally it is relative to the build directory. However, during + # testing, for example, we might be running a non-installed python + # from a different directory. + if python_build and os.name == "posix": + base = project_base + if (not os.path.isabs(_config_vars['srcdir']) and + base != os.getcwd()): + # srcdir is relative and we are not in the same directory + # as the executable. Assume executable is in the build + # directory and make srcdir absolute. + srcdir = os.path.join(base, _config_vars['srcdir']) + _config_vars['srcdir'] = os.path.normpath(srcdir) + + # OS X platforms require special customization to handle + # multi-architecture, multi-os-version installers + if sys.platform == 'darwin': + import _osx_support + _osx_support.customize_config_vars(_config_vars) + + if args: + vals = [] + for name in args: + vals.append(_config_vars.get(name)) + return vals + else: + return _config_vars + +def get_config_var(name): + """Return the value of a single variable using the dictionary + returned by 'get_config_vars()'. Equivalent to + get_config_vars().get(name) + """ + if name == 'SO': + import warnings + warnings.warn('SO is deprecated, use EXT_SUFFIX', DeprecationWarning, 2) + return get_config_vars().get(name) diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/text_file.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/text_file.py new file mode 100644 index 00000000..93abad38 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/text_file.py @@ -0,0 +1,286 @@ +"""text_file + +provides the TextFile class, which gives an interface to text files +that (optionally) takes care of stripping comments, ignoring blank +lines, and joining lines with backslashes.""" + +import sys, io + + +class TextFile: + """Provides a file-like object that takes care of all the things you + commonly want to do when processing a text file that has some + line-by-line syntax: strip comments (as long as "#" is your + comment character), skip blank lines, join adjacent lines by + escaping the newline (ie. backslash at end of line), strip + leading and/or trailing whitespace. All of these are optional + and independently controllable. + + Provides a 'warn()' method so you can generate warning messages that + report physical line number, even if the logical line in question + spans multiple physical lines. Also provides 'unreadline()' for + implementing line-at-a-time lookahead. + + Constructor is called as: + + TextFile (filename=None, file=None, **options) + + It bombs (RuntimeError) if both 'filename' and 'file' are None; + 'filename' should be a string, and 'file' a file object (or + something that provides 'readline()' and 'close()' methods). It is + recommended that you supply at least 'filename', so that TextFile + can include it in warning messages. If 'file' is not supplied, + TextFile creates its own using 'io.open()'. + + The options are all boolean, and affect the value returned by + 'readline()': + strip_comments [default: true] + strip from "#" to end-of-line, as well as any whitespace + leading up to the "#" -- unless it is escaped by a backslash + lstrip_ws [default: false] + strip leading whitespace from each line before returning it + rstrip_ws [default: true] + strip trailing whitespace (including line terminator!) from + each line before returning it + skip_blanks [default: true} + skip lines that are empty *after* stripping comments and + whitespace. (If both lstrip_ws and rstrip_ws are false, + then some lines may consist of solely whitespace: these will + *not* be skipped, even if 'skip_blanks' is true.) + join_lines [default: false] + if a backslash is the last non-newline character on a line + after stripping comments and whitespace, join the following line + to it to form one "logical line"; if N consecutive lines end + with a backslash, then N+1 physical lines will be joined to + form one logical line. + collapse_join [default: false] + strip leading whitespace from lines that are joined to their + predecessor; only matters if (join_lines and not lstrip_ws) + errors [default: 'strict'] + error handler used to decode the file content + + Note that since 'rstrip_ws' can strip the trailing newline, the + semantics of 'readline()' must differ from those of the builtin file + object's 'readline()' method! In particular, 'readline()' returns + None for end-of-file: an empty string might just be a blank line (or + an all-whitespace line), if 'rstrip_ws' is true but 'skip_blanks' is + not.""" + + default_options = { 'strip_comments': 1, + 'skip_blanks': 1, + 'lstrip_ws': 0, + 'rstrip_ws': 1, + 'join_lines': 0, + 'collapse_join': 0, + 'errors': 'strict', + } + + def __init__(self, filename=None, file=None, **options): + """Construct a new TextFile object. At least one of 'filename' + (a string) and 'file' (a file-like object) must be supplied. + They keyword argument options are described above and affect + the values returned by 'readline()'.""" + if filename is None and file is None: + raise RuntimeError("you must supply either or both of 'filename' and 'file'") + + # set values for all options -- either from client option hash + # or fallback to default_options + for opt in self.default_options.keys(): + if opt in options: + setattr(self, opt, options[opt]) + else: + setattr(self, opt, self.default_options[opt]) + + # sanity check client option hash + for opt in options.keys(): + if opt not in self.default_options: + raise KeyError("invalid TextFile option '%s'" % opt) + + if file is None: + self.open(filename) + else: + self.filename = filename + self.file = file + self.current_line = 0 # assuming that file is at BOF! + + # 'linebuf' is a stack of lines that will be emptied before we + # actually read from the file; it's only populated by an + # 'unreadline()' operation + self.linebuf = [] + + def open(self, filename): + """Open a new file named 'filename'. This overrides both the + 'filename' and 'file' arguments to the constructor.""" + self.filename = filename + self.file = io.open(self.filename, 'r', errors=self.errors) + self.current_line = 0 + + def close(self): + """Close the current file and forget everything we know about it + (filename, current line number).""" + file = self.file + self.file = None + self.filename = None + self.current_line = None + file.close() + + def gen_error(self, msg, line=None): + outmsg = [] + if line is None: + line = self.current_line + outmsg.append(self.filename + ", ") + if isinstance(line, (list, tuple)): + outmsg.append("lines %d-%d: " % tuple(line)) + else: + outmsg.append("line %d: " % line) + outmsg.append(str(msg)) + return "".join(outmsg) + + def error(self, msg, line=None): + raise ValueError("error: " + self.gen_error(msg, line)) + + def warn(self, msg, line=None): + """Print (to stderr) a warning message tied to the current logical + line in the current file. If the current logical line in the + file spans multiple physical lines, the warning refers to the + whole range, eg. "lines 3-5". If 'line' supplied, it overrides + the current line number; it may be a list or tuple to indicate a + range of physical lines, or an integer for a single physical + line.""" + sys.stderr.write("warning: " + self.gen_error(msg, line) + "\n") + + def readline(self): + """Read and return a single logical line from the current file (or + from an internal buffer if lines have previously been "unread" + with 'unreadline()'). If the 'join_lines' option is true, this + may involve reading multiple physical lines concatenated into a + single string. Updates the current line number, so calling + 'warn()' after 'readline()' emits a warning about the physical + line(s) just read. Returns None on end-of-file, since the empty + string can occur if 'rstrip_ws' is true but 'strip_blanks' is + not.""" + # If any "unread" lines waiting in 'linebuf', return the top + # one. (We don't actually buffer read-ahead data -- lines only + # get put in 'linebuf' if the client explicitly does an + # 'unreadline()'. + if self.linebuf: + line = self.linebuf[-1] + del self.linebuf[-1] + return line + + buildup_line = '' + + while True: + # read the line, make it None if EOF + line = self.file.readline() + if line == '': + line = None + + if self.strip_comments and line: + + # Look for the first "#" in the line. If none, never + # mind. If we find one and it's the first character, or + # is not preceded by "\", then it starts a comment -- + # strip the comment, strip whitespace before it, and + # carry on. Otherwise, it's just an escaped "#", so + # unescape it (and any other escaped "#"'s that might be + # lurking in there) and otherwise leave the line alone. + + pos = line.find("#") + if pos == -1: # no "#" -- no comments + pass + + # It's definitely a comment -- either "#" is the first + # character, or it's elsewhere and unescaped. + elif pos == 0 or line[pos-1] != "\\": + # Have to preserve the trailing newline, because it's + # the job of a later step (rstrip_ws) to remove it -- + # and if rstrip_ws is false, we'd better preserve it! + # (NB. this means that if the final line is all comment + # and has no trailing newline, we will think that it's + # EOF; I think that's OK.) + eol = (line[-1] == '\n') and '\n' or '' + line = line[0:pos] + eol + + # If all that's left is whitespace, then skip line + # *now*, before we try to join it to 'buildup_line' -- + # that way constructs like + # hello \\ + # # comment that should be ignored + # there + # result in "hello there". + if line.strip() == "": + continue + else: # it's an escaped "#" + line = line.replace("\\#", "#") + + # did previous line end with a backslash? then accumulate + if self.join_lines and buildup_line: + # oops: end of file + if line is None: + self.warn("continuation line immediately precedes " + "end-of-file") + return buildup_line + + if self.collapse_join: + line = line.lstrip() + line = buildup_line + line + + # careful: pay attention to line number when incrementing it + if isinstance(self.current_line, list): + self.current_line[1] = self.current_line[1] + 1 + else: + self.current_line = [self.current_line, + self.current_line + 1] + # just an ordinary line, read it as usual + else: + if line is None: # eof + return None + + # still have to be careful about incrementing the line number! + if isinstance(self.current_line, list): + self.current_line = self.current_line[1] + 1 + else: + self.current_line = self.current_line + 1 + + # strip whitespace however the client wants (leading and + # trailing, or one or the other, or neither) + if self.lstrip_ws and self.rstrip_ws: + line = line.strip() + elif self.lstrip_ws: + line = line.lstrip() + elif self.rstrip_ws: + line = line.rstrip() + + # blank line (whether we rstrip'ed or not)? skip to next line + # if appropriate + if (line == '' or line == '\n') and self.skip_blanks: + continue + + if self.join_lines: + if line[-1] == '\\': + buildup_line = line[:-1] + continue + + if line[-2:] == '\\\n': + buildup_line = line[0:-2] + '\n' + continue + + # well, I guess there's some actual content there: return it + return line + + def readlines(self): + """Read and return the list of all logical lines remaining in the + current file.""" + lines = [] + while True: + line = self.readline() + if line is None: + return lines + lines.append(line) + + def unreadline(self, line): + """Push 'line' (a string) onto an internal buffer that will be + checked by future 'readline()' calls. Handy for implementing + a parser with line-at-a-time lookahead.""" + self.linebuf.append(line) diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/unixccompiler.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/unixccompiler.py new file mode 100644 index 00000000..a07e5988 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/unixccompiler.py @@ -0,0 +1,325 @@ +"""distutils.unixccompiler + +Contains the UnixCCompiler class, a subclass of CCompiler that handles +the "typical" Unix-style command-line C compiler: + * macros defined with -Dname[=value] + * macros undefined with -Uname + * include search directories specified with -Idir + * libraries specified with -lllib + * library search directories specified with -Ldir + * compile handled by 'cc' (or similar) executable with -c option: + compiles .c to .o + * link static library handled by 'ar' command (possibly with 'ranlib') + * link shared library handled by 'cc -shared' +""" + +import os, sys, re, shlex + +from distutils import sysconfig +from distutils.dep_util import newer +from distutils.ccompiler import \ + CCompiler, gen_preprocess_options, gen_lib_options +from distutils.errors import \ + DistutilsExecError, CompileError, LibError, LinkError +from distutils import log + +if sys.platform == 'darwin': + import _osx_support + +# XXX Things not currently handled: +# * optimization/debug/warning flags; we just use whatever's in Python's +# Makefile and live with it. Is this adequate? If not, we might +# have to have a bunch of subclasses GNUCCompiler, SGICCompiler, +# SunCCompiler, and I suspect down that road lies madness. +# * even if we don't know a warning flag from an optimization flag, +# we need some way for outsiders to feed preprocessor/compiler/linker +# flags in to us -- eg. a sysadmin might want to mandate certain flags +# via a site config file, or a user might want to set something for +# compiling this module distribution only via the setup.py command +# line, whatever. As long as these options come from something on the +# current system, they can be as system-dependent as they like, and we +# should just happily stuff them into the preprocessor/compiler/linker +# options and carry on. + + +class UnixCCompiler(CCompiler): + + compiler_type = 'unix' + + # These are used by CCompiler in two places: the constructor sets + # instance attributes 'preprocessor', 'compiler', etc. from them, and + # 'set_executable()' allows any of these to be set. The defaults here + # are pretty generic; they will probably have to be set by an outsider + # (eg. using information discovered by the sysconfig about building + # Python extensions). + executables = {'preprocessor' : None, + 'compiler' : ["cc"], + 'compiler_so' : ["cc"], + 'compiler_cxx' : ["cc"], + 'linker_so' : ["cc", "-shared"], + 'linker_exe' : ["cc"], + 'archiver' : ["ar", "-cr"], + 'ranlib' : None, + } + + if sys.platform[:6] == "darwin": + executables['ranlib'] = ["ranlib"] + + # Needed for the filename generation methods provided by the base + # class, CCompiler. NB. whoever instantiates/uses a particular + # UnixCCompiler instance should set 'shared_lib_ext' -- we set a + # reasonable common default here, but it's not necessarily used on all + # Unices! + + src_extensions = [".c",".C",".cc",".cxx",".cpp",".m"] + obj_extension = ".o" + static_lib_extension = ".a" + shared_lib_extension = ".so" + dylib_lib_extension = ".dylib" + xcode_stub_lib_extension = ".tbd" + static_lib_format = shared_lib_format = dylib_lib_format = "lib%s%s" + xcode_stub_lib_format = dylib_lib_format + if sys.platform == "cygwin": + exe_extension = ".exe" + + def preprocess(self, source, output_file=None, macros=None, + include_dirs=None, extra_preargs=None, extra_postargs=None): + fixed_args = self._fix_compile_args(None, macros, include_dirs) + ignore, macros, include_dirs = fixed_args + pp_opts = gen_preprocess_options(macros, include_dirs) + pp_args = self.preprocessor + pp_opts + if output_file: + pp_args.extend(['-o', output_file]) + if extra_preargs: + pp_args[:0] = extra_preargs + if extra_postargs: + pp_args.extend(extra_postargs) + pp_args.append(source) + + # We need to preprocess: either we're being forced to, or we're + # generating output to stdout, or there's a target output file and + # the source file is newer than the target (or the target doesn't + # exist). + if self.force or output_file is None or newer(source, output_file): + if output_file: + self.mkpath(os.path.dirname(output_file)) + try: + self.spawn(pp_args) + except DistutilsExecError as msg: + raise CompileError(msg) + + def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts): + compiler_so = self.compiler_so + if sys.platform == 'darwin': + compiler_so = _osx_support.compiler_fixup(compiler_so, + cc_args + extra_postargs) + try: + self.spawn(compiler_so + cc_args + [src, '-o', obj] + + extra_postargs) + except DistutilsExecError as msg: + raise CompileError(msg) + + def create_static_lib(self, objects, output_libname, + output_dir=None, debug=0, target_lang=None): + objects, output_dir = self._fix_object_args(objects, output_dir) + + output_filename = \ + self.library_filename(output_libname, output_dir=output_dir) + + if self._need_link(objects, output_filename): + self.mkpath(os.path.dirname(output_filename)) + self.spawn(self.archiver + + [output_filename] + + objects + self.objects) + + # Not many Unices required ranlib anymore -- SunOS 4.x is, I + # think the only major Unix that does. Maybe we need some + # platform intelligence here to skip ranlib if it's not + # needed -- or maybe Python's configure script took care of + # it for us, hence the check for leading colon. + if self.ranlib: + try: + self.spawn(self.ranlib + [output_filename]) + except DistutilsExecError as msg: + raise LibError(msg) + else: + log.debug("skipping %s (up-to-date)", output_filename) + + def link(self, target_desc, objects, + output_filename, output_dir=None, libraries=None, + library_dirs=None, runtime_library_dirs=None, + export_symbols=None, debug=0, extra_preargs=None, + extra_postargs=None, build_temp=None, target_lang=None): + objects, output_dir = self._fix_object_args(objects, output_dir) + fixed_args = self._fix_lib_args(libraries, library_dirs, + runtime_library_dirs) + libraries, library_dirs, runtime_library_dirs = fixed_args + + lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs, + libraries) + if not isinstance(output_dir, (str, type(None))): + raise TypeError("'output_dir' must be a string or None") + if output_dir is not None: + output_filename = os.path.join(output_dir, output_filename) + + if self._need_link(objects, output_filename): + ld_args = (objects + self.objects + + lib_opts + ['-o', output_filename]) + if debug: + ld_args[:0] = ['-g'] + if extra_preargs: + ld_args[:0] = extra_preargs + if extra_postargs: + ld_args.extend(extra_postargs) + self.mkpath(os.path.dirname(output_filename)) + try: + if target_desc == CCompiler.EXECUTABLE: + linker = self.linker_exe[:] + else: + linker = self.linker_so[:] + if target_lang == "c++" and self.compiler_cxx: + # skip over environment variable settings if /usr/bin/env + # is used to set up the linker's environment. + # This is needed on OSX. Note: this assumes that the + # normal and C++ compiler have the same environment + # settings. + i = 0 + if os.path.basename(linker[0]) == "env": + i = 1 + while '=' in linker[i]: + i += 1 + + if os.path.basename(linker[i]) == 'ld_so_aix': + # AIX platforms prefix the compiler with the ld_so_aix + # script, so we need to adjust our linker index + offset = 1 + else: + offset = 0 + + linker[i+offset] = self.compiler_cxx[i] + + if sys.platform == 'darwin': + linker = _osx_support.compiler_fixup(linker, ld_args) + + self.spawn(linker + ld_args) + except DistutilsExecError as msg: + raise LinkError(msg) + else: + log.debug("skipping %s (up-to-date)", output_filename) + + # -- Miscellaneous methods ----------------------------------------- + # These are all used by the 'gen_lib_options() function, in + # ccompiler.py. + + def library_dir_option(self, dir): + return "-L" + dir + + def _is_gcc(self, compiler_name): + return "gcc" in compiler_name or "g++" in compiler_name + + def runtime_library_dir_option(self, dir): + # XXX Hackish, at the very least. See Python bug #445902: + # http://sourceforge.net/tracker/index.php + # ?func=detail&aid=445902&group_id=5470&atid=105470 + # Linkers on different platforms need different options to + # specify that directories need to be added to the list of + # directories searched for dependencies when a dynamic library + # is sought. GCC on GNU systems (Linux, FreeBSD, ...) has to + # be told to pass the -R option through to the linker, whereas + # other compilers and gcc on other systems just know this. + # Other compilers may need something slightly different. At + # this time, there's no way to determine this information from + # the configuration data stored in the Python installation, so + # we use this hack. + compiler = os.path.basename(shlex.split(sysconfig.get_config_var("CC"))[0]) + if sys.platform[:6] == "darwin": + from distutils.util import get_macosx_target_ver, split_version + macosx_target_ver = get_macosx_target_ver() + if macosx_target_ver and split_version(macosx_target_ver) >= [10, 5]: + return "-Wl,-rpath," + dir + else: # no support for -rpath on earlier macOS versions + return "-L" + dir + elif sys.platform[:7] == "freebsd": + return "-Wl,-rpath=" + dir + elif sys.platform[:5] == "hp-ux": + if self._is_gcc(compiler): + return ["-Wl,+s", "-L" + dir] + return ["+s", "-L" + dir] + + # For all compilers, `-Wl` is the presumed way to + # pass a compiler option to the linker and `-R` is + # the way to pass an RPATH. + if sysconfig.get_config_var("GNULD") == "yes": + # GNU ld needs an extra option to get a RUNPATH + # instead of just an RPATH. + return "-Wl,--enable-new-dtags,-R" + dir + else: + return "-Wl,-R" + dir + + def library_option(self, lib): + return "-l" + lib + + def find_library_file(self, dirs, lib, debug=0): + shared_f = self.library_filename(lib, lib_type='shared') + dylib_f = self.library_filename(lib, lib_type='dylib') + xcode_stub_f = self.library_filename(lib, lib_type='xcode_stub') + static_f = self.library_filename(lib, lib_type='static') + + if sys.platform == 'darwin': + # On OSX users can specify an alternate SDK using + # '-isysroot', calculate the SDK root if it is specified + # (and use it further on) + # + # Note that, as of Xcode 7, Apple SDKs may contain textual stub + # libraries with .tbd extensions rather than the normal .dylib + # shared libraries installed in /. The Apple compiler tool + # chain handles this transparently but it can cause problems + # for programs that are being built with an SDK and searching + # for specific libraries. Callers of find_library_file need to + # keep in mind that the base filename of the returned SDK library + # file might have a different extension from that of the library + # file installed on the running system, for example: + # /Applications/Xcode.app/Contents/Developer/Platforms/ + # MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/ + # usr/lib/libedit.tbd + # vs + # /usr/lib/libedit.dylib + cflags = sysconfig.get_config_var('CFLAGS') + m = re.search(r'-isysroot\s*(\S+)', cflags) + if m is None: + sysroot = '/' + else: + sysroot = m.group(1) + + + + for dir in dirs: + shared = os.path.join(dir, shared_f) + dylib = os.path.join(dir, dylib_f) + static = os.path.join(dir, static_f) + xcode_stub = os.path.join(dir, xcode_stub_f) + + if sys.platform == 'darwin' and ( + dir.startswith('/System/') or ( + dir.startswith('/usr/') and not dir.startswith('/usr/local/'))): + + shared = os.path.join(sysroot, dir[1:], shared_f) + dylib = os.path.join(sysroot, dir[1:], dylib_f) + static = os.path.join(sysroot, dir[1:], static_f) + xcode_stub = os.path.join(sysroot, dir[1:], xcode_stub_f) + + # We're second-guessing the linker here, with not much hard + # data to go on: GCC seems to prefer the shared library, so I'm + # assuming that *all* Unix C compilers do. And of course I'm + # ignoring even GCC's "-static" option. So sue me. + if os.path.exists(dylib): + return dylib + elif os.path.exists(xcode_stub): + return xcode_stub + elif os.path.exists(shared): + return shared + elif os.path.exists(static): + return static + + # Oops, didn't find it in *any* of 'dirs' + return None diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/util.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/util.py new file mode 100644 index 00000000..ac6d446d --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/util.py @@ -0,0 +1,548 @@ +"""distutils.util + +Miscellaneous utility functions -- anything that doesn't fit into +one of the other *util.py modules. +""" + +import os +import re +import importlib.util +import string +import sys +from distutils.errors import DistutilsPlatformError +from distutils.dep_util import newer +from distutils.spawn import spawn +from distutils import log +from distutils.errors import DistutilsByteCompileError +from .py35compat import _optim_args_from_interpreter_flags + + +def get_host_platform(): + """Return a string that identifies the current platform. This is used mainly to + distinguish platform-specific build directories and platform-specific built + distributions. Typically includes the OS name and version and the + architecture (as supplied by 'os.uname()'), although the exact information + included depends on the OS; eg. on Linux, the kernel version isn't + particularly important. + + Examples of returned values: + linux-i586 + linux-alpha (?) + solaris-2.6-sun4u + + Windows will return one of: + win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc) + win32 (all others - specifically, sys.platform is returned) + + For other non-POSIX platforms, currently just returns 'sys.platform'. + + """ + if os.name == 'nt': + if 'amd64' in sys.version.lower(): + return 'win-amd64' + if '(arm)' in sys.version.lower(): + return 'win-arm32' + if '(arm64)' in sys.version.lower(): + return 'win-arm64' + return sys.platform + + # Set for cross builds explicitly + if "_PYTHON_HOST_PLATFORM" in os.environ: + return os.environ["_PYTHON_HOST_PLATFORM"] + + if os.name != "posix" or not hasattr(os, 'uname'): + # XXX what about the architecture? NT is Intel or Alpha, + # Mac OS is M68k or PPC, etc. + return sys.platform + + # Try to distinguish various flavours of Unix + + (osname, host, release, version, machine) = os.uname() + + # Convert the OS name to lowercase, remove '/' characters, and translate + # spaces (for "Power Macintosh") + osname = osname.lower().replace('/', '') + machine = machine.replace(' ', '_') + machine = machine.replace('/', '-') + + if osname[:5] == "linux": + # At least on Linux/Intel, 'machine' is the processor -- + # i386, etc. + # XXX what about Alpha, SPARC, etc? + return "%s-%s" % (osname, machine) + elif osname[:5] == "sunos": + if release[0] >= "5": # SunOS 5 == Solaris 2 + osname = "solaris" + release = "%d.%s" % (int(release[0]) - 3, release[2:]) + # We can't use "platform.architecture()[0]" because a + # bootstrap problem. We use a dict to get an error + # if some suspicious happens. + bitness = {2147483647:"32bit", 9223372036854775807:"64bit"} + machine += ".%s" % bitness[sys.maxsize] + # fall through to standard osname-release-machine representation + elif osname[:3] == "aix": + from .py38compat import aix_platform + return aix_platform(osname, version, release) + elif osname[:6] == "cygwin": + osname = "cygwin" + rel_re = re.compile (r'[\d.]+', re.ASCII) + m = rel_re.match(release) + if m: + release = m.group() + elif osname[:6] == "darwin": + import _osx_support, distutils.sysconfig + osname, release, machine = _osx_support.get_platform_osx( + distutils.sysconfig.get_config_vars(), + osname, release, machine) + + return "%s-%s-%s" % (osname, release, machine) + +def get_platform(): + if os.name == 'nt': + TARGET_TO_PLAT = { + 'x86' : 'win32', + 'x64' : 'win-amd64', + 'arm' : 'win-arm32', + 'arm64': 'win-arm64', + } + return TARGET_TO_PLAT.get(os.environ.get('VSCMD_ARG_TGT_ARCH')) or get_host_platform() + else: + return get_host_platform() + + +if sys.platform == 'darwin': + _syscfg_macosx_ver = None # cache the version pulled from sysconfig +MACOSX_VERSION_VAR = 'MACOSX_DEPLOYMENT_TARGET' + +def _clear_cached_macosx_ver(): + """For testing only. Do not call.""" + global _syscfg_macosx_ver + _syscfg_macosx_ver = None + +def get_macosx_target_ver_from_syscfg(): + """Get the version of macOS latched in the Python interpreter configuration. + Returns the version as a string or None if can't obtain one. Cached.""" + global _syscfg_macosx_ver + if _syscfg_macosx_ver is None: + from distutils import sysconfig + ver = sysconfig.get_config_var(MACOSX_VERSION_VAR) or '' + if ver: + _syscfg_macosx_ver = ver + return _syscfg_macosx_ver + +def get_macosx_target_ver(): + """Return the version of macOS for which we are building. + + The target version defaults to the version in sysconfig latched at time + the Python interpreter was built, unless overridden by an environment + variable. If neither source has a value, then None is returned""" + + syscfg_ver = get_macosx_target_ver_from_syscfg() + env_ver = os.environ.get(MACOSX_VERSION_VAR) + + if env_ver: + # Validate overridden version against sysconfig version, if have both. + # Ensure that the deployment target of the build process is not less + # than 10.3 if the interpreter was built for 10.3 or later. This + # ensures extension modules are built with correct compatibility + # values, specifically LDSHARED which can use + # '-undefined dynamic_lookup' which only works on >= 10.3. + if syscfg_ver and split_version(syscfg_ver) >= [10, 3] and \ + split_version(env_ver) < [10, 3]: + my_msg = ('$' + MACOSX_VERSION_VAR + ' mismatch: ' + 'now "%s" but "%s" during configure; ' + 'must use 10.3 or later' + % (env_ver, syscfg_ver)) + raise DistutilsPlatformError(my_msg) + return env_ver + return syscfg_ver + + +def split_version(s): + """Convert a dot-separated string into a list of numbers for comparisons""" + return [int(n) for n in s.split('.')] + + +def convert_path (pathname): + """Return 'pathname' as a name that will work on the native filesystem, + i.e. split it on '/' and put it back together again using the current + directory separator. Needed because filenames in the setup script are + always supplied in Unix style, and have to be converted to the local + convention before we can actually use them in the filesystem. Raises + ValueError on non-Unix-ish systems if 'pathname' either starts or + ends with a slash. + """ + if os.sep == '/': + return pathname + if not pathname: + return pathname + if pathname[0] == '/': + raise ValueError("path '%s' cannot be absolute" % pathname) + if pathname[-1] == '/': + raise ValueError("path '%s' cannot end with '/'" % pathname) + + paths = pathname.split('/') + while '.' in paths: + paths.remove('.') + if not paths: + return os.curdir + return os.path.join(*paths) + +# convert_path () + + +def change_root (new_root, pathname): + """Return 'pathname' with 'new_root' prepended. If 'pathname' is + relative, this is equivalent to "os.path.join(new_root,pathname)". + Otherwise, it requires making 'pathname' relative and then joining the + two, which is tricky on DOS/Windows and Mac OS. + """ + if os.name == 'posix': + if not os.path.isabs(pathname): + return os.path.join(new_root, pathname) + else: + return os.path.join(new_root, pathname[1:]) + + elif os.name == 'nt': + (drive, path) = os.path.splitdrive(pathname) + if path[0] == '\\': + path = path[1:] + return os.path.join(new_root, path) + + else: + raise DistutilsPlatformError("nothing known about platform '%s'" % os.name) + + +_environ_checked = 0 +def check_environ (): + """Ensure that 'os.environ' has all the environment variables we + guarantee that users can use in config files, command-line options, + etc. Currently this includes: + HOME - user's home directory (Unix only) + PLAT - description of the current platform, including hardware + and OS (see 'get_platform()') + """ + global _environ_checked + if _environ_checked: + return + + if os.name == 'posix' and 'HOME' not in os.environ: + try: + import pwd + os.environ['HOME'] = pwd.getpwuid(os.getuid())[5] + except (ImportError, KeyError): + # bpo-10496: if the current user identifier doesn't exist in the + # password database, do nothing + pass + + if 'PLAT' not in os.environ: + os.environ['PLAT'] = get_platform() + + _environ_checked = 1 + + +def subst_vars (s, local_vars): + """ + Perform variable substitution on 'string'. + Variables are indicated by format-style braces ("{var}"). + Variable is substituted by the value found in the 'local_vars' + dictionary or in 'os.environ' if it's not in 'local_vars'. + 'os.environ' is first checked/augmented to guarantee that it contains + certain values: see 'check_environ()'. Raise ValueError for any + variables not found in either 'local_vars' or 'os.environ'. + """ + check_environ() + lookup = dict(os.environ) + lookup.update((name, str(value)) for name, value in local_vars.items()) + try: + return _subst_compat(s).format_map(lookup) + except KeyError as var: + raise ValueError(f"invalid variable {var}") + +# subst_vars () + + +def _subst_compat(s): + """ + Replace shell/Perl-style variable substitution with + format-style. For compatibility. + """ + def _subst(match): + return f'{{{match.group(1)}}}' + repl = re.sub(r'\$([a-zA-Z_][a-zA-Z_0-9]*)', _subst, s) + if repl != s: + import warnings + warnings.warn( + "shell/Perl-style substitions are deprecated", + DeprecationWarning, + ) + return repl + + +def grok_environment_error (exc, prefix="error: "): + # Function kept for backward compatibility. + # Used to try clever things with EnvironmentErrors, + # but nowadays str(exception) produces good messages. + return prefix + str(exc) + + +# Needed by 'split_quoted()' +_wordchars_re = _squote_re = _dquote_re = None +def _init_regex(): + global _wordchars_re, _squote_re, _dquote_re + _wordchars_re = re.compile(r'[^\\\'\"%s ]*' % string.whitespace) + _squote_re = re.compile(r"'(?:[^'\\]|\\.)*'") + _dquote_re = re.compile(r'"(?:[^"\\]|\\.)*"') + +def split_quoted (s): + """Split a string up according to Unix shell-like rules for quotes and + backslashes. In short: words are delimited by spaces, as long as those + spaces are not escaped by a backslash, or inside a quoted string. + Single and double quotes are equivalent, and the quote characters can + be backslash-escaped. The backslash is stripped from any two-character + escape sequence, leaving only the escaped character. The quote + characters are stripped from any quoted string. Returns a list of + words. + """ + + # This is a nice algorithm for splitting up a single string, since it + # doesn't require character-by-character examination. It was a little + # bit of a brain-bender to get it working right, though... + if _wordchars_re is None: _init_regex() + + s = s.strip() + words = [] + pos = 0 + + while s: + m = _wordchars_re.match(s, pos) + end = m.end() + if end == len(s): + words.append(s[:end]) + break + + if s[end] in string.whitespace: # unescaped, unquoted whitespace: now + words.append(s[:end]) # we definitely have a word delimiter + s = s[end:].lstrip() + pos = 0 + + elif s[end] == '\\': # preserve whatever is being escaped; + # will become part of the current word + s = s[:end] + s[end+1:] + pos = end+1 + + else: + if s[end] == "'": # slurp singly-quoted string + m = _squote_re.match(s, end) + elif s[end] == '"': # slurp doubly-quoted string + m = _dquote_re.match(s, end) + else: + raise RuntimeError("this can't happen (bad char '%c')" % s[end]) + + if m is None: + raise ValueError("bad string (mismatched %s quotes?)" % s[end]) + + (beg, end) = m.span() + s = s[:beg] + s[beg+1:end-1] + s[end:] + pos = m.end() - 2 + + if pos >= len(s): + words.append(s) + break + + return words + +# split_quoted () + + +def execute (func, args, msg=None, verbose=0, dry_run=0): + """Perform some action that affects the outside world (eg. by + writing to the filesystem). Such actions are special because they + are disabled by the 'dry_run' flag. This method takes care of all + that bureaucracy for you; all you have to do is supply the + function to call and an argument tuple for it (to embody the + "external action" being performed), and an optional message to + print. + """ + if msg is None: + msg = "%s%r" % (func.__name__, args) + if msg[-2:] == ',)': # correct for singleton tuple + msg = msg[0:-2] + ')' + + log.info(msg) + if not dry_run: + func(*args) + + +def strtobool (val): + """Convert a string representation of truth to true (1) or false (0). + + True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values + are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if + 'val' is anything else. + """ + val = val.lower() + if val in ('y', 'yes', 't', 'true', 'on', '1'): + return 1 + elif val in ('n', 'no', 'f', 'false', 'off', '0'): + return 0 + else: + raise ValueError("invalid truth value %r" % (val,)) + + +def byte_compile (py_files, + optimize=0, force=0, + prefix=None, base_dir=None, + verbose=1, dry_run=0, + direct=None): + """Byte-compile a collection of Python source files to .pyc + files in a __pycache__ subdirectory. 'py_files' is a list + of files to compile; any files that don't end in ".py" are silently + skipped. 'optimize' must be one of the following: + 0 - don't optimize + 1 - normal optimization (like "python -O") + 2 - extra optimization (like "python -OO") + If 'force' is true, all files are recompiled regardless of + timestamps. + + The source filename encoded in each bytecode file defaults to the + filenames listed in 'py_files'; you can modify these with 'prefix' and + 'basedir'. 'prefix' is a string that will be stripped off of each + source filename, and 'base_dir' is a directory name that will be + prepended (after 'prefix' is stripped). You can supply either or both + (or neither) of 'prefix' and 'base_dir', as you wish. + + If 'dry_run' is true, doesn't actually do anything that would + affect the filesystem. + + Byte-compilation is either done directly in this interpreter process + with the standard py_compile module, or indirectly by writing a + temporary script and executing it. Normally, you should let + 'byte_compile()' figure out to use direct compilation or not (see + the source for details). The 'direct' flag is used by the script + generated in indirect mode; unless you know what you're doing, leave + it set to None. + """ + + # Late import to fix a bootstrap issue: _posixsubprocess is built by + # setup.py, but setup.py uses distutils. + import subprocess + + # nothing is done if sys.dont_write_bytecode is True + if sys.dont_write_bytecode: + raise DistutilsByteCompileError('byte-compiling is disabled.') + + # First, if the caller didn't force us into direct or indirect mode, + # figure out which mode we should be in. We take a conservative + # approach: choose direct mode *only* if the current interpreter is + # in debug mode and optimize is 0. If we're not in debug mode (-O + # or -OO), we don't know which level of optimization this + # interpreter is running with, so we can't do direct + # byte-compilation and be certain that it's the right thing. Thus, + # always compile indirectly if the current interpreter is in either + # optimize mode, or if either optimization level was requested by + # the caller. + if direct is None: + direct = (__debug__ and optimize == 0) + + # "Indirect" byte-compilation: write a temporary script and then + # run it with the appropriate flags. + if not direct: + try: + from tempfile import mkstemp + (script_fd, script_name) = mkstemp(".py") + except ImportError: + from tempfile import mktemp + (script_fd, script_name) = None, mktemp(".py") + log.info("writing byte-compilation script '%s'", script_name) + if not dry_run: + if script_fd is not None: + script = os.fdopen(script_fd, "w") + else: + script = open(script_name, "w") + + with script: + script.write("""\ +from distutils.util import byte_compile +files = [ +""") + + # XXX would be nice to write absolute filenames, just for + # safety's sake (script should be more robust in the face of + # chdir'ing before running it). But this requires abspath'ing + # 'prefix' as well, and that breaks the hack in build_lib's + # 'byte_compile()' method that carefully tacks on a trailing + # slash (os.sep really) to make sure the prefix here is "just + # right". This whole prefix business is rather delicate -- the + # problem is that it's really a directory, but I'm treating it + # as a dumb string, so trailing slashes and so forth matter. + + #py_files = map(os.path.abspath, py_files) + #if prefix: + # prefix = os.path.abspath(prefix) + + script.write(",\n".join(map(repr, py_files)) + "]\n") + script.write(""" +byte_compile(files, optimize=%r, force=%r, + prefix=%r, base_dir=%r, + verbose=%r, dry_run=0, + direct=1) +""" % (optimize, force, prefix, base_dir, verbose)) + + cmd = [sys.executable] + cmd.extend(_optim_args_from_interpreter_flags()) + cmd.append(script_name) + spawn(cmd, dry_run=dry_run) + execute(os.remove, (script_name,), "removing %s" % script_name, + dry_run=dry_run) + + # "Direct" byte-compilation: use the py_compile module to compile + # right here, right now. Note that the script generated in indirect + # mode simply calls 'byte_compile()' in direct mode, a weird sort of + # cross-process recursion. Hey, it works! + else: + from py_compile import compile + + for file in py_files: + if file[-3:] != ".py": + # This lets us be lazy and not filter filenames in + # the "install_lib" command. + continue + + # Terminology from the py_compile module: + # cfile - byte-compiled file + # dfile - purported source filename (same as 'file' by default) + if optimize >= 0: + opt = '' if optimize == 0 else optimize + cfile = importlib.util.cache_from_source( + file, optimization=opt) + else: + cfile = importlib.util.cache_from_source(file) + dfile = file + if prefix: + if file[:len(prefix)] != prefix: + raise ValueError("invalid prefix: filename %r doesn't start with %r" + % (file, prefix)) + dfile = dfile[len(prefix):] + if base_dir: + dfile = os.path.join(base_dir, dfile) + + cfile_base = os.path.basename(cfile) + if direct: + if force or newer(file, cfile): + log.info("byte-compiling %s to %s", file, cfile_base) + if not dry_run: + compile(file, cfile, dfile) + else: + log.debug("skipping byte-compilation of %s to %s", + file, cfile_base) + +# byte_compile () + +def rfc822_escape (header): + """Return a version of the string escaped for inclusion in an + RFC-822 header, by ensuring there are 8 spaces space after each newline. + """ + lines = header.split('\n') + sep = '\n' + 8 * ' ' + return sep.join(lines) diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/version.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/version.py new file mode 100644 index 00000000..35e181db --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/version.py @@ -0,0 +1,363 @@ +# +# distutils/version.py +# +# Implements multiple version numbering conventions for the +# Python Module Distribution Utilities. +# +# $Id$ +# + +"""Provides classes to represent module version numbers (one class for +each style of version numbering). There are currently two such classes +implemented: StrictVersion and LooseVersion. + +Every version number class implements the following interface: + * the 'parse' method takes a string and parses it to some internal + representation; if the string is an invalid version number, + 'parse' raises a ValueError exception + * the class constructor takes an optional string argument which, + if supplied, is passed to 'parse' + * __str__ reconstructs the string that was passed to 'parse' (or + an equivalent string -- ie. one that will generate an equivalent + version number instance) + * __repr__ generates Python code to recreate the version number instance + * _cmp compares the current instance with either another instance + of the same class or a string (which will be parsed to an instance + of the same class, thus must follow the same rules) +""" + +import re +import warnings +import contextlib + + +@contextlib.contextmanager +def suppress_known_deprecation(): + with warnings.catch_warnings(record=True) as ctx: + warnings.filterwarnings( + action='default', + category=DeprecationWarning, + message="distutils Version classes are deprecated.", + ) + yield ctx + + +class Version: + """Abstract base class for version numbering classes. Just provides + constructor (__init__) and reproducer (__repr__), because those + seem to be the same for all version numbering classes; and route + rich comparisons to _cmp. + """ + + def __init__ (self, vstring=None): + warnings.warn( + "distutils Version classes are deprecated. " + "Use packaging.version instead.", + DeprecationWarning, + stacklevel=2, + ) + if vstring: + self.parse(vstring) + + def __repr__ (self): + return "%s ('%s')" % (self.__class__.__name__, str(self)) + + def __eq__(self, other): + c = self._cmp(other) + if c is NotImplemented: + return c + return c == 0 + + def __lt__(self, other): + c = self._cmp(other) + if c is NotImplemented: + return c + return c < 0 + + def __le__(self, other): + c = self._cmp(other) + if c is NotImplemented: + return c + return c <= 0 + + def __gt__(self, other): + c = self._cmp(other) + if c is NotImplemented: + return c + return c > 0 + + def __ge__(self, other): + c = self._cmp(other) + if c is NotImplemented: + return c + return c >= 0 + + +# Interface for version-number classes -- must be implemented +# by the following classes (the concrete ones -- Version should +# be treated as an abstract class). +# __init__ (string) - create and take same action as 'parse' +# (string parameter is optional) +# parse (string) - convert a string representation to whatever +# internal representation is appropriate for +# this style of version numbering +# __str__ (self) - convert back to a string; should be very similar +# (if not identical to) the string supplied to parse +# __repr__ (self) - generate Python code to recreate +# the instance +# _cmp (self, other) - compare two version numbers ('other' may +# be an unparsed version string, or another +# instance of your version class) + + +class StrictVersion (Version): + + """Version numbering for anal retentives and software idealists. + Implements the standard interface for version number classes as + described above. A version number consists of two or three + dot-separated numeric components, with an optional "pre-release" tag + on the end. The pre-release tag consists of the letter 'a' or 'b' + followed by a number. If the numeric components of two version + numbers are equal, then one with a pre-release tag will always + be deemed earlier (lesser) than one without. + + The following are valid version numbers (shown in the order that + would be obtained by sorting according to the supplied cmp function): + + 0.4 0.4.0 (these two are equivalent) + 0.4.1 + 0.5a1 + 0.5b3 + 0.5 + 0.9.6 + 1.0 + 1.0.4a3 + 1.0.4b1 + 1.0.4 + + The following are examples of invalid version numbers: + + 1 + 2.7.2.2 + 1.3.a4 + 1.3pl1 + 1.3c4 + + The rationale for this version numbering system will be explained + in the distutils documentation. + """ + + version_re = re.compile(r'^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$', + re.VERBOSE | re.ASCII) + + + def parse (self, vstring): + match = self.version_re.match(vstring) + if not match: + raise ValueError("invalid version number '%s'" % vstring) + + (major, minor, patch, prerelease, prerelease_num) = \ + match.group(1, 2, 4, 5, 6) + + if patch: + self.version = tuple(map(int, [major, minor, patch])) + else: + self.version = tuple(map(int, [major, minor])) + (0,) + + if prerelease: + self.prerelease = (prerelease[0], int(prerelease_num)) + else: + self.prerelease = None + + + def __str__ (self): + + if self.version[2] == 0: + vstring = '.'.join(map(str, self.version[0:2])) + else: + vstring = '.'.join(map(str, self.version)) + + if self.prerelease: + vstring = vstring + self.prerelease[0] + str(self.prerelease[1]) + + return vstring + + + def _cmp (self, other): + if isinstance(other, str): + with suppress_known_deprecation(): + other = StrictVersion(other) + elif not isinstance(other, StrictVersion): + return NotImplemented + + if self.version != other.version: + # numeric versions don't match + # prerelease stuff doesn't matter + if self.version < other.version: + return -1 + else: + return 1 + + # have to compare prerelease + # case 1: neither has prerelease; they're equal + # case 2: self has prerelease, other doesn't; other is greater + # case 3: self doesn't have prerelease, other does: self is greater + # case 4: both have prerelease: must compare them! + + if (not self.prerelease and not other.prerelease): + return 0 + elif (self.prerelease and not other.prerelease): + return -1 + elif (not self.prerelease and other.prerelease): + return 1 + elif (self.prerelease and other.prerelease): + if self.prerelease == other.prerelease: + return 0 + elif self.prerelease < other.prerelease: + return -1 + else: + return 1 + else: + assert False, "never get here" + +# end class StrictVersion + + +# The rules according to Greg Stein: +# 1) a version number has 1 or more numbers separated by a period or by +# sequences of letters. If only periods, then these are compared +# left-to-right to determine an ordering. +# 2) sequences of letters are part of the tuple for comparison and are +# compared lexicographically +# 3) recognize the numeric components may have leading zeroes +# +# The LooseVersion class below implements these rules: a version number +# string is split up into a tuple of integer and string components, and +# comparison is a simple tuple comparison. This means that version +# numbers behave in a predictable and obvious way, but a way that might +# not necessarily be how people *want* version numbers to behave. There +# wouldn't be a problem if people could stick to purely numeric version +# numbers: just split on period and compare the numbers as tuples. +# However, people insist on putting letters into their version numbers; +# the most common purpose seems to be: +# - indicating a "pre-release" version +# ('alpha', 'beta', 'a', 'b', 'pre', 'p') +# - indicating a post-release patch ('p', 'pl', 'patch') +# but of course this can't cover all version number schemes, and there's +# no way to know what a programmer means without asking him. +# +# The problem is what to do with letters (and other non-numeric +# characters) in a version number. The current implementation does the +# obvious and predictable thing: keep them as strings and compare +# lexically within a tuple comparison. This has the desired effect if +# an appended letter sequence implies something "post-release": +# eg. "0.99" < "0.99pl14" < "1.0", and "5.001" < "5.001m" < "5.002". +# +# However, if letters in a version number imply a pre-release version, +# the "obvious" thing isn't correct. Eg. you would expect that +# "1.5.1" < "1.5.2a2" < "1.5.2", but under the tuple/lexical comparison +# implemented here, this just isn't so. +# +# Two possible solutions come to mind. The first is to tie the +# comparison algorithm to a particular set of semantic rules, as has +# been done in the StrictVersion class above. This works great as long +# as everyone can go along with bondage and discipline. Hopefully a +# (large) subset of Python module programmers will agree that the +# particular flavour of bondage and discipline provided by StrictVersion +# provides enough benefit to be worth using, and will submit their +# version numbering scheme to its domination. The free-thinking +# anarchists in the lot will never give in, though, and something needs +# to be done to accommodate them. +# +# Perhaps a "moderately strict" version class could be implemented that +# lets almost anything slide (syntactically), and makes some heuristic +# assumptions about non-digits in version number strings. This could +# sink into special-case-hell, though; if I was as talented and +# idiosyncratic as Larry Wall, I'd go ahead and implement a class that +# somehow knows that "1.2.1" < "1.2.2a2" < "1.2.2" < "1.2.2pl3", and is +# just as happy dealing with things like "2g6" and "1.13++". I don't +# think I'm smart enough to do it right though. +# +# In any case, I've coded the test suite for this module (see +# ../test/test_version.py) specifically to fail on things like comparing +# "1.2a2" and "1.2". That's not because the *code* is doing anything +# wrong, it's because the simple, obvious design doesn't match my +# complicated, hairy expectations for real-world version numbers. It +# would be a snap to fix the test suite to say, "Yep, LooseVersion does +# the Right Thing" (ie. the code matches the conception). But I'd rather +# have a conception that matches common notions about version numbers. + +class LooseVersion (Version): + + """Version numbering for anarchists and software realists. + Implements the standard interface for version number classes as + described above. A version number consists of a series of numbers, + separated by either periods or strings of letters. When comparing + version numbers, the numeric components will be compared + numerically, and the alphabetic components lexically. The following + are all valid version numbers, in no particular order: + + 1.5.1 + 1.5.2b2 + 161 + 3.10a + 8.02 + 3.4j + 1996.07.12 + 3.2.pl0 + 3.1.1.6 + 2g6 + 11g + 0.960923 + 2.2beta29 + 1.13++ + 5.5.kw + 2.0b1pl0 + + In fact, there is no such thing as an invalid version number under + this scheme; the rules for comparison are simple and predictable, + but may not always give the results you want (for some definition + of "want"). + """ + + component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE) + + def parse (self, vstring): + # I've given up on thinking I can reconstruct the version string + # from the parsed tuple -- so I just store the string here for + # use by __str__ + self.vstring = vstring + components = [x for x in self.component_re.split(vstring) + if x and x != '.'] + for i, obj in enumerate(components): + try: + components[i] = int(obj) + except ValueError: + pass + + self.version = components + + + def __str__ (self): + return self.vstring + + + def __repr__ (self): + return "LooseVersion ('%s')" % str(self) + + + def _cmp (self, other): + if isinstance(other, str): + other = LooseVersion(other) + elif not isinstance(other, LooseVersion): + return NotImplemented + + if self.version == other.version: + return 0 + if self.version < other.version: + return -1 + if self.version > other.version: + return 1 + + +# end class LooseVersion diff --git a/venv/lib/python3.10/site-packages/setuptools/_distutils/versionpredicate.py b/venv/lib/python3.10/site-packages/setuptools/_distutils/versionpredicate.py new file mode 100644 index 00000000..55f25d91 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_distutils/versionpredicate.py @@ -0,0 +1,169 @@ +"""Module for parsing and testing package version predicate strings. +""" +import re +import distutils.version +import operator + + +re_validPackage = re.compile(r"(?i)^\s*([a-z_]\w*(?:\.[a-z_]\w*)*)(.*)", + re.ASCII) +# (package) (rest) + +re_paren = re.compile(r"^\s*\((.*)\)\s*$") # (list) inside of parentheses +re_splitComparison = re.compile(r"^\s*(<=|>=|<|>|!=|==)\s*([^\s,]+)\s*$") +# (comp) (version) + + +def splitUp(pred): + """Parse a single version comparison. + + Return (comparison string, StrictVersion) + """ + res = re_splitComparison.match(pred) + if not res: + raise ValueError("bad package restriction syntax: %r" % pred) + comp, verStr = res.groups() + with distutils.version.suppress_known_deprecation(): + other = distutils.version.StrictVersion(verStr) + return (comp, other) + +compmap = {"<": operator.lt, "<=": operator.le, "==": operator.eq, + ">": operator.gt, ">=": operator.ge, "!=": operator.ne} + +class VersionPredicate: + """Parse and test package version predicates. + + >>> v = VersionPredicate('pyepat.abc (>1.0, <3333.3a1, !=1555.1b3)') + + The `name` attribute provides the full dotted name that is given:: + + >>> v.name + 'pyepat.abc' + + The str() of a `VersionPredicate` provides a normalized + human-readable version of the expression:: + + >>> print(v) + pyepat.abc (> 1.0, < 3333.3a1, != 1555.1b3) + + The `satisfied_by()` method can be used to determine with a given + version number is included in the set described by the version + restrictions:: + + >>> v.satisfied_by('1.1') + True + >>> v.satisfied_by('1.4') + True + >>> v.satisfied_by('1.0') + False + >>> v.satisfied_by('4444.4') + False + >>> v.satisfied_by('1555.1b3') + False + + `VersionPredicate` is flexible in accepting extra whitespace:: + + >>> v = VersionPredicate(' pat( == 0.1 ) ') + >>> v.name + 'pat' + >>> v.satisfied_by('0.1') + True + >>> v.satisfied_by('0.2') + False + + If any version numbers passed in do not conform to the + restrictions of `StrictVersion`, a `ValueError` is raised:: + + >>> v = VersionPredicate('p1.p2.p3.p4(>=1.0, <=1.3a1, !=1.2zb3)') + Traceback (most recent call last): + ... + ValueError: invalid version number '1.2zb3' + + It the module or package name given does not conform to what's + allowed as a legal module or package name, `ValueError` is + raised:: + + >>> v = VersionPredicate('foo-bar') + Traceback (most recent call last): + ... + ValueError: expected parenthesized list: '-bar' + + >>> v = VersionPredicate('foo bar (12.21)') + Traceback (most recent call last): + ... + ValueError: expected parenthesized list: 'bar (12.21)' + + """ + + def __init__(self, versionPredicateStr): + """Parse a version predicate string. + """ + # Fields: + # name: package name + # pred: list of (comparison string, StrictVersion) + + versionPredicateStr = versionPredicateStr.strip() + if not versionPredicateStr: + raise ValueError("empty package restriction") + match = re_validPackage.match(versionPredicateStr) + if not match: + raise ValueError("bad package name in %r" % versionPredicateStr) + self.name, paren = match.groups() + paren = paren.strip() + if paren: + match = re_paren.match(paren) + if not match: + raise ValueError("expected parenthesized list: %r" % paren) + str = match.groups()[0] + self.pred = [splitUp(aPred) for aPred in str.split(",")] + if not self.pred: + raise ValueError("empty parenthesized list in %r" + % versionPredicateStr) + else: + self.pred = [] + + def __str__(self): + if self.pred: + seq = [cond + " " + str(ver) for cond, ver in self.pred] + return self.name + " (" + ", ".join(seq) + ")" + else: + return self.name + + def satisfied_by(self, version): + """True if version is compatible with all the predicates in self. + The parameter version must be acceptable to the StrictVersion + constructor. It may be either a string or StrictVersion. + """ + for cond, ver in self.pred: + if not compmap[cond](version, ver): + return False + return True + + +_provision_rx = None + +def split_provision(value): + """Return the name and optional version number of a provision. + + The version number, if given, will be returned as a `StrictVersion` + instance, otherwise it will be `None`. + + >>> split_provision('mypkg') + ('mypkg', None) + >>> split_provision(' mypkg( 1.2 ) ') + ('mypkg', StrictVersion ('1.2')) + """ + global _provision_rx + if _provision_rx is None: + _provision_rx = re.compile( + r"([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)*)(?:\s*\(\s*([^)\s]+)\s*\))?$", + re.ASCII) + value = value.strip() + m = _provision_rx.match(value) + if not m: + raise ValueError("illegal provides specification: %r" % value) + ver = m.group(2) or None + if ver: + with distutils.version.suppress_known_deprecation(): + ver = distutils.version.StrictVersion(ver) + return m.group(1), ver diff --git a/venv/lib/python3.10/site-packages/setuptools/_imp.py b/venv/lib/python3.10/site-packages/setuptools/_imp.py new file mode 100644 index 00000000..47efd792 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_imp.py @@ -0,0 +1,82 @@ +""" +Re-implementation of find_module and get_frozen_object +from the deprecated imp module. +""" + +import os +import importlib.util +import importlib.machinery + +from .py34compat import module_from_spec + + +PY_SOURCE = 1 +PY_COMPILED = 2 +C_EXTENSION = 3 +C_BUILTIN = 6 +PY_FROZEN = 7 + + +def find_spec(module, paths): + finder = ( + importlib.machinery.PathFinder().find_spec + if isinstance(paths, list) else + importlib.util.find_spec + ) + return finder(module, paths) + + +def find_module(module, paths=None): + """Just like 'imp.find_module()', but with package support""" + spec = find_spec(module, paths) + if spec is None: + raise ImportError("Can't find %s" % module) + if not spec.has_location and hasattr(spec, 'submodule_search_locations'): + spec = importlib.util.spec_from_loader('__init__.py', spec.loader) + + kind = -1 + file = None + static = isinstance(spec.loader, type) + if spec.origin == 'frozen' or static and issubclass( + spec.loader, importlib.machinery.FrozenImporter): + kind = PY_FROZEN + path = None # imp compabilty + suffix = mode = '' # imp compatibility + elif spec.origin == 'built-in' or static and issubclass( + spec.loader, importlib.machinery.BuiltinImporter): + kind = C_BUILTIN + path = None # imp compabilty + suffix = mode = '' # imp compatibility + elif spec.has_location: + path = spec.origin + suffix = os.path.splitext(path)[1] + mode = 'r' if suffix in importlib.machinery.SOURCE_SUFFIXES else 'rb' + + if suffix in importlib.machinery.SOURCE_SUFFIXES: + kind = PY_SOURCE + elif suffix in importlib.machinery.BYTECODE_SUFFIXES: + kind = PY_COMPILED + elif suffix in importlib.machinery.EXTENSION_SUFFIXES: + kind = C_EXTENSION + + if kind in {PY_SOURCE, PY_COMPILED}: + file = open(path, mode) + else: + path = None + suffix = mode = '' + + return file, path, (suffix, mode, kind) + + +def get_frozen_object(module, paths=None): + spec = find_spec(module, paths) + if not spec: + raise ImportError("Can't find %s" % module) + return spec.loader.get_code(module) + + +def get_module(module, paths, info): + spec = find_spec(module, paths) + if not spec: + raise ImportError("Can't find %s" % module) + return module_from_spec(spec) diff --git a/venv/lib/python3.10/site-packages/setuptools/_vendor/__init__.py b/venv/lib/python3.10/site-packages/setuptools/_vendor/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/venv/lib/python3.10/site-packages/setuptools/_vendor/more_itertools/__init__.py b/venv/lib/python3.10/site-packages/setuptools/_vendor/more_itertools/__init__.py new file mode 100644 index 00000000..19a169fc --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_vendor/more_itertools/__init__.py @@ -0,0 +1,4 @@ +from .more import * # noqa +from .recipes import * # noqa + +__version__ = '8.8.0' diff --git a/venv/lib/python3.10/site-packages/setuptools/_vendor/more_itertools/more.py b/venv/lib/python3.10/site-packages/setuptools/_vendor/more_itertools/more.py new file mode 100644 index 00000000..0f7d282a --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_vendor/more_itertools/more.py @@ -0,0 +1,3825 @@ +import warnings + +from collections import Counter, defaultdict, deque, abc +from collections.abc import Sequence +from concurrent.futures import ThreadPoolExecutor +from functools import partial, reduce, wraps +from heapq import merge, heapify, heapreplace, heappop +from itertools import ( + chain, + compress, + count, + cycle, + dropwhile, + groupby, + islice, + repeat, + starmap, + takewhile, + tee, + zip_longest, +) +from math import exp, factorial, floor, log +from queue import Empty, Queue +from random import random, randrange, uniform +from operator import itemgetter, mul, sub, gt, lt +from sys import hexversion, maxsize +from time import monotonic + +from .recipes import ( + consume, + flatten, + pairwise, + powerset, + take, + unique_everseen, +) + +__all__ = [ + 'AbortThread', + 'adjacent', + 'always_iterable', + 'always_reversible', + 'bucket', + 'callback_iter', + 'chunked', + 'circular_shifts', + 'collapse', + 'collate', + 'consecutive_groups', + 'consumer', + 'countable', + 'count_cycle', + 'mark_ends', + 'difference', + 'distinct_combinations', + 'distinct_permutations', + 'distribute', + 'divide', + 'exactly_n', + 'filter_except', + 'first', + 'groupby_transform', + 'ilen', + 'interleave_longest', + 'interleave', + 'intersperse', + 'islice_extended', + 'iterate', + 'ichunked', + 'is_sorted', + 'last', + 'locate', + 'lstrip', + 'make_decorator', + 'map_except', + 'map_reduce', + 'nth_or_last', + 'nth_permutation', + 'nth_product', + 'numeric_range', + 'one', + 'only', + 'padded', + 'partitions', + 'set_partitions', + 'peekable', + 'repeat_last', + 'replace', + 'rlocate', + 'rstrip', + 'run_length', + 'sample', + 'seekable', + 'SequenceView', + 'side_effect', + 'sliced', + 'sort_together', + 'split_at', + 'split_after', + 'split_before', + 'split_when', + 'split_into', + 'spy', + 'stagger', + 'strip', + 'substrings', + 'substrings_indexes', + 'time_limited', + 'unique_to_each', + 'unzip', + 'windowed', + 'with_iter', + 'UnequalIterablesError', + 'zip_equal', + 'zip_offset', + 'windowed_complete', + 'all_unique', + 'value_chain', + 'product_index', + 'combination_index', + 'permutation_index', +] + +_marker = object() + + +def chunked(iterable, n, strict=False): + """Break *iterable* into lists of length *n*: + + >>> list(chunked([1, 2, 3, 4, 5, 6], 3)) + [[1, 2, 3], [4, 5, 6]] + + By the default, the last yielded list will have fewer than *n* elements + if the length of *iterable* is not divisible by *n*: + + >>> list(chunked([1, 2, 3, 4, 5, 6, 7, 8], 3)) + [[1, 2, 3], [4, 5, 6], [7, 8]] + + To use a fill-in value instead, see the :func:`grouper` recipe. + + If the length of *iterable* is not divisible by *n* and *strict* is + ``True``, then ``ValueError`` will be raised before the last + list is yielded. + + """ + iterator = iter(partial(take, n, iter(iterable)), []) + if strict: + + def ret(): + for chunk in iterator: + if len(chunk) != n: + raise ValueError('iterable is not divisible by n.') + yield chunk + + return iter(ret()) + else: + return iterator + + +def first(iterable, default=_marker): + """Return the first item of *iterable*, or *default* if *iterable* is + empty. + + >>> first([0, 1, 2, 3]) + 0 + >>> first([], 'some default') + 'some default' + + If *default* is not provided and there are no items in the iterable, + raise ``ValueError``. + + :func:`first` is useful when you have a generator of expensive-to-retrieve + values and want any arbitrary one. It is marginally shorter than + ``next(iter(iterable), default)``. + + """ + try: + return next(iter(iterable)) + except StopIteration as e: + if default is _marker: + raise ValueError( + 'first() was called on an empty iterable, and no ' + 'default value was provided.' + ) from e + return default + + +def last(iterable, default=_marker): + """Return the last item of *iterable*, or *default* if *iterable* is + empty. + + >>> last([0, 1, 2, 3]) + 3 + >>> last([], 'some default') + 'some default' + + If *default* is not provided and there are no items in the iterable, + raise ``ValueError``. + """ + try: + if isinstance(iterable, Sequence): + return iterable[-1] + # Work around https://bugs.python.org/issue38525 + elif hasattr(iterable, '__reversed__') and (hexversion != 0x030800F0): + return next(reversed(iterable)) + else: + return deque(iterable, maxlen=1)[-1] + except (IndexError, TypeError, StopIteration): + if default is _marker: + raise ValueError( + 'last() was called on an empty iterable, and no default was ' + 'provided.' + ) + return default + + +def nth_or_last(iterable, n, default=_marker): + """Return the nth or the last item of *iterable*, + or *default* if *iterable* is empty. + + >>> nth_or_last([0, 1, 2, 3], 2) + 2 + >>> nth_or_last([0, 1], 2) + 1 + >>> nth_or_last([], 0, 'some default') + 'some default' + + If *default* is not provided and there are no items in the iterable, + raise ``ValueError``. + """ + return last(islice(iterable, n + 1), default=default) + + +class peekable: + """Wrap an iterator to allow lookahead and prepending elements. + + Call :meth:`peek` on the result to get the value that will be returned + by :func:`next`. This won't advance the iterator: + + >>> p = peekable(['a', 'b']) + >>> p.peek() + 'a' + >>> next(p) + 'a' + + Pass :meth:`peek` a default value to return that instead of raising + ``StopIteration`` when the iterator is exhausted. + + >>> p = peekable([]) + >>> p.peek('hi') + 'hi' + + peekables also offer a :meth:`prepend` method, which "inserts" items + at the head of the iterable: + + >>> p = peekable([1, 2, 3]) + >>> p.prepend(10, 11, 12) + >>> next(p) + 10 + >>> p.peek() + 11 + >>> list(p) + [11, 12, 1, 2, 3] + + peekables can be indexed. Index 0 is the item that will be returned by + :func:`next`, index 1 is the item after that, and so on: + The values up to the given index will be cached. + + >>> p = peekable(['a', 'b', 'c', 'd']) + >>> p[0] + 'a' + >>> p[1] + 'b' + >>> next(p) + 'a' + + Negative indexes are supported, but be aware that they will cache the + remaining items in the source iterator, which may require significant + storage. + + To check whether a peekable is exhausted, check its truth value: + + >>> p = peekable(['a', 'b']) + >>> if p: # peekable has items + ... list(p) + ['a', 'b'] + >>> if not p: # peekable is exhausted + ... list(p) + [] + + """ + + def __init__(self, iterable): + self._it = iter(iterable) + self._cache = deque() + + def __iter__(self): + return self + + def __bool__(self): + try: + self.peek() + except StopIteration: + return False + return True + + def peek(self, default=_marker): + """Return the item that will be next returned from ``next()``. + + Return ``default`` if there are no items left. If ``default`` is not + provided, raise ``StopIteration``. + + """ + if not self._cache: + try: + self._cache.append(next(self._it)) + except StopIteration: + if default is _marker: + raise + return default + return self._cache[0] + + def prepend(self, *items): + """Stack up items to be the next ones returned from ``next()`` or + ``self.peek()``. The items will be returned in + first in, first out order:: + + >>> p = peekable([1, 2, 3]) + >>> p.prepend(10, 11, 12) + >>> next(p) + 10 + >>> list(p) + [11, 12, 1, 2, 3] + + It is possible, by prepending items, to "resurrect" a peekable that + previously raised ``StopIteration``. + + >>> p = peekable([]) + >>> next(p) + Traceback (most recent call last): + ... + StopIteration + >>> p.prepend(1) + >>> next(p) + 1 + >>> next(p) + Traceback (most recent call last): + ... + StopIteration + + """ + self._cache.extendleft(reversed(items)) + + def __next__(self): + if self._cache: + return self._cache.popleft() + + return next(self._it) + + def _get_slice(self, index): + # Normalize the slice's arguments + step = 1 if (index.step is None) else index.step + if step > 0: + start = 0 if (index.start is None) else index.start + stop = maxsize if (index.stop is None) else index.stop + elif step < 0: + start = -1 if (index.start is None) else index.start + stop = (-maxsize - 1) if (index.stop is None) else index.stop + else: + raise ValueError('slice step cannot be zero') + + # If either the start or stop index is negative, we'll need to cache + # the rest of the iterable in order to slice from the right side. + if (start < 0) or (stop < 0): + self._cache.extend(self._it) + # Otherwise we'll need to find the rightmost index and cache to that + # point. + else: + n = min(max(start, stop) + 1, maxsize) + cache_len = len(self._cache) + if n >= cache_len: + self._cache.extend(islice(self._it, n - cache_len)) + + return list(self._cache)[index] + + def __getitem__(self, index): + if isinstance(index, slice): + return self._get_slice(index) + + cache_len = len(self._cache) + if index < 0: + self._cache.extend(self._it) + elif index >= cache_len: + self._cache.extend(islice(self._it, index + 1 - cache_len)) + + return self._cache[index] + + +def collate(*iterables, **kwargs): + """Return a sorted merge of the items from each of several already-sorted + *iterables*. + + >>> list(collate('ACDZ', 'AZ', 'JKL')) + ['A', 'A', 'C', 'D', 'J', 'K', 'L', 'Z', 'Z'] + + Works lazily, keeping only the next value from each iterable in memory. Use + :func:`collate` to, for example, perform a n-way mergesort of items that + don't fit in memory. + + If a *key* function is specified, the iterables will be sorted according + to its result: + + >>> key = lambda s: int(s) # Sort by numeric value, not by string + >>> list(collate(['1', '10'], ['2', '11'], key=key)) + ['1', '2', '10', '11'] + + + If the *iterables* are sorted in descending order, set *reverse* to + ``True``: + + >>> list(collate([5, 3, 1], [4, 2, 0], reverse=True)) + [5, 4, 3, 2, 1, 0] + + If the elements of the passed-in iterables are out of order, you might get + unexpected results. + + On Python 3.5+, this function is an alias for :func:`heapq.merge`. + + """ + warnings.warn( + "collate is no longer part of more_itertools, use heapq.merge", + DeprecationWarning, + ) + return merge(*iterables, **kwargs) + + +def consumer(func): + """Decorator that automatically advances a PEP-342-style "reverse iterator" + to its first yield point so you don't have to call ``next()`` on it + manually. + + >>> @consumer + ... def tally(): + ... i = 0 + ... while True: + ... print('Thing number %s is %s.' % (i, (yield))) + ... i += 1 + ... + >>> t = tally() + >>> t.send('red') + Thing number 0 is red. + >>> t.send('fish') + Thing number 1 is fish. + + Without the decorator, you would have to call ``next(t)`` before + ``t.send()`` could be used. + + """ + + @wraps(func) + def wrapper(*args, **kwargs): + gen = func(*args, **kwargs) + next(gen) + return gen + + return wrapper + + +def ilen(iterable): + """Return the number of items in *iterable*. + + >>> ilen(x for x in range(1000000) if x % 3 == 0) + 333334 + + This consumes the iterable, so handle with care. + + """ + # This approach was selected because benchmarks showed it's likely the + # fastest of the known implementations at the time of writing. + # See GitHub tracker: #236, #230. + counter = count() + deque(zip(iterable, counter), maxlen=0) + return next(counter) + + +def iterate(func, start): + """Return ``start``, ``func(start)``, ``func(func(start))``, ... + + >>> from itertools import islice + >>> list(islice(iterate(lambda x: 2*x, 1), 10)) + [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] + + """ + while True: + yield start + start = func(start) + + +def with_iter(context_manager): + """Wrap an iterable in a ``with`` statement, so it closes once exhausted. + + For example, this will close the file when the iterator is exhausted:: + + upper_lines = (line.upper() for line in with_iter(open('foo'))) + + Any context manager which returns an iterable is a candidate for + ``with_iter``. + + """ + with context_manager as iterable: + yield from iterable + + +def one(iterable, too_short=None, too_long=None): + """Return the first item from *iterable*, which is expected to contain only + that item. Raise an exception if *iterable* is empty or has more than one + item. + + :func:`one` is useful for ensuring that an iterable contains only one item. + For example, it can be used to retrieve the result of a database query + that is expected to return a single row. + + If *iterable* is empty, ``ValueError`` will be raised. You may specify a + different exception with the *too_short* keyword: + + >>> it = [] + >>> one(it) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + ValueError: too many items in iterable (expected 1)' + >>> too_short = IndexError('too few items') + >>> one(it, too_short=too_short) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + IndexError: too few items + + Similarly, if *iterable* contains more than one item, ``ValueError`` will + be raised. You may specify a different exception with the *too_long* + keyword: + + >>> it = ['too', 'many'] + >>> one(it) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + ValueError: Expected exactly one item in iterable, but got 'too', + 'many', and perhaps more. + >>> too_long = RuntimeError + >>> one(it, too_long=too_long) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + RuntimeError + + Note that :func:`one` attempts to advance *iterable* twice to ensure there + is only one item. See :func:`spy` or :func:`peekable` to check iterable + contents less destructively. + + """ + it = iter(iterable) + + try: + first_value = next(it) + except StopIteration as e: + raise ( + too_short or ValueError('too few items in iterable (expected 1)') + ) from e + + try: + second_value = next(it) + except StopIteration: + pass + else: + msg = ( + 'Expected exactly one item in iterable, but got {!r}, {!r}, ' + 'and perhaps more.'.format(first_value, second_value) + ) + raise too_long or ValueError(msg) + + return first_value + + +def distinct_permutations(iterable, r=None): + """Yield successive distinct permutations of the elements in *iterable*. + + >>> sorted(distinct_permutations([1, 0, 1])) + [(0, 1, 1), (1, 0, 1), (1, 1, 0)] + + Equivalent to ``set(permutations(iterable))``, except duplicates are not + generated and thrown away. For larger input sequences this is much more + efficient. + + Duplicate permutations arise when there are duplicated elements in the + input iterable. The number of items returned is + `n! / (x_1! * x_2! * ... * x_n!)`, where `n` is the total number of + items input, and each `x_i` is the count of a distinct item in the input + sequence. + + If *r* is given, only the *r*-length permutations are yielded. + + >>> sorted(distinct_permutations([1, 0, 1], r=2)) + [(0, 1), (1, 0), (1, 1)] + >>> sorted(distinct_permutations(range(3), r=2)) + [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)] + + """ + # Algorithm: https://w.wiki/Qai + def _full(A): + while True: + # Yield the permutation we have + yield tuple(A) + + # Find the largest index i such that A[i] < A[i + 1] + for i in range(size - 2, -1, -1): + if A[i] < A[i + 1]: + break + # If no such index exists, this permutation is the last one + else: + return + + # Find the largest index j greater than j such that A[i] < A[j] + for j in range(size - 1, i, -1): + if A[i] < A[j]: + break + + # Swap the value of A[i] with that of A[j], then reverse the + # sequence from A[i + 1] to form the new permutation + A[i], A[j] = A[j], A[i] + A[i + 1 :] = A[: i - size : -1] # A[i + 1:][::-1] + + # Algorithm: modified from the above + def _partial(A, r): + # Split A into the first r items and the last r items + head, tail = A[:r], A[r:] + right_head_indexes = range(r - 1, -1, -1) + left_tail_indexes = range(len(tail)) + + while True: + # Yield the permutation we have + yield tuple(head) + + # Starting from the right, find the first index of the head with + # value smaller than the maximum value of the tail - call it i. + pivot = tail[-1] + for i in right_head_indexes: + if head[i] < pivot: + break + pivot = head[i] + else: + return + + # Starting from the left, find the first value of the tail + # with a value greater than head[i] and swap. + for j in left_tail_indexes: + if tail[j] > head[i]: + head[i], tail[j] = tail[j], head[i] + break + # If we didn't find one, start from the right and find the first + # index of the head with a value greater than head[i] and swap. + else: + for j in right_head_indexes: + if head[j] > head[i]: + head[i], head[j] = head[j], head[i] + break + + # Reverse head[i + 1:] and swap it with tail[:r - (i + 1)] + tail += head[: i - r : -1] # head[i + 1:][::-1] + i += 1 + head[i:], tail[:] = tail[: r - i], tail[r - i :] + + items = sorted(iterable) + + size = len(items) + if r is None: + r = size + + if 0 < r <= size: + return _full(items) if (r == size) else _partial(items, r) + + return iter(() if r else ((),)) + + +def intersperse(e, iterable, n=1): + """Intersperse filler element *e* among the items in *iterable*, leaving + *n* items between each filler element. + + >>> list(intersperse('!', [1, 2, 3, 4, 5])) + [1, '!', 2, '!', 3, '!', 4, '!', 5] + + >>> list(intersperse(None, [1, 2, 3, 4, 5], n=2)) + [1, 2, None, 3, 4, None, 5] + + """ + if n == 0: + raise ValueError('n must be > 0') + elif n == 1: + # interleave(repeat(e), iterable) -> e, x_0, e, e, x_1, e, x_2... + # islice(..., 1, None) -> x_0, e, e, x_1, e, x_2... + return islice(interleave(repeat(e), iterable), 1, None) + else: + # interleave(filler, chunks) -> [e], [x_0, x_1], [e], [x_2, x_3]... + # islice(..., 1, None) -> [x_0, x_1], [e], [x_2, x_3]... + # flatten(...) -> x_0, x_1, e, x_2, x_3... + filler = repeat([e]) + chunks = chunked(iterable, n) + return flatten(islice(interleave(filler, chunks), 1, None)) + + +def unique_to_each(*iterables): + """Return the elements from each of the input iterables that aren't in the + other input iterables. + + For example, suppose you have a set of packages, each with a set of + dependencies:: + + {'pkg_1': {'A', 'B'}, 'pkg_2': {'B', 'C'}, 'pkg_3': {'B', 'D'}} + + If you remove one package, which dependencies can also be removed? + + If ``pkg_1`` is removed, then ``A`` is no longer necessary - it is not + associated with ``pkg_2`` or ``pkg_3``. Similarly, ``C`` is only needed for + ``pkg_2``, and ``D`` is only needed for ``pkg_3``:: + + >>> unique_to_each({'A', 'B'}, {'B', 'C'}, {'B', 'D'}) + [['A'], ['C'], ['D']] + + If there are duplicates in one input iterable that aren't in the others + they will be duplicated in the output. Input order is preserved:: + + >>> unique_to_each("mississippi", "missouri") + [['p', 'p'], ['o', 'u', 'r']] + + It is assumed that the elements of each iterable are hashable. + + """ + pool = [list(it) for it in iterables] + counts = Counter(chain.from_iterable(map(set, pool))) + uniques = {element for element in counts if counts[element] == 1} + return [list(filter(uniques.__contains__, it)) for it in pool] + + +def windowed(seq, n, fillvalue=None, step=1): + """Return a sliding window of width *n* over the given iterable. + + >>> all_windows = windowed([1, 2, 3, 4, 5], 3) + >>> list(all_windows) + [(1, 2, 3), (2, 3, 4), (3, 4, 5)] + + When the window is larger than the iterable, *fillvalue* is used in place + of missing values: + + >>> list(windowed([1, 2, 3], 4)) + [(1, 2, 3, None)] + + Each window will advance in increments of *step*: + + >>> list(windowed([1, 2, 3, 4, 5, 6], 3, fillvalue='!', step=2)) + [(1, 2, 3), (3, 4, 5), (5, 6, '!')] + + To slide into the iterable's items, use :func:`chain` to add filler items + to the left: + + >>> iterable = [1, 2, 3, 4] + >>> n = 3 + >>> padding = [None] * (n - 1) + >>> list(windowed(chain(padding, iterable), 3)) + [(None, None, 1), (None, 1, 2), (1, 2, 3), (2, 3, 4)] + """ + if n < 0: + raise ValueError('n must be >= 0') + if n == 0: + yield tuple() + return + if step < 1: + raise ValueError('step must be >= 1') + + window = deque(maxlen=n) + i = n + for _ in map(window.append, seq): + i -= 1 + if not i: + i = step + yield tuple(window) + + size = len(window) + if size < n: + yield tuple(chain(window, repeat(fillvalue, n - size))) + elif 0 < i < min(step, n): + window += (fillvalue,) * i + yield tuple(window) + + +def substrings(iterable): + """Yield all of the substrings of *iterable*. + + >>> [''.join(s) for s in substrings('more')] + ['m', 'o', 'r', 'e', 'mo', 'or', 're', 'mor', 'ore', 'more'] + + Note that non-string iterables can also be subdivided. + + >>> list(substrings([0, 1, 2])) + [(0,), (1,), (2,), (0, 1), (1, 2), (0, 1, 2)] + + """ + # The length-1 substrings + seq = [] + for item in iter(iterable): + seq.append(item) + yield (item,) + seq = tuple(seq) + item_count = len(seq) + + # And the rest + for n in range(2, item_count + 1): + for i in range(item_count - n + 1): + yield seq[i : i + n] + + +def substrings_indexes(seq, reverse=False): + """Yield all substrings and their positions in *seq* + + The items yielded will be a tuple of the form ``(substr, i, j)``, where + ``substr == seq[i:j]``. + + This function only works for iterables that support slicing, such as + ``str`` objects. + + >>> for item in substrings_indexes('more'): + ... print(item) + ('m', 0, 1) + ('o', 1, 2) + ('r', 2, 3) + ('e', 3, 4) + ('mo', 0, 2) + ('or', 1, 3) + ('re', 2, 4) + ('mor', 0, 3) + ('ore', 1, 4) + ('more', 0, 4) + + Set *reverse* to ``True`` to yield the same items in the opposite order. + + + """ + r = range(1, len(seq) + 1) + if reverse: + r = reversed(r) + return ( + (seq[i : i + L], i, i + L) for L in r for i in range(len(seq) - L + 1) + ) + + +class bucket: + """Wrap *iterable* and return an object that buckets it iterable into + child iterables based on a *key* function. + + >>> iterable = ['a1', 'b1', 'c1', 'a2', 'b2', 'c2', 'b3'] + >>> s = bucket(iterable, key=lambda x: x[0]) # Bucket by 1st character + >>> sorted(list(s)) # Get the keys + ['a', 'b', 'c'] + >>> a_iterable = s['a'] + >>> next(a_iterable) + 'a1' + >>> next(a_iterable) + 'a2' + >>> list(s['b']) + ['b1', 'b2', 'b3'] + + The original iterable will be advanced and its items will be cached until + they are used by the child iterables. This may require significant storage. + + By default, attempting to select a bucket to which no items belong will + exhaust the iterable and cache all values. + If you specify a *validator* function, selected buckets will instead be + checked against it. + + >>> from itertools import count + >>> it = count(1, 2) # Infinite sequence of odd numbers + >>> key = lambda x: x % 10 # Bucket by last digit + >>> validator = lambda x: x in {1, 3, 5, 7, 9} # Odd digits only + >>> s = bucket(it, key=key, validator=validator) + >>> 2 in s + False + >>> list(s[2]) + [] + + """ + + def __init__(self, iterable, key, validator=None): + self._it = iter(iterable) + self._key = key + self._cache = defaultdict(deque) + self._validator = validator or (lambda x: True) + + def __contains__(self, value): + if not self._validator(value): + return False + + try: + item = next(self[value]) + except StopIteration: + return False + else: + self._cache[value].appendleft(item) + + return True + + def _get_values(self, value): + """ + Helper to yield items from the parent iterator that match *value*. + Items that don't match are stored in the local cache as they + are encountered. + """ + while True: + # If we've cached some items that match the target value, emit + # the first one and evict it from the cache. + if self._cache[value]: + yield self._cache[value].popleft() + # Otherwise we need to advance the parent iterator to search for + # a matching item, caching the rest. + else: + while True: + try: + item = next(self._it) + except StopIteration: + return + item_value = self._key(item) + if item_value == value: + yield item + break + elif self._validator(item_value): + self._cache[item_value].append(item) + + def __iter__(self): + for item in self._it: + item_value = self._key(item) + if self._validator(item_value): + self._cache[item_value].append(item) + + yield from self._cache.keys() + + def __getitem__(self, value): + if not self._validator(value): + return iter(()) + + return self._get_values(value) + + +def spy(iterable, n=1): + """Return a 2-tuple with a list containing the first *n* elements of + *iterable*, and an iterator with the same items as *iterable*. + This allows you to "look ahead" at the items in the iterable without + advancing it. + + There is one item in the list by default: + + >>> iterable = 'abcdefg' + >>> head, iterable = spy(iterable) + >>> head + ['a'] + >>> list(iterable) + ['a', 'b', 'c', 'd', 'e', 'f', 'g'] + + You may use unpacking to retrieve items instead of lists: + + >>> (head,), iterable = spy('abcdefg') + >>> head + 'a' + >>> (first, second), iterable = spy('abcdefg', 2) + >>> first + 'a' + >>> second + 'b' + + The number of items requested can be larger than the number of items in + the iterable: + + >>> iterable = [1, 2, 3, 4, 5] + >>> head, iterable = spy(iterable, 10) + >>> head + [1, 2, 3, 4, 5] + >>> list(iterable) + [1, 2, 3, 4, 5] + + """ + it = iter(iterable) + head = take(n, it) + + return head.copy(), chain(head, it) + + +def interleave(*iterables): + """Return a new iterable yielding from each iterable in turn, + until the shortest is exhausted. + + >>> list(interleave([1, 2, 3], [4, 5], [6, 7, 8])) + [1, 4, 6, 2, 5, 7] + + For a version that doesn't terminate after the shortest iterable is + exhausted, see :func:`interleave_longest`. + + """ + return chain.from_iterable(zip(*iterables)) + + +def interleave_longest(*iterables): + """Return a new iterable yielding from each iterable in turn, + skipping any that are exhausted. + + >>> list(interleave_longest([1, 2, 3], [4, 5], [6, 7, 8])) + [1, 4, 6, 2, 5, 7, 3, 8] + + This function produces the same output as :func:`roundrobin`, but may + perform better for some inputs (in particular when the number of iterables + is large). + + """ + i = chain.from_iterable(zip_longest(*iterables, fillvalue=_marker)) + return (x for x in i if x is not _marker) + + +def collapse(iterable, base_type=None, levels=None): + """Flatten an iterable with multiple levels of nesting (e.g., a list of + lists of tuples) into non-iterable types. + + >>> iterable = [(1, 2), ([3, 4], [[5], [6]])] + >>> list(collapse(iterable)) + [1, 2, 3, 4, 5, 6] + + Binary and text strings are not considered iterable and + will not be collapsed. + + To avoid collapsing other types, specify *base_type*: + + >>> iterable = ['ab', ('cd', 'ef'), ['gh', 'ij']] + >>> list(collapse(iterable, base_type=tuple)) + ['ab', ('cd', 'ef'), 'gh', 'ij'] + + Specify *levels* to stop flattening after a certain level: + + >>> iterable = [('a', ['b']), ('c', ['d'])] + >>> list(collapse(iterable)) # Fully flattened + ['a', 'b', 'c', 'd'] + >>> list(collapse(iterable, levels=1)) # Only one level flattened + ['a', ['b'], 'c', ['d']] + + """ + + def walk(node, level): + if ( + ((levels is not None) and (level > levels)) + or isinstance(node, (str, bytes)) + or ((base_type is not None) and isinstance(node, base_type)) + ): + yield node + return + + try: + tree = iter(node) + except TypeError: + yield node + return + else: + for child in tree: + yield from walk(child, level + 1) + + yield from walk(iterable, 0) + + +def side_effect(func, iterable, chunk_size=None, before=None, after=None): + """Invoke *func* on each item in *iterable* (or on each *chunk_size* group + of items) before yielding the item. + + `func` must be a function that takes a single argument. Its return value + will be discarded. + + *before* and *after* are optional functions that take no arguments. They + will be executed before iteration starts and after it ends, respectively. + + `side_effect` can be used for logging, updating progress bars, or anything + that is not functionally "pure." + + Emitting a status message: + + >>> from more_itertools import consume + >>> func = lambda item: print('Received {}'.format(item)) + >>> consume(side_effect(func, range(2))) + Received 0 + Received 1 + + Operating on chunks of items: + + >>> pair_sums = [] + >>> func = lambda chunk: pair_sums.append(sum(chunk)) + >>> list(side_effect(func, [0, 1, 2, 3, 4, 5], 2)) + [0, 1, 2, 3, 4, 5] + >>> list(pair_sums) + [1, 5, 9] + + Writing to a file-like object: + + >>> from io import StringIO + >>> from more_itertools import consume + >>> f = StringIO() + >>> func = lambda x: print(x, file=f) + >>> before = lambda: print(u'HEADER', file=f) + >>> after = f.close + >>> it = [u'a', u'b', u'c'] + >>> consume(side_effect(func, it, before=before, after=after)) + >>> f.closed + True + + """ + try: + if before is not None: + before() + + if chunk_size is None: + for item in iterable: + func(item) + yield item + else: + for chunk in chunked(iterable, chunk_size): + func(chunk) + yield from chunk + finally: + if after is not None: + after() + + +def sliced(seq, n, strict=False): + """Yield slices of length *n* from the sequence *seq*. + + >>> list(sliced((1, 2, 3, 4, 5, 6), 3)) + [(1, 2, 3), (4, 5, 6)] + + By the default, the last yielded slice will have fewer than *n* elements + if the length of *seq* is not divisible by *n*: + + >>> list(sliced((1, 2, 3, 4, 5, 6, 7, 8), 3)) + [(1, 2, 3), (4, 5, 6), (7, 8)] + + If the length of *seq* is not divisible by *n* and *strict* is + ``True``, then ``ValueError`` will be raised before the last + slice is yielded. + + This function will only work for iterables that support slicing. + For non-sliceable iterables, see :func:`chunked`. + + """ + iterator = takewhile(len, (seq[i : i + n] for i in count(0, n))) + if strict: + + def ret(): + for _slice in iterator: + if len(_slice) != n: + raise ValueError("seq is not divisible by n.") + yield _slice + + return iter(ret()) + else: + return iterator + + +def split_at(iterable, pred, maxsplit=-1, keep_separator=False): + """Yield lists of items from *iterable*, where each list is delimited by + an item where callable *pred* returns ``True``. + + >>> list(split_at('abcdcba', lambda x: x == 'b')) + [['a'], ['c', 'd', 'c'], ['a']] + + >>> list(split_at(range(10), lambda n: n % 2 == 1)) + [[0], [2], [4], [6], [8], []] + + At most *maxsplit* splits are done. If *maxsplit* is not specified or -1, + then there is no limit on the number of splits: + + >>> list(split_at(range(10), lambda n: n % 2 == 1, maxsplit=2)) + [[0], [2], [4, 5, 6, 7, 8, 9]] + + By default, the delimiting items are not included in the output. + The include them, set *keep_separator* to ``True``. + + >>> list(split_at('abcdcba', lambda x: x == 'b', keep_separator=True)) + [['a'], ['b'], ['c', 'd', 'c'], ['b'], ['a']] + + """ + if maxsplit == 0: + yield list(iterable) + return + + buf = [] + it = iter(iterable) + for item in it: + if pred(item): + yield buf + if keep_separator: + yield [item] + if maxsplit == 1: + yield list(it) + return + buf = [] + maxsplit -= 1 + else: + buf.append(item) + yield buf + + +def split_before(iterable, pred, maxsplit=-1): + """Yield lists of items from *iterable*, where each list ends just before + an item for which callable *pred* returns ``True``: + + >>> list(split_before('OneTwo', lambda s: s.isupper())) + [['O', 'n', 'e'], ['T', 'w', 'o']] + + >>> list(split_before(range(10), lambda n: n % 3 == 0)) + [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]] + + At most *maxsplit* splits are done. If *maxsplit* is not specified or -1, + then there is no limit on the number of splits: + + >>> list(split_before(range(10), lambda n: n % 3 == 0, maxsplit=2)) + [[0, 1, 2], [3, 4, 5], [6, 7, 8, 9]] + """ + if maxsplit == 0: + yield list(iterable) + return + + buf = [] + it = iter(iterable) + for item in it: + if pred(item) and buf: + yield buf + if maxsplit == 1: + yield [item] + list(it) + return + buf = [] + maxsplit -= 1 + buf.append(item) + if buf: + yield buf + + +def split_after(iterable, pred, maxsplit=-1): + """Yield lists of items from *iterable*, where each list ends with an + item where callable *pred* returns ``True``: + + >>> list(split_after('one1two2', lambda s: s.isdigit())) + [['o', 'n', 'e', '1'], ['t', 'w', 'o', '2']] + + >>> list(split_after(range(10), lambda n: n % 3 == 0)) + [[0], [1, 2, 3], [4, 5, 6], [7, 8, 9]] + + At most *maxsplit* splits are done. If *maxsplit* is not specified or -1, + then there is no limit on the number of splits: + + >>> list(split_after(range(10), lambda n: n % 3 == 0, maxsplit=2)) + [[0], [1, 2, 3], [4, 5, 6, 7, 8, 9]] + + """ + if maxsplit == 0: + yield list(iterable) + return + + buf = [] + it = iter(iterable) + for item in it: + buf.append(item) + if pred(item) and buf: + yield buf + if maxsplit == 1: + yield list(it) + return + buf = [] + maxsplit -= 1 + if buf: + yield buf + + +def split_when(iterable, pred, maxsplit=-1): + """Split *iterable* into pieces based on the output of *pred*. + *pred* should be a function that takes successive pairs of items and + returns ``True`` if the iterable should be split in between them. + + For example, to find runs of increasing numbers, split the iterable when + element ``i`` is larger than element ``i + 1``: + + >>> list(split_when([1, 2, 3, 3, 2, 5, 2, 4, 2], lambda x, y: x > y)) + [[1, 2, 3, 3], [2, 5], [2, 4], [2]] + + At most *maxsplit* splits are done. If *maxsplit* is not specified or -1, + then there is no limit on the number of splits: + + >>> list(split_when([1, 2, 3, 3, 2, 5, 2, 4, 2], + ... lambda x, y: x > y, maxsplit=2)) + [[1, 2, 3, 3], [2, 5], [2, 4, 2]] + + """ + if maxsplit == 0: + yield list(iterable) + return + + it = iter(iterable) + try: + cur_item = next(it) + except StopIteration: + return + + buf = [cur_item] + for next_item in it: + if pred(cur_item, next_item): + yield buf + if maxsplit == 1: + yield [next_item] + list(it) + return + buf = [] + maxsplit -= 1 + + buf.append(next_item) + cur_item = next_item + + yield buf + + +def split_into(iterable, sizes): + """Yield a list of sequential items from *iterable* of length 'n' for each + integer 'n' in *sizes*. + + >>> list(split_into([1,2,3,4,5,6], [1,2,3])) + [[1], [2, 3], [4, 5, 6]] + + If the sum of *sizes* is smaller than the length of *iterable*, then the + remaining items of *iterable* will not be returned. + + >>> list(split_into([1,2,3,4,5,6], [2,3])) + [[1, 2], [3, 4, 5]] + + If the sum of *sizes* is larger than the length of *iterable*, fewer items + will be returned in the iteration that overruns *iterable* and further + lists will be empty: + + >>> list(split_into([1,2,3,4], [1,2,3,4])) + [[1], [2, 3], [4], []] + + When a ``None`` object is encountered in *sizes*, the returned list will + contain items up to the end of *iterable* the same way that itertools.slice + does: + + >>> list(split_into([1,2,3,4,5,6,7,8,9,0], [2,3,None])) + [[1, 2], [3, 4, 5], [6, 7, 8, 9, 0]] + + :func:`split_into` can be useful for grouping a series of items where the + sizes of the groups are not uniform. An example would be where in a row + from a table, multiple columns represent elements of the same feature + (e.g. a point represented by x,y,z) but, the format is not the same for + all columns. + """ + # convert the iterable argument into an iterator so its contents can + # be consumed by islice in case it is a generator + it = iter(iterable) + + for size in sizes: + if size is None: + yield list(it) + return + else: + yield list(islice(it, size)) + + +def padded(iterable, fillvalue=None, n=None, next_multiple=False): + """Yield the elements from *iterable*, followed by *fillvalue*, such that + at least *n* items are emitted. + + >>> list(padded([1, 2, 3], '?', 5)) + [1, 2, 3, '?', '?'] + + If *next_multiple* is ``True``, *fillvalue* will be emitted until the + number of items emitted is a multiple of *n*:: + + >>> list(padded([1, 2, 3, 4], n=3, next_multiple=True)) + [1, 2, 3, 4, None, None] + + If *n* is ``None``, *fillvalue* will be emitted indefinitely. + + """ + it = iter(iterable) + if n is None: + yield from chain(it, repeat(fillvalue)) + elif n < 1: + raise ValueError('n must be at least 1') + else: + item_count = 0 + for item in it: + yield item + item_count += 1 + + remaining = (n - item_count) % n if next_multiple else n - item_count + for _ in range(remaining): + yield fillvalue + + +def repeat_last(iterable, default=None): + """After the *iterable* is exhausted, keep yielding its last element. + + >>> list(islice(repeat_last(range(3)), 5)) + [0, 1, 2, 2, 2] + + If the iterable is empty, yield *default* forever:: + + >>> list(islice(repeat_last(range(0), 42), 5)) + [42, 42, 42, 42, 42] + + """ + item = _marker + for item in iterable: + yield item + final = default if item is _marker else item + yield from repeat(final) + + +def distribute(n, iterable): + """Distribute the items from *iterable* among *n* smaller iterables. + + >>> group_1, group_2 = distribute(2, [1, 2, 3, 4, 5, 6]) + >>> list(group_1) + [1, 3, 5] + >>> list(group_2) + [2, 4, 6] + + If the length of *iterable* is not evenly divisible by *n*, then the + length of the returned iterables will not be identical: + + >>> children = distribute(3, [1, 2, 3, 4, 5, 6, 7]) + >>> [list(c) for c in children] + [[1, 4, 7], [2, 5], [3, 6]] + + If the length of *iterable* is smaller than *n*, then the last returned + iterables will be empty: + + >>> children = distribute(5, [1, 2, 3]) + >>> [list(c) for c in children] + [[1], [2], [3], [], []] + + This function uses :func:`itertools.tee` and may require significant + storage. If you need the order items in the smaller iterables to match the + original iterable, see :func:`divide`. + + """ + if n < 1: + raise ValueError('n must be at least 1') + + children = tee(iterable, n) + return [islice(it, index, None, n) for index, it in enumerate(children)] + + +def stagger(iterable, offsets=(-1, 0, 1), longest=False, fillvalue=None): + """Yield tuples whose elements are offset from *iterable*. + The amount by which the `i`-th item in each tuple is offset is given by + the `i`-th item in *offsets*. + + >>> list(stagger([0, 1, 2, 3])) + [(None, 0, 1), (0, 1, 2), (1, 2, 3)] + >>> list(stagger(range(8), offsets=(0, 2, 4))) + [(0, 2, 4), (1, 3, 5), (2, 4, 6), (3, 5, 7)] + + By default, the sequence will end when the final element of a tuple is the + last item in the iterable. To continue until the first element of a tuple + is the last item in the iterable, set *longest* to ``True``:: + + >>> list(stagger([0, 1, 2, 3], longest=True)) + [(None, 0, 1), (0, 1, 2), (1, 2, 3), (2, 3, None), (3, None, None)] + + By default, ``None`` will be used to replace offsets beyond the end of the + sequence. Specify *fillvalue* to use some other value. + + """ + children = tee(iterable, len(offsets)) + + return zip_offset( + *children, offsets=offsets, longest=longest, fillvalue=fillvalue + ) + + +class UnequalIterablesError(ValueError): + def __init__(self, details=None): + msg = 'Iterables have different lengths' + if details is not None: + msg += (': index 0 has length {}; index {} has length {}').format( + *details + ) + + super().__init__(msg) + + +def _zip_equal_generator(iterables): + for combo in zip_longest(*iterables, fillvalue=_marker): + for val in combo: + if val is _marker: + raise UnequalIterablesError() + yield combo + + +def zip_equal(*iterables): + """``zip`` the input *iterables* together, but raise + ``UnequalIterablesError`` if they aren't all the same length. + + >>> it_1 = range(3) + >>> it_2 = iter('abc') + >>> list(zip_equal(it_1, it_2)) + [(0, 'a'), (1, 'b'), (2, 'c')] + + >>> it_1 = range(3) + >>> it_2 = iter('abcd') + >>> list(zip_equal(it_1, it_2)) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + more_itertools.more.UnequalIterablesError: Iterables have different + lengths + + """ + if hexversion >= 0x30A00A6: + warnings.warn( + ( + 'zip_equal will be removed in a future version of ' + 'more-itertools. Use the builtin zip function with ' + 'strict=True instead.' + ), + DeprecationWarning, + ) + # Check whether the iterables are all the same size. + try: + first_size = len(iterables[0]) + for i, it in enumerate(iterables[1:], 1): + size = len(it) + if size != first_size: + break + else: + # If we didn't break out, we can use the built-in zip. + return zip(*iterables) + + # If we did break out, there was a mismatch. + raise UnequalIterablesError(details=(first_size, i, size)) + # If any one of the iterables didn't have a length, start reading + # them until one runs out. + except TypeError: + return _zip_equal_generator(iterables) + + +def zip_offset(*iterables, offsets, longest=False, fillvalue=None): + """``zip`` the input *iterables* together, but offset the `i`-th iterable + by the `i`-th item in *offsets*. + + >>> list(zip_offset('0123', 'abcdef', offsets=(0, 1))) + [('0', 'b'), ('1', 'c'), ('2', 'd'), ('3', 'e')] + + This can be used as a lightweight alternative to SciPy or pandas to analyze + data sets in which some series have a lead or lag relationship. + + By default, the sequence will end when the shortest iterable is exhausted. + To continue until the longest iterable is exhausted, set *longest* to + ``True``. + + >>> list(zip_offset('0123', 'abcdef', offsets=(0, 1), longest=True)) + [('0', 'b'), ('1', 'c'), ('2', 'd'), ('3', 'e'), (None, 'f')] + + By default, ``None`` will be used to replace offsets beyond the end of the + sequence. Specify *fillvalue* to use some other value. + + """ + if len(iterables) != len(offsets): + raise ValueError("Number of iterables and offsets didn't match") + + staggered = [] + for it, n in zip(iterables, offsets): + if n < 0: + staggered.append(chain(repeat(fillvalue, -n), it)) + elif n > 0: + staggered.append(islice(it, n, None)) + else: + staggered.append(it) + + if longest: + return zip_longest(*staggered, fillvalue=fillvalue) + + return zip(*staggered) + + +def sort_together(iterables, key_list=(0,), key=None, reverse=False): + """Return the input iterables sorted together, with *key_list* as the + priority for sorting. All iterables are trimmed to the length of the + shortest one. + + This can be used like the sorting function in a spreadsheet. If each + iterable represents a column of data, the key list determines which + columns are used for sorting. + + By default, all iterables are sorted using the ``0``-th iterable:: + + >>> iterables = [(4, 3, 2, 1), ('a', 'b', 'c', 'd')] + >>> sort_together(iterables) + [(1, 2, 3, 4), ('d', 'c', 'b', 'a')] + + Set a different key list to sort according to another iterable. + Specifying multiple keys dictates how ties are broken:: + + >>> iterables = [(3, 1, 2), (0, 1, 0), ('c', 'b', 'a')] + >>> sort_together(iterables, key_list=(1, 2)) + [(2, 3, 1), (0, 0, 1), ('a', 'c', 'b')] + + To sort by a function of the elements of the iterable, pass a *key* + function. Its arguments are the elements of the iterables corresponding to + the key list:: + + >>> names = ('a', 'b', 'c') + >>> lengths = (1, 2, 3) + >>> widths = (5, 2, 1) + >>> def area(length, width): + ... return length * width + >>> sort_together([names, lengths, widths], key_list=(1, 2), key=area) + [('c', 'b', 'a'), (3, 2, 1), (1, 2, 5)] + + Set *reverse* to ``True`` to sort in descending order. + + >>> sort_together([(1, 2, 3), ('c', 'b', 'a')], reverse=True) + [(3, 2, 1), ('a', 'b', 'c')] + + """ + if key is None: + # if there is no key function, the key argument to sorted is an + # itemgetter + key_argument = itemgetter(*key_list) + else: + # if there is a key function, call it with the items at the offsets + # specified by the key function as arguments + key_list = list(key_list) + if len(key_list) == 1: + # if key_list contains a single item, pass the item at that offset + # as the only argument to the key function + key_offset = key_list[0] + key_argument = lambda zipped_items: key(zipped_items[key_offset]) + else: + # if key_list contains multiple items, use itemgetter to return a + # tuple of items, which we pass as *args to the key function + get_key_items = itemgetter(*key_list) + key_argument = lambda zipped_items: key( + *get_key_items(zipped_items) + ) + + return list( + zip(*sorted(zip(*iterables), key=key_argument, reverse=reverse)) + ) + + +def unzip(iterable): + """The inverse of :func:`zip`, this function disaggregates the elements + of the zipped *iterable*. + + The ``i``-th iterable contains the ``i``-th element from each element + of the zipped iterable. The first element is used to to determine the + length of the remaining elements. + + >>> iterable = [('a', 1), ('b', 2), ('c', 3), ('d', 4)] + >>> letters, numbers = unzip(iterable) + >>> list(letters) + ['a', 'b', 'c', 'd'] + >>> list(numbers) + [1, 2, 3, 4] + + This is similar to using ``zip(*iterable)``, but it avoids reading + *iterable* into memory. Note, however, that this function uses + :func:`itertools.tee` and thus may require significant storage. + + """ + head, iterable = spy(iter(iterable)) + if not head: + # empty iterable, e.g. zip([], [], []) + return () + # spy returns a one-length iterable as head + head = head[0] + iterables = tee(iterable, len(head)) + + def itemgetter(i): + def getter(obj): + try: + return obj[i] + except IndexError: + # basically if we have an iterable like + # iter([(1, 2, 3), (4, 5), (6,)]) + # the second unzipped iterable would fail at the third tuple + # since it would try to access tup[1] + # same with the third unzipped iterable and the second tuple + # to support these "improperly zipped" iterables, + # we create a custom itemgetter + # which just stops the unzipped iterables + # at first length mismatch + raise StopIteration + + return getter + + return tuple(map(itemgetter(i), it) for i, it in enumerate(iterables)) + + +def divide(n, iterable): + """Divide the elements from *iterable* into *n* parts, maintaining + order. + + >>> group_1, group_2 = divide(2, [1, 2, 3, 4, 5, 6]) + >>> list(group_1) + [1, 2, 3] + >>> list(group_2) + [4, 5, 6] + + If the length of *iterable* is not evenly divisible by *n*, then the + length of the returned iterables will not be identical: + + >>> children = divide(3, [1, 2, 3, 4, 5, 6, 7]) + >>> [list(c) for c in children] + [[1, 2, 3], [4, 5], [6, 7]] + + If the length of the iterable is smaller than n, then the last returned + iterables will be empty: + + >>> children = divide(5, [1, 2, 3]) + >>> [list(c) for c in children] + [[1], [2], [3], [], []] + + This function will exhaust the iterable before returning and may require + significant storage. If order is not important, see :func:`distribute`, + which does not first pull the iterable into memory. + + """ + if n < 1: + raise ValueError('n must be at least 1') + + try: + iterable[:0] + except TypeError: + seq = tuple(iterable) + else: + seq = iterable + + q, r = divmod(len(seq), n) + + ret = [] + stop = 0 + for i in range(1, n + 1): + start = stop + stop += q + 1 if i <= r else q + ret.append(iter(seq[start:stop])) + + return ret + + +def always_iterable(obj, base_type=(str, bytes)): + """If *obj* is iterable, return an iterator over its items:: + + >>> obj = (1, 2, 3) + >>> list(always_iterable(obj)) + [1, 2, 3] + + If *obj* is not iterable, return a one-item iterable containing *obj*:: + + >>> obj = 1 + >>> list(always_iterable(obj)) + [1] + + If *obj* is ``None``, return an empty iterable: + + >>> obj = None + >>> list(always_iterable(None)) + [] + + By default, binary and text strings are not considered iterable:: + + >>> obj = 'foo' + >>> list(always_iterable(obj)) + ['foo'] + + If *base_type* is set, objects for which ``isinstance(obj, base_type)`` + returns ``True`` won't be considered iterable. + + >>> obj = {'a': 1} + >>> list(always_iterable(obj)) # Iterate over the dict's keys + ['a'] + >>> list(always_iterable(obj, base_type=dict)) # Treat dicts as a unit + [{'a': 1}] + + Set *base_type* to ``None`` to avoid any special handling and treat objects + Python considers iterable as iterable: + + >>> obj = 'foo' + >>> list(always_iterable(obj, base_type=None)) + ['f', 'o', 'o'] + """ + if obj is None: + return iter(()) + + if (base_type is not None) and isinstance(obj, base_type): + return iter((obj,)) + + try: + return iter(obj) + except TypeError: + return iter((obj,)) + + +def adjacent(predicate, iterable, distance=1): + """Return an iterable over `(bool, item)` tuples where the `item` is + drawn from *iterable* and the `bool` indicates whether + that item satisfies the *predicate* or is adjacent to an item that does. + + For example, to find whether items are adjacent to a ``3``:: + + >>> list(adjacent(lambda x: x == 3, range(6))) + [(False, 0), (False, 1), (True, 2), (True, 3), (True, 4), (False, 5)] + + Set *distance* to change what counts as adjacent. For example, to find + whether items are two places away from a ``3``: + + >>> list(adjacent(lambda x: x == 3, range(6), distance=2)) + [(False, 0), (True, 1), (True, 2), (True, 3), (True, 4), (True, 5)] + + This is useful for contextualizing the results of a search function. + For example, a code comparison tool might want to identify lines that + have changed, but also surrounding lines to give the viewer of the diff + context. + + The predicate function will only be called once for each item in the + iterable. + + See also :func:`groupby_transform`, which can be used with this function + to group ranges of items with the same `bool` value. + + """ + # Allow distance=0 mainly for testing that it reproduces results with map() + if distance < 0: + raise ValueError('distance must be at least 0') + + i1, i2 = tee(iterable) + padding = [False] * distance + selected = chain(padding, map(predicate, i1), padding) + adjacent_to_selected = map(any, windowed(selected, 2 * distance + 1)) + return zip(adjacent_to_selected, i2) + + +def groupby_transform(iterable, keyfunc=None, valuefunc=None, reducefunc=None): + """An extension of :func:`itertools.groupby` that can apply transformations + to the grouped data. + + * *keyfunc* is a function computing a key value for each item in *iterable* + * *valuefunc* is a function that transforms the individual items from + *iterable* after grouping + * *reducefunc* is a function that transforms each group of items + + >>> iterable = 'aAAbBBcCC' + >>> keyfunc = lambda k: k.upper() + >>> valuefunc = lambda v: v.lower() + >>> reducefunc = lambda g: ''.join(g) + >>> list(groupby_transform(iterable, keyfunc, valuefunc, reducefunc)) + [('A', 'aaa'), ('B', 'bbb'), ('C', 'ccc')] + + Each optional argument defaults to an identity function if not specified. + + :func:`groupby_transform` is useful when grouping elements of an iterable + using a separate iterable as the key. To do this, :func:`zip` the iterables + and pass a *keyfunc* that extracts the first element and a *valuefunc* + that extracts the second element:: + + >>> from operator import itemgetter + >>> keys = [0, 0, 1, 1, 1, 2, 2, 2, 3] + >>> values = 'abcdefghi' + >>> iterable = zip(keys, values) + >>> grouper = groupby_transform(iterable, itemgetter(0), itemgetter(1)) + >>> [(k, ''.join(g)) for k, g in grouper] + [(0, 'ab'), (1, 'cde'), (2, 'fgh'), (3, 'i')] + + Note that the order of items in the iterable is significant. + Only adjacent items are grouped together, so if you don't want any + duplicate groups, you should sort the iterable by the key function. + + """ + ret = groupby(iterable, keyfunc) + if valuefunc: + ret = ((k, map(valuefunc, g)) for k, g in ret) + if reducefunc: + ret = ((k, reducefunc(g)) for k, g in ret) + + return ret + + +class numeric_range(abc.Sequence, abc.Hashable): + """An extension of the built-in ``range()`` function whose arguments can + be any orderable numeric type. + + With only *stop* specified, *start* defaults to ``0`` and *step* + defaults to ``1``. The output items will match the type of *stop*: + + >>> list(numeric_range(3.5)) + [0.0, 1.0, 2.0, 3.0] + + With only *start* and *stop* specified, *step* defaults to ``1``. The + output items will match the type of *start*: + + >>> from decimal import Decimal + >>> start = Decimal('2.1') + >>> stop = Decimal('5.1') + >>> list(numeric_range(start, stop)) + [Decimal('2.1'), Decimal('3.1'), Decimal('4.1')] + + With *start*, *stop*, and *step* specified the output items will match + the type of ``start + step``: + + >>> from fractions import Fraction + >>> start = Fraction(1, 2) # Start at 1/2 + >>> stop = Fraction(5, 2) # End at 5/2 + >>> step = Fraction(1, 2) # Count by 1/2 + >>> list(numeric_range(start, stop, step)) + [Fraction(1, 2), Fraction(1, 1), Fraction(3, 2), Fraction(2, 1)] + + If *step* is zero, ``ValueError`` is raised. Negative steps are supported: + + >>> list(numeric_range(3, -1, -1.0)) + [3.0, 2.0, 1.0, 0.0] + + Be aware of the limitations of floating point numbers; the representation + of the yielded numbers may be surprising. + + ``datetime.datetime`` objects can be used for *start* and *stop*, if *step* + is a ``datetime.timedelta`` object: + + >>> import datetime + >>> start = datetime.datetime(2019, 1, 1) + >>> stop = datetime.datetime(2019, 1, 3) + >>> step = datetime.timedelta(days=1) + >>> items = iter(numeric_range(start, stop, step)) + >>> next(items) + datetime.datetime(2019, 1, 1, 0, 0) + >>> next(items) + datetime.datetime(2019, 1, 2, 0, 0) + + """ + + _EMPTY_HASH = hash(range(0, 0)) + + def __init__(self, *args): + argc = len(args) + if argc == 1: + (self._stop,) = args + self._start = type(self._stop)(0) + self._step = type(self._stop - self._start)(1) + elif argc == 2: + self._start, self._stop = args + self._step = type(self._stop - self._start)(1) + elif argc == 3: + self._start, self._stop, self._step = args + elif argc == 0: + raise TypeError( + 'numeric_range expected at least ' + '1 argument, got {}'.format(argc) + ) + else: + raise TypeError( + 'numeric_range expected at most ' + '3 arguments, got {}'.format(argc) + ) + + self._zero = type(self._step)(0) + if self._step == self._zero: + raise ValueError('numeric_range() arg 3 must not be zero') + self._growing = self._step > self._zero + self._init_len() + + def __bool__(self): + if self._growing: + return self._start < self._stop + else: + return self._start > self._stop + + def __contains__(self, elem): + if self._growing: + if self._start <= elem < self._stop: + return (elem - self._start) % self._step == self._zero + else: + if self._start >= elem > self._stop: + return (self._start - elem) % (-self._step) == self._zero + + return False + + def __eq__(self, other): + if isinstance(other, numeric_range): + empty_self = not bool(self) + empty_other = not bool(other) + if empty_self or empty_other: + return empty_self and empty_other # True if both empty + else: + return ( + self._start == other._start + and self._step == other._step + and self._get_by_index(-1) == other._get_by_index(-1) + ) + else: + return False + + def __getitem__(self, key): + if isinstance(key, int): + return self._get_by_index(key) + elif isinstance(key, slice): + step = self._step if key.step is None else key.step * self._step + + if key.start is None or key.start <= -self._len: + start = self._start + elif key.start >= self._len: + start = self._stop + else: # -self._len < key.start < self._len + start = self._get_by_index(key.start) + + if key.stop is None or key.stop >= self._len: + stop = self._stop + elif key.stop <= -self._len: + stop = self._start + else: # -self._len < key.stop < self._len + stop = self._get_by_index(key.stop) + + return numeric_range(start, stop, step) + else: + raise TypeError( + 'numeric range indices must be ' + 'integers or slices, not {}'.format(type(key).__name__) + ) + + def __hash__(self): + if self: + return hash((self._start, self._get_by_index(-1), self._step)) + else: + return self._EMPTY_HASH + + def __iter__(self): + values = (self._start + (n * self._step) for n in count()) + if self._growing: + return takewhile(partial(gt, self._stop), values) + else: + return takewhile(partial(lt, self._stop), values) + + def __len__(self): + return self._len + + def _init_len(self): + if self._growing: + start = self._start + stop = self._stop + step = self._step + else: + start = self._stop + stop = self._start + step = -self._step + distance = stop - start + if distance <= self._zero: + self._len = 0 + else: # distance > 0 and step > 0: regular euclidean division + q, r = divmod(distance, step) + self._len = int(q) + int(r != self._zero) + + def __reduce__(self): + return numeric_range, (self._start, self._stop, self._step) + + def __repr__(self): + if self._step == 1: + return "numeric_range({}, {})".format( + repr(self._start), repr(self._stop) + ) + else: + return "numeric_range({}, {}, {})".format( + repr(self._start), repr(self._stop), repr(self._step) + ) + + def __reversed__(self): + return iter( + numeric_range( + self._get_by_index(-1), self._start - self._step, -self._step + ) + ) + + def count(self, value): + return int(value in self) + + def index(self, value): + if self._growing: + if self._start <= value < self._stop: + q, r = divmod(value - self._start, self._step) + if r == self._zero: + return int(q) + else: + if self._start >= value > self._stop: + q, r = divmod(self._start - value, -self._step) + if r == self._zero: + return int(q) + + raise ValueError("{} is not in numeric range".format(value)) + + def _get_by_index(self, i): + if i < 0: + i += self._len + if i < 0 or i >= self._len: + raise IndexError("numeric range object index out of range") + return self._start + i * self._step + + +def count_cycle(iterable, n=None): + """Cycle through the items from *iterable* up to *n* times, yielding + the number of completed cycles along with each item. If *n* is omitted the + process repeats indefinitely. + + >>> list(count_cycle('AB', 3)) + [(0, 'A'), (0, 'B'), (1, 'A'), (1, 'B'), (2, 'A'), (2, 'B')] + + """ + iterable = tuple(iterable) + if not iterable: + return iter(()) + counter = count() if n is None else range(n) + return ((i, item) for i in counter for item in iterable) + + +def mark_ends(iterable): + """Yield 3-tuples of the form ``(is_first, is_last, item)``. + + >>> list(mark_ends('ABC')) + [(True, False, 'A'), (False, False, 'B'), (False, True, 'C')] + + Use this when looping over an iterable to take special action on its first + and/or last items: + + >>> iterable = ['Header', 100, 200, 'Footer'] + >>> total = 0 + >>> for is_first, is_last, item in mark_ends(iterable): + ... if is_first: + ... continue # Skip the header + ... if is_last: + ... continue # Skip the footer + ... total += item + >>> print(total) + 300 + """ + it = iter(iterable) + + try: + b = next(it) + except StopIteration: + return + + try: + for i in count(): + a = b + b = next(it) + yield i == 0, False, a + + except StopIteration: + yield i == 0, True, a + + +def locate(iterable, pred=bool, window_size=None): + """Yield the index of each item in *iterable* for which *pred* returns + ``True``. + + *pred* defaults to :func:`bool`, which will select truthy items: + + >>> list(locate([0, 1, 1, 0, 1, 0, 0])) + [1, 2, 4] + + Set *pred* to a custom function to, e.g., find the indexes for a particular + item. + + >>> list(locate(['a', 'b', 'c', 'b'], lambda x: x == 'b')) + [1, 3] + + If *window_size* is given, then the *pred* function will be called with + that many items. This enables searching for sub-sequences: + + >>> iterable = [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3] + >>> pred = lambda *args: args == (1, 2, 3) + >>> list(locate(iterable, pred=pred, window_size=3)) + [1, 5, 9] + + Use with :func:`seekable` to find indexes and then retrieve the associated + items: + + >>> from itertools import count + >>> from more_itertools import seekable + >>> source = (3 * n + 1 if (n % 2) else n // 2 for n in count()) + >>> it = seekable(source) + >>> pred = lambda x: x > 100 + >>> indexes = locate(it, pred=pred) + >>> i = next(indexes) + >>> it.seek(i) + >>> next(it) + 106 + + """ + if window_size is None: + return compress(count(), map(pred, iterable)) + + if window_size < 1: + raise ValueError('window size must be at least 1') + + it = windowed(iterable, window_size, fillvalue=_marker) + return compress(count(), starmap(pred, it)) + + +def lstrip(iterable, pred): + """Yield the items from *iterable*, but strip any from the beginning + for which *pred* returns ``True``. + + For example, to remove a set of items from the start of an iterable: + + >>> iterable = (None, False, None, 1, 2, None, 3, False, None) + >>> pred = lambda x: x in {None, False, ''} + >>> list(lstrip(iterable, pred)) + [1, 2, None, 3, False, None] + + This function is analogous to to :func:`str.lstrip`, and is essentially + an wrapper for :func:`itertools.dropwhile`. + + """ + return dropwhile(pred, iterable) + + +def rstrip(iterable, pred): + """Yield the items from *iterable*, but strip any from the end + for which *pred* returns ``True``. + + For example, to remove a set of items from the end of an iterable: + + >>> iterable = (None, False, None, 1, 2, None, 3, False, None) + >>> pred = lambda x: x in {None, False, ''} + >>> list(rstrip(iterable, pred)) + [None, False, None, 1, 2, None, 3] + + This function is analogous to :func:`str.rstrip`. + + """ + cache = [] + cache_append = cache.append + cache_clear = cache.clear + for x in iterable: + if pred(x): + cache_append(x) + else: + yield from cache + cache_clear() + yield x + + +def strip(iterable, pred): + """Yield the items from *iterable*, but strip any from the + beginning and end for which *pred* returns ``True``. + + For example, to remove a set of items from both ends of an iterable: + + >>> iterable = (None, False, None, 1, 2, None, 3, False, None) + >>> pred = lambda x: x in {None, False, ''} + >>> list(strip(iterable, pred)) + [1, 2, None, 3] + + This function is analogous to :func:`str.strip`. + + """ + return rstrip(lstrip(iterable, pred), pred) + + +class islice_extended: + """An extension of :func:`itertools.islice` that supports negative values + for *stop*, *start*, and *step*. + + >>> iterable = iter('abcdefgh') + >>> list(islice_extended(iterable, -4, -1)) + ['e', 'f', 'g'] + + Slices with negative values require some caching of *iterable*, but this + function takes care to minimize the amount of memory required. + + For example, you can use a negative step with an infinite iterator: + + >>> from itertools import count + >>> list(islice_extended(count(), 110, 99, -2)) + [110, 108, 106, 104, 102, 100] + + You can also use slice notation directly: + + >>> iterable = map(str, count()) + >>> it = islice_extended(iterable)[10:20:2] + >>> list(it) + ['10', '12', '14', '16', '18'] + + """ + + def __init__(self, iterable, *args): + it = iter(iterable) + if args: + self._iterable = _islice_helper(it, slice(*args)) + else: + self._iterable = it + + def __iter__(self): + return self + + def __next__(self): + return next(self._iterable) + + def __getitem__(self, key): + if isinstance(key, slice): + return islice_extended(_islice_helper(self._iterable, key)) + + raise TypeError('islice_extended.__getitem__ argument must be a slice') + + +def _islice_helper(it, s): + start = s.start + stop = s.stop + if s.step == 0: + raise ValueError('step argument must be a non-zero integer or None.') + step = s.step or 1 + + if step > 0: + start = 0 if (start is None) else start + + if start < 0: + # Consume all but the last -start items + cache = deque(enumerate(it, 1), maxlen=-start) + len_iter = cache[-1][0] if cache else 0 + + # Adjust start to be positive + i = max(len_iter + start, 0) + + # Adjust stop to be positive + if stop is None: + j = len_iter + elif stop >= 0: + j = min(stop, len_iter) + else: + j = max(len_iter + stop, 0) + + # Slice the cache + n = j - i + if n <= 0: + return + + for index, item in islice(cache, 0, n, step): + yield item + elif (stop is not None) and (stop < 0): + # Advance to the start position + next(islice(it, start, start), None) + + # When stop is negative, we have to carry -stop items while + # iterating + cache = deque(islice(it, -stop), maxlen=-stop) + + for index, item in enumerate(it): + cached_item = cache.popleft() + if index % step == 0: + yield cached_item + cache.append(item) + else: + # When both start and stop are positive we have the normal case + yield from islice(it, start, stop, step) + else: + start = -1 if (start is None) else start + + if (stop is not None) and (stop < 0): + # Consume all but the last items + n = -stop - 1 + cache = deque(enumerate(it, 1), maxlen=n) + len_iter = cache[-1][0] if cache else 0 + + # If start and stop are both negative they are comparable and + # we can just slice. Otherwise we can adjust start to be negative + # and then slice. + if start < 0: + i, j = start, stop + else: + i, j = min(start - len_iter, -1), None + + for index, item in list(cache)[i:j:step]: + yield item + else: + # Advance to the stop position + if stop is not None: + m = stop + 1 + next(islice(it, m, m), None) + + # stop is positive, so if start is negative they are not comparable + # and we need the rest of the items. + if start < 0: + i = start + n = None + # stop is None and start is positive, so we just need items up to + # the start index. + elif stop is None: + i = None + n = start + 1 + # Both stop and start are positive, so they are comparable. + else: + i = None + n = start - stop + if n <= 0: + return + + cache = list(islice(it, n)) + + yield from cache[i::step] + + +def always_reversible(iterable): + """An extension of :func:`reversed` that supports all iterables, not + just those which implement the ``Reversible`` or ``Sequence`` protocols. + + >>> print(*always_reversible(x for x in range(3))) + 2 1 0 + + If the iterable is already reversible, this function returns the + result of :func:`reversed()`. If the iterable is not reversible, + this function will cache the remaining items in the iterable and + yield them in reverse order, which may require significant storage. + """ + try: + return reversed(iterable) + except TypeError: + return reversed(list(iterable)) + + +def consecutive_groups(iterable, ordering=lambda x: x): + """Yield groups of consecutive items using :func:`itertools.groupby`. + The *ordering* function determines whether two items are adjacent by + returning their position. + + By default, the ordering function is the identity function. This is + suitable for finding runs of numbers: + + >>> iterable = [1, 10, 11, 12, 20, 30, 31, 32, 33, 40] + >>> for group in consecutive_groups(iterable): + ... print(list(group)) + [1] + [10, 11, 12] + [20] + [30, 31, 32, 33] + [40] + + For finding runs of adjacent letters, try using the :meth:`index` method + of a string of letters: + + >>> from string import ascii_lowercase + >>> iterable = 'abcdfgilmnop' + >>> ordering = ascii_lowercase.index + >>> for group in consecutive_groups(iterable, ordering): + ... print(list(group)) + ['a', 'b', 'c', 'd'] + ['f', 'g'] + ['i'] + ['l', 'm', 'n', 'o', 'p'] + + Each group of consecutive items is an iterator that shares it source with + *iterable*. When an an output group is advanced, the previous group is + no longer available unless its elements are copied (e.g., into a ``list``). + + >>> iterable = [1, 2, 11, 12, 21, 22] + >>> saved_groups = [] + >>> for group in consecutive_groups(iterable): + ... saved_groups.append(list(group)) # Copy group elements + >>> saved_groups + [[1, 2], [11, 12], [21, 22]] + + """ + for k, g in groupby( + enumerate(iterable), key=lambda x: x[0] - ordering(x[1]) + ): + yield map(itemgetter(1), g) + + +def difference(iterable, func=sub, *, initial=None): + """This function is the inverse of :func:`itertools.accumulate`. By default + it will compute the first difference of *iterable* using + :func:`operator.sub`: + + >>> from itertools import accumulate + >>> iterable = accumulate([0, 1, 2, 3, 4]) # produces 0, 1, 3, 6, 10 + >>> list(difference(iterable)) + [0, 1, 2, 3, 4] + + *func* defaults to :func:`operator.sub`, but other functions can be + specified. They will be applied as follows:: + + A, B, C, D, ... --> A, func(B, A), func(C, B), func(D, C), ... + + For example, to do progressive division: + + >>> iterable = [1, 2, 6, 24, 120] + >>> func = lambda x, y: x // y + >>> list(difference(iterable, func)) + [1, 2, 3, 4, 5] + + If the *initial* keyword is set, the first element will be skipped when + computing successive differences. + + >>> it = [10, 11, 13, 16] # from accumulate([1, 2, 3], initial=10) + >>> list(difference(it, initial=10)) + [1, 2, 3] + + """ + a, b = tee(iterable) + try: + first = [next(b)] + except StopIteration: + return iter([]) + + if initial is not None: + first = [] + + return chain(first, starmap(func, zip(b, a))) + + +class SequenceView(Sequence): + """Return a read-only view of the sequence object *target*. + + :class:`SequenceView` objects are analogous to Python's built-in + "dictionary view" types. They provide a dynamic view of a sequence's items, + meaning that when the sequence updates, so does the view. + + >>> seq = ['0', '1', '2'] + >>> view = SequenceView(seq) + >>> view + SequenceView(['0', '1', '2']) + >>> seq.append('3') + >>> view + SequenceView(['0', '1', '2', '3']) + + Sequence views support indexing, slicing, and length queries. They act + like the underlying sequence, except they don't allow assignment: + + >>> view[1] + '1' + >>> view[1:-1] + ['1', '2'] + >>> len(view) + 4 + + Sequence views are useful as an alternative to copying, as they don't + require (much) extra storage. + + """ + + def __init__(self, target): + if not isinstance(target, Sequence): + raise TypeError + self._target = target + + def __getitem__(self, index): + return self._target[index] + + def __len__(self): + return len(self._target) + + def __repr__(self): + return '{}({})'.format(self.__class__.__name__, repr(self._target)) + + +class seekable: + """Wrap an iterator to allow for seeking backward and forward. This + progressively caches the items in the source iterable so they can be + re-visited. + + Call :meth:`seek` with an index to seek to that position in the source + iterable. + + To "reset" an iterator, seek to ``0``: + + >>> from itertools import count + >>> it = seekable((str(n) for n in count())) + >>> next(it), next(it), next(it) + ('0', '1', '2') + >>> it.seek(0) + >>> next(it), next(it), next(it) + ('0', '1', '2') + >>> next(it) + '3' + + You can also seek forward: + + >>> it = seekable((str(n) for n in range(20))) + >>> it.seek(10) + >>> next(it) + '10' + >>> it.seek(20) # Seeking past the end of the source isn't a problem + >>> list(it) + [] + >>> it.seek(0) # Resetting works even after hitting the end + >>> next(it), next(it), next(it) + ('0', '1', '2') + + Call :meth:`peek` to look ahead one item without advancing the iterator: + + >>> it = seekable('1234') + >>> it.peek() + '1' + >>> list(it) + ['1', '2', '3', '4'] + >>> it.peek(default='empty') + 'empty' + + Before the iterator is at its end, calling :func:`bool` on it will return + ``True``. After it will return ``False``: + + >>> it = seekable('5678') + >>> bool(it) + True + >>> list(it) + ['5', '6', '7', '8'] + >>> bool(it) + False + + You may view the contents of the cache with the :meth:`elements` method. + That returns a :class:`SequenceView`, a view that updates automatically: + + >>> it = seekable((str(n) for n in range(10))) + >>> next(it), next(it), next(it) + ('0', '1', '2') + >>> elements = it.elements() + >>> elements + SequenceView(['0', '1', '2']) + >>> next(it) + '3' + >>> elements + SequenceView(['0', '1', '2', '3']) + + By default, the cache grows as the source iterable progresses, so beware of + wrapping very large or infinite iterables. Supply *maxlen* to limit the + size of the cache (this of course limits how far back you can seek). + + >>> from itertools import count + >>> it = seekable((str(n) for n in count()), maxlen=2) + >>> next(it), next(it), next(it), next(it) + ('0', '1', '2', '3') + >>> list(it.elements()) + ['2', '3'] + >>> it.seek(0) + >>> next(it), next(it), next(it), next(it) + ('2', '3', '4', '5') + >>> next(it) + '6' + + """ + + def __init__(self, iterable, maxlen=None): + self._source = iter(iterable) + if maxlen is None: + self._cache = [] + else: + self._cache = deque([], maxlen) + self._index = None + + def __iter__(self): + return self + + def __next__(self): + if self._index is not None: + try: + item = self._cache[self._index] + except IndexError: + self._index = None + else: + self._index += 1 + return item + + item = next(self._source) + self._cache.append(item) + return item + + def __bool__(self): + try: + self.peek() + except StopIteration: + return False + return True + + def peek(self, default=_marker): + try: + peeked = next(self) + except StopIteration: + if default is _marker: + raise + return default + if self._index is None: + self._index = len(self._cache) + self._index -= 1 + return peeked + + def elements(self): + return SequenceView(self._cache) + + def seek(self, index): + self._index = index + remainder = index - len(self._cache) + if remainder > 0: + consume(self, remainder) + + +class run_length: + """ + :func:`run_length.encode` compresses an iterable with run-length encoding. + It yields groups of repeated items with the count of how many times they + were repeated: + + >>> uncompressed = 'abbcccdddd' + >>> list(run_length.encode(uncompressed)) + [('a', 1), ('b', 2), ('c', 3), ('d', 4)] + + :func:`run_length.decode` decompresses an iterable that was previously + compressed with run-length encoding. It yields the items of the + decompressed iterable: + + >>> compressed = [('a', 1), ('b', 2), ('c', 3), ('d', 4)] + >>> list(run_length.decode(compressed)) + ['a', 'b', 'b', 'c', 'c', 'c', 'd', 'd', 'd', 'd'] + + """ + + @staticmethod + def encode(iterable): + return ((k, ilen(g)) for k, g in groupby(iterable)) + + @staticmethod + def decode(iterable): + return chain.from_iterable(repeat(k, n) for k, n in iterable) + + +def exactly_n(iterable, n, predicate=bool): + """Return ``True`` if exactly ``n`` items in the iterable are ``True`` + according to the *predicate* function. + + >>> exactly_n([True, True, False], 2) + True + >>> exactly_n([True, True, False], 1) + False + >>> exactly_n([0, 1, 2, 3, 4, 5], 3, lambda x: x < 3) + True + + The iterable will be advanced until ``n + 1`` truthy items are encountered, + so avoid calling it on infinite iterables. + + """ + return len(take(n + 1, filter(predicate, iterable))) == n + + +def circular_shifts(iterable): + """Return a list of circular shifts of *iterable*. + + >>> circular_shifts(range(4)) + [(0, 1, 2, 3), (1, 2, 3, 0), (2, 3, 0, 1), (3, 0, 1, 2)] + """ + lst = list(iterable) + return take(len(lst), windowed(cycle(lst), len(lst))) + + +def make_decorator(wrapping_func, result_index=0): + """Return a decorator version of *wrapping_func*, which is a function that + modifies an iterable. *result_index* is the position in that function's + signature where the iterable goes. + + This lets you use itertools on the "production end," i.e. at function + definition. This can augment what the function returns without changing the + function's code. + + For example, to produce a decorator version of :func:`chunked`: + + >>> from more_itertools import chunked + >>> chunker = make_decorator(chunked, result_index=0) + >>> @chunker(3) + ... def iter_range(n): + ... return iter(range(n)) + ... + >>> list(iter_range(9)) + [[0, 1, 2], [3, 4, 5], [6, 7, 8]] + + To only allow truthy items to be returned: + + >>> truth_serum = make_decorator(filter, result_index=1) + >>> @truth_serum(bool) + ... def boolean_test(): + ... return [0, 1, '', ' ', False, True] + ... + >>> list(boolean_test()) + [1, ' ', True] + + The :func:`peekable` and :func:`seekable` wrappers make for practical + decorators: + + >>> from more_itertools import peekable + >>> peekable_function = make_decorator(peekable) + >>> @peekable_function() + ... def str_range(*args): + ... return (str(x) for x in range(*args)) + ... + >>> it = str_range(1, 20, 2) + >>> next(it), next(it), next(it) + ('1', '3', '5') + >>> it.peek() + '7' + >>> next(it) + '7' + + """ + # See https://sites.google.com/site/bbayles/index/decorator_factory for + # notes on how this works. + def decorator(*wrapping_args, **wrapping_kwargs): + def outer_wrapper(f): + def inner_wrapper(*args, **kwargs): + result = f(*args, **kwargs) + wrapping_args_ = list(wrapping_args) + wrapping_args_.insert(result_index, result) + return wrapping_func(*wrapping_args_, **wrapping_kwargs) + + return inner_wrapper + + return outer_wrapper + + return decorator + + +def map_reduce(iterable, keyfunc, valuefunc=None, reducefunc=None): + """Return a dictionary that maps the items in *iterable* to categories + defined by *keyfunc*, transforms them with *valuefunc*, and + then summarizes them by category with *reducefunc*. + + *valuefunc* defaults to the identity function if it is unspecified. + If *reducefunc* is unspecified, no summarization takes place: + + >>> keyfunc = lambda x: x.upper() + >>> result = map_reduce('abbccc', keyfunc) + >>> sorted(result.items()) + [('A', ['a']), ('B', ['b', 'b']), ('C', ['c', 'c', 'c'])] + + Specifying *valuefunc* transforms the categorized items: + + >>> keyfunc = lambda x: x.upper() + >>> valuefunc = lambda x: 1 + >>> result = map_reduce('abbccc', keyfunc, valuefunc) + >>> sorted(result.items()) + [('A', [1]), ('B', [1, 1]), ('C', [1, 1, 1])] + + Specifying *reducefunc* summarizes the categorized items: + + >>> keyfunc = lambda x: x.upper() + >>> valuefunc = lambda x: 1 + >>> reducefunc = sum + >>> result = map_reduce('abbccc', keyfunc, valuefunc, reducefunc) + >>> sorted(result.items()) + [('A', 1), ('B', 2), ('C', 3)] + + You may want to filter the input iterable before applying the map/reduce + procedure: + + >>> all_items = range(30) + >>> items = [x for x in all_items if 10 <= x <= 20] # Filter + >>> keyfunc = lambda x: x % 2 # Evens map to 0; odds to 1 + >>> categories = map_reduce(items, keyfunc=keyfunc) + >>> sorted(categories.items()) + [(0, [10, 12, 14, 16, 18, 20]), (1, [11, 13, 15, 17, 19])] + >>> summaries = map_reduce(items, keyfunc=keyfunc, reducefunc=sum) + >>> sorted(summaries.items()) + [(0, 90), (1, 75)] + + Note that all items in the iterable are gathered into a list before the + summarization step, which may require significant storage. + + The returned object is a :obj:`collections.defaultdict` with the + ``default_factory`` set to ``None``, such that it behaves like a normal + dictionary. + + """ + valuefunc = (lambda x: x) if (valuefunc is None) else valuefunc + + ret = defaultdict(list) + for item in iterable: + key = keyfunc(item) + value = valuefunc(item) + ret[key].append(value) + + if reducefunc is not None: + for key, value_list in ret.items(): + ret[key] = reducefunc(value_list) + + ret.default_factory = None + return ret + + +def rlocate(iterable, pred=bool, window_size=None): + """Yield the index of each item in *iterable* for which *pred* returns + ``True``, starting from the right and moving left. + + *pred* defaults to :func:`bool`, which will select truthy items: + + >>> list(rlocate([0, 1, 1, 0, 1, 0, 0])) # Truthy at 1, 2, and 4 + [4, 2, 1] + + Set *pred* to a custom function to, e.g., find the indexes for a particular + item: + + >>> iterable = iter('abcb') + >>> pred = lambda x: x == 'b' + >>> list(rlocate(iterable, pred)) + [3, 1] + + If *window_size* is given, then the *pred* function will be called with + that many items. This enables searching for sub-sequences: + + >>> iterable = [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3] + >>> pred = lambda *args: args == (1, 2, 3) + >>> list(rlocate(iterable, pred=pred, window_size=3)) + [9, 5, 1] + + Beware, this function won't return anything for infinite iterables. + If *iterable* is reversible, ``rlocate`` will reverse it and search from + the right. Otherwise, it will search from the left and return the results + in reverse order. + + See :func:`locate` to for other example applications. + + """ + if window_size is None: + try: + len_iter = len(iterable) + return (len_iter - i - 1 for i in locate(reversed(iterable), pred)) + except TypeError: + pass + + return reversed(list(locate(iterable, pred, window_size))) + + +def replace(iterable, pred, substitutes, count=None, window_size=1): + """Yield the items from *iterable*, replacing the items for which *pred* + returns ``True`` with the items from the iterable *substitutes*. + + >>> iterable = [1, 1, 0, 1, 1, 0, 1, 1] + >>> pred = lambda x: x == 0 + >>> substitutes = (2, 3) + >>> list(replace(iterable, pred, substitutes)) + [1, 1, 2, 3, 1, 1, 2, 3, 1, 1] + + If *count* is given, the number of replacements will be limited: + + >>> iterable = [1, 1, 0, 1, 1, 0, 1, 1, 0] + >>> pred = lambda x: x == 0 + >>> substitutes = [None] + >>> list(replace(iterable, pred, substitutes, count=2)) + [1, 1, None, 1, 1, None, 1, 1, 0] + + Use *window_size* to control the number of items passed as arguments to + *pred*. This allows for locating and replacing subsequences. + + >>> iterable = [0, 1, 2, 5, 0, 1, 2, 5] + >>> window_size = 3 + >>> pred = lambda *args: args == (0, 1, 2) # 3 items passed to pred + >>> substitutes = [3, 4] # Splice in these items + >>> list(replace(iterable, pred, substitutes, window_size=window_size)) + [3, 4, 5, 3, 4, 5] + + """ + if window_size < 1: + raise ValueError('window_size must be at least 1') + + # Save the substitutes iterable, since it's used more than once + substitutes = tuple(substitutes) + + # Add padding such that the number of windows matches the length of the + # iterable + it = chain(iterable, [_marker] * (window_size - 1)) + windows = windowed(it, window_size) + + n = 0 + for w in windows: + # If the current window matches our predicate (and we haven't hit + # our maximum number of replacements), splice in the substitutes + # and then consume the following windows that overlap with this one. + # For example, if the iterable is (0, 1, 2, 3, 4...) + # and the window size is 2, we have (0, 1), (1, 2), (2, 3)... + # If the predicate matches on (0, 1), we need to zap (0, 1) and (1, 2) + if pred(*w): + if (count is None) or (n < count): + n += 1 + yield from substitutes + consume(windows, window_size - 1) + continue + + # If there was no match (or we've reached the replacement limit), + # yield the first item from the window. + if w and (w[0] is not _marker): + yield w[0] + + +def partitions(iterable): + """Yield all possible order-preserving partitions of *iterable*. + + >>> iterable = 'abc' + >>> for part in partitions(iterable): + ... print([''.join(p) for p in part]) + ['abc'] + ['a', 'bc'] + ['ab', 'c'] + ['a', 'b', 'c'] + + This is unrelated to :func:`partition`. + + """ + sequence = list(iterable) + n = len(sequence) + for i in powerset(range(1, n)): + yield [sequence[i:j] for i, j in zip((0,) + i, i + (n,))] + + +def set_partitions(iterable, k=None): + """ + Yield the set partitions of *iterable* into *k* parts. Set partitions are + not order-preserving. + + >>> iterable = 'abc' + >>> for part in set_partitions(iterable, 2): + ... print([''.join(p) for p in part]) + ['a', 'bc'] + ['ab', 'c'] + ['b', 'ac'] + + + If *k* is not given, every set partition is generated. + + >>> iterable = 'abc' + >>> for part in set_partitions(iterable): + ... print([''.join(p) for p in part]) + ['abc'] + ['a', 'bc'] + ['ab', 'c'] + ['b', 'ac'] + ['a', 'b', 'c'] + + """ + L = list(iterable) + n = len(L) + if k is not None: + if k < 1: + raise ValueError( + "Can't partition in a negative or zero number of groups" + ) + elif k > n: + return + + def set_partitions_helper(L, k): + n = len(L) + if k == 1: + yield [L] + elif n == k: + yield [[s] for s in L] + else: + e, *M = L + for p in set_partitions_helper(M, k - 1): + yield [[e], *p] + for p in set_partitions_helper(M, k): + for i in range(len(p)): + yield p[:i] + [[e] + p[i]] + p[i + 1 :] + + if k is None: + for k in range(1, n + 1): + yield from set_partitions_helper(L, k) + else: + yield from set_partitions_helper(L, k) + + +class time_limited: + """ + Yield items from *iterable* until *limit_seconds* have passed. + If the time limit expires before all items have been yielded, the + ``timed_out`` parameter will be set to ``True``. + + >>> from time import sleep + >>> def generator(): + ... yield 1 + ... yield 2 + ... sleep(0.2) + ... yield 3 + >>> iterable = time_limited(0.1, generator()) + >>> list(iterable) + [1, 2] + >>> iterable.timed_out + True + + Note that the time is checked before each item is yielded, and iteration + stops if the time elapsed is greater than *limit_seconds*. If your time + limit is 1 second, but it takes 2 seconds to generate the first item from + the iterable, the function will run for 2 seconds and not yield anything. + + """ + + def __init__(self, limit_seconds, iterable): + if limit_seconds < 0: + raise ValueError('limit_seconds must be positive') + self.limit_seconds = limit_seconds + self._iterable = iter(iterable) + self._start_time = monotonic() + self.timed_out = False + + def __iter__(self): + return self + + def __next__(self): + item = next(self._iterable) + if monotonic() - self._start_time > self.limit_seconds: + self.timed_out = True + raise StopIteration + + return item + + +def only(iterable, default=None, too_long=None): + """If *iterable* has only one item, return it. + If it has zero items, return *default*. + If it has more than one item, raise the exception given by *too_long*, + which is ``ValueError`` by default. + + >>> only([], default='missing') + 'missing' + >>> only([1]) + 1 + >>> only([1, 2]) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + ValueError: Expected exactly one item in iterable, but got 1, 2, + and perhaps more.' + >>> only([1, 2], too_long=TypeError) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + TypeError + + Note that :func:`only` attempts to advance *iterable* twice to ensure there + is only one item. See :func:`spy` or :func:`peekable` to check + iterable contents less destructively. + """ + it = iter(iterable) + first_value = next(it, default) + + try: + second_value = next(it) + except StopIteration: + pass + else: + msg = ( + 'Expected exactly one item in iterable, but got {!r}, {!r}, ' + 'and perhaps more.'.format(first_value, second_value) + ) + raise too_long or ValueError(msg) + + return first_value + + +def ichunked(iterable, n): + """Break *iterable* into sub-iterables with *n* elements each. + :func:`ichunked` is like :func:`chunked`, but it yields iterables + instead of lists. + + If the sub-iterables are read in order, the elements of *iterable* + won't be stored in memory. + If they are read out of order, :func:`itertools.tee` is used to cache + elements as necessary. + + >>> from itertools import count + >>> all_chunks = ichunked(count(), 4) + >>> c_1, c_2, c_3 = next(all_chunks), next(all_chunks), next(all_chunks) + >>> list(c_2) # c_1's elements have been cached; c_3's haven't been + [4, 5, 6, 7] + >>> list(c_1) + [0, 1, 2, 3] + >>> list(c_3) + [8, 9, 10, 11] + + """ + source = iter(iterable) + + while True: + # Check to see whether we're at the end of the source iterable + item = next(source, _marker) + if item is _marker: + return + + # Clone the source and yield an n-length slice + source, it = tee(chain([item], source)) + yield islice(it, n) + + # Advance the source iterable + consume(source, n) + + +def distinct_combinations(iterable, r): + """Yield the distinct combinations of *r* items taken from *iterable*. + + >>> list(distinct_combinations([0, 0, 1], 2)) + [(0, 0), (0, 1)] + + Equivalent to ``set(combinations(iterable))``, except duplicates are not + generated and thrown away. For larger input sequences this is much more + efficient. + + """ + if r < 0: + raise ValueError('r must be non-negative') + elif r == 0: + yield () + return + pool = tuple(iterable) + generators = [unique_everseen(enumerate(pool), key=itemgetter(1))] + current_combo = [None] * r + level = 0 + while generators: + try: + cur_idx, p = next(generators[-1]) + except StopIteration: + generators.pop() + level -= 1 + continue + current_combo[level] = p + if level + 1 == r: + yield tuple(current_combo) + else: + generators.append( + unique_everseen( + enumerate(pool[cur_idx + 1 :], cur_idx + 1), + key=itemgetter(1), + ) + ) + level += 1 + + +def filter_except(validator, iterable, *exceptions): + """Yield the items from *iterable* for which the *validator* function does + not raise one of the specified *exceptions*. + + *validator* is called for each item in *iterable*. + It should be a function that accepts one argument and raises an exception + if that item is not valid. + + >>> iterable = ['1', '2', 'three', '4', None] + >>> list(filter_except(int, iterable, ValueError, TypeError)) + ['1', '2', '4'] + + If an exception other than one given by *exceptions* is raised by + *validator*, it is raised like normal. + """ + for item in iterable: + try: + validator(item) + except exceptions: + pass + else: + yield item + + +def map_except(function, iterable, *exceptions): + """Transform each item from *iterable* with *function* and yield the + result, unless *function* raises one of the specified *exceptions*. + + *function* is called to transform each item in *iterable*. + It should be a accept one argument. + + >>> iterable = ['1', '2', 'three', '4', None] + >>> list(map_except(int, iterable, ValueError, TypeError)) + [1, 2, 4] + + If an exception other than one given by *exceptions* is raised by + *function*, it is raised like normal. + """ + for item in iterable: + try: + yield function(item) + except exceptions: + pass + + +def _sample_unweighted(iterable, k): + # Implementation of "Algorithm L" from the 1994 paper by Kim-Hung Li: + # "Reservoir-Sampling Algorithms of Time Complexity O(n(1+log(N/n)))". + + # Fill up the reservoir (collection of samples) with the first `k` samples + reservoir = take(k, iterable) + + # Generate random number that's the largest in a sample of k U(0,1) numbers + # Largest order statistic: https://en.wikipedia.org/wiki/Order_statistic + W = exp(log(random()) / k) + + # The number of elements to skip before changing the reservoir is a random + # number with a geometric distribution. Sample it using random() and logs. + next_index = k + floor(log(random()) / log(1 - W)) + + for index, element in enumerate(iterable, k): + + if index == next_index: + reservoir[randrange(k)] = element + # The new W is the largest in a sample of k U(0, `old_W`) numbers + W *= exp(log(random()) / k) + next_index += floor(log(random()) / log(1 - W)) + 1 + + return reservoir + + +def _sample_weighted(iterable, k, weights): + # Implementation of "A-ExpJ" from the 2006 paper by Efraimidis et al. : + # "Weighted random sampling with a reservoir". + + # Log-transform for numerical stability for weights that are small/large + weight_keys = (log(random()) / weight for weight in weights) + + # Fill up the reservoir (collection of samples) with the first `k` + # weight-keys and elements, then heapify the list. + reservoir = take(k, zip(weight_keys, iterable)) + heapify(reservoir) + + # The number of jumps before changing the reservoir is a random variable + # with an exponential distribution. Sample it using random() and logs. + smallest_weight_key, _ = reservoir[0] + weights_to_skip = log(random()) / smallest_weight_key + + for weight, element in zip(weights, iterable): + if weight >= weights_to_skip: + # The notation here is consistent with the paper, but we store + # the weight-keys in log-space for better numerical stability. + smallest_weight_key, _ = reservoir[0] + t_w = exp(weight * smallest_weight_key) + r_2 = uniform(t_w, 1) # generate U(t_w, 1) + weight_key = log(r_2) / weight + heapreplace(reservoir, (weight_key, element)) + smallest_weight_key, _ = reservoir[0] + weights_to_skip = log(random()) / smallest_weight_key + else: + weights_to_skip -= weight + + # Equivalent to [element for weight_key, element in sorted(reservoir)] + return [heappop(reservoir)[1] for _ in range(k)] + + +def sample(iterable, k, weights=None): + """Return a *k*-length list of elements chosen (without replacement) + from the *iterable*. Like :func:`random.sample`, but works on iterables + of unknown length. + + >>> iterable = range(100) + >>> sample(iterable, 5) # doctest: +SKIP + [81, 60, 96, 16, 4] + + An iterable with *weights* may also be given: + + >>> iterable = range(100) + >>> weights = (i * i + 1 for i in range(100)) + >>> sampled = sample(iterable, 5, weights=weights) # doctest: +SKIP + [79, 67, 74, 66, 78] + + The algorithm can also be used to generate weighted random permutations. + The relative weight of each item determines the probability that it + appears late in the permutation. + + >>> data = "abcdefgh" + >>> weights = range(1, len(data) + 1) + >>> sample(data, k=len(data), weights=weights) # doctest: +SKIP + ['c', 'a', 'b', 'e', 'g', 'd', 'h', 'f'] + """ + if k == 0: + return [] + + iterable = iter(iterable) + if weights is None: + return _sample_unweighted(iterable, k) + else: + weights = iter(weights) + return _sample_weighted(iterable, k, weights) + + +def is_sorted(iterable, key=None, reverse=False): + """Returns ``True`` if the items of iterable are in sorted order, and + ``False`` otherwise. *key* and *reverse* have the same meaning that they do + in the built-in :func:`sorted` function. + + >>> is_sorted(['1', '2', '3', '4', '5'], key=int) + True + >>> is_sorted([5, 4, 3, 1, 2], reverse=True) + False + + The function returns ``False`` after encountering the first out-of-order + item. If there are no out-of-order items, the iterable is exhausted. + """ + + compare = lt if reverse else gt + it = iterable if (key is None) else map(key, iterable) + return not any(starmap(compare, pairwise(it))) + + +class AbortThread(BaseException): + pass + + +class callback_iter: + """Convert a function that uses callbacks to an iterator. + + Let *func* be a function that takes a `callback` keyword argument. + For example: + + >>> def func(callback=None): + ... for i, c in [(1, 'a'), (2, 'b'), (3, 'c')]: + ... if callback: + ... callback(i, c) + ... return 4 + + + Use ``with callback_iter(func)`` to get an iterator over the parameters + that are delivered to the callback. + + >>> with callback_iter(func) as it: + ... for args, kwargs in it: + ... print(args) + (1, 'a') + (2, 'b') + (3, 'c') + + The function will be called in a background thread. The ``done`` property + indicates whether it has completed execution. + + >>> it.done + True + + If it completes successfully, its return value will be available + in the ``result`` property. + + >>> it.result + 4 + + Notes: + + * If the function uses some keyword argument besides ``callback``, supply + *callback_kwd*. + * If it finished executing, but raised an exception, accessing the + ``result`` property will raise the same exception. + * If it hasn't finished executing, accessing the ``result`` + property from within the ``with`` block will raise ``RuntimeError``. + * If it hasn't finished executing, accessing the ``result`` property from + outside the ``with`` block will raise a + ``more_itertools.AbortThread`` exception. + * Provide *wait_seconds* to adjust how frequently the it is polled for + output. + + """ + + def __init__(self, func, callback_kwd='callback', wait_seconds=0.1): + self._func = func + self._callback_kwd = callback_kwd + self._aborted = False + self._future = None + self._wait_seconds = wait_seconds + self._executor = ThreadPoolExecutor(max_workers=1) + self._iterator = self._reader() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self._aborted = True + self._executor.shutdown() + + def __iter__(self): + return self + + def __next__(self): + return next(self._iterator) + + @property + def done(self): + if self._future is None: + return False + return self._future.done() + + @property + def result(self): + if not self.done: + raise RuntimeError('Function has not yet completed') + + return self._future.result() + + def _reader(self): + q = Queue() + + def callback(*args, **kwargs): + if self._aborted: + raise AbortThread('canceled by user') + + q.put((args, kwargs)) + + self._future = self._executor.submit( + self._func, **{self._callback_kwd: callback} + ) + + while True: + try: + item = q.get(timeout=self._wait_seconds) + except Empty: + pass + else: + q.task_done() + yield item + + if self._future.done(): + break + + remaining = [] + while True: + try: + item = q.get_nowait() + except Empty: + break + else: + q.task_done() + remaining.append(item) + q.join() + yield from remaining + + +def windowed_complete(iterable, n): + """ + Yield ``(beginning, middle, end)`` tuples, where: + + * Each ``middle`` has *n* items from *iterable* + * Each ``beginning`` has the items before the ones in ``middle`` + * Each ``end`` has the items after the ones in ``middle`` + + >>> iterable = range(7) + >>> n = 3 + >>> for beginning, middle, end in windowed_complete(iterable, n): + ... print(beginning, middle, end) + () (0, 1, 2) (3, 4, 5, 6) + (0,) (1, 2, 3) (4, 5, 6) + (0, 1) (2, 3, 4) (5, 6) + (0, 1, 2) (3, 4, 5) (6,) + (0, 1, 2, 3) (4, 5, 6) () + + Note that *n* must be at least 0 and most equal to the length of + *iterable*. + + This function will exhaust the iterable and may require significant + storage. + """ + if n < 0: + raise ValueError('n must be >= 0') + + seq = tuple(iterable) + size = len(seq) + + if n > size: + raise ValueError('n must be <= len(seq)') + + for i in range(size - n + 1): + beginning = seq[:i] + middle = seq[i : i + n] + end = seq[i + n :] + yield beginning, middle, end + + +def all_unique(iterable, key=None): + """ + Returns ``True`` if all the elements of *iterable* are unique (no two + elements are equal). + + >>> all_unique('ABCB') + False + + If a *key* function is specified, it will be used to make comparisons. + + >>> all_unique('ABCb') + True + >>> all_unique('ABCb', str.lower) + False + + The function returns as soon as the first non-unique element is + encountered. Iterables with a mix of hashable and unhashable items can + be used, but the function will be slower for unhashable items. + """ + seenset = set() + seenset_add = seenset.add + seenlist = [] + seenlist_add = seenlist.append + for element in map(key, iterable) if key else iterable: + try: + if element in seenset: + return False + seenset_add(element) + except TypeError: + if element in seenlist: + return False + seenlist_add(element) + return True + + +def nth_product(index, *args): + """Equivalent to ``list(product(*args))[index]``. + + The products of *args* can be ordered lexicographically. + :func:`nth_product` computes the product at sort position *index* without + computing the previous products. + + >>> nth_product(8, range(2), range(2), range(2), range(2)) + (1, 0, 0, 0) + + ``IndexError`` will be raised if the given *index* is invalid. + """ + pools = list(map(tuple, reversed(args))) + ns = list(map(len, pools)) + + c = reduce(mul, ns) + + if index < 0: + index += c + + if not 0 <= index < c: + raise IndexError + + result = [] + for pool, n in zip(pools, ns): + result.append(pool[index % n]) + index //= n + + return tuple(reversed(result)) + + +def nth_permutation(iterable, r, index): + """Equivalent to ``list(permutations(iterable, r))[index]``` + + The subsequences of *iterable* that are of length *r* where order is + important can be ordered lexicographically. :func:`nth_permutation` + computes the subsequence at sort position *index* directly, without + computing the previous subsequences. + + >>> nth_permutation('ghijk', 2, 5) + ('h', 'i') + + ``ValueError`` will be raised If *r* is negative or greater than the length + of *iterable*. + ``IndexError`` will be raised if the given *index* is invalid. + """ + pool = list(iterable) + n = len(pool) + + if r is None or r == n: + r, c = n, factorial(n) + elif not 0 <= r < n: + raise ValueError + else: + c = factorial(n) // factorial(n - r) + + if index < 0: + index += c + + if not 0 <= index < c: + raise IndexError + + if c == 0: + return tuple() + + result = [0] * r + q = index * factorial(n) // c if r < n else index + for d in range(1, n + 1): + q, i = divmod(q, d) + if 0 <= n - d < r: + result[n - d] = i + if q == 0: + break + + return tuple(map(pool.pop, result)) + + +def value_chain(*args): + """Yield all arguments passed to the function in the same order in which + they were passed. If an argument itself is iterable then iterate over its + values. + + >>> list(value_chain(1, 2, 3, [4, 5, 6])) + [1, 2, 3, 4, 5, 6] + + Binary and text strings are not considered iterable and are emitted + as-is: + + >>> list(value_chain('12', '34', ['56', '78'])) + ['12', '34', '56', '78'] + + + Multiple levels of nesting are not flattened. + + """ + for value in args: + if isinstance(value, (str, bytes)): + yield value + continue + try: + yield from value + except TypeError: + yield value + + +def product_index(element, *args): + """Equivalent to ``list(product(*args)).index(element)`` + + The products of *args* can be ordered lexicographically. + :func:`product_index` computes the first index of *element* without + computing the previous products. + + >>> product_index([8, 2], range(10), range(5)) + 42 + + ``ValueError`` will be raised if the given *element* isn't in the product + of *args*. + """ + index = 0 + + for x, pool in zip_longest(element, args, fillvalue=_marker): + if x is _marker or pool is _marker: + raise ValueError('element is not a product of args') + + pool = tuple(pool) + index = index * len(pool) + pool.index(x) + + return index + + +def combination_index(element, iterable): + """Equivalent to ``list(combinations(iterable, r)).index(element)`` + + The subsequences of *iterable* that are of length *r* can be ordered + lexicographically. :func:`combination_index` computes the index of the + first *element*, without computing the previous combinations. + + >>> combination_index('adf', 'abcdefg') + 10 + + ``ValueError`` will be raised if the given *element* isn't one of the + combinations of *iterable*. + """ + element = enumerate(element) + k, y = next(element, (None, None)) + if k is None: + return 0 + + indexes = [] + pool = enumerate(iterable) + for n, x in pool: + if x == y: + indexes.append(n) + tmp, y = next(element, (None, None)) + if tmp is None: + break + else: + k = tmp + else: + raise ValueError('element is not a combination of iterable') + + n, _ = last(pool, default=(n, None)) + + # Python versiosn below 3.8 don't have math.comb + index = 1 + for i, j in enumerate(reversed(indexes), start=1): + j = n - j + if i <= j: + index += factorial(j) // (factorial(i) * factorial(j - i)) + + return factorial(n + 1) // (factorial(k + 1) * factorial(n - k)) - index + + +def permutation_index(element, iterable): + """Equivalent to ``list(permutations(iterable, r)).index(element)``` + + The subsequences of *iterable* that are of length *r* where order is + important can be ordered lexicographically. :func:`permutation_index` + computes the index of the first *element* directly, without computing + the previous permutations. + + >>> permutation_index([1, 3, 2], range(5)) + 19 + + ``ValueError`` will be raised if the given *element* isn't one of the + permutations of *iterable*. + """ + index = 0 + pool = list(iterable) + for i, x in zip(range(len(pool), -1, -1), element): + r = pool.index(x) + index = index * i + r + del pool[r] + + return index + + +class countable: + """Wrap *iterable* and keep a count of how many items have been consumed. + + The ``items_seen`` attribute starts at ``0`` and increments as the iterable + is consumed: + + >>> iterable = map(str, range(10)) + >>> it = countable(iterable) + >>> it.items_seen + 0 + >>> next(it), next(it) + ('0', '1') + >>> list(it) + ['2', '3', '4', '5', '6', '7', '8', '9'] + >>> it.items_seen + 10 + """ + + def __init__(self, iterable): + self._it = iter(iterable) + self.items_seen = 0 + + def __iter__(self): + return self + + def __next__(self): + item = next(self._it) + self.items_seen += 1 + + return item diff --git a/venv/lib/python3.10/site-packages/setuptools/_vendor/more_itertools/recipes.py b/venv/lib/python3.10/site-packages/setuptools/_vendor/more_itertools/recipes.py new file mode 100644 index 00000000..521abd7c --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_vendor/more_itertools/recipes.py @@ -0,0 +1,620 @@ +"""Imported from the recipes section of the itertools documentation. + +All functions taken from the recipes section of the itertools library docs +[1]_. +Some backward-compatible usability improvements have been made. + +.. [1] http://docs.python.org/library/itertools.html#recipes + +""" +import warnings +from collections import deque +from itertools import ( + chain, + combinations, + count, + cycle, + groupby, + islice, + repeat, + starmap, + tee, + zip_longest, +) +import operator +from random import randrange, sample, choice + +__all__ = [ + 'all_equal', + 'consume', + 'convolve', + 'dotproduct', + 'first_true', + 'flatten', + 'grouper', + 'iter_except', + 'ncycles', + 'nth', + 'nth_combination', + 'padnone', + 'pad_none', + 'pairwise', + 'partition', + 'powerset', + 'prepend', + 'quantify', + 'random_combination_with_replacement', + 'random_combination', + 'random_permutation', + 'random_product', + 'repeatfunc', + 'roundrobin', + 'tabulate', + 'tail', + 'take', + 'unique_everseen', + 'unique_justseen', +] + + +def take(n, iterable): + """Return first *n* items of the iterable as a list. + + >>> take(3, range(10)) + [0, 1, 2] + + If there are fewer than *n* items in the iterable, all of them are + returned. + + >>> take(10, range(3)) + [0, 1, 2] + + """ + return list(islice(iterable, n)) + + +def tabulate(function, start=0): + """Return an iterator over the results of ``func(start)``, + ``func(start + 1)``, ``func(start + 2)``... + + *func* should be a function that accepts one integer argument. + + If *start* is not specified it defaults to 0. It will be incremented each + time the iterator is advanced. + + >>> square = lambda x: x ** 2 + >>> iterator = tabulate(square, -3) + >>> take(4, iterator) + [9, 4, 1, 0] + + """ + return map(function, count(start)) + + +def tail(n, iterable): + """Return an iterator over the last *n* items of *iterable*. + + >>> t = tail(3, 'ABCDEFG') + >>> list(t) + ['E', 'F', 'G'] + + """ + return iter(deque(iterable, maxlen=n)) + + +def consume(iterator, n=None): + """Advance *iterable* by *n* steps. If *n* is ``None``, consume it + entirely. + + Efficiently exhausts an iterator without returning values. Defaults to + consuming the whole iterator, but an optional second argument may be + provided to limit consumption. + + >>> i = (x for x in range(10)) + >>> next(i) + 0 + >>> consume(i, 3) + >>> next(i) + 4 + >>> consume(i) + >>> next(i) + Traceback (most recent call last): + File "", line 1, in + StopIteration + + If the iterator has fewer items remaining than the provided limit, the + whole iterator will be consumed. + + >>> i = (x for x in range(3)) + >>> consume(i, 5) + >>> next(i) + Traceback (most recent call last): + File "", line 1, in + StopIteration + + """ + # Use functions that consume iterators at C speed. + if n is None: + # feed the entire iterator into a zero-length deque + deque(iterator, maxlen=0) + else: + # advance to the empty slice starting at position n + next(islice(iterator, n, n), None) + + +def nth(iterable, n, default=None): + """Returns the nth item or a default value. + + >>> l = range(10) + >>> nth(l, 3) + 3 + >>> nth(l, 20, "zebra") + 'zebra' + + """ + return next(islice(iterable, n, None), default) + + +def all_equal(iterable): + """ + Returns ``True`` if all the elements are equal to each other. + + >>> all_equal('aaaa') + True + >>> all_equal('aaab') + False + + """ + g = groupby(iterable) + return next(g, True) and not next(g, False) + + +def quantify(iterable, pred=bool): + """Return the how many times the predicate is true. + + >>> quantify([True, False, True]) + 2 + + """ + return sum(map(pred, iterable)) + + +def pad_none(iterable): + """Returns the sequence of elements and then returns ``None`` indefinitely. + + >>> take(5, pad_none(range(3))) + [0, 1, 2, None, None] + + Useful for emulating the behavior of the built-in :func:`map` function. + + See also :func:`padded`. + + """ + return chain(iterable, repeat(None)) + + +padnone = pad_none + + +def ncycles(iterable, n): + """Returns the sequence elements *n* times + + >>> list(ncycles(["a", "b"], 3)) + ['a', 'b', 'a', 'b', 'a', 'b'] + + """ + return chain.from_iterable(repeat(tuple(iterable), n)) + + +def dotproduct(vec1, vec2): + """Returns the dot product of the two iterables. + + >>> dotproduct([10, 10], [20, 20]) + 400 + + """ + return sum(map(operator.mul, vec1, vec2)) + + +def flatten(listOfLists): + """Return an iterator flattening one level of nesting in a list of lists. + + >>> list(flatten([[0, 1], [2, 3]])) + [0, 1, 2, 3] + + See also :func:`collapse`, which can flatten multiple levels of nesting. + + """ + return chain.from_iterable(listOfLists) + + +def repeatfunc(func, times=None, *args): + """Call *func* with *args* repeatedly, returning an iterable over the + results. + + If *times* is specified, the iterable will terminate after that many + repetitions: + + >>> from operator import add + >>> times = 4 + >>> args = 3, 5 + >>> list(repeatfunc(add, times, *args)) + [8, 8, 8, 8] + + If *times* is ``None`` the iterable will not terminate: + + >>> from random import randrange + >>> times = None + >>> args = 1, 11 + >>> take(6, repeatfunc(randrange, times, *args)) # doctest:+SKIP + [2, 4, 8, 1, 8, 4] + + """ + if times is None: + return starmap(func, repeat(args)) + return starmap(func, repeat(args, times)) + + +def _pairwise(iterable): + """Returns an iterator of paired items, overlapping, from the original + + >>> take(4, pairwise(count())) + [(0, 1), (1, 2), (2, 3), (3, 4)] + + On Python 3.10 and above, this is an alias for :func:`itertools.pairwise`. + + """ + a, b = tee(iterable) + next(b, None) + yield from zip(a, b) + + +try: + from itertools import pairwise as itertools_pairwise +except ImportError: + pairwise = _pairwise +else: + + def pairwise(iterable): + yield from itertools_pairwise(iterable) + + pairwise.__doc__ = _pairwise.__doc__ + + +def grouper(iterable, n, fillvalue=None): + """Collect data into fixed-length chunks or blocks. + + >>> list(grouper('ABCDEFG', 3, 'x')) + [('A', 'B', 'C'), ('D', 'E', 'F'), ('G', 'x', 'x')] + + """ + if isinstance(iterable, int): + warnings.warn( + "grouper expects iterable as first parameter", DeprecationWarning + ) + n, iterable = iterable, n + args = [iter(iterable)] * n + return zip_longest(fillvalue=fillvalue, *args) + + +def roundrobin(*iterables): + """Yields an item from each iterable, alternating between them. + + >>> list(roundrobin('ABC', 'D', 'EF')) + ['A', 'D', 'E', 'B', 'F', 'C'] + + This function produces the same output as :func:`interleave_longest`, but + may perform better for some inputs (in particular when the number of + iterables is small). + + """ + # Recipe credited to George Sakkis + pending = len(iterables) + nexts = cycle(iter(it).__next__ for it in iterables) + while pending: + try: + for next in nexts: + yield next() + except StopIteration: + pending -= 1 + nexts = cycle(islice(nexts, pending)) + + +def partition(pred, iterable): + """ + Returns a 2-tuple of iterables derived from the input iterable. + The first yields the items that have ``pred(item) == False``. + The second yields the items that have ``pred(item) == True``. + + >>> is_odd = lambda x: x % 2 != 0 + >>> iterable = range(10) + >>> even_items, odd_items = partition(is_odd, iterable) + >>> list(even_items), list(odd_items) + ([0, 2, 4, 6, 8], [1, 3, 5, 7, 9]) + + If *pred* is None, :func:`bool` is used. + + >>> iterable = [0, 1, False, True, '', ' '] + >>> false_items, true_items = partition(None, iterable) + >>> list(false_items), list(true_items) + ([0, False, ''], [1, True, ' ']) + + """ + if pred is None: + pred = bool + + evaluations = ((pred(x), x) for x in iterable) + t1, t2 = tee(evaluations) + return ( + (x for (cond, x) in t1 if not cond), + (x for (cond, x) in t2 if cond), + ) + + +def powerset(iterable): + """Yields all possible subsets of the iterable. + + >>> list(powerset([1, 2, 3])) + [(), (1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)] + + :func:`powerset` will operate on iterables that aren't :class:`set` + instances, so repeated elements in the input will produce repeated elements + in the output. Use :func:`unique_everseen` on the input to avoid generating + duplicates: + + >>> seq = [1, 1, 0] + >>> list(powerset(seq)) + [(), (1,), (1,), (0,), (1, 1), (1, 0), (1, 0), (1, 1, 0)] + >>> from more_itertools import unique_everseen + >>> list(powerset(unique_everseen(seq))) + [(), (1,), (0,), (1, 0)] + + """ + s = list(iterable) + return chain.from_iterable(combinations(s, r) for r in range(len(s) + 1)) + + +def unique_everseen(iterable, key=None): + """ + Yield unique elements, preserving order. + + >>> list(unique_everseen('AAAABBBCCDAABBB')) + ['A', 'B', 'C', 'D'] + >>> list(unique_everseen('ABBCcAD', str.lower)) + ['A', 'B', 'C', 'D'] + + Sequences with a mix of hashable and unhashable items can be used. + The function will be slower (i.e., `O(n^2)`) for unhashable items. + + Remember that ``list`` objects are unhashable - you can use the *key* + parameter to transform the list to a tuple (which is hashable) to + avoid a slowdown. + + >>> iterable = ([1, 2], [2, 3], [1, 2]) + >>> list(unique_everseen(iterable)) # Slow + [[1, 2], [2, 3]] + >>> list(unique_everseen(iterable, key=tuple)) # Faster + [[1, 2], [2, 3]] + + Similary, you may want to convert unhashable ``set`` objects with + ``key=frozenset``. For ``dict`` objects, + ``key=lambda x: frozenset(x.items())`` can be used. + + """ + seenset = set() + seenset_add = seenset.add + seenlist = [] + seenlist_add = seenlist.append + use_key = key is not None + + for element in iterable: + k = key(element) if use_key else element + try: + if k not in seenset: + seenset_add(k) + yield element + except TypeError: + if k not in seenlist: + seenlist_add(k) + yield element + + +def unique_justseen(iterable, key=None): + """Yields elements in order, ignoring serial duplicates + + >>> list(unique_justseen('AAAABBBCCDAABBB')) + ['A', 'B', 'C', 'D', 'A', 'B'] + >>> list(unique_justseen('ABBCcAD', str.lower)) + ['A', 'B', 'C', 'A', 'D'] + + """ + return map(next, map(operator.itemgetter(1), groupby(iterable, key))) + + +def iter_except(func, exception, first=None): + """Yields results from a function repeatedly until an exception is raised. + + Converts a call-until-exception interface to an iterator interface. + Like ``iter(func, sentinel)``, but uses an exception instead of a sentinel + to end the loop. + + >>> l = [0, 1, 2] + >>> list(iter_except(l.pop, IndexError)) + [2, 1, 0] + + """ + try: + if first is not None: + yield first() + while 1: + yield func() + except exception: + pass + + +def first_true(iterable, default=None, pred=None): + """ + Returns the first true value in the iterable. + + If no true value is found, returns *default* + + If *pred* is not None, returns the first item for which + ``pred(item) == True`` . + + >>> first_true(range(10)) + 1 + >>> first_true(range(10), pred=lambda x: x > 5) + 6 + >>> first_true(range(10), default='missing', pred=lambda x: x > 9) + 'missing' + + """ + return next(filter(pred, iterable), default) + + +def random_product(*args, repeat=1): + """Draw an item at random from each of the input iterables. + + >>> random_product('abc', range(4), 'XYZ') # doctest:+SKIP + ('c', 3, 'Z') + + If *repeat* is provided as a keyword argument, that many items will be + drawn from each iterable. + + >>> random_product('abcd', range(4), repeat=2) # doctest:+SKIP + ('a', 2, 'd', 3) + + This equivalent to taking a random selection from + ``itertools.product(*args, **kwarg)``. + + """ + pools = [tuple(pool) for pool in args] * repeat + return tuple(choice(pool) for pool in pools) + + +def random_permutation(iterable, r=None): + """Return a random *r* length permutation of the elements in *iterable*. + + If *r* is not specified or is ``None``, then *r* defaults to the length of + *iterable*. + + >>> random_permutation(range(5)) # doctest:+SKIP + (3, 4, 0, 1, 2) + + This equivalent to taking a random selection from + ``itertools.permutations(iterable, r)``. + + """ + pool = tuple(iterable) + r = len(pool) if r is None else r + return tuple(sample(pool, r)) + + +def random_combination(iterable, r): + """Return a random *r* length subsequence of the elements in *iterable*. + + >>> random_combination(range(5), 3) # doctest:+SKIP + (2, 3, 4) + + This equivalent to taking a random selection from + ``itertools.combinations(iterable, r)``. + + """ + pool = tuple(iterable) + n = len(pool) + indices = sorted(sample(range(n), r)) + return tuple(pool[i] for i in indices) + + +def random_combination_with_replacement(iterable, r): + """Return a random *r* length subsequence of elements in *iterable*, + allowing individual elements to be repeated. + + >>> random_combination_with_replacement(range(3), 5) # doctest:+SKIP + (0, 0, 1, 2, 2) + + This equivalent to taking a random selection from + ``itertools.combinations_with_replacement(iterable, r)``. + + """ + pool = tuple(iterable) + n = len(pool) + indices = sorted(randrange(n) for i in range(r)) + return tuple(pool[i] for i in indices) + + +def nth_combination(iterable, r, index): + """Equivalent to ``list(combinations(iterable, r))[index]``. + + The subsequences of *iterable* that are of length *r* can be ordered + lexicographically. :func:`nth_combination` computes the subsequence at + sort position *index* directly, without computing the previous + subsequences. + + >>> nth_combination(range(5), 3, 5) + (0, 3, 4) + + ``ValueError`` will be raised If *r* is negative or greater than the length + of *iterable*. + ``IndexError`` will be raised if the given *index* is invalid. + """ + pool = tuple(iterable) + n = len(pool) + if (r < 0) or (r > n): + raise ValueError + + c = 1 + k = min(r, n - r) + for i in range(1, k + 1): + c = c * (n - k + i) // i + + if index < 0: + index += c + + if (index < 0) or (index >= c): + raise IndexError + + result = [] + while r: + c, n, r = c * r // n, n - 1, r - 1 + while index >= c: + index -= c + c, n = c * (n - r) // n, n - 1 + result.append(pool[-1 - n]) + + return tuple(result) + + +def prepend(value, iterator): + """Yield *value*, followed by the elements in *iterator*. + + >>> value = '0' + >>> iterator = ['1', '2', '3'] + >>> list(prepend(value, iterator)) + ['0', '1', '2', '3'] + + To prepend multiple values, see :func:`itertools.chain` + or :func:`value_chain`. + + """ + return chain([value], iterator) + + +def convolve(signal, kernel): + """Convolve the iterable *signal* with the iterable *kernel*. + + >>> signal = (1, 2, 3, 4, 5) + >>> kernel = [3, 2, 1] + >>> list(convolve(signal, kernel)) + [3, 8, 14, 20, 26, 14, 5] + + Note: the input arguments are not interchangeable, as the *kernel* + is immediately consumed and stored. + + """ + kernel = tuple(kernel)[::-1] + n = len(kernel) + window = deque([0], maxlen=n) * n + for x in chain(signal, repeat(0, n - 1)): + window.append(x) + yield sum(map(operator.mul, kernel, window)) diff --git a/venv/lib/python3.10/site-packages/setuptools/_vendor/ordered_set.py b/venv/lib/python3.10/site-packages/setuptools/_vendor/ordered_set.py new file mode 100644 index 00000000..14876000 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_vendor/ordered_set.py @@ -0,0 +1,488 @@ +""" +An OrderedSet is a custom MutableSet that remembers its order, so that every +entry has an index that can be looked up. + +Based on a recipe originally posted to ActiveState Recipes by Raymond Hettiger, +and released under the MIT license. +""" +import itertools as it +from collections import deque + +try: + # Python 3 + from collections.abc import MutableSet, Sequence +except ImportError: + # Python 2.7 + from collections import MutableSet, Sequence + +SLICE_ALL = slice(None) +__version__ = "3.1" + + +def is_iterable(obj): + """ + Are we being asked to look up a list of things, instead of a single thing? + We check for the `__iter__` attribute so that this can cover types that + don't have to be known by this module, such as NumPy arrays. + + Strings, however, should be considered as atomic values to look up, not + iterables. The same goes for tuples, since they are immutable and therefore + valid entries. + + We don't need to check for the Python 2 `unicode` type, because it doesn't + have an `__iter__` attribute anyway. + """ + return ( + hasattr(obj, "__iter__") + and not isinstance(obj, str) + and not isinstance(obj, tuple) + ) + + +class OrderedSet(MutableSet, Sequence): + """ + An OrderedSet is a custom MutableSet that remembers its order, so that + every entry has an index that can be looked up. + + Example: + >>> OrderedSet([1, 1, 2, 3, 2]) + OrderedSet([1, 2, 3]) + """ + + def __init__(self, iterable=None): + self.items = [] + self.map = {} + if iterable is not None: + self |= iterable + + def __len__(self): + """ + Returns the number of unique elements in the ordered set + + Example: + >>> len(OrderedSet([])) + 0 + >>> len(OrderedSet([1, 2])) + 2 + """ + return len(self.items) + + def __getitem__(self, index): + """ + Get the item at a given index. + + If `index` is a slice, you will get back that slice of items, as a + new OrderedSet. + + If `index` is a list or a similar iterable, you'll get a list of + items corresponding to those indices. This is similar to NumPy's + "fancy indexing". The result is not an OrderedSet because you may ask + for duplicate indices, and the number of elements returned should be + the number of elements asked for. + + Example: + >>> oset = OrderedSet([1, 2, 3]) + >>> oset[1] + 2 + """ + if isinstance(index, slice) and index == SLICE_ALL: + return self.copy() + elif is_iterable(index): + return [self.items[i] for i in index] + elif hasattr(index, "__index__") or isinstance(index, slice): + result = self.items[index] + if isinstance(result, list): + return self.__class__(result) + else: + return result + else: + raise TypeError("Don't know how to index an OrderedSet by %r" % index) + + def copy(self): + """ + Return a shallow copy of this object. + + Example: + >>> this = OrderedSet([1, 2, 3]) + >>> other = this.copy() + >>> this == other + True + >>> this is other + False + """ + return self.__class__(self) + + def __getstate__(self): + if len(self) == 0: + # The state can't be an empty list. + # We need to return a truthy value, or else __setstate__ won't be run. + # + # This could have been done more gracefully by always putting the state + # in a tuple, but this way is backwards- and forwards- compatible with + # previous versions of OrderedSet. + return (None,) + else: + return list(self) + + def __setstate__(self, state): + if state == (None,): + self.__init__([]) + else: + self.__init__(state) + + def __contains__(self, key): + """ + Test if the item is in this ordered set + + Example: + >>> 1 in OrderedSet([1, 3, 2]) + True + >>> 5 in OrderedSet([1, 3, 2]) + False + """ + return key in self.map + + def add(self, key): + """ + Add `key` as an item to this OrderedSet, then return its index. + + If `key` is already in the OrderedSet, return the index it already + had. + + Example: + >>> oset = OrderedSet() + >>> oset.append(3) + 0 + >>> print(oset) + OrderedSet([3]) + """ + if key not in self.map: + self.map[key] = len(self.items) + self.items.append(key) + return self.map[key] + + append = add + + def update(self, sequence): + """ + Update the set with the given iterable sequence, then return the index + of the last element inserted. + + Example: + >>> oset = OrderedSet([1, 2, 3]) + >>> oset.update([3, 1, 5, 1, 4]) + 4 + >>> print(oset) + OrderedSet([1, 2, 3, 5, 4]) + """ + item_index = None + try: + for item in sequence: + item_index = self.add(item) + except TypeError: + raise ValueError( + "Argument needs to be an iterable, got %s" % type(sequence) + ) + return item_index + + def index(self, key): + """ + Get the index of a given entry, raising an IndexError if it's not + present. + + `key` can be an iterable of entries that is not a string, in which case + this returns a list of indices. + + Example: + >>> oset = OrderedSet([1, 2, 3]) + >>> oset.index(2) + 1 + """ + if is_iterable(key): + return [self.index(subkey) for subkey in key] + return self.map[key] + + # Provide some compatibility with pd.Index + get_loc = index + get_indexer = index + + def pop(self): + """ + Remove and return the last element from the set. + + Raises KeyError if the set is empty. + + Example: + >>> oset = OrderedSet([1, 2, 3]) + >>> oset.pop() + 3 + """ + if not self.items: + raise KeyError("Set is empty") + + elem = self.items[-1] + del self.items[-1] + del self.map[elem] + return elem + + def discard(self, key): + """ + Remove an element. Do not raise an exception if absent. + + The MutableSet mixin uses this to implement the .remove() method, which + *does* raise an error when asked to remove a non-existent item. + + Example: + >>> oset = OrderedSet([1, 2, 3]) + >>> oset.discard(2) + >>> print(oset) + OrderedSet([1, 3]) + >>> oset.discard(2) + >>> print(oset) + OrderedSet([1, 3]) + """ + if key in self: + i = self.map[key] + del self.items[i] + del self.map[key] + for k, v in self.map.items(): + if v >= i: + self.map[k] = v - 1 + + def clear(self): + """ + Remove all items from this OrderedSet. + """ + del self.items[:] + self.map.clear() + + def __iter__(self): + """ + Example: + >>> list(iter(OrderedSet([1, 2, 3]))) + [1, 2, 3] + """ + return iter(self.items) + + def __reversed__(self): + """ + Example: + >>> list(reversed(OrderedSet([1, 2, 3]))) + [3, 2, 1] + """ + return reversed(self.items) + + def __repr__(self): + if not self: + return "%s()" % (self.__class__.__name__,) + return "%s(%r)" % (self.__class__.__name__, list(self)) + + def __eq__(self, other): + """ + Returns true if the containers have the same items. If `other` is a + Sequence, then order is checked, otherwise it is ignored. + + Example: + >>> oset = OrderedSet([1, 3, 2]) + >>> oset == [1, 3, 2] + True + >>> oset == [1, 2, 3] + False + >>> oset == [2, 3] + False + >>> oset == OrderedSet([3, 2, 1]) + False + """ + # In Python 2 deque is not a Sequence, so treat it as one for + # consistent behavior with Python 3. + if isinstance(other, (Sequence, deque)): + # Check that this OrderedSet contains the same elements, in the + # same order, as the other object. + return list(self) == list(other) + try: + other_as_set = set(other) + except TypeError: + # If `other` can't be converted into a set, it's not equal. + return False + else: + return set(self) == other_as_set + + def union(self, *sets): + """ + Combines all unique items. + Each items order is defined by its first appearance. + + Example: + >>> oset = OrderedSet.union(OrderedSet([3, 1, 4, 1, 5]), [1, 3], [2, 0]) + >>> print(oset) + OrderedSet([3, 1, 4, 5, 2, 0]) + >>> oset.union([8, 9]) + OrderedSet([3, 1, 4, 5, 2, 0, 8, 9]) + >>> oset | {10} + OrderedSet([3, 1, 4, 5, 2, 0, 10]) + """ + cls = self.__class__ if isinstance(self, OrderedSet) else OrderedSet + containers = map(list, it.chain([self], sets)) + items = it.chain.from_iterable(containers) + return cls(items) + + def __and__(self, other): + # the parent implementation of this is backwards + return self.intersection(other) + + def intersection(self, *sets): + """ + Returns elements in common between all sets. Order is defined only + by the first set. + + Example: + >>> oset = OrderedSet.intersection(OrderedSet([0, 1, 2, 3]), [1, 2, 3]) + >>> print(oset) + OrderedSet([1, 2, 3]) + >>> oset.intersection([2, 4, 5], [1, 2, 3, 4]) + OrderedSet([2]) + >>> oset.intersection() + OrderedSet([1, 2, 3]) + """ + cls = self.__class__ if isinstance(self, OrderedSet) else OrderedSet + if sets: + common = set.intersection(*map(set, sets)) + items = (item for item in self if item in common) + else: + items = self + return cls(items) + + def difference(self, *sets): + """ + Returns all elements that are in this set but not the others. + + Example: + >>> OrderedSet([1, 2, 3]).difference(OrderedSet([2])) + OrderedSet([1, 3]) + >>> OrderedSet([1, 2, 3]).difference(OrderedSet([2]), OrderedSet([3])) + OrderedSet([1]) + >>> OrderedSet([1, 2, 3]) - OrderedSet([2]) + OrderedSet([1, 3]) + >>> OrderedSet([1, 2, 3]).difference() + OrderedSet([1, 2, 3]) + """ + cls = self.__class__ + if sets: + other = set.union(*map(set, sets)) + items = (item for item in self if item not in other) + else: + items = self + return cls(items) + + def issubset(self, other): + """ + Report whether another set contains this set. + + Example: + >>> OrderedSet([1, 2, 3]).issubset({1, 2}) + False + >>> OrderedSet([1, 2, 3]).issubset({1, 2, 3, 4}) + True + >>> OrderedSet([1, 2, 3]).issubset({1, 4, 3, 5}) + False + """ + if len(self) > len(other): # Fast check for obvious cases + return False + return all(item in other for item in self) + + def issuperset(self, other): + """ + Report whether this set contains another set. + + Example: + >>> OrderedSet([1, 2]).issuperset([1, 2, 3]) + False + >>> OrderedSet([1, 2, 3, 4]).issuperset({1, 2, 3}) + True + >>> OrderedSet([1, 4, 3, 5]).issuperset({1, 2, 3}) + False + """ + if len(self) < len(other): # Fast check for obvious cases + return False + return all(item in self for item in other) + + def symmetric_difference(self, other): + """ + Return the symmetric difference of two OrderedSets as a new set. + That is, the new set will contain all elements that are in exactly + one of the sets. + + Their order will be preserved, with elements from `self` preceding + elements from `other`. + + Example: + >>> this = OrderedSet([1, 4, 3, 5, 7]) + >>> other = OrderedSet([9, 7, 1, 3, 2]) + >>> this.symmetric_difference(other) + OrderedSet([4, 5, 9, 2]) + """ + cls = self.__class__ if isinstance(self, OrderedSet) else OrderedSet + diff1 = cls(self).difference(other) + diff2 = cls(other).difference(self) + return diff1.union(diff2) + + def _update_items(self, items): + """ + Replace the 'items' list of this OrderedSet with a new one, updating + self.map accordingly. + """ + self.items = items + self.map = {item: idx for (idx, item) in enumerate(items)} + + def difference_update(self, *sets): + """ + Update this OrderedSet to remove items from one or more other sets. + + Example: + >>> this = OrderedSet([1, 2, 3]) + >>> this.difference_update(OrderedSet([2, 4])) + >>> print(this) + OrderedSet([1, 3]) + + >>> this = OrderedSet([1, 2, 3, 4, 5]) + >>> this.difference_update(OrderedSet([2, 4]), OrderedSet([1, 4, 6])) + >>> print(this) + OrderedSet([3, 5]) + """ + items_to_remove = set() + for other in sets: + items_to_remove |= set(other) + self._update_items([item for item in self.items if item not in items_to_remove]) + + def intersection_update(self, other): + """ + Update this OrderedSet to keep only items in another set, preserving + their order in this set. + + Example: + >>> this = OrderedSet([1, 4, 3, 5, 7]) + >>> other = OrderedSet([9, 7, 1, 3, 2]) + >>> this.intersection_update(other) + >>> print(this) + OrderedSet([1, 3, 7]) + """ + other = set(other) + self._update_items([item for item in self.items if item in other]) + + def symmetric_difference_update(self, other): + """ + Update this OrderedSet to remove items from another set, then + add items from the other set that were not present in this set. + + Example: + >>> this = OrderedSet([1, 4, 3, 5, 7]) + >>> other = OrderedSet([9, 7, 1, 3, 2]) + >>> this.symmetric_difference_update(other) + >>> print(this) + OrderedSet([4, 5, 9, 2]) + """ + items_to_add = [item for item in other if item not in self] + items_to_remove = set(other) + self._update_items( + [item for item in self.items if item not in items_to_remove] + items_to_add + ) diff --git a/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/__about__.py b/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/__about__.py new file mode 100644 index 00000000..c359122f --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/__about__.py @@ -0,0 +1,26 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +__all__ = [ + "__title__", + "__summary__", + "__uri__", + "__version__", + "__author__", + "__email__", + "__license__", + "__copyright__", +] + +__title__ = "packaging" +__summary__ = "Core utilities for Python packages" +__uri__ = "https://github.com/pypa/packaging" + +__version__ = "21.2" + +__author__ = "Donald Stufft and individual contributors" +__email__ = "donald@stufft.io" + +__license__ = "BSD-2-Clause or Apache-2.0" +__copyright__ = "2014-2019 %s" % __author__ diff --git a/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/__init__.py b/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/__init__.py new file mode 100644 index 00000000..3c50c5dc --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/__init__.py @@ -0,0 +1,25 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from .__about__ import ( + __author__, + __copyright__, + __email__, + __license__, + __summary__, + __title__, + __uri__, + __version__, +) + +__all__ = [ + "__title__", + "__summary__", + "__uri__", + "__version__", + "__author__", + "__email__", + "__license__", + "__copyright__", +] diff --git a/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/_manylinux.py b/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/_manylinux.py new file mode 100644 index 00000000..4c379aa6 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/_manylinux.py @@ -0,0 +1,301 @@ +import collections +import functools +import os +import re +import struct +import sys +import warnings +from typing import IO, Dict, Iterator, NamedTuple, Optional, Tuple + + +# Python does not provide platform information at sufficient granularity to +# identify the architecture of the running executable in some cases, so we +# determine it dynamically by reading the information from the running +# process. This only applies on Linux, which uses the ELF format. +class _ELFFileHeader: + # https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header + class _InvalidELFFileHeader(ValueError): + """ + An invalid ELF file header was found. + """ + + ELF_MAGIC_NUMBER = 0x7F454C46 + ELFCLASS32 = 1 + ELFCLASS64 = 2 + ELFDATA2LSB = 1 + ELFDATA2MSB = 2 + EM_386 = 3 + EM_S390 = 22 + EM_ARM = 40 + EM_X86_64 = 62 + EF_ARM_ABIMASK = 0xFF000000 + EF_ARM_ABI_VER5 = 0x05000000 + EF_ARM_ABI_FLOAT_HARD = 0x00000400 + + def __init__(self, file: IO[bytes]) -> None: + def unpack(fmt: str) -> int: + try: + data = file.read(struct.calcsize(fmt)) + result: Tuple[int, ...] = struct.unpack(fmt, data) + except struct.error: + raise _ELFFileHeader._InvalidELFFileHeader() + return result[0] + + self.e_ident_magic = unpack(">I") + if self.e_ident_magic != self.ELF_MAGIC_NUMBER: + raise _ELFFileHeader._InvalidELFFileHeader() + self.e_ident_class = unpack("B") + if self.e_ident_class not in {self.ELFCLASS32, self.ELFCLASS64}: + raise _ELFFileHeader._InvalidELFFileHeader() + self.e_ident_data = unpack("B") + if self.e_ident_data not in {self.ELFDATA2LSB, self.ELFDATA2MSB}: + raise _ELFFileHeader._InvalidELFFileHeader() + self.e_ident_version = unpack("B") + self.e_ident_osabi = unpack("B") + self.e_ident_abiversion = unpack("B") + self.e_ident_pad = file.read(7) + format_h = "H" + format_i = "I" + format_q = "Q" + format_p = format_i if self.e_ident_class == self.ELFCLASS32 else format_q + self.e_type = unpack(format_h) + self.e_machine = unpack(format_h) + self.e_version = unpack(format_i) + self.e_entry = unpack(format_p) + self.e_phoff = unpack(format_p) + self.e_shoff = unpack(format_p) + self.e_flags = unpack(format_i) + self.e_ehsize = unpack(format_h) + self.e_phentsize = unpack(format_h) + self.e_phnum = unpack(format_h) + self.e_shentsize = unpack(format_h) + self.e_shnum = unpack(format_h) + self.e_shstrndx = unpack(format_h) + + +def _get_elf_header() -> Optional[_ELFFileHeader]: + try: + with open(sys.executable, "rb") as f: + elf_header = _ELFFileHeader(f) + except (OSError, TypeError, _ELFFileHeader._InvalidELFFileHeader): + return None + return elf_header + + +def _is_linux_armhf() -> bool: + # hard-float ABI can be detected from the ELF header of the running + # process + # https://static.docs.arm.com/ihi0044/g/aaelf32.pdf + elf_header = _get_elf_header() + if elf_header is None: + return False + result = elf_header.e_ident_class == elf_header.ELFCLASS32 + result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB + result &= elf_header.e_machine == elf_header.EM_ARM + result &= ( + elf_header.e_flags & elf_header.EF_ARM_ABIMASK + ) == elf_header.EF_ARM_ABI_VER5 + result &= ( + elf_header.e_flags & elf_header.EF_ARM_ABI_FLOAT_HARD + ) == elf_header.EF_ARM_ABI_FLOAT_HARD + return result + + +def _is_linux_i686() -> bool: + elf_header = _get_elf_header() + if elf_header is None: + return False + result = elf_header.e_ident_class == elf_header.ELFCLASS32 + result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB + result &= elf_header.e_machine == elf_header.EM_386 + return result + + +def _have_compatible_abi(arch: str) -> bool: + if arch == "armv7l": + return _is_linux_armhf() + if arch == "i686": + return _is_linux_i686() + return arch in {"x86_64", "aarch64", "ppc64", "ppc64le", "s390x"} + + +# If glibc ever changes its major version, we need to know what the last +# minor version was, so we can build the complete list of all versions. +# For now, guess what the highest minor version might be, assume it will +# be 50 for testing. Once this actually happens, update the dictionary +# with the actual value. +_LAST_GLIBC_MINOR: Dict[int, int] = collections.defaultdict(lambda: 50) + + +class _GLibCVersion(NamedTuple): + major: int + minor: int + + +def _glibc_version_string_confstr() -> Optional[str]: + """ + Primary implementation of glibc_version_string using os.confstr. + """ + # os.confstr is quite a bit faster than ctypes.DLL. It's also less likely + # to be broken or missing. This strategy is used in the standard library + # platform module. + # https://github.com/python/cpython/blob/fcf1d003bf4f0100c/Lib/platform.py#L175-L183 + try: + # os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc 2.17". + version_string = os.confstr("CS_GNU_LIBC_VERSION") + assert version_string is not None + _, version = version_string.split() + except (AssertionError, AttributeError, OSError, ValueError): + # os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)... + return None + return version + + +def _glibc_version_string_ctypes() -> Optional[str]: + """ + Fallback implementation of glibc_version_string using ctypes. + """ + try: + import ctypes + except ImportError: + return None + + # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen + # manpage says, "If filename is NULL, then the returned handle is for the + # main program". This way we can let the linker do the work to figure out + # which libc our process is actually using. + # + # We must also handle the special case where the executable is not a + # dynamically linked executable. This can occur when using musl libc, + # for example. In this situation, dlopen() will error, leading to an + # OSError. Interestingly, at least in the case of musl, there is no + # errno set on the OSError. The single string argument used to construct + # OSError comes from libc itself and is therefore not portable to + # hard code here. In any case, failure to call dlopen() means we + # can proceed, so we bail on our attempt. + try: + process_namespace = ctypes.CDLL(None) + except OSError: + return None + + try: + gnu_get_libc_version = process_namespace.gnu_get_libc_version + except AttributeError: + # Symbol doesn't exist -> therefore, we are not linked to + # glibc. + return None + + # Call gnu_get_libc_version, which returns a string like "2.5" + gnu_get_libc_version.restype = ctypes.c_char_p + version_str: str = gnu_get_libc_version() + # py2 / py3 compatibility: + if not isinstance(version_str, str): + version_str = version_str.decode("ascii") + + return version_str + + +def _glibc_version_string() -> Optional[str]: + """Returns glibc version string, or None if not using glibc.""" + return _glibc_version_string_confstr() or _glibc_version_string_ctypes() + + +def _parse_glibc_version(version_str: str) -> Tuple[int, int]: + """Parse glibc version. + + We use a regexp instead of str.split because we want to discard any + random junk that might come after the minor version -- this might happen + in patched/forked versions of glibc (e.g. Linaro's version of glibc + uses version strings like "2.20-2014.11"). See gh-3588. + """ + m = re.match(r"(?P[0-9]+)\.(?P[0-9]+)", version_str) + if not m: + warnings.warn( + "Expected glibc version with 2 components major.minor," + " got: %s" % version_str, + RuntimeWarning, + ) + return -1, -1 + return int(m.group("major")), int(m.group("minor")) + + +@functools.lru_cache() +def _get_glibc_version() -> Tuple[int, int]: + version_str = _glibc_version_string() + if version_str is None: + return (-1, -1) + return _parse_glibc_version(version_str) + + +# From PEP 513, PEP 600 +def _is_compatible(name: str, arch: str, version: _GLibCVersion) -> bool: + sys_glibc = _get_glibc_version() + if sys_glibc < version: + return False + # Check for presence of _manylinux module. + try: + import _manylinux # noqa + except ImportError: + return True + if hasattr(_manylinux, "manylinux_compatible"): + result = _manylinux.manylinux_compatible(version[0], version[1], arch) + if result is not None: + return bool(result) + return True + if version == _GLibCVersion(2, 5): + if hasattr(_manylinux, "manylinux1_compatible"): + return bool(_manylinux.manylinux1_compatible) + if version == _GLibCVersion(2, 12): + if hasattr(_manylinux, "manylinux2010_compatible"): + return bool(_manylinux.manylinux2010_compatible) + if version == _GLibCVersion(2, 17): + if hasattr(_manylinux, "manylinux2014_compatible"): + return bool(_manylinux.manylinux2014_compatible) + return True + + +_LEGACY_MANYLINUX_MAP = { + # CentOS 7 w/ glibc 2.17 (PEP 599) + (2, 17): "manylinux2014", + # CentOS 6 w/ glibc 2.12 (PEP 571) + (2, 12): "manylinux2010", + # CentOS 5 w/ glibc 2.5 (PEP 513) + (2, 5): "manylinux1", +} + + +def platform_tags(linux: str, arch: str) -> Iterator[str]: + if not _have_compatible_abi(arch): + return + # Oldest glibc to be supported regardless of architecture is (2, 17). + too_old_glibc2 = _GLibCVersion(2, 16) + if arch in {"x86_64", "i686"}: + # On x86/i686 also oldest glibc to be supported is (2, 5). + too_old_glibc2 = _GLibCVersion(2, 4) + current_glibc = _GLibCVersion(*_get_glibc_version()) + glibc_max_list = [current_glibc] + # We can assume compatibility across glibc major versions. + # https://sourceware.org/bugzilla/show_bug.cgi?id=24636 + # + # Build a list of maximum glibc versions so that we can + # output the canonical list of all glibc from current_glibc + # down to too_old_glibc2, including all intermediary versions. + for glibc_major in range(current_glibc.major - 1, 1, -1): + glibc_minor = _LAST_GLIBC_MINOR[glibc_major] + glibc_max_list.append(_GLibCVersion(glibc_major, glibc_minor)) + for glibc_max in glibc_max_list: + if glibc_max.major == too_old_glibc2.major: + min_minor = too_old_glibc2.minor + else: + # For other glibc major versions oldest supported is (x, 0). + min_minor = -1 + for glibc_minor in range(glibc_max.minor, min_minor, -1): + glibc_version = _GLibCVersion(glibc_max.major, glibc_minor) + tag = "manylinux_{}_{}".format(*glibc_version) + if _is_compatible(tag, arch, glibc_version): + yield linux.replace("linux", tag) + # Handle the legacy manylinux1, manylinux2010, manylinux2014 tags. + if glibc_version in _LEGACY_MANYLINUX_MAP: + legacy_tag = _LEGACY_MANYLINUX_MAP[glibc_version] + if _is_compatible(legacy_tag, arch, glibc_version): + yield linux.replace("linux", legacy_tag) diff --git a/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/_musllinux.py b/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/_musllinux.py new file mode 100644 index 00000000..85450faf --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/_musllinux.py @@ -0,0 +1,136 @@ +"""PEP 656 support. + +This module implements logic to detect if the currently running Python is +linked against musl, and what musl version is used. +""" + +import contextlib +import functools +import operator +import os +import re +import struct +import subprocess +import sys +from typing import IO, Iterator, NamedTuple, Optional, Tuple + + +def _read_unpacked(f: IO[bytes], fmt: str) -> Tuple[int, ...]: + return struct.unpack(fmt, f.read(struct.calcsize(fmt))) + + +def _parse_ld_musl_from_elf(f: IO[bytes]) -> Optional[str]: + """Detect musl libc location by parsing the Python executable. + + Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca + ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html + """ + f.seek(0) + try: + ident = _read_unpacked(f, "16B") + except struct.error: + return None + if ident[:4] != tuple(b"\x7fELF"): # Invalid magic, not ELF. + return None + f.seek(struct.calcsize("HHI"), 1) # Skip file type, machine, and version. + + try: + # e_fmt: Format for program header. + # p_fmt: Format for section header. + # p_idx: Indexes to find p_type, p_offset, and p_filesz. + e_fmt, p_fmt, p_idx = { + 1: ("IIIIHHH", "IIIIIIII", (0, 1, 4)), # 32-bit. + 2: ("QQQIHHH", "IIQQQQQQ", (0, 2, 5)), # 64-bit. + }[ident[4]] + except KeyError: + return None + else: + p_get = operator.itemgetter(*p_idx) + + # Find the interpreter section and return its content. + try: + _, e_phoff, _, _, _, e_phentsize, e_phnum = _read_unpacked(f, e_fmt) + except struct.error: + return None + for i in range(e_phnum + 1): + f.seek(e_phoff + e_phentsize * i) + try: + p_type, p_offset, p_filesz = p_get(_read_unpacked(f, p_fmt)) + except struct.error: + return None + if p_type != 3: # Not PT_INTERP. + continue + f.seek(p_offset) + interpreter = os.fsdecode(f.read(p_filesz)).strip("\0") + if "musl" not in interpreter: + return None + return interpreter + return None + + +class _MuslVersion(NamedTuple): + major: int + minor: int + + +def _parse_musl_version(output: str) -> Optional[_MuslVersion]: + lines = [n for n in (n.strip() for n in output.splitlines()) if n] + if len(lines) < 2 or lines[0][:4] != "musl": + return None + m = re.match(r"Version (\d+)\.(\d+)", lines[1]) + if not m: + return None + return _MuslVersion(major=int(m.group(1)), minor=int(m.group(2))) + + +@functools.lru_cache() +def _get_musl_version(executable: str) -> Optional[_MuslVersion]: + """Detect currently-running musl runtime version. + + This is done by checking the specified executable's dynamic linking + information, and invoking the loader to parse its output for a version + string. If the loader is musl, the output would be something like:: + + musl libc (x86_64) + Version 1.2.2 + Dynamic Program Loader + """ + with contextlib.ExitStack() as stack: + try: + f = stack.enter_context(open(executable, "rb")) + except IOError: + return None + ld = _parse_ld_musl_from_elf(f) + if not ld: + return None + proc = subprocess.run([ld], stderr=subprocess.PIPE, universal_newlines=True) + return _parse_musl_version(proc.stderr) + + +def platform_tags(arch: str) -> Iterator[str]: + """Generate musllinux tags compatible to the current platform. + + :param arch: Should be the part of platform tag after the ``linux_`` + prefix, e.g. ``x86_64``. The ``linux_`` prefix is assumed as a + prerequisite for the current platform to be musllinux-compatible. + + :returns: An iterator of compatible musllinux tags. + """ + sys_musl = _get_musl_version(sys.executable) + if sys_musl is None: # Python not dynamically linked against musl. + return + for minor in range(sys_musl.minor, -1, -1): + yield f"musllinux_{sys_musl.major}_{minor}_{arch}" + + +if __name__ == "__main__": # pragma: no cover + import sysconfig + + plat = sysconfig.get_platform() + assert plat.startswith("linux-"), "not linux" + + print("plat:", plat) + print("musl:", _get_musl_version(sys.executable)) + print("tags:", end=" ") + for t in platform_tags(re.sub(r"[.-]", "_", plat.split("-", 1)[-1])): + print(t, end="\n ") diff --git a/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/_structures.py b/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/_structures.py new file mode 100644 index 00000000..95154975 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/_structures.py @@ -0,0 +1,67 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + + +class InfinityType: + def __repr__(self) -> str: + return "Infinity" + + def __hash__(self) -> int: + return hash(repr(self)) + + def __lt__(self, other: object) -> bool: + return False + + def __le__(self, other: object) -> bool: + return False + + def __eq__(self, other: object) -> bool: + return isinstance(other, self.__class__) + + def __ne__(self, other: object) -> bool: + return not isinstance(other, self.__class__) + + def __gt__(self, other: object) -> bool: + return True + + def __ge__(self, other: object) -> bool: + return True + + def __neg__(self: object) -> "NegativeInfinityType": + return NegativeInfinity + + +Infinity = InfinityType() + + +class NegativeInfinityType: + def __repr__(self) -> str: + return "-Infinity" + + def __hash__(self) -> int: + return hash(repr(self)) + + def __lt__(self, other: object) -> bool: + return True + + def __le__(self, other: object) -> bool: + return True + + def __eq__(self, other: object) -> bool: + return isinstance(other, self.__class__) + + def __ne__(self, other: object) -> bool: + return not isinstance(other, self.__class__) + + def __gt__(self, other: object) -> bool: + return False + + def __ge__(self, other: object) -> bool: + return False + + def __neg__(self: object) -> InfinityType: + return Infinity + + +NegativeInfinity = NegativeInfinityType() diff --git a/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/markers.py b/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/markers.py new file mode 100644 index 00000000..eb0541b8 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/markers.py @@ -0,0 +1,304 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import operator +import os +import platform +import sys +from typing import Any, Callable, Dict, List, Optional, Tuple, Union + +from setuptools.extern.pyparsing import ( # noqa: N817 + Forward, + Group, + Literal as L, + ParseException, + ParseResults, + QuotedString, + ZeroOrMore, + stringEnd, + stringStart, +) + +from .specifiers import InvalidSpecifier, Specifier + +__all__ = [ + "InvalidMarker", + "UndefinedComparison", + "UndefinedEnvironmentName", + "Marker", + "default_environment", +] + +Operator = Callable[[str, str], bool] + + +class InvalidMarker(ValueError): + """ + An invalid marker was found, users should refer to PEP 508. + """ + + +class UndefinedComparison(ValueError): + """ + An invalid operation was attempted on a value that doesn't support it. + """ + + +class UndefinedEnvironmentName(ValueError): + """ + A name was attempted to be used that does not exist inside of the + environment. + """ + + +class Node: + def __init__(self, value: Any) -> None: + self.value = value + + def __str__(self) -> str: + return str(self.value) + + def __repr__(self) -> str: + return f"<{self.__class__.__name__}('{self}')>" + + def serialize(self) -> str: + raise NotImplementedError + + +class Variable(Node): + def serialize(self) -> str: + return str(self) + + +class Value(Node): + def serialize(self) -> str: + return f'"{self}"' + + +class Op(Node): + def serialize(self) -> str: + return str(self) + + +VARIABLE = ( + L("implementation_version") + | L("platform_python_implementation") + | L("implementation_name") + | L("python_full_version") + | L("platform_release") + | L("platform_version") + | L("platform_machine") + | L("platform_system") + | L("python_version") + | L("sys_platform") + | L("os_name") + | L("os.name") # PEP-345 + | L("sys.platform") # PEP-345 + | L("platform.version") # PEP-345 + | L("platform.machine") # PEP-345 + | L("platform.python_implementation") # PEP-345 + | L("python_implementation") # undocumented setuptools legacy + | L("extra") # PEP-508 +) +ALIASES = { + "os.name": "os_name", + "sys.platform": "sys_platform", + "platform.version": "platform_version", + "platform.machine": "platform_machine", + "platform.python_implementation": "platform_python_implementation", + "python_implementation": "platform_python_implementation", +} +VARIABLE.setParseAction(lambda s, l, t: Variable(ALIASES.get(t[0], t[0]))) + +VERSION_CMP = ( + L("===") | L("==") | L(">=") | L("<=") | L("!=") | L("~=") | L(">") | L("<") +) + +MARKER_OP = VERSION_CMP | L("not in") | L("in") +MARKER_OP.setParseAction(lambda s, l, t: Op(t[0])) + +MARKER_VALUE = QuotedString("'") | QuotedString('"') +MARKER_VALUE.setParseAction(lambda s, l, t: Value(t[0])) + +BOOLOP = L("and") | L("or") + +MARKER_VAR = VARIABLE | MARKER_VALUE + +MARKER_ITEM = Group(MARKER_VAR + MARKER_OP + MARKER_VAR) +MARKER_ITEM.setParseAction(lambda s, l, t: tuple(t[0])) + +LPAREN = L("(").suppress() +RPAREN = L(")").suppress() + +MARKER_EXPR = Forward() +MARKER_ATOM = MARKER_ITEM | Group(LPAREN + MARKER_EXPR + RPAREN) +MARKER_EXPR << MARKER_ATOM + ZeroOrMore(BOOLOP + MARKER_EXPR) + +MARKER = stringStart + MARKER_EXPR + stringEnd + + +def _coerce_parse_result(results: Union[ParseResults, List[Any]]) -> List[Any]: + if isinstance(results, ParseResults): + return [_coerce_parse_result(i) for i in results] + else: + return results + + +def _format_marker( + marker: Union[List[str], Tuple[Node, ...], str], first: Optional[bool] = True +) -> str: + + assert isinstance(marker, (list, tuple, str)) + + # Sometimes we have a structure like [[...]] which is a single item list + # where the single item is itself it's own list. In that case we want skip + # the rest of this function so that we don't get extraneous () on the + # outside. + if ( + isinstance(marker, list) + and len(marker) == 1 + and isinstance(marker[0], (list, tuple)) + ): + return _format_marker(marker[0]) + + if isinstance(marker, list): + inner = (_format_marker(m, first=False) for m in marker) + if first: + return " ".join(inner) + else: + return "(" + " ".join(inner) + ")" + elif isinstance(marker, tuple): + return " ".join([m.serialize() for m in marker]) + else: + return marker + + +_operators: Dict[str, Operator] = { + "in": lambda lhs, rhs: lhs in rhs, + "not in": lambda lhs, rhs: lhs not in rhs, + "<": operator.lt, + "<=": operator.le, + "==": operator.eq, + "!=": operator.ne, + ">=": operator.ge, + ">": operator.gt, +} + + +def _eval_op(lhs: str, op: Op, rhs: str) -> bool: + try: + spec = Specifier("".join([op.serialize(), rhs])) + except InvalidSpecifier: + pass + else: + return spec.contains(lhs) + + oper: Optional[Operator] = _operators.get(op.serialize()) + if oper is None: + raise UndefinedComparison(f"Undefined {op!r} on {lhs!r} and {rhs!r}.") + + return oper(lhs, rhs) + + +class Undefined: + pass + + +_undefined = Undefined() + + +def _get_env(environment: Dict[str, str], name: str) -> str: + value: Union[str, Undefined] = environment.get(name, _undefined) + + if isinstance(value, Undefined): + raise UndefinedEnvironmentName( + f"{name!r} does not exist in evaluation environment." + ) + + return value + + +def _evaluate_markers(markers: List[Any], environment: Dict[str, str]) -> bool: + groups: List[List[bool]] = [[]] + + for marker in markers: + assert isinstance(marker, (list, tuple, str)) + + if isinstance(marker, list): + groups[-1].append(_evaluate_markers(marker, environment)) + elif isinstance(marker, tuple): + lhs, op, rhs = marker + + if isinstance(lhs, Variable): + lhs_value = _get_env(environment, lhs.value) + rhs_value = rhs.value + else: + lhs_value = lhs.value + rhs_value = _get_env(environment, rhs.value) + + groups[-1].append(_eval_op(lhs_value, op, rhs_value)) + else: + assert marker in ["and", "or"] + if marker == "or": + groups.append([]) + + return any(all(item) for item in groups) + + +def format_full_version(info: "sys._version_info") -> str: + version = "{0.major}.{0.minor}.{0.micro}".format(info) + kind = info.releaselevel + if kind != "final": + version += kind[0] + str(info.serial) + return version + + +def default_environment() -> Dict[str, str]: + iver = format_full_version(sys.implementation.version) + implementation_name = sys.implementation.name + return { + "implementation_name": implementation_name, + "implementation_version": iver, + "os_name": os.name, + "platform_machine": platform.machine(), + "platform_release": platform.release(), + "platform_system": platform.system(), + "platform_version": platform.version(), + "python_full_version": platform.python_version(), + "platform_python_implementation": platform.python_implementation(), + "python_version": ".".join(platform.python_version_tuple()[:2]), + "sys_platform": sys.platform, + } + + +class Marker: + def __init__(self, marker: str) -> None: + try: + self._markers = _coerce_parse_result(MARKER.parseString(marker)) + except ParseException as e: + raise InvalidMarker( + f"Invalid marker: {marker!r}, parse error at " + f"{marker[e.loc : e.loc + 8]!r}" + ) + + def __str__(self) -> str: + return _format_marker(self._markers) + + def __repr__(self) -> str: + return f"" + + def evaluate(self, environment: Optional[Dict[str, str]] = None) -> bool: + """Evaluate a marker. + + Return the boolean from evaluating the given marker against the + environment. environment is an optional argument to override all or + part of the determined environment. + + The environment is determined from the current Python process. + """ + current_environment = default_environment() + if environment is not None: + current_environment.update(environment) + + return _evaluate_markers(self._markers, current_environment) diff --git a/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/requirements.py b/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/requirements.py new file mode 100644 index 00000000..0d93231b --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/requirements.py @@ -0,0 +1,146 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import re +import string +import urllib.parse +from typing import List, Optional as TOptional, Set + +from setuptools.extern.pyparsing import ( # noqa + Combine, + Literal as L, + Optional, + ParseException, + Regex, + Word, + ZeroOrMore, + originalTextFor, + stringEnd, + stringStart, +) + +from .markers import MARKER_EXPR, Marker +from .specifiers import LegacySpecifier, Specifier, SpecifierSet + + +class InvalidRequirement(ValueError): + """ + An invalid requirement was found, users should refer to PEP 508. + """ + + +ALPHANUM = Word(string.ascii_letters + string.digits) + +LBRACKET = L("[").suppress() +RBRACKET = L("]").suppress() +LPAREN = L("(").suppress() +RPAREN = L(")").suppress() +COMMA = L(",").suppress() +SEMICOLON = L(";").suppress() +AT = L("@").suppress() + +PUNCTUATION = Word("-_.") +IDENTIFIER_END = ALPHANUM | (ZeroOrMore(PUNCTUATION) + ALPHANUM) +IDENTIFIER = Combine(ALPHANUM + ZeroOrMore(IDENTIFIER_END)) + +NAME = IDENTIFIER("name") +EXTRA = IDENTIFIER + +URI = Regex(r"[^ ]+")("url") +URL = AT + URI + +EXTRAS_LIST = EXTRA + ZeroOrMore(COMMA + EXTRA) +EXTRAS = (LBRACKET + Optional(EXTRAS_LIST) + RBRACKET)("extras") + +VERSION_PEP440 = Regex(Specifier._regex_str, re.VERBOSE | re.IGNORECASE) +VERSION_LEGACY = Regex(LegacySpecifier._regex_str, re.VERBOSE | re.IGNORECASE) + +VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY +VERSION_MANY = Combine( + VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), joinString=",", adjacent=False +)("_raw_spec") +_VERSION_SPEC = Optional((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY) +_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or "") + +VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier") +VERSION_SPEC.setParseAction(lambda s, l, t: t[1]) + +MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker") +MARKER_EXPR.setParseAction( + lambda s, l, t: Marker(s[t._original_start : t._original_end]) +) +MARKER_SEPARATOR = SEMICOLON +MARKER = MARKER_SEPARATOR + MARKER_EXPR + +VERSION_AND_MARKER = VERSION_SPEC + Optional(MARKER) +URL_AND_MARKER = URL + Optional(MARKER) + +NAMED_REQUIREMENT = NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER) + +REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd +# setuptools.extern.pyparsing isn't thread safe during initialization, so we do it eagerly, see +# issue #104 +REQUIREMENT.parseString("x[]") + + +class Requirement: + """Parse a requirement. + + Parse a given requirement string into its parts, such as name, specifier, + URL, and extras. Raises InvalidRequirement on a badly-formed requirement + string. + """ + + # TODO: Can we test whether something is contained within a requirement? + # If so how do we do that? Do we need to test against the _name_ of + # the thing as well as the version? What about the markers? + # TODO: Can we normalize the name and extra name? + + def __init__(self, requirement_string: str) -> None: + try: + req = REQUIREMENT.parseString(requirement_string) + except ParseException as e: + raise InvalidRequirement( + f'Parse error at "{ requirement_string[e.loc : e.loc + 8]!r}": {e.msg}' + ) + + self.name: str = req.name + if req.url: + parsed_url = urllib.parse.urlparse(req.url) + if parsed_url.scheme == "file": + if urllib.parse.urlunparse(parsed_url) != req.url: + raise InvalidRequirement("Invalid URL given") + elif not (parsed_url.scheme and parsed_url.netloc) or ( + not parsed_url.scheme and not parsed_url.netloc + ): + raise InvalidRequirement(f"Invalid URL: {req.url}") + self.url: TOptional[str] = req.url + else: + self.url = None + self.extras: Set[str] = set(req.extras.asList() if req.extras else []) + self.specifier: SpecifierSet = SpecifierSet(req.specifier) + self.marker: TOptional[Marker] = req.marker if req.marker else None + + def __str__(self) -> str: + parts: List[str] = [self.name] + + if self.extras: + formatted_extras = ",".join(sorted(self.extras)) + parts.append(f"[{formatted_extras}]") + + if self.specifier: + parts.append(str(self.specifier)) + + if self.url: + parts.append(f"@ {self.url}") + if self.marker: + parts.append(" ") + + if self.marker: + parts.append(f"; {self.marker}") + + return "".join(parts) + + def __repr__(self) -> str: + return f"" diff --git a/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/specifiers.py b/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/specifiers.py new file mode 100644 index 00000000..ce66bd4a --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/specifiers.py @@ -0,0 +1,828 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import abc +import functools +import itertools +import re +import warnings +from typing import ( + Callable, + Dict, + Iterable, + Iterator, + List, + Optional, + Pattern, + Set, + Tuple, + TypeVar, + Union, +) + +from .utils import canonicalize_version +from .version import LegacyVersion, Version, parse + +ParsedVersion = Union[Version, LegacyVersion] +UnparsedVersion = Union[Version, LegacyVersion, str] +VersionTypeVar = TypeVar("VersionTypeVar", bound=UnparsedVersion) +CallableOperator = Callable[[ParsedVersion, str], bool] + + +class InvalidSpecifier(ValueError): + """ + An invalid specifier was found, users should refer to PEP 440. + """ + + +class BaseSpecifier(metaclass=abc.ABCMeta): + @abc.abstractmethod + def __str__(self) -> str: + """ + Returns the str representation of this Specifier like object. This + should be representative of the Specifier itself. + """ + + @abc.abstractmethod + def __hash__(self) -> int: + """ + Returns a hash value for this Specifier like object. + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Returns a boolean representing whether or not the two Specifier like + objects are equal. + """ + + @abc.abstractmethod + def __ne__(self, other: object) -> bool: + """ + Returns a boolean representing whether or not the two Specifier like + objects are not equal. + """ + + @abc.abstractproperty + def prereleases(self) -> Optional[bool]: + """ + Returns whether or not pre-releases as a whole are allowed by this + specifier. + """ + + @prereleases.setter + def prereleases(self, value: bool) -> None: + """ + Sets whether or not pre-releases as a whole are allowed by this + specifier. + """ + + @abc.abstractmethod + def contains(self, item: str, prereleases: Optional[bool] = None) -> bool: + """ + Determines if the given item is contained within this specifier. + """ + + @abc.abstractmethod + def filter( + self, iterable: Iterable[VersionTypeVar], prereleases: Optional[bool] = None + ) -> Iterable[VersionTypeVar]: + """ + Takes an iterable of items and filters them so that only items which + are contained within this specifier are allowed in it. + """ + + +class _IndividualSpecifier(BaseSpecifier): + + _operators: Dict[str, str] = {} + _regex: Pattern[str] + + def __init__(self, spec: str = "", prereleases: Optional[bool] = None) -> None: + match = self._regex.search(spec) + if not match: + raise InvalidSpecifier(f"Invalid specifier: '{spec}'") + + self._spec: Tuple[str, str] = ( + match.group("operator").strip(), + match.group("version").strip(), + ) + + # Store whether or not this Specifier should accept prereleases + self._prereleases = prereleases + + def __repr__(self) -> str: + pre = ( + f", prereleases={self.prereleases!r}" + if self._prereleases is not None + else "" + ) + + return "<{}({!r}{})>".format(self.__class__.__name__, str(self), pre) + + def __str__(self) -> str: + return "{}{}".format(*self._spec) + + @property + def _canonical_spec(self) -> Tuple[str, str]: + return self._spec[0], canonicalize_version(self._spec[1]) + + def __hash__(self) -> int: + return hash(self._canonical_spec) + + def __eq__(self, other: object) -> bool: + if isinstance(other, str): + try: + other = self.__class__(str(other)) + except InvalidSpecifier: + return NotImplemented + elif not isinstance(other, self.__class__): + return NotImplemented + + return self._canonical_spec == other._canonical_spec + + def __ne__(self, other: object) -> bool: + if isinstance(other, str): + try: + other = self.__class__(str(other)) + except InvalidSpecifier: + return NotImplemented + elif not isinstance(other, self.__class__): + return NotImplemented + + return self._spec != other._spec + + def _get_operator(self, op: str) -> CallableOperator: + operator_callable: CallableOperator = getattr( + self, f"_compare_{self._operators[op]}" + ) + return operator_callable + + def _coerce_version(self, version: UnparsedVersion) -> ParsedVersion: + if not isinstance(version, (LegacyVersion, Version)): + version = parse(version) + return version + + @property + def operator(self) -> str: + return self._spec[0] + + @property + def version(self) -> str: + return self._spec[1] + + @property + def prereleases(self) -> Optional[bool]: + return self._prereleases + + @prereleases.setter + def prereleases(self, value: bool) -> None: + self._prereleases = value + + def __contains__(self, item: str) -> bool: + return self.contains(item) + + def contains( + self, item: UnparsedVersion, prereleases: Optional[bool] = None + ) -> bool: + + # Determine if prereleases are to be allowed or not. + if prereleases is None: + prereleases = self.prereleases + + # Normalize item to a Version or LegacyVersion, this allows us to have + # a shortcut for ``"2.0" in Specifier(">=2") + normalized_item = self._coerce_version(item) + + # Determine if we should be supporting prereleases in this specifier + # or not, if we do not support prereleases than we can short circuit + # logic if this version is a prereleases. + if normalized_item.is_prerelease and not prereleases: + return False + + # Actually do the comparison to determine if this item is contained + # within this Specifier or not. + operator_callable: CallableOperator = self._get_operator(self.operator) + return operator_callable(normalized_item, self.version) + + def filter( + self, iterable: Iterable[VersionTypeVar], prereleases: Optional[bool] = None + ) -> Iterable[VersionTypeVar]: + + yielded = False + found_prereleases = [] + + kw = {"prereleases": prereleases if prereleases is not None else True} + + # Attempt to iterate over all the values in the iterable and if any of + # them match, yield them. + for version in iterable: + parsed_version = self._coerce_version(version) + + if self.contains(parsed_version, **kw): + # If our version is a prerelease, and we were not set to allow + # prereleases, then we'll store it for later in case nothing + # else matches this specifier. + if parsed_version.is_prerelease and not ( + prereleases or self.prereleases + ): + found_prereleases.append(version) + # Either this is not a prerelease, or we should have been + # accepting prereleases from the beginning. + else: + yielded = True + yield version + + # Now that we've iterated over everything, determine if we've yielded + # any values, and if we have not and we have any prereleases stored up + # then we will go ahead and yield the prereleases. + if not yielded and found_prereleases: + for version in found_prereleases: + yield version + + +class LegacySpecifier(_IndividualSpecifier): + + _regex_str = r""" + (?P(==|!=|<=|>=|<|>)) + \s* + (?P + [^,;\s)]* # Since this is a "legacy" specifier, and the version + # string can be just about anything, we match everything + # except for whitespace, a semi-colon for marker support, + # a closing paren since versions can be enclosed in + # them, and a comma since it's a version separator. + ) + """ + + _regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE) + + _operators = { + "==": "equal", + "!=": "not_equal", + "<=": "less_than_equal", + ">=": "greater_than_equal", + "<": "less_than", + ">": "greater_than", + } + + def __init__(self, spec: str = "", prereleases: Optional[bool] = None) -> None: + super().__init__(spec, prereleases) + + warnings.warn( + "Creating a LegacyVersion has been deprecated and will be " + "removed in the next major release", + DeprecationWarning, + ) + + def _coerce_version(self, version: UnparsedVersion) -> LegacyVersion: + if not isinstance(version, LegacyVersion): + version = LegacyVersion(str(version)) + return version + + def _compare_equal(self, prospective: LegacyVersion, spec: str) -> bool: + return prospective == self._coerce_version(spec) + + def _compare_not_equal(self, prospective: LegacyVersion, spec: str) -> bool: + return prospective != self._coerce_version(spec) + + def _compare_less_than_equal(self, prospective: LegacyVersion, spec: str) -> bool: + return prospective <= self._coerce_version(spec) + + def _compare_greater_than_equal( + self, prospective: LegacyVersion, spec: str + ) -> bool: + return prospective >= self._coerce_version(spec) + + def _compare_less_than(self, prospective: LegacyVersion, spec: str) -> bool: + return prospective < self._coerce_version(spec) + + def _compare_greater_than(self, prospective: LegacyVersion, spec: str) -> bool: + return prospective > self._coerce_version(spec) + + +def _require_version_compare( + fn: Callable[["Specifier", ParsedVersion, str], bool] +) -> Callable[["Specifier", ParsedVersion, str], bool]: + @functools.wraps(fn) + def wrapped(self: "Specifier", prospective: ParsedVersion, spec: str) -> bool: + if not isinstance(prospective, Version): + return False + return fn(self, prospective, spec) + + return wrapped + + +class Specifier(_IndividualSpecifier): + + _regex_str = r""" + (?P(~=|==|!=|<=|>=|<|>|===)) + (?P + (?: + # The identity operators allow for an escape hatch that will + # do an exact string match of the version you wish to install. + # This will not be parsed by PEP 440 and we cannot determine + # any semantic meaning from it. This operator is discouraged + # but included entirely as an escape hatch. + (?<====) # Only match for the identity operator + \s* + [^\s]* # We just match everything, except for whitespace + # since we are only testing for strict identity. + ) + | + (?: + # The (non)equality operators allow for wild card and local + # versions to be specified so we have to define these two + # operators separately to enable that. + (?<===|!=) # Only match for equals and not equals + + \s* + v? + (?:[0-9]+!)? # epoch + [0-9]+(?:\.[0-9]+)* # release + (?: # pre release + [-_\.]? + (a|b|c|rc|alpha|beta|pre|preview) + [-_\.]? + [0-9]* + )? + (?: # post release + (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) + )? + + # You cannot use a wild card and a dev or local version + # together so group them with a | and make them optional. + (?: + (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release + (?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local + | + \.\* # Wild card syntax of .* + )? + ) + | + (?: + # The compatible operator requires at least two digits in the + # release segment. + (?<=~=) # Only match for the compatible operator + + \s* + v? + (?:[0-9]+!)? # epoch + [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *) + (?: # pre release + [-_\.]? + (a|b|c|rc|alpha|beta|pre|preview) + [-_\.]? + [0-9]* + )? + (?: # post release + (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) + )? + (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release + ) + | + (?: + # All other operators only allow a sub set of what the + # (non)equality operators do. Specifically they do not allow + # local versions to be specified nor do they allow the prefix + # matching wild cards. + (?=": "greater_than_equal", + "<": "less_than", + ">": "greater_than", + "===": "arbitrary", + } + + @_require_version_compare + def _compare_compatible(self, prospective: ParsedVersion, spec: str) -> bool: + + # Compatible releases have an equivalent combination of >= and ==. That + # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to + # implement this in terms of the other specifiers instead of + # implementing it ourselves. The only thing we need to do is construct + # the other specifiers. + + # We want everything but the last item in the version, but we want to + # ignore suffix segments. + prefix = ".".join( + list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1] + ) + + # Add the prefix notation to the end of our string + prefix += ".*" + + return self._get_operator(">=")(prospective, spec) and self._get_operator("==")( + prospective, prefix + ) + + @_require_version_compare + def _compare_equal(self, prospective: ParsedVersion, spec: str) -> bool: + + # We need special logic to handle prefix matching + if spec.endswith(".*"): + # In the case of prefix matching we want to ignore local segment. + prospective = Version(prospective.public) + # Split the spec out by dots, and pretend that there is an implicit + # dot in between a release segment and a pre-release segment. + split_spec = _version_split(spec[:-2]) # Remove the trailing .* + + # Split the prospective version out by dots, and pretend that there + # is an implicit dot in between a release segment and a pre-release + # segment. + split_prospective = _version_split(str(prospective)) + + # Shorten the prospective version to be the same length as the spec + # so that we can determine if the specifier is a prefix of the + # prospective version or not. + shortened_prospective = split_prospective[: len(split_spec)] + + # Pad out our two sides with zeros so that they both equal the same + # length. + padded_spec, padded_prospective = _pad_version( + split_spec, shortened_prospective + ) + + return padded_prospective == padded_spec + else: + # Convert our spec string into a Version + spec_version = Version(spec) + + # If the specifier does not have a local segment, then we want to + # act as if the prospective version also does not have a local + # segment. + if not spec_version.local: + prospective = Version(prospective.public) + + return prospective == spec_version + + @_require_version_compare + def _compare_not_equal(self, prospective: ParsedVersion, spec: str) -> bool: + return not self._compare_equal(prospective, spec) + + @_require_version_compare + def _compare_less_than_equal(self, prospective: ParsedVersion, spec: str) -> bool: + + # NB: Local version identifiers are NOT permitted in the version + # specifier, so local version labels can be universally removed from + # the prospective version. + return Version(prospective.public) <= Version(spec) + + @_require_version_compare + def _compare_greater_than_equal( + self, prospective: ParsedVersion, spec: str + ) -> bool: + + # NB: Local version identifiers are NOT permitted in the version + # specifier, so local version labels can be universally removed from + # the prospective version. + return Version(prospective.public) >= Version(spec) + + @_require_version_compare + def _compare_less_than(self, prospective: ParsedVersion, spec_str: str) -> bool: + + # Convert our spec to a Version instance, since we'll want to work with + # it as a version. + spec = Version(spec_str) + + # Check to see if the prospective version is less than the spec + # version. If it's not we can short circuit and just return False now + # instead of doing extra unneeded work. + if not prospective < spec: + return False + + # This special case is here so that, unless the specifier itself + # includes is a pre-release version, that we do not accept pre-release + # versions for the version mentioned in the specifier (e.g. <3.1 should + # not match 3.1.dev0, but should match 3.0.dev0). + if not spec.is_prerelease and prospective.is_prerelease: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # If we've gotten to here, it means that prospective version is both + # less than the spec version *and* it's not a pre-release of the same + # version in the spec. + return True + + @_require_version_compare + def _compare_greater_than(self, prospective: ParsedVersion, spec_str: str) -> bool: + + # Convert our spec to a Version instance, since we'll want to work with + # it as a version. + spec = Version(spec_str) + + # Check to see if the prospective version is greater than the spec + # version. If it's not we can short circuit and just return False now + # instead of doing extra unneeded work. + if not prospective > spec: + return False + + # This special case is here so that, unless the specifier itself + # includes is a post-release version, that we do not accept + # post-release versions for the version mentioned in the specifier + # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0). + if not spec.is_postrelease and prospective.is_postrelease: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # Ensure that we do not allow a local version of the version mentioned + # in the specifier, which is technically greater than, to match. + if prospective.local is not None: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # If we've gotten to here, it means that prospective version is both + # greater than the spec version *and* it's not a pre-release of the + # same version in the spec. + return True + + def _compare_arbitrary(self, prospective: Version, spec: str) -> bool: + return str(prospective).lower() == str(spec).lower() + + @property + def prereleases(self) -> bool: + + # If there is an explicit prereleases set for this, then we'll just + # blindly use that. + if self._prereleases is not None: + return self._prereleases + + # Look at all of our specifiers and determine if they are inclusive + # operators, and if they are if they are including an explicit + # prerelease. + operator, version = self._spec + if operator in ["==", ">=", "<=", "~=", "==="]: + # The == specifier can include a trailing .*, if it does we + # want to remove before parsing. + if operator == "==" and version.endswith(".*"): + version = version[:-2] + + # Parse the version, and if it is a pre-release than this + # specifier allows pre-releases. + if parse(version).is_prerelease: + return True + + return False + + @prereleases.setter + def prereleases(self, value: bool) -> None: + self._prereleases = value + + +_prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$") + + +def _version_split(version: str) -> List[str]: + result: List[str] = [] + for item in version.split("."): + match = _prefix_regex.search(item) + if match: + result.extend(match.groups()) + else: + result.append(item) + return result + + +def _is_not_suffix(segment: str) -> bool: + return not any( + segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post") + ) + + +def _pad_version(left: List[str], right: List[str]) -> Tuple[List[str], List[str]]: + left_split, right_split = [], [] + + # Get the release segment of our versions + left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left))) + right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right))) + + # Get the rest of our versions + left_split.append(left[len(left_split[0]) :]) + right_split.append(right[len(right_split[0]) :]) + + # Insert our padding + left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0]))) + right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0]))) + + return (list(itertools.chain(*left_split)), list(itertools.chain(*right_split))) + + +class SpecifierSet(BaseSpecifier): + def __init__( + self, specifiers: str = "", prereleases: Optional[bool] = None + ) -> None: + + # Split on , to break each individual specifier into it's own item, and + # strip each item to remove leading/trailing whitespace. + split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()] + + # Parsed each individual specifier, attempting first to make it a + # Specifier and falling back to a LegacySpecifier. + parsed: Set[_IndividualSpecifier] = set() + for specifier in split_specifiers: + try: + parsed.add(Specifier(specifier)) + except InvalidSpecifier: + parsed.add(LegacySpecifier(specifier)) + + # Turn our parsed specifiers into a frozen set and save them for later. + self._specs = frozenset(parsed) + + # Store our prereleases value so we can use it later to determine if + # we accept prereleases or not. + self._prereleases = prereleases + + def __repr__(self) -> str: + pre = ( + f", prereleases={self.prereleases!r}" + if self._prereleases is not None + else "" + ) + + return "".format(str(self), pre) + + def __str__(self) -> str: + return ",".join(sorted(str(s) for s in self._specs)) + + def __hash__(self) -> int: + return hash(self._specs) + + def __and__(self, other: Union["SpecifierSet", str]) -> "SpecifierSet": + if isinstance(other, str): + other = SpecifierSet(other) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + specifier = SpecifierSet() + specifier._specs = frozenset(self._specs | other._specs) + + if self._prereleases is None and other._prereleases is not None: + specifier._prereleases = other._prereleases + elif self._prereleases is not None and other._prereleases is None: + specifier._prereleases = self._prereleases + elif self._prereleases == other._prereleases: + specifier._prereleases = self._prereleases + else: + raise ValueError( + "Cannot combine SpecifierSets with True and False prerelease " + "overrides." + ) + + return specifier + + def __eq__(self, other: object) -> bool: + if isinstance(other, (str, _IndividualSpecifier)): + other = SpecifierSet(str(other)) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + return self._specs == other._specs + + def __ne__(self, other: object) -> bool: + if isinstance(other, (str, _IndividualSpecifier)): + other = SpecifierSet(str(other)) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + return self._specs != other._specs + + def __len__(self) -> int: + return len(self._specs) + + def __iter__(self) -> Iterator[_IndividualSpecifier]: + return iter(self._specs) + + @property + def prereleases(self) -> Optional[bool]: + + # If we have been given an explicit prerelease modifier, then we'll + # pass that through here. + if self._prereleases is not None: + return self._prereleases + + # If we don't have any specifiers, and we don't have a forced value, + # then we'll just return None since we don't know if this should have + # pre-releases or not. + if not self._specs: + return None + + # Otherwise we'll see if any of the given specifiers accept + # prereleases, if any of them do we'll return True, otherwise False. + return any(s.prereleases for s in self._specs) + + @prereleases.setter + def prereleases(self, value: bool) -> None: + self._prereleases = value + + def __contains__(self, item: UnparsedVersion) -> bool: + return self.contains(item) + + def contains( + self, item: UnparsedVersion, prereleases: Optional[bool] = None + ) -> bool: + + # Ensure that our item is a Version or LegacyVersion instance. + if not isinstance(item, (LegacyVersion, Version)): + item = parse(item) + + # Determine if we're forcing a prerelease or not, if we're not forcing + # one for this particular filter call, then we'll use whatever the + # SpecifierSet thinks for whether or not we should support prereleases. + if prereleases is None: + prereleases = self.prereleases + + # We can determine if we're going to allow pre-releases by looking to + # see if any of the underlying items supports them. If none of them do + # and this item is a pre-release then we do not allow it and we can + # short circuit that here. + # Note: This means that 1.0.dev1 would not be contained in something + # like >=1.0.devabc however it would be in >=1.0.debabc,>0.0.dev0 + if not prereleases and item.is_prerelease: + return False + + # We simply dispatch to the underlying specs here to make sure that the + # given version is contained within all of them. + # Note: This use of all() here means that an empty set of specifiers + # will always return True, this is an explicit design decision. + return all(s.contains(item, prereleases=prereleases) for s in self._specs) + + def filter( + self, iterable: Iterable[VersionTypeVar], prereleases: Optional[bool] = None + ) -> Iterable[VersionTypeVar]: + + # Determine if we're forcing a prerelease or not, if we're not forcing + # one for this particular filter call, then we'll use whatever the + # SpecifierSet thinks for whether or not we should support prereleases. + if prereleases is None: + prereleases = self.prereleases + + # If we have any specifiers, then we want to wrap our iterable in the + # filter method for each one, this will act as a logical AND amongst + # each specifier. + if self._specs: + for spec in self._specs: + iterable = spec.filter(iterable, prereleases=bool(prereleases)) + return iterable + # If we do not have any specifiers, then we need to have a rough filter + # which will filter out any pre-releases, unless there are no final + # releases, and which will filter out LegacyVersion in general. + else: + filtered: List[VersionTypeVar] = [] + found_prereleases: List[VersionTypeVar] = [] + + item: UnparsedVersion + parsed_version: Union[Version, LegacyVersion] + + for item in iterable: + # Ensure that we some kind of Version class for this item. + if not isinstance(item, (LegacyVersion, Version)): + parsed_version = parse(item) + else: + parsed_version = item + + # Filter out any item which is parsed as a LegacyVersion + if isinstance(parsed_version, LegacyVersion): + continue + + # Store any item which is a pre-release for later unless we've + # already found a final version or we are accepting prereleases + if parsed_version.is_prerelease and not prereleases: + if not filtered: + found_prereleases.append(item) + else: + filtered.append(item) + + # If we've found no items except for pre-releases, then we'll go + # ahead and use the pre-releases + if not filtered and found_prereleases and prereleases is None: + return found_prereleases + + return filtered diff --git a/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/tags.py b/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/tags.py new file mode 100644 index 00000000..e65890a9 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/tags.py @@ -0,0 +1,484 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import logging +import platform +import sys +import sysconfig +from importlib.machinery import EXTENSION_SUFFIXES +from typing import ( + Dict, + FrozenSet, + Iterable, + Iterator, + List, + Optional, + Sequence, + Tuple, + Union, + cast, +) + +from . import _manylinux, _musllinux + +logger = logging.getLogger(__name__) + +PythonVersion = Sequence[int] +MacVersion = Tuple[int, int] + +INTERPRETER_SHORT_NAMES: Dict[str, str] = { + "python": "py", # Generic. + "cpython": "cp", + "pypy": "pp", + "ironpython": "ip", + "jython": "jy", +} + + +_32_BIT_INTERPRETER = sys.maxsize <= 2 ** 32 + + +class Tag: + """ + A representation of the tag triple for a wheel. + + Instances are considered immutable and thus are hashable. Equality checking + is also supported. + """ + + __slots__ = ["_interpreter", "_abi", "_platform", "_hash"] + + def __init__(self, interpreter: str, abi: str, platform: str) -> None: + self._interpreter = interpreter.lower() + self._abi = abi.lower() + self._platform = platform.lower() + # The __hash__ of every single element in a Set[Tag] will be evaluated each time + # that a set calls its `.disjoint()` method, which may be called hundreds of + # times when scanning a page of links for packages with tags matching that + # Set[Tag]. Pre-computing the value here produces significant speedups for + # downstream consumers. + self._hash = hash((self._interpreter, self._abi, self._platform)) + + @property + def interpreter(self) -> str: + return self._interpreter + + @property + def abi(self) -> str: + return self._abi + + @property + def platform(self) -> str: + return self._platform + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Tag): + return NotImplemented + + return ( + (self._hash == other._hash) # Short-circuit ASAP for perf reasons. + and (self._platform == other._platform) + and (self._abi == other._abi) + and (self._interpreter == other._interpreter) + ) + + def __hash__(self) -> int: + return self._hash + + def __str__(self) -> str: + return f"{self._interpreter}-{self._abi}-{self._platform}" + + def __repr__(self) -> str: + return "<{self} @ {self_id}>".format(self=self, self_id=id(self)) + + +def parse_tag(tag: str) -> FrozenSet[Tag]: + """ + Parses the provided tag (e.g. `py3-none-any`) into a frozenset of Tag instances. + + Returning a set is required due to the possibility that the tag is a + compressed tag set. + """ + tags = set() + interpreters, abis, platforms = tag.split("-") + for interpreter in interpreters.split("."): + for abi in abis.split("."): + for platform_ in platforms.split("."): + tags.add(Tag(interpreter, abi, platform_)) + return frozenset(tags) + + +def _get_config_var(name: str, warn: bool = False) -> Union[int, str, None]: + value = sysconfig.get_config_var(name) + if value is None and warn: + logger.debug( + "Config variable '%s' is unset, Python ABI tag may be incorrect", name + ) + return value + + +def _normalize_string(string: str) -> str: + return string.replace(".", "_").replace("-", "_") + + +def _abi3_applies(python_version: PythonVersion) -> bool: + """ + Determine if the Python version supports abi3. + + PEP 384 was first implemented in Python 3.2. + """ + return len(python_version) > 1 and tuple(python_version) >= (3, 2) + + +def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> List[str]: + py_version = tuple(py_version) # To allow for version comparison. + abis = [] + version = _version_nodot(py_version[:2]) + debug = pymalloc = ucs4 = "" + with_debug = _get_config_var("Py_DEBUG", warn) + has_refcount = hasattr(sys, "gettotalrefcount") + # Windows doesn't set Py_DEBUG, so checking for support of debug-compiled + # extension modules is the best option. + # https://github.com/pypa/pip/issues/3383#issuecomment-173267692 + has_ext = "_d.pyd" in EXTENSION_SUFFIXES + if with_debug or (with_debug is None and (has_refcount or has_ext)): + debug = "d" + if py_version < (3, 8): + with_pymalloc = _get_config_var("WITH_PYMALLOC", warn) + if with_pymalloc or with_pymalloc is None: + pymalloc = "m" + if py_version < (3, 3): + unicode_size = _get_config_var("Py_UNICODE_SIZE", warn) + if unicode_size == 4 or ( + unicode_size is None and sys.maxunicode == 0x10FFFF + ): + ucs4 = "u" + elif debug: + # Debug builds can also load "normal" extension modules. + # We can also assume no UCS-4 or pymalloc requirement. + abis.append(f"cp{version}") + abis.insert( + 0, + "cp{version}{debug}{pymalloc}{ucs4}".format( + version=version, debug=debug, pymalloc=pymalloc, ucs4=ucs4 + ), + ) + return abis + + +def cpython_tags( + python_version: Optional[PythonVersion] = None, + abis: Optional[Iterable[str]] = None, + platforms: Optional[Iterable[str]] = None, + *, + warn: bool = False, +) -> Iterator[Tag]: + """ + Yields the tags for a CPython interpreter. + + The tags consist of: + - cp-- + - cp-abi3- + - cp-none- + - cp-abi3- # Older Python versions down to 3.2. + + If python_version only specifies a major version then user-provided ABIs and + the 'none' ABItag will be used. + + If 'abi3' or 'none' are specified in 'abis' then they will be yielded at + their normal position and not at the beginning. + """ + if not python_version: + python_version = sys.version_info[:2] + + interpreter = "cp{}".format(_version_nodot(python_version[:2])) + + if abis is None: + if len(python_version) > 1: + abis = _cpython_abis(python_version, warn) + else: + abis = [] + abis = list(abis) + # 'abi3' and 'none' are explicitly handled later. + for explicit_abi in ("abi3", "none"): + try: + abis.remove(explicit_abi) + except ValueError: + pass + + platforms = list(platforms or platform_tags()) + for abi in abis: + for platform_ in platforms: + yield Tag(interpreter, abi, platform_) + if _abi3_applies(python_version): + yield from (Tag(interpreter, "abi3", platform_) for platform_ in platforms) + yield from (Tag(interpreter, "none", platform_) for platform_ in platforms) + + if _abi3_applies(python_version): + for minor_version in range(python_version[1] - 1, 1, -1): + for platform_ in platforms: + interpreter = "cp{version}".format( + version=_version_nodot((python_version[0], minor_version)) + ) + yield Tag(interpreter, "abi3", platform_) + + +def _generic_abi() -> Iterator[str]: + abi = sysconfig.get_config_var("SOABI") + if abi: + yield _normalize_string(abi) + + +def generic_tags( + interpreter: Optional[str] = None, + abis: Optional[Iterable[str]] = None, + platforms: Optional[Iterable[str]] = None, + *, + warn: bool = False, +) -> Iterator[Tag]: + """ + Yields the tags for a generic interpreter. + + The tags consist of: + - -- + + The "none" ABI will be added if it was not explicitly provided. + """ + if not interpreter: + interp_name = interpreter_name() + interp_version = interpreter_version(warn=warn) + interpreter = "".join([interp_name, interp_version]) + if abis is None: + abis = _generic_abi() + platforms = list(platforms or platform_tags()) + abis = list(abis) + if "none" not in abis: + abis.append("none") + for abi in abis: + for platform_ in platforms: + yield Tag(interpreter, abi, platform_) + + +def _py_interpreter_range(py_version: PythonVersion) -> Iterator[str]: + """ + Yields Python versions in descending order. + + After the latest version, the major-only version will be yielded, and then + all previous versions of that major version. + """ + if len(py_version) > 1: + yield "py{version}".format(version=_version_nodot(py_version[:2])) + yield "py{major}".format(major=py_version[0]) + if len(py_version) > 1: + for minor in range(py_version[1] - 1, -1, -1): + yield "py{version}".format(version=_version_nodot((py_version[0], minor))) + + +def compatible_tags( + python_version: Optional[PythonVersion] = None, + interpreter: Optional[str] = None, + platforms: Optional[Iterable[str]] = None, +) -> Iterator[Tag]: + """ + Yields the sequence of tags that are compatible with a specific version of Python. + + The tags consist of: + - py*-none- + - -none-any # ... if `interpreter` is provided. + - py*-none-any + """ + if not python_version: + python_version = sys.version_info[:2] + platforms = list(platforms or platform_tags()) + for version in _py_interpreter_range(python_version): + for platform_ in platforms: + yield Tag(version, "none", platform_) + if interpreter: + yield Tag(interpreter, "none", "any") + for version in _py_interpreter_range(python_version): + yield Tag(version, "none", "any") + + +def _mac_arch(arch: str, is_32bit: bool = _32_BIT_INTERPRETER) -> str: + if not is_32bit: + return arch + + if arch.startswith("ppc"): + return "ppc" + + return "i386" + + +def _mac_binary_formats(version: MacVersion, cpu_arch: str) -> List[str]: + formats = [cpu_arch] + if cpu_arch == "x86_64": + if version < (10, 4): + return [] + formats.extend(["intel", "fat64", "fat32"]) + + elif cpu_arch == "i386": + if version < (10, 4): + return [] + formats.extend(["intel", "fat32", "fat"]) + + elif cpu_arch == "ppc64": + # TODO: Need to care about 32-bit PPC for ppc64 through 10.2? + if version > (10, 5) or version < (10, 4): + return [] + formats.append("fat64") + + elif cpu_arch == "ppc": + if version > (10, 6): + return [] + formats.extend(["fat32", "fat"]) + + if cpu_arch in {"arm64", "x86_64"}: + formats.append("universal2") + + if cpu_arch in {"x86_64", "i386", "ppc64", "ppc", "intel"}: + formats.append("universal") + + return formats + + +def mac_platforms( + version: Optional[MacVersion] = None, arch: Optional[str] = None +) -> Iterator[str]: + """ + Yields the platform tags for a macOS system. + + The `version` parameter is a two-item tuple specifying the macOS version to + generate platform tags for. The `arch` parameter is the CPU architecture to + generate platform tags for. Both parameters default to the appropriate value + for the current system. + """ + version_str, _, cpu_arch = platform.mac_ver() + if version is None: + version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2]))) + else: + version = version + if arch is None: + arch = _mac_arch(cpu_arch) + else: + arch = arch + + if (10, 0) <= version and version < (11, 0): + # Prior to Mac OS 11, each yearly release of Mac OS bumped the + # "minor" version number. The major version was always 10. + for minor_version in range(version[1], -1, -1): + compat_version = 10, minor_version + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + yield "macosx_{major}_{minor}_{binary_format}".format( + major=10, minor=minor_version, binary_format=binary_format + ) + + if version >= (11, 0): + # Starting with Mac OS 11, each yearly release bumps the major version + # number. The minor versions are now the midyear updates. + for major_version in range(version[0], 10, -1): + compat_version = major_version, 0 + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + yield "macosx_{major}_{minor}_{binary_format}".format( + major=major_version, minor=0, binary_format=binary_format + ) + + if version >= (11, 0): + # Mac OS 11 on x86_64 is compatible with binaries from previous releases. + # Arm64 support was introduced in 11.0, so no Arm binaries from previous + # releases exist. + # + # However, the "universal2" binary format can have a + # macOS version earlier than 11.0 when the x86_64 part of the binary supports + # that version of macOS. + if arch == "x86_64": + for minor_version in range(16, 3, -1): + compat_version = 10, minor_version + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + yield "macosx_{major}_{minor}_{binary_format}".format( + major=compat_version[0], + minor=compat_version[1], + binary_format=binary_format, + ) + else: + for minor_version in range(16, 3, -1): + compat_version = 10, minor_version + binary_format = "universal2" + yield "macosx_{major}_{minor}_{binary_format}".format( + major=compat_version[0], + minor=compat_version[1], + binary_format=binary_format, + ) + + +def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]: + linux = _normalize_string(sysconfig.get_platform()) + if is_32bit: + if linux == "linux_x86_64": + linux = "linux_i686" + elif linux == "linux_aarch64": + linux = "linux_armv7l" + _, arch = linux.split("_", 1) + yield from _manylinux.platform_tags(linux, arch) + yield from _musllinux.platform_tags(arch) + yield linux + + +def _generic_platforms() -> Iterator[str]: + yield _normalize_string(sysconfig.get_platform()) + + +def platform_tags() -> Iterator[str]: + """ + Provides the platform tags for this installation. + """ + if platform.system() == "Darwin": + return mac_platforms() + elif platform.system() == "Linux": + return _linux_platforms() + else: + return _generic_platforms() + + +def interpreter_name() -> str: + """ + Returns the name of the running interpreter. + """ + name = sys.implementation.name + return INTERPRETER_SHORT_NAMES.get(name) or name + + +def interpreter_version(*, warn: bool = False) -> str: + """ + Returns the version of the running interpreter. + """ + version = _get_config_var("py_version_nodot", warn=warn) + if version: + version = str(version) + else: + version = _version_nodot(sys.version_info[:2]) + return version + + +def _version_nodot(version: PythonVersion) -> str: + return "".join(map(str, version)) + + +def sys_tags(*, warn: bool = False) -> Iterator[Tag]: + """ + Returns the sequence of tag triples for the running interpreter. + + The order of the sequence corresponds to priority order for the + interpreter, from most to least important. + """ + + interp_name = interpreter_name() + if interp_name == "cp": + yield from cpython_tags(warn=warn) + else: + yield from generic_tags() + + yield from compatible_tags() diff --git a/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/utils.py b/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/utils.py new file mode 100644 index 00000000..bab11b80 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/utils.py @@ -0,0 +1,136 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import re +from typing import FrozenSet, NewType, Tuple, Union, cast + +from .tags import Tag, parse_tag +from .version import InvalidVersion, Version + +BuildTag = Union[Tuple[()], Tuple[int, str]] +NormalizedName = NewType("NormalizedName", str) + + +class InvalidWheelFilename(ValueError): + """ + An invalid wheel filename was found, users should refer to PEP 427. + """ + + +class InvalidSdistFilename(ValueError): + """ + An invalid sdist filename was found, users should refer to the packaging user guide. + """ + + +_canonicalize_regex = re.compile(r"[-_.]+") +# PEP 427: The build number must start with a digit. +_build_tag_regex = re.compile(r"(\d+)(.*)") + + +def canonicalize_name(name: str) -> NormalizedName: + # This is taken from PEP 503. + value = _canonicalize_regex.sub("-", name).lower() + return cast(NormalizedName, value) + + +def canonicalize_version(version: Union[Version, str]) -> str: + """ + This is very similar to Version.__str__, but has one subtle difference + with the way it handles the release segment. + """ + if isinstance(version, str): + try: + parsed = Version(version) + except InvalidVersion: + # Legacy versions cannot be normalized + return version + else: + parsed = version + + parts = [] + + # Epoch + if parsed.epoch != 0: + parts.append(f"{parsed.epoch}!") + + # Release segment + # NB: This strips trailing '.0's to normalize + parts.append(re.sub(r"(\.0)+$", "", ".".join(str(x) for x in parsed.release))) + + # Pre-release + if parsed.pre is not None: + parts.append("".join(str(x) for x in parsed.pre)) + + # Post-release + if parsed.post is not None: + parts.append(f".post{parsed.post}") + + # Development release + if parsed.dev is not None: + parts.append(f".dev{parsed.dev}") + + # Local version segment + if parsed.local is not None: + parts.append(f"+{parsed.local}") + + return "".join(parts) + + +def parse_wheel_filename( + filename: str, +) -> Tuple[NormalizedName, Version, BuildTag, FrozenSet[Tag]]: + if not filename.endswith(".whl"): + raise InvalidWheelFilename( + f"Invalid wheel filename (extension must be '.whl'): {filename}" + ) + + filename = filename[:-4] + dashes = filename.count("-") + if dashes not in (4, 5): + raise InvalidWheelFilename( + f"Invalid wheel filename (wrong number of parts): {filename}" + ) + + parts = filename.split("-", dashes - 2) + name_part = parts[0] + # See PEP 427 for the rules on escaping the project name + if "__" in name_part or re.match(r"^[\w\d._]*$", name_part, re.UNICODE) is None: + raise InvalidWheelFilename(f"Invalid project name: {filename}") + name = canonicalize_name(name_part) + version = Version(parts[1]) + if dashes == 5: + build_part = parts[2] + build_match = _build_tag_regex.match(build_part) + if build_match is None: + raise InvalidWheelFilename( + f"Invalid build number: {build_part} in '{filename}'" + ) + build = cast(BuildTag, (int(build_match.group(1)), build_match.group(2))) + else: + build = () + tags = parse_tag(parts[-1]) + return (name, version, build, tags) + + +def parse_sdist_filename(filename: str) -> Tuple[NormalizedName, Version]: + if filename.endswith(".tar.gz"): + file_stem = filename[: -len(".tar.gz")] + elif filename.endswith(".zip"): + file_stem = filename[: -len(".zip")] + else: + raise InvalidSdistFilename( + f"Invalid sdist filename (extension must be '.tar.gz' or '.zip'):" + f" {filename}" + ) + + # We are requiring a PEP 440 version, which cannot contain dashes, + # so we split on the last dash. + name_part, sep, version_part = file_stem.rpartition("-") + if not sep: + raise InvalidSdistFilename(f"Invalid sdist filename: {filename}") + + name = canonicalize_name(name_part) + version = Version(version_part) + return (name, version) diff --git a/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/version.py b/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/version.py new file mode 100644 index 00000000..de9a09a4 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/_vendor/packaging/version.py @@ -0,0 +1,504 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import collections +import itertools +import re +import warnings +from typing import Callable, Iterator, List, Optional, SupportsInt, Tuple, Union + +from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType + +__all__ = ["parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"] + +InfiniteTypes = Union[InfinityType, NegativeInfinityType] +PrePostDevType = Union[InfiniteTypes, Tuple[str, int]] +SubLocalType = Union[InfiniteTypes, int, str] +LocalType = Union[ + NegativeInfinityType, + Tuple[ + Union[ + SubLocalType, + Tuple[SubLocalType, str], + Tuple[NegativeInfinityType, SubLocalType], + ], + ..., + ], +] +CmpKey = Tuple[ + int, Tuple[int, ...], PrePostDevType, PrePostDevType, PrePostDevType, LocalType +] +LegacyCmpKey = Tuple[int, Tuple[str, ...]] +VersionComparisonMethod = Callable[ + [Union[CmpKey, LegacyCmpKey], Union[CmpKey, LegacyCmpKey]], bool +] + +_Version = collections.namedtuple( + "_Version", ["epoch", "release", "dev", "pre", "post", "local"] +) + + +def parse(version: str) -> Union["LegacyVersion", "Version"]: + """ + Parse the given version string and return either a :class:`Version` object + or a :class:`LegacyVersion` object depending on if the given version is + a valid PEP 440 version or a legacy version. + """ + try: + return Version(version) + except InvalidVersion: + return LegacyVersion(version) + + +class InvalidVersion(ValueError): + """ + An invalid version was found, users should refer to PEP 440. + """ + + +class _BaseVersion: + _key: Union[CmpKey, LegacyCmpKey] + + def __hash__(self) -> int: + return hash(self._key) + + # Please keep the duplicated `isinstance` check + # in the six comparisons hereunder + # unless you find a way to avoid adding overhead function calls. + def __lt__(self, other: "_BaseVersion") -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key < other._key + + def __le__(self, other: "_BaseVersion") -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key <= other._key + + def __eq__(self, other: object) -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key == other._key + + def __ge__(self, other: "_BaseVersion") -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key >= other._key + + def __gt__(self, other: "_BaseVersion") -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key > other._key + + def __ne__(self, other: object) -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key != other._key + + +class LegacyVersion(_BaseVersion): + def __init__(self, version: str) -> None: + self._version = str(version) + self._key = _legacy_cmpkey(self._version) + + warnings.warn( + "Creating a LegacyVersion has been deprecated and will be " + "removed in the next major release", + DeprecationWarning, + ) + + def __str__(self) -> str: + return self._version + + def __repr__(self) -> str: + return f"" + + @property + def public(self) -> str: + return self._version + + @property + def base_version(self) -> str: + return self._version + + @property + def epoch(self) -> int: + return -1 + + @property + def release(self) -> None: + return None + + @property + def pre(self) -> None: + return None + + @property + def post(self) -> None: + return None + + @property + def dev(self) -> None: + return None + + @property + def local(self) -> None: + return None + + @property + def is_prerelease(self) -> bool: + return False + + @property + def is_postrelease(self) -> bool: + return False + + @property + def is_devrelease(self) -> bool: + return False + + +_legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE) + +_legacy_version_replacement_map = { + "pre": "c", + "preview": "c", + "-": "final-", + "rc": "c", + "dev": "@", +} + + +def _parse_version_parts(s: str) -> Iterator[str]: + for part in _legacy_version_component_re.split(s): + part = _legacy_version_replacement_map.get(part, part) + + if not part or part == ".": + continue + + if part[:1] in "0123456789": + # pad for numeric comparison + yield part.zfill(8) + else: + yield "*" + part + + # ensure that alpha/beta/candidate are before final + yield "*final" + + +def _legacy_cmpkey(version: str) -> LegacyCmpKey: + + # We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch + # greater than or equal to 0. This will effectively put the LegacyVersion, + # which uses the defacto standard originally implemented by setuptools, + # as before all PEP 440 versions. + epoch = -1 + + # This scheme is taken from pkg_resources.parse_version setuptools prior to + # it's adoption of the packaging library. + parts: List[str] = [] + for part in _parse_version_parts(version.lower()): + if part.startswith("*"): + # remove "-" before a prerelease tag + if part < "*final": + while parts and parts[-1] == "*final-": + parts.pop() + + # remove trailing zeros from each series of numeric parts + while parts and parts[-1] == "00000000": + parts.pop() + + parts.append(part) + + return epoch, tuple(parts) + + +# Deliberately not anchored to the start and end of the string, to make it +# easier for 3rd party code to reuse +VERSION_PATTERN = r""" + v? + (?: + (?:(?P[0-9]+)!)? # epoch + (?P[0-9]+(?:\.[0-9]+)*) # release segment + (?P
                                          # pre-release
+            [-_\.]?
+            (?P(a|b|c|rc|alpha|beta|pre|preview))
+            [-_\.]?
+            (?P[0-9]+)?
+        )?
+        (?P                                         # post release
+            (?:-(?P[0-9]+))
+            |
+            (?:
+                [-_\.]?
+                (?Ppost|rev|r)
+                [-_\.]?
+                (?P[0-9]+)?
+            )
+        )?
+        (?P                                          # dev release
+            [-_\.]?
+            (?Pdev)
+            [-_\.]?
+            (?P[0-9]+)?
+        )?
+    )
+    (?:\+(?P[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?       # local version
+"""
+
+
+class Version(_BaseVersion):
+
+    _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
+
+    def __init__(self, version: str) -> None:
+
+        # Validate the version and parse it into pieces
+        match = self._regex.search(version)
+        if not match:
+            raise InvalidVersion(f"Invalid version: '{version}'")
+
+        # Store the parsed out pieces of the version
+        self._version = _Version(
+            epoch=int(match.group("epoch")) if match.group("epoch") else 0,
+            release=tuple(int(i) for i in match.group("release").split(".")),
+            pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")),
+            post=_parse_letter_version(
+                match.group("post_l"), match.group("post_n1") or match.group("post_n2")
+            ),
+            dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")),
+            local=_parse_local_version(match.group("local")),
+        )
+
+        # Generate a key which will be used for sorting
+        self._key = _cmpkey(
+            self._version.epoch,
+            self._version.release,
+            self._version.pre,
+            self._version.post,
+            self._version.dev,
+            self._version.local,
+        )
+
+    def __repr__(self) -> str:
+        return f""
+
+    def __str__(self) -> str:
+        parts = []
+
+        # Epoch
+        if self.epoch != 0:
+            parts.append(f"{self.epoch}!")
+
+        # Release segment
+        parts.append(".".join(str(x) for x in self.release))
+
+        # Pre-release
+        if self.pre is not None:
+            parts.append("".join(str(x) for x in self.pre))
+
+        # Post-release
+        if self.post is not None:
+            parts.append(f".post{self.post}")
+
+        # Development release
+        if self.dev is not None:
+            parts.append(f".dev{self.dev}")
+
+        # Local version segment
+        if self.local is not None:
+            parts.append(f"+{self.local}")
+
+        return "".join(parts)
+
+    @property
+    def epoch(self) -> int:
+        _epoch: int = self._version.epoch
+        return _epoch
+
+    @property
+    def release(self) -> Tuple[int, ...]:
+        _release: Tuple[int, ...] = self._version.release
+        return _release
+
+    @property
+    def pre(self) -> Optional[Tuple[str, int]]:
+        _pre: Optional[Tuple[str, int]] = self._version.pre
+        return _pre
+
+    @property
+    def post(self) -> Optional[int]:
+        return self._version.post[1] if self._version.post else None
+
+    @property
+    def dev(self) -> Optional[int]:
+        return self._version.dev[1] if self._version.dev else None
+
+    @property
+    def local(self) -> Optional[str]:
+        if self._version.local:
+            return ".".join(str(x) for x in self._version.local)
+        else:
+            return None
+
+    @property
+    def public(self) -> str:
+        return str(self).split("+", 1)[0]
+
+    @property
+    def base_version(self) -> str:
+        parts = []
+
+        # Epoch
+        if self.epoch != 0:
+            parts.append(f"{self.epoch}!")
+
+        # Release segment
+        parts.append(".".join(str(x) for x in self.release))
+
+        return "".join(parts)
+
+    @property
+    def is_prerelease(self) -> bool:
+        return self.dev is not None or self.pre is not None
+
+    @property
+    def is_postrelease(self) -> bool:
+        return self.post is not None
+
+    @property
+    def is_devrelease(self) -> bool:
+        return self.dev is not None
+
+    @property
+    def major(self) -> int:
+        return self.release[0] if len(self.release) >= 1 else 0
+
+    @property
+    def minor(self) -> int:
+        return self.release[1] if len(self.release) >= 2 else 0
+
+    @property
+    def micro(self) -> int:
+        return self.release[2] if len(self.release) >= 3 else 0
+
+
+def _parse_letter_version(
+    letter: str, number: Union[str, bytes, SupportsInt]
+) -> Optional[Tuple[str, int]]:
+
+    if letter:
+        # We consider there to be an implicit 0 in a pre-release if there is
+        # not a numeral associated with it.
+        if number is None:
+            number = 0
+
+        # We normalize any letters to their lower case form
+        letter = letter.lower()
+
+        # We consider some words to be alternate spellings of other words and
+        # in those cases we want to normalize the spellings to our preferred
+        # spelling.
+        if letter == "alpha":
+            letter = "a"
+        elif letter == "beta":
+            letter = "b"
+        elif letter in ["c", "pre", "preview"]:
+            letter = "rc"
+        elif letter in ["rev", "r"]:
+            letter = "post"
+
+        return letter, int(number)
+    if not letter and number:
+        # We assume if we are given a number, but we are not given a letter
+        # then this is using the implicit post release syntax (e.g. 1.0-1)
+        letter = "post"
+
+        return letter, int(number)
+
+    return None
+
+
+_local_version_separators = re.compile(r"[\._-]")
+
+
+def _parse_local_version(local: str) -> Optional[LocalType]:
+    """
+    Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
+    """
+    if local is not None:
+        return tuple(
+            part.lower() if not part.isdigit() else int(part)
+            for part in _local_version_separators.split(local)
+        )
+    return None
+
+
+def _cmpkey(
+    epoch: int,
+    release: Tuple[int, ...],
+    pre: Optional[Tuple[str, int]],
+    post: Optional[Tuple[str, int]],
+    dev: Optional[Tuple[str, int]],
+    local: Optional[Tuple[SubLocalType]],
+) -> CmpKey:
+
+    # When we compare a release version, we want to compare it with all of the
+    # trailing zeros removed. So we'll use a reverse the list, drop all the now
+    # leading zeros until we come to something non zero, then take the rest
+    # re-reverse it back into the correct order and make it a tuple and use
+    # that for our sorting key.
+    _release = tuple(
+        reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))
+    )
+
+    # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
+    # We'll do this by abusing the pre segment, but we _only_ want to do this
+    # if there is not a pre or a post segment. If we have one of those then
+    # the normal sorting rules will handle this case correctly.
+    if pre is None and post is None and dev is not None:
+        _pre: PrePostDevType = NegativeInfinity
+    # Versions without a pre-release (except as noted above) should sort after
+    # those with one.
+    elif pre is None:
+        _pre = Infinity
+    else:
+        _pre = pre
+
+    # Versions without a post segment should sort before those with one.
+    if post is None:
+        _post: PrePostDevType = NegativeInfinity
+
+    else:
+        _post = post
+
+    # Versions without a development segment should sort after those with one.
+    if dev is None:
+        _dev: PrePostDevType = Infinity
+
+    else:
+        _dev = dev
+
+    if local is None:
+        # Versions without a local segment should sort before those with one.
+        _local: LocalType = NegativeInfinity
+    else:
+        # Versions with a local segment need that segment parsed to implement
+        # the sorting rules in PEP440.
+        # - Alpha numeric segments sort before numeric segments
+        # - Alpha numeric segments sort lexicographically
+        # - Numeric segments sort numerically
+        # - Shorter versions sort before longer versions when the prefixes
+        #   match exactly
+        _local = tuple(
+            (i, "") if isinstance(i, int) else (NegativeInfinity, i) for i in local
+        )
+
+    return epoch, _release, _pre, _post, _dev, _local
diff --git a/venv/lib/python3.10/site-packages/setuptools/_vendor/pyparsing.py b/venv/lib/python3.10/site-packages/setuptools/_vendor/pyparsing.py
new file mode 100644
index 00000000..cf75e1e5
--- /dev/null
+++ b/venv/lib/python3.10/site-packages/setuptools/_vendor/pyparsing.py
@@ -0,0 +1,5742 @@
+# module pyparsing.py
+#
+# Copyright (c) 2003-2018  Paul T. McGuire
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__doc__ = \
+"""
+pyparsing module - Classes and methods to define and execute parsing grammars
+=============================================================================
+
+The pyparsing module is an alternative approach to creating and executing simple grammars,
+vs. the traditional lex/yacc approach, or the use of regular expressions.  With pyparsing, you
+don't need to learn a new syntax for defining grammars or matching expressions - the parsing module
+provides a library of classes that you use to construct the grammar directly in Python.
+
+Here is a program to parse "Hello, World!" (or any greeting of the form 
+C{", !"}), built up using L{Word}, L{Literal}, and L{And} elements 
+(L{'+'} operator gives L{And} expressions, strings are auto-converted to
+L{Literal} expressions)::
+
+    from pyparsing import Word, alphas
+
+    # define grammar of a greeting
+    greet = Word(alphas) + "," + Word(alphas) + "!"
+
+    hello = "Hello, World!"
+    print (hello, "->", greet.parseString(hello))
+
+The program outputs the following::
+
+    Hello, World! -> ['Hello', ',', 'World', '!']
+
+The Python representation of the grammar is quite readable, owing to the self-explanatory
+class names, and the use of '+', '|' and '^' operators.
+
+The L{ParseResults} object returned from L{ParserElement.parseString} can be accessed as a nested list, a dictionary, or an
+object with named attributes.
+
+The pyparsing module handles some of the problems that are typically vexing when writing text parsers:
+ - extra or missing whitespace (the above program will also handle "Hello,World!", "Hello  ,  World  !", etc.)
+ - quoted strings
+ - embedded comments
+
+
+Getting Started -
+-----------------
+Visit the classes L{ParserElement} and L{ParseResults} to see the base classes that most other pyparsing
+classes inherit from. Use the docstrings for examples of how to:
+ - construct literal match expressions from L{Literal} and L{CaselessLiteral} classes
+ - construct character word-group expressions using the L{Word} class
+ - see how to create repetitive expressions using L{ZeroOrMore} and L{OneOrMore} classes
+ - use L{'+'}, L{'|'}, L{'^'}, and L{'&'} operators to combine simple expressions into more complex ones
+ - associate names with your parsed results using L{ParserElement.setResultsName}
+ - find some helpful expression short-cuts like L{delimitedList} and L{oneOf}
+ - find more useful common expressions in the L{pyparsing_common} namespace class
+"""
+
+__version__ = "2.2.1"
+__versionTime__ = "18 Sep 2018 00:49 UTC"
+__author__ = "Paul McGuire "
+
+import string
+from weakref import ref as wkref
+import copy
+import sys
+import warnings
+import re
+import sre_constants
+import collections
+import pprint
+import traceback
+import types
+from datetime import datetime
+
+try:
+    from _thread import RLock
+except ImportError:
+    from threading import RLock
+
+try:
+    # Python 3
+    from collections.abc import Iterable
+    from collections.abc import MutableMapping
+except ImportError:
+    # Python 2.7
+    from collections import Iterable
+    from collections import MutableMapping
+
+try:
+    from collections import OrderedDict as _OrderedDict
+except ImportError:
+    try:
+        from ordereddict import OrderedDict as _OrderedDict
+    except ImportError:
+        _OrderedDict = None
+
+#~ sys.stderr.write( "testing pyparsing module, version %s, %s\n" % (__version__,__versionTime__ ) )
+
+__all__ = [
+'And', 'CaselessKeyword', 'CaselessLiteral', 'CharsNotIn', 'Combine', 'Dict', 'Each', 'Empty',
+'FollowedBy', 'Forward', 'GoToColumn', 'Group', 'Keyword', 'LineEnd', 'LineStart', 'Literal',
+'MatchFirst', 'NoMatch', 'NotAny', 'OneOrMore', 'OnlyOnce', 'Optional', 'Or',
+'ParseBaseException', 'ParseElementEnhance', 'ParseException', 'ParseExpression', 'ParseFatalException',
+'ParseResults', 'ParseSyntaxException', 'ParserElement', 'QuotedString', 'RecursiveGrammarException',
+'Regex', 'SkipTo', 'StringEnd', 'StringStart', 'Suppress', 'Token', 'TokenConverter', 
+'White', 'Word', 'WordEnd', 'WordStart', 'ZeroOrMore',
+'alphanums', 'alphas', 'alphas8bit', 'anyCloseTag', 'anyOpenTag', 'cStyleComment', 'col',
+'commaSeparatedList', 'commonHTMLEntity', 'countedArray', 'cppStyleComment', 'dblQuotedString',
+'dblSlashComment', 'delimitedList', 'dictOf', 'downcaseTokens', 'empty', 'hexnums',
+'htmlComment', 'javaStyleComment', 'line', 'lineEnd', 'lineStart', 'lineno',
+'makeHTMLTags', 'makeXMLTags', 'matchOnlyAtCol', 'matchPreviousExpr', 'matchPreviousLiteral',
+'nestedExpr', 'nullDebugAction', 'nums', 'oneOf', 'opAssoc', 'operatorPrecedence', 'printables',
+'punc8bit', 'pythonStyleComment', 'quotedString', 'removeQuotes', 'replaceHTMLEntity', 
+'replaceWith', 'restOfLine', 'sglQuotedString', 'srange', 'stringEnd',
+'stringStart', 'traceParseAction', 'unicodeString', 'upcaseTokens', 'withAttribute',
+'indentedBlock', 'originalTextFor', 'ungroup', 'infixNotation','locatedExpr', 'withClass',
+'CloseMatch', 'tokenMap', 'pyparsing_common',
+]
+
+system_version = tuple(sys.version_info)[:3]
+PY_3 = system_version[0] == 3
+if PY_3:
+    _MAX_INT = sys.maxsize
+    basestring = str
+    unichr = chr
+    _ustr = str
+
+    # build list of single arg builtins, that can be used as parse actions
+    singleArgBuiltins = [sum, len, sorted, reversed, list, tuple, set, any, all, min, max]
+
+else:
+    _MAX_INT = sys.maxint
+    range = xrange
+
+    def _ustr(obj):
+        """Drop-in replacement for str(obj) that tries to be Unicode friendly. It first tries
+           str(obj). If that fails with a UnicodeEncodeError, then it tries unicode(obj). It
+           then < returns the unicode object | encodes it with the default encoding | ... >.
+        """
+        if isinstance(obj,unicode):
+            return obj
+
+        try:
+            # If this works, then _ustr(obj) has the same behaviour as str(obj), so
+            # it won't break any existing code.
+            return str(obj)
+
+        except UnicodeEncodeError:
+            # Else encode it
+            ret = unicode(obj).encode(sys.getdefaultencoding(), 'xmlcharrefreplace')
+            xmlcharref = Regex(r'&#\d+;')
+            xmlcharref.setParseAction(lambda t: '\\u' + hex(int(t[0][2:-1]))[2:])
+            return xmlcharref.transformString(ret)
+
+    # build list of single arg builtins, tolerant of Python version, that can be used as parse actions
+    singleArgBuiltins = []
+    import __builtin__
+    for fname in "sum len sorted reversed list tuple set any all min max".split():
+        try:
+            singleArgBuiltins.append(getattr(__builtin__,fname))
+        except AttributeError:
+            continue
+            
+_generatorType = type((y for y in range(1)))
+ 
+def _xml_escape(data):
+    """Escape &, <, >, ", ', etc. in a string of data."""
+
+    # ampersand must be replaced first
+    from_symbols = '&><"\''
+    to_symbols = ('&'+s+';' for s in "amp gt lt quot apos".split())
+    for from_,to_ in zip(from_symbols, to_symbols):
+        data = data.replace(from_, to_)
+    return data
+
+class _Constants(object):
+    pass
+
+alphas     = string.ascii_uppercase + string.ascii_lowercase
+nums       = "0123456789"
+hexnums    = nums + "ABCDEFabcdef"
+alphanums  = alphas + nums
+_bslash    = chr(92)
+printables = "".join(c for c in string.printable if c not in string.whitespace)
+
+class ParseBaseException(Exception):
+    """base exception class for all parsing runtime exceptions"""
+    # Performance tuning: we construct a *lot* of these, so keep this
+    # constructor as small and fast as possible
+    def __init__( self, pstr, loc=0, msg=None, elem=None ):
+        self.loc = loc
+        if msg is None:
+            self.msg = pstr
+            self.pstr = ""
+        else:
+            self.msg = msg
+            self.pstr = pstr
+        self.parserElement = elem
+        self.args = (pstr, loc, msg)
+
+    @classmethod
+    def _from_exception(cls, pe):
+        """
+        internal factory method to simplify creating one type of ParseException 
+        from another - avoids having __init__ signature conflicts among subclasses
+        """
+        return cls(pe.pstr, pe.loc, pe.msg, pe.parserElement)
+
+    def __getattr__( self, aname ):
+        """supported attributes by name are:
+            - lineno - returns the line number of the exception text
+            - col - returns the column number of the exception text
+            - line - returns the line containing the exception text
+        """
+        if( aname == "lineno" ):
+            return lineno( self.loc, self.pstr )
+        elif( aname in ("col", "column") ):
+            return col( self.loc, self.pstr )
+        elif( aname == "line" ):
+            return line( self.loc, self.pstr )
+        else:
+            raise AttributeError(aname)
+
+    def __str__( self ):
+        return "%s (at char %d), (line:%d, col:%d)" % \
+                ( self.msg, self.loc, self.lineno, self.column )
+    def __repr__( self ):
+        return _ustr(self)
+    def markInputline( self, markerString = ">!<" ):
+        """Extracts the exception line from the input string, and marks
+           the location of the exception with a special symbol.
+        """
+        line_str = self.line
+        line_column = self.column - 1
+        if markerString:
+            line_str = "".join((line_str[:line_column],
+                                markerString, line_str[line_column:]))
+        return line_str.strip()
+    def __dir__(self):
+        return "lineno col line".split() + dir(type(self))
+
+class ParseException(ParseBaseException):
+    """
+    Exception thrown when parse expressions don't match class;
+    supported attributes by name are:
+     - lineno - returns the line number of the exception text
+     - col - returns the column number of the exception text
+     - line - returns the line containing the exception text
+        
+    Example::
+        try:
+            Word(nums).setName("integer").parseString("ABC")
+        except ParseException as pe:
+            print(pe)
+            print("column: {}".format(pe.col))
+            
+    prints::
+       Expected integer (at char 0), (line:1, col:1)
+        column: 1
+    """
+    pass
+
+class ParseFatalException(ParseBaseException):
+    """user-throwable exception thrown when inconsistent parse content
+       is found; stops all parsing immediately"""
+    pass
+
+class ParseSyntaxException(ParseFatalException):
+    """just like L{ParseFatalException}, but thrown internally when an
+       L{ErrorStop} ('-' operator) indicates that parsing is to stop 
+       immediately because an unbacktrackable syntax error has been found"""
+    pass
+
+#~ class ReparseException(ParseBaseException):
+    #~ """Experimental class - parse actions can raise this exception to cause
+       #~ pyparsing to reparse the input string:
+        #~ - with a modified input string, and/or
+        #~ - with a modified start location
+       #~ Set the values of the ReparseException in the constructor, and raise the
+       #~ exception in a parse action to cause pyparsing to use the new string/location.
+       #~ Setting the values as None causes no change to be made.
+       #~ """
+    #~ def __init_( self, newstring, restartLoc ):
+        #~ self.newParseText = newstring
+        #~ self.reparseLoc = restartLoc
+
+class RecursiveGrammarException(Exception):
+    """exception thrown by L{ParserElement.validate} if the grammar could be improperly recursive"""
+    def __init__( self, parseElementList ):
+        self.parseElementTrace = parseElementList
+
+    def __str__( self ):
+        return "RecursiveGrammarException: %s" % self.parseElementTrace
+
+class _ParseResultsWithOffset(object):
+    def __init__(self,p1,p2):
+        self.tup = (p1,p2)
+    def __getitem__(self,i):
+        return self.tup[i]
+    def __repr__(self):
+        return repr(self.tup[0])
+    def setOffset(self,i):
+        self.tup = (self.tup[0],i)
+
+class ParseResults(object):
+    """
+    Structured parse results, to provide multiple means of access to the parsed data:
+       - as a list (C{len(results)})
+       - by list index (C{results[0], results[1]}, etc.)
+       - by attribute (C{results.} - see L{ParserElement.setResultsName})
+
+    Example::
+        integer = Word(nums)
+        date_str = (integer.setResultsName("year") + '/' 
+                        + integer.setResultsName("month") + '/' 
+                        + integer.setResultsName("day"))
+        # equivalent form:
+        # date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+
+        # parseString returns a ParseResults object
+        result = date_str.parseString("1999/12/31")
+
+        def test(s, fn=repr):
+            print("%s -> %s" % (s, fn(eval(s))))
+        test("list(result)")
+        test("result[0]")
+        test("result['month']")
+        test("result.day")
+        test("'month' in result")
+        test("'minutes' in result")
+        test("result.dump()", str)
+    prints::
+        list(result) -> ['1999', '/', '12', '/', '31']
+        result[0] -> '1999'
+        result['month'] -> '12'
+        result.day -> '31'
+        'month' in result -> True
+        'minutes' in result -> False
+        result.dump() -> ['1999', '/', '12', '/', '31']
+        - day: 31
+        - month: 12
+        - year: 1999
+    """
+    def __new__(cls, toklist=None, name=None, asList=True, modal=True ):
+        if isinstance(toklist, cls):
+            return toklist
+        retobj = object.__new__(cls)
+        retobj.__doinit = True
+        return retobj
+
+    # Performance tuning: we construct a *lot* of these, so keep this
+    # constructor as small and fast as possible
+    def __init__( self, toklist=None, name=None, asList=True, modal=True, isinstance=isinstance ):
+        if self.__doinit:
+            self.__doinit = False
+            self.__name = None
+            self.__parent = None
+            self.__accumNames = {}
+            self.__asList = asList
+            self.__modal = modal
+            if toklist is None:
+                toklist = []
+            if isinstance(toklist, list):
+                self.__toklist = toklist[:]
+            elif isinstance(toklist, _generatorType):
+                self.__toklist = list(toklist)
+            else:
+                self.__toklist = [toklist]
+            self.__tokdict = dict()
+
+        if name is not None and name:
+            if not modal:
+                self.__accumNames[name] = 0
+            if isinstance(name,int):
+                name = _ustr(name) # will always return a str, but use _ustr for consistency
+            self.__name = name
+            if not (isinstance(toklist, (type(None), basestring, list)) and toklist in (None,'',[])):
+                if isinstance(toklist,basestring):
+                    toklist = [ toklist ]
+                if asList:
+                    if isinstance(toklist,ParseResults):
+                        self[name] = _ParseResultsWithOffset(toklist.copy(),0)
+                    else:
+                        self[name] = _ParseResultsWithOffset(ParseResults(toklist[0]),0)
+                    self[name].__name = name
+                else:
+                    try:
+                        self[name] = toklist[0]
+                    except (KeyError,TypeError,IndexError):
+                        self[name] = toklist
+
+    def __getitem__( self, i ):
+        if isinstance( i, (int,slice) ):
+            return self.__toklist[i]
+        else:
+            if i not in self.__accumNames:
+                return self.__tokdict[i][-1][0]
+            else:
+                return ParseResults([ v[0] for v in self.__tokdict[i] ])
+
+    def __setitem__( self, k, v, isinstance=isinstance ):
+        if isinstance(v,_ParseResultsWithOffset):
+            self.__tokdict[k] = self.__tokdict.get(k,list()) + [v]
+            sub = v[0]
+        elif isinstance(k,(int,slice)):
+            self.__toklist[k] = v
+            sub = v
+        else:
+            self.__tokdict[k] = self.__tokdict.get(k,list()) + [_ParseResultsWithOffset(v,0)]
+            sub = v
+        if isinstance(sub,ParseResults):
+            sub.__parent = wkref(self)
+
+    def __delitem__( self, i ):
+        if isinstance(i,(int,slice)):
+            mylen = len( self.__toklist )
+            del self.__toklist[i]
+
+            # convert int to slice
+            if isinstance(i, int):
+                if i < 0:
+                    i += mylen
+                i = slice(i, i+1)
+            # get removed indices
+            removed = list(range(*i.indices(mylen)))
+            removed.reverse()
+            # fixup indices in token dictionary
+            for name,occurrences in self.__tokdict.items():
+                for j in removed:
+                    for k, (value, position) in enumerate(occurrences):
+                        occurrences[k] = _ParseResultsWithOffset(value, position - (position > j))
+        else:
+            del self.__tokdict[i]
+
+    def __contains__( self, k ):
+        return k in self.__tokdict
+
+    def __len__( self ): return len( self.__toklist )
+    def __bool__(self): return ( not not self.__toklist )
+    __nonzero__ = __bool__
+    def __iter__( self ): return iter( self.__toklist )
+    def __reversed__( self ): return iter( self.__toklist[::-1] )
+    def _iterkeys( self ):
+        if hasattr(self.__tokdict, "iterkeys"):
+            return self.__tokdict.iterkeys()
+        else:
+            return iter(self.__tokdict)
+
+    def _itervalues( self ):
+        return (self[k] for k in self._iterkeys())
+            
+    def _iteritems( self ):
+        return ((k, self[k]) for k in self._iterkeys())
+
+    if PY_3:
+        keys = _iterkeys       
+        """Returns an iterator of all named result keys (Python 3.x only)."""
+
+        values = _itervalues
+        """Returns an iterator of all named result values (Python 3.x only)."""
+
+        items = _iteritems
+        """Returns an iterator of all named result key-value tuples (Python 3.x only)."""
+
+    else:
+        iterkeys = _iterkeys
+        """Returns an iterator of all named result keys (Python 2.x only)."""
+
+        itervalues = _itervalues
+        """Returns an iterator of all named result values (Python 2.x only)."""
+
+        iteritems = _iteritems
+        """Returns an iterator of all named result key-value tuples (Python 2.x only)."""
+
+        def keys( self ):
+            """Returns all named result keys (as a list in Python 2.x, as an iterator in Python 3.x)."""
+            return list(self.iterkeys())
+
+        def values( self ):
+            """Returns all named result values (as a list in Python 2.x, as an iterator in Python 3.x)."""
+            return list(self.itervalues())
+                
+        def items( self ):
+            """Returns all named result key-values (as a list of tuples in Python 2.x, as an iterator in Python 3.x)."""
+            return list(self.iteritems())
+
+    def haskeys( self ):
+        """Since keys() returns an iterator, this method is helpful in bypassing
+           code that looks for the existence of any defined results names."""
+        return bool(self.__tokdict)
+        
+    def pop( self, *args, **kwargs):
+        """
+        Removes and returns item at specified index (default=C{last}).
+        Supports both C{list} and C{dict} semantics for C{pop()}. If passed no
+        argument or an integer argument, it will use C{list} semantics
+        and pop tokens from the list of parsed tokens. If passed a 
+        non-integer argument (most likely a string), it will use C{dict}
+        semantics and pop the corresponding value from any defined 
+        results names. A second default return value argument is 
+        supported, just as in C{dict.pop()}.
+
+        Example::
+            def remove_first(tokens):
+                tokens.pop(0)
+            print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321']
+            print(OneOrMore(Word(nums)).addParseAction(remove_first).parseString("0 123 321")) # -> ['123', '321']
+
+            label = Word(alphas)
+            patt = label("LABEL") + OneOrMore(Word(nums))
+            print(patt.parseString("AAB 123 321").dump())
+
+            # Use pop() in a parse action to remove named result (note that corresponding value is not
+            # removed from list form of results)
+            def remove_LABEL(tokens):
+                tokens.pop("LABEL")
+                return tokens
+            patt.addParseAction(remove_LABEL)
+            print(patt.parseString("AAB 123 321").dump())
+        prints::
+            ['AAB', '123', '321']
+            - LABEL: AAB
+
+            ['AAB', '123', '321']
+        """
+        if not args:
+            args = [-1]
+        for k,v in kwargs.items():
+            if k == 'default':
+                args = (args[0], v)
+            else:
+                raise TypeError("pop() got an unexpected keyword argument '%s'" % k)
+        if (isinstance(args[0], int) or 
+                        len(args) == 1 or 
+                        args[0] in self):
+            index = args[0]
+            ret = self[index]
+            del self[index]
+            return ret
+        else:
+            defaultvalue = args[1]
+            return defaultvalue
+
+    def get(self, key, defaultValue=None):
+        """
+        Returns named result matching the given key, or if there is no
+        such name, then returns the given C{defaultValue} or C{None} if no
+        C{defaultValue} is specified.
+
+        Similar to C{dict.get()}.
+        
+        Example::
+            integer = Word(nums)
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")           
+
+            result = date_str.parseString("1999/12/31")
+            print(result.get("year")) # -> '1999'
+            print(result.get("hour", "not specified")) # -> 'not specified'
+            print(result.get("hour")) # -> None
+        """
+        if key in self:
+            return self[key]
+        else:
+            return defaultValue
+
+    def insert( self, index, insStr ):
+        """
+        Inserts new element at location index in the list of parsed tokens.
+        
+        Similar to C{list.insert()}.
+
+        Example::
+            print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321']
+
+            # use a parse action to insert the parse location in the front of the parsed results
+            def insert_locn(locn, tokens):
+                tokens.insert(0, locn)
+            print(OneOrMore(Word(nums)).addParseAction(insert_locn).parseString("0 123 321")) # -> [0, '0', '123', '321']
+        """
+        self.__toklist.insert(index, insStr)
+        # fixup indices in token dictionary
+        for name,occurrences in self.__tokdict.items():
+            for k, (value, position) in enumerate(occurrences):
+                occurrences[k] = _ParseResultsWithOffset(value, position + (position > index))
+
+    def append( self, item ):
+        """
+        Add single element to end of ParseResults list of elements.
+
+        Example::
+            print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321']
+            
+            # use a parse action to compute the sum of the parsed integers, and add it to the end
+            def append_sum(tokens):
+                tokens.append(sum(map(int, tokens)))
+            print(OneOrMore(Word(nums)).addParseAction(append_sum).parseString("0 123 321")) # -> ['0', '123', '321', 444]
+        """
+        self.__toklist.append(item)
+
+    def extend( self, itemseq ):
+        """
+        Add sequence of elements to end of ParseResults list of elements.
+
+        Example::
+            patt = OneOrMore(Word(alphas))
+            
+            # use a parse action to append the reverse of the matched strings, to make a palindrome
+            def make_palindrome(tokens):
+                tokens.extend(reversed([t[::-1] for t in tokens]))
+                return ''.join(tokens)
+            print(patt.addParseAction(make_palindrome).parseString("lskdj sdlkjf lksd")) # -> 'lskdjsdlkjflksddsklfjkldsjdksl'
+        """
+        if isinstance(itemseq, ParseResults):
+            self += itemseq
+        else:
+            self.__toklist.extend(itemseq)
+
+    def clear( self ):
+        """
+        Clear all elements and results names.
+        """
+        del self.__toklist[:]
+        self.__tokdict.clear()
+
+    def __getattr__( self, name ):
+        try:
+            return self[name]
+        except KeyError:
+            return ""
+            
+        if name in self.__tokdict:
+            if name not in self.__accumNames:
+                return self.__tokdict[name][-1][0]
+            else:
+                return ParseResults([ v[0] for v in self.__tokdict[name] ])
+        else:
+            return ""
+
+    def __add__( self, other ):
+        ret = self.copy()
+        ret += other
+        return ret
+
+    def __iadd__( self, other ):
+        if other.__tokdict:
+            offset = len(self.__toklist)
+            addoffset = lambda a: offset if a<0 else a+offset
+            otheritems = other.__tokdict.items()
+            otherdictitems = [(k, _ParseResultsWithOffset(v[0],addoffset(v[1])) )
+                                for (k,vlist) in otheritems for v in vlist]
+            for k,v in otherdictitems:
+                self[k] = v
+                if isinstance(v[0],ParseResults):
+                    v[0].__parent = wkref(self)
+            
+        self.__toklist += other.__toklist
+        self.__accumNames.update( other.__accumNames )
+        return self
+
+    def __radd__(self, other):
+        if isinstance(other,int) and other == 0:
+            # useful for merging many ParseResults using sum() builtin
+            return self.copy()
+        else:
+            # this may raise a TypeError - so be it
+            return other + self
+        
+    def __repr__( self ):
+        return "(%s, %s)" % ( repr( self.__toklist ), repr( self.__tokdict ) )
+
+    def __str__( self ):
+        return '[' + ', '.join(_ustr(i) if isinstance(i, ParseResults) else repr(i) for i in self.__toklist) + ']'
+
+    def _asStringList( self, sep='' ):
+        out = []
+        for item in self.__toklist:
+            if out and sep:
+                out.append(sep)
+            if isinstance( item, ParseResults ):
+                out += item._asStringList()
+            else:
+                out.append( _ustr(item) )
+        return out
+
+    def asList( self ):
+        """
+        Returns the parse results as a nested list of matching tokens, all converted to strings.
+
+        Example::
+            patt = OneOrMore(Word(alphas))
+            result = patt.parseString("sldkj lsdkj sldkj")
+            # even though the result prints in string-like form, it is actually a pyparsing ParseResults
+            print(type(result), result) # ->  ['sldkj', 'lsdkj', 'sldkj']
+            
+            # Use asList() to create an actual list
+            result_list = result.asList()
+            print(type(result_list), result_list) # ->  ['sldkj', 'lsdkj', 'sldkj']
+        """
+        return [res.asList() if isinstance(res,ParseResults) else res for res in self.__toklist]
+
+    def asDict( self ):
+        """
+        Returns the named parse results as a nested dictionary.
+
+        Example::
+            integer = Word(nums)
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+            
+            result = date_str.parseString('12/31/1999')
+            print(type(result), repr(result)) # ->  (['12', '/', '31', '/', '1999'], {'day': [('1999', 4)], 'year': [('12', 0)], 'month': [('31', 2)]})
+            
+            result_dict = result.asDict()
+            print(type(result_dict), repr(result_dict)) # ->  {'day': '1999', 'year': '12', 'month': '31'}
+
+            # even though a ParseResults supports dict-like access, sometime you just need to have a dict
+            import json
+            print(json.dumps(result)) # -> Exception: TypeError: ... is not JSON serializable
+            print(json.dumps(result.asDict())) # -> {"month": "31", "day": "1999", "year": "12"}
+        """
+        if PY_3:
+            item_fn = self.items
+        else:
+            item_fn = self.iteritems
+            
+        def toItem(obj):
+            if isinstance(obj, ParseResults):
+                if obj.haskeys():
+                    return obj.asDict()
+                else:
+                    return [toItem(v) for v in obj]
+            else:
+                return obj
+                
+        return dict((k,toItem(v)) for k,v in item_fn())
+
+    def copy( self ):
+        """
+        Returns a new copy of a C{ParseResults} object.
+        """
+        ret = ParseResults( self.__toklist )
+        ret.__tokdict = self.__tokdict.copy()
+        ret.__parent = self.__parent
+        ret.__accumNames.update( self.__accumNames )
+        ret.__name = self.__name
+        return ret
+
+    def asXML( self, doctag=None, namedItemsOnly=False, indent="", formatted=True ):
+        """
+        (Deprecated) Returns the parse results as XML. Tags are created for tokens and lists that have defined results names.
+        """
+        nl = "\n"
+        out = []
+        namedItems = dict((v[1],k) for (k,vlist) in self.__tokdict.items()
+                                                            for v in vlist)
+        nextLevelIndent = indent + "  "
+
+        # collapse out indents if formatting is not desired
+        if not formatted:
+            indent = ""
+            nextLevelIndent = ""
+            nl = ""
+
+        selfTag = None
+        if doctag is not None:
+            selfTag = doctag
+        else:
+            if self.__name:
+                selfTag = self.__name
+
+        if not selfTag:
+            if namedItemsOnly:
+                return ""
+            else:
+                selfTag = "ITEM"
+
+        out += [ nl, indent, "<", selfTag, ">" ]
+
+        for i,res in enumerate(self.__toklist):
+            if isinstance(res,ParseResults):
+                if i in namedItems:
+                    out += [ res.asXML(namedItems[i],
+                                        namedItemsOnly and doctag is None,
+                                        nextLevelIndent,
+                                        formatted)]
+                else:
+                    out += [ res.asXML(None,
+                                        namedItemsOnly and doctag is None,
+                                        nextLevelIndent,
+                                        formatted)]
+            else:
+                # individual token, see if there is a name for it
+                resTag = None
+                if i in namedItems:
+                    resTag = namedItems[i]
+                if not resTag:
+                    if namedItemsOnly:
+                        continue
+                    else:
+                        resTag = "ITEM"
+                xmlBodyText = _xml_escape(_ustr(res))
+                out += [ nl, nextLevelIndent, "<", resTag, ">",
+                                                xmlBodyText,
+                                                "" ]
+
+        out += [ nl, indent, "" ]
+        return "".join(out)
+
+    def __lookup(self,sub):
+        for k,vlist in self.__tokdict.items():
+            for v,loc in vlist:
+                if sub is v:
+                    return k
+        return None
+
+    def getName(self):
+        r"""
+        Returns the results name for this token expression. Useful when several 
+        different expressions might match at a particular location.
+
+        Example::
+            integer = Word(nums)
+            ssn_expr = Regex(r"\d\d\d-\d\d-\d\d\d\d")
+            house_number_expr = Suppress('#') + Word(nums, alphanums)
+            user_data = (Group(house_number_expr)("house_number") 
+                        | Group(ssn_expr)("ssn")
+                        | Group(integer)("age"))
+            user_info = OneOrMore(user_data)
+            
+            result = user_info.parseString("22 111-22-3333 #221B")
+            for item in result:
+                print(item.getName(), ':', item[0])
+        prints::
+            age : 22
+            ssn : 111-22-3333
+            house_number : 221B
+        """
+        if self.__name:
+            return self.__name
+        elif self.__parent:
+            par = self.__parent()
+            if par:
+                return par.__lookup(self)
+            else:
+                return None
+        elif (len(self) == 1 and
+               len(self.__tokdict) == 1 and
+               next(iter(self.__tokdict.values()))[0][1] in (0,-1)):
+            return next(iter(self.__tokdict.keys()))
+        else:
+            return None
+
+    def dump(self, indent='', depth=0, full=True):
+        """
+        Diagnostic method for listing out the contents of a C{ParseResults}.
+        Accepts an optional C{indent} argument so that this string can be embedded
+        in a nested display of other data.
+
+        Example::
+            integer = Word(nums)
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+            
+            result = date_str.parseString('12/31/1999')
+            print(result.dump())
+        prints::
+            ['12', '/', '31', '/', '1999']
+            - day: 1999
+            - month: 31
+            - year: 12
+        """
+        out = []
+        NL = '\n'
+        out.append( indent+_ustr(self.asList()) )
+        if full:
+            if self.haskeys():
+                items = sorted((str(k), v) for k,v in self.items())
+                for k,v in items:
+                    if out:
+                        out.append(NL)
+                    out.append( "%s%s- %s: " % (indent,('  '*depth), k) )
+                    if isinstance(v,ParseResults):
+                        if v:
+                            out.append( v.dump(indent,depth+1) )
+                        else:
+                            out.append(_ustr(v))
+                    else:
+                        out.append(repr(v))
+            elif any(isinstance(vv,ParseResults) for vv in self):
+                v = self
+                for i,vv in enumerate(v):
+                    if isinstance(vv,ParseResults):
+                        out.append("\n%s%s[%d]:\n%s%s%s" % (indent,('  '*(depth)),i,indent,('  '*(depth+1)),vv.dump(indent,depth+1) ))
+                    else:
+                        out.append("\n%s%s[%d]:\n%s%s%s" % (indent,('  '*(depth)),i,indent,('  '*(depth+1)),_ustr(vv)))
+            
+        return "".join(out)
+
+    def pprint(self, *args, **kwargs):
+        """
+        Pretty-printer for parsed results as a list, using the C{pprint} module.
+        Accepts additional positional or keyword args as defined for the 
+        C{pprint.pprint} method. (U{http://docs.python.org/3/library/pprint.html#pprint.pprint})
+
+        Example::
+            ident = Word(alphas, alphanums)
+            num = Word(nums)
+            func = Forward()
+            term = ident | num | Group('(' + func + ')')
+            func <<= ident + Group(Optional(delimitedList(term)))
+            result = func.parseString("fna a,b,(fnb c,d,200),100")
+            result.pprint(width=40)
+        prints::
+            ['fna',
+             ['a',
+              'b',
+              ['(', 'fnb', ['c', 'd', '200'], ')'],
+              '100']]
+        """
+        pprint.pprint(self.asList(), *args, **kwargs)
+
+    # add support for pickle protocol
+    def __getstate__(self):
+        return ( self.__toklist,
+                 ( self.__tokdict.copy(),
+                   self.__parent is not None and self.__parent() or None,
+                   self.__accumNames,
+                   self.__name ) )
+
+    def __setstate__(self,state):
+        self.__toklist = state[0]
+        (self.__tokdict,
+         par,
+         inAccumNames,
+         self.__name) = state[1]
+        self.__accumNames = {}
+        self.__accumNames.update(inAccumNames)
+        if par is not None:
+            self.__parent = wkref(par)
+        else:
+            self.__parent = None
+
+    def __getnewargs__(self):
+        return self.__toklist, self.__name, self.__asList, self.__modal
+
+    def __dir__(self):
+        return (dir(type(self)) + list(self.keys()))
+
+MutableMapping.register(ParseResults)
+
+def col (loc,strg):
+    """Returns current column within a string, counting newlines as line separators.
+   The first column is number 1.
+
+   Note: the default parsing behavior is to expand tabs in the input string
+   before starting the parsing process.  See L{I{ParserElement.parseString}} for more information
+   on parsing strings containing C{}s, and suggested methods to maintain a
+   consistent view of the parsed string, the parse location, and line and column
+   positions within the parsed string.
+   """
+    s = strg
+    return 1 if 0} for more information
+   on parsing strings containing C{}s, and suggested methods to maintain a
+   consistent view of the parsed string, the parse location, and line and column
+   positions within the parsed string.
+   """
+    return strg.count("\n",0,loc) + 1
+
+def line( loc, strg ):
+    """Returns the line of text containing loc within a string, counting newlines as line separators.
+       """
+    lastCR = strg.rfind("\n", 0, loc)
+    nextCR = strg.find("\n", loc)
+    if nextCR >= 0:
+        return strg[lastCR+1:nextCR]
+    else:
+        return strg[lastCR+1:]
+
+def _defaultStartDebugAction( instring, loc, expr ):
+    print (("Match " + _ustr(expr) + " at loc " + _ustr(loc) + "(%d,%d)" % ( lineno(loc,instring), col(loc,instring) )))
+
+def _defaultSuccessDebugAction( instring, startloc, endloc, expr, toks ):
+    print ("Matched " + _ustr(expr) + " -> " + str(toks.asList()))
+
+def _defaultExceptionDebugAction( instring, loc, expr, exc ):
+    print ("Exception raised:" + _ustr(exc))
+
+def nullDebugAction(*args):
+    """'Do-nothing' debug action, to suppress debugging output during parsing."""
+    pass
+
+# Only works on Python 3.x - nonlocal is toxic to Python 2 installs
+#~ 'decorator to trim function calls to match the arity of the target'
+#~ def _trim_arity(func, maxargs=3):
+    #~ if func in singleArgBuiltins:
+        #~ return lambda s,l,t: func(t)
+    #~ limit = 0
+    #~ foundArity = False
+    #~ def wrapper(*args):
+        #~ nonlocal limit,foundArity
+        #~ while 1:
+            #~ try:
+                #~ ret = func(*args[limit:])
+                #~ foundArity = True
+                #~ return ret
+            #~ except TypeError:
+                #~ if limit == maxargs or foundArity:
+                    #~ raise
+                #~ limit += 1
+                #~ continue
+    #~ return wrapper
+
+# this version is Python 2.x-3.x cross-compatible
+'decorator to trim function calls to match the arity of the target'
+def _trim_arity(func, maxargs=2):
+    if func in singleArgBuiltins:
+        return lambda s,l,t: func(t)
+    limit = [0]
+    foundArity = [False]
+    
+    # traceback return data structure changed in Py3.5 - normalize back to plain tuples
+    if system_version[:2] >= (3,5):
+        def extract_stack(limit=0):
+            # special handling for Python 3.5.0 - extra deep call stack by 1
+            offset = -3 if system_version == (3,5,0) else -2
+            frame_summary = traceback.extract_stack(limit=-offset+limit-1)[offset]
+            return [frame_summary[:2]]
+        def extract_tb(tb, limit=0):
+            frames = traceback.extract_tb(tb, limit=limit)
+            frame_summary = frames[-1]
+            return [frame_summary[:2]]
+    else:
+        extract_stack = traceback.extract_stack
+        extract_tb = traceback.extract_tb
+    
+    # synthesize what would be returned by traceback.extract_stack at the call to 
+    # user's parse action 'func', so that we don't incur call penalty at parse time
+    
+    LINE_DIFF = 6
+    # IF ANY CODE CHANGES, EVEN JUST COMMENTS OR BLANK LINES, BETWEEN THE NEXT LINE AND 
+    # THE CALL TO FUNC INSIDE WRAPPER, LINE_DIFF MUST BE MODIFIED!!!!
+    this_line = extract_stack(limit=2)[-1]
+    pa_call_line_synth = (this_line[0], this_line[1]+LINE_DIFF)
+
+    def wrapper(*args):
+        while 1:
+            try:
+                ret = func(*args[limit[0]:])
+                foundArity[0] = True
+                return ret
+            except TypeError:
+                # re-raise TypeErrors if they did not come from our arity testing
+                if foundArity[0]:
+                    raise
+                else:
+                    try:
+                        tb = sys.exc_info()[-1]
+                        if not extract_tb(tb, limit=2)[-1][:2] == pa_call_line_synth:
+                            raise
+                    finally:
+                        del tb
+
+                if limit[0] <= maxargs:
+                    limit[0] += 1
+                    continue
+                raise
+
+    # copy func name to wrapper for sensible debug output
+    func_name = ""
+    try:
+        func_name = getattr(func, '__name__', 
+                            getattr(func, '__class__').__name__)
+    except Exception:
+        func_name = str(func)
+    wrapper.__name__ = func_name
+
+    return wrapper
+
+class ParserElement(object):
+    """Abstract base level parser element class."""
+    DEFAULT_WHITE_CHARS = " \n\t\r"
+    verbose_stacktrace = False
+
+    @staticmethod
+    def setDefaultWhitespaceChars( chars ):
+        r"""
+        Overrides the default whitespace chars
+
+        Example::
+            # default whitespace chars are space,  and newline
+            OneOrMore(Word(alphas)).parseString("abc def\nghi jkl")  # -> ['abc', 'def', 'ghi', 'jkl']
+            
+            # change to just treat newline as significant
+            ParserElement.setDefaultWhitespaceChars(" \t")
+            OneOrMore(Word(alphas)).parseString("abc def\nghi jkl")  # -> ['abc', 'def']
+        """
+        ParserElement.DEFAULT_WHITE_CHARS = chars
+
+    @staticmethod
+    def inlineLiteralsUsing(cls):
+        """
+        Set class to be used for inclusion of string literals into a parser.
+        
+        Example::
+            # default literal class used is Literal
+            integer = Word(nums)
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")           
+
+            date_str.parseString("1999/12/31")  # -> ['1999', '/', '12', '/', '31']
+
+
+            # change to Suppress
+            ParserElement.inlineLiteralsUsing(Suppress)
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")           
+
+            date_str.parseString("1999/12/31")  # -> ['1999', '12', '31']
+        """
+        ParserElement._literalStringClass = cls
+
+    def __init__( self, savelist=False ):
+        self.parseAction = list()
+        self.failAction = None
+        #~ self.name = ""  # don't define self.name, let subclasses try/except upcall
+        self.strRepr = None
+        self.resultsName = None
+        self.saveAsList = savelist
+        self.skipWhitespace = True
+        self.whiteChars = ParserElement.DEFAULT_WHITE_CHARS
+        self.copyDefaultWhiteChars = True
+        self.mayReturnEmpty = False # used when checking for left-recursion
+        self.keepTabs = False
+        self.ignoreExprs = list()
+        self.debug = False
+        self.streamlined = False
+        self.mayIndexError = True # used to optimize exception handling for subclasses that don't advance parse index
+        self.errmsg = ""
+        self.modalResults = True # used to mark results names as modal (report only last) or cumulative (list all)
+        self.debugActions = ( None, None, None ) #custom debug actions
+        self.re = None
+        self.callPreparse = True # used to avoid redundant calls to preParse
+        self.callDuringTry = False
+
+    def copy( self ):
+        """
+        Make a copy of this C{ParserElement}.  Useful for defining different parse actions
+        for the same parsing pattern, using copies of the original parse element.
+        
+        Example::
+            integer = Word(nums).setParseAction(lambda toks: int(toks[0]))
+            integerK = integer.copy().addParseAction(lambda toks: toks[0]*1024) + Suppress("K")
+            integerM = integer.copy().addParseAction(lambda toks: toks[0]*1024*1024) + Suppress("M")
+            
+            print(OneOrMore(integerK | integerM | integer).parseString("5K 100 640K 256M"))
+        prints::
+            [5120, 100, 655360, 268435456]
+        Equivalent form of C{expr.copy()} is just C{expr()}::
+            integerM = integer().addParseAction(lambda toks: toks[0]*1024*1024) + Suppress("M")
+        """
+        cpy = copy.copy( self )
+        cpy.parseAction = self.parseAction[:]
+        cpy.ignoreExprs = self.ignoreExprs[:]
+        if self.copyDefaultWhiteChars:
+            cpy.whiteChars = ParserElement.DEFAULT_WHITE_CHARS
+        return cpy
+
+    def setName( self, name ):
+        """
+        Define name for this expression, makes debugging and exception messages clearer.
+        
+        Example::
+            Word(nums).parseString("ABC")  # -> Exception: Expected W:(0123...) (at char 0), (line:1, col:1)
+            Word(nums).setName("integer").parseString("ABC")  # -> Exception: Expected integer (at char 0), (line:1, col:1)
+        """
+        self.name = name
+        self.errmsg = "Expected " + self.name
+        if hasattr(self,"exception"):
+            self.exception.msg = self.errmsg
+        return self
+
+    def setResultsName( self, name, listAllMatches=False ):
+        """
+        Define name for referencing matching tokens as a nested attribute
+        of the returned parse results.
+        NOTE: this returns a *copy* of the original C{ParserElement} object;
+        this is so that the client can define a basic element, such as an
+        integer, and reference it in multiple places with different names.
+
+        You can also set results names using the abbreviated syntax,
+        C{expr("name")} in place of C{expr.setResultsName("name")} - 
+        see L{I{__call__}<__call__>}.
+
+        Example::
+            date_str = (integer.setResultsName("year") + '/' 
+                        + integer.setResultsName("month") + '/' 
+                        + integer.setResultsName("day"))
+
+            # equivalent form:
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+        """
+        newself = self.copy()
+        if name.endswith("*"):
+            name = name[:-1]
+            listAllMatches=True
+        newself.resultsName = name
+        newself.modalResults = not listAllMatches
+        return newself
+
+    def setBreak(self,breakFlag = True):
+        """Method to invoke the Python pdb debugger when this element is
+           about to be parsed. Set C{breakFlag} to True to enable, False to
+           disable.
+        """
+        if breakFlag:
+            _parseMethod = self._parse
+            def breaker(instring, loc, doActions=True, callPreParse=True):
+                import pdb
+                pdb.set_trace()
+                return _parseMethod( instring, loc, doActions, callPreParse )
+            breaker._originalParseMethod = _parseMethod
+            self._parse = breaker
+        else:
+            if hasattr(self._parse,"_originalParseMethod"):
+                self._parse = self._parse._originalParseMethod
+        return self
+
+    def setParseAction( self, *fns, **kwargs ):
+        """
+        Define one or more actions to perform when successfully matching parse element definition.
+        Parse action fn is a callable method with 0-3 arguments, called as C{fn(s,loc,toks)},
+        C{fn(loc,toks)}, C{fn(toks)}, or just C{fn()}, where:
+         - s   = the original string being parsed (see note below)
+         - loc = the location of the matching substring
+         - toks = a list of the matched tokens, packaged as a C{L{ParseResults}} object
+        If the functions in fns modify the tokens, they can return them as the return
+        value from fn, and the modified list of tokens will replace the original.
+        Otherwise, fn does not need to return any value.
+
+        Optional keyword arguments:
+         - callDuringTry = (default=C{False}) indicate if parse action should be run during lookaheads and alternate testing
+
+        Note: the default parsing behavior is to expand tabs in the input string
+        before starting the parsing process.  See L{I{parseString}} for more information
+        on parsing strings containing C{}s, and suggested methods to maintain a
+        consistent view of the parsed string, the parse location, and line and column
+        positions within the parsed string.
+        
+        Example::
+            integer = Word(nums)
+            date_str = integer + '/' + integer + '/' + integer
+
+            date_str.parseString("1999/12/31")  # -> ['1999', '/', '12', '/', '31']
+
+            # use parse action to convert to ints at parse time
+            integer = Word(nums).setParseAction(lambda toks: int(toks[0]))
+            date_str = integer + '/' + integer + '/' + integer
+
+            # note that integer fields are now ints, not strings
+            date_str.parseString("1999/12/31")  # -> [1999, '/', 12, '/', 31]
+        """
+        self.parseAction = list(map(_trim_arity, list(fns)))
+        self.callDuringTry = kwargs.get("callDuringTry", False)
+        return self
+
+    def addParseAction( self, *fns, **kwargs ):
+        """
+        Add one or more parse actions to expression's list of parse actions. See L{I{setParseAction}}.
+        
+        See examples in L{I{copy}}.
+        """
+        self.parseAction += list(map(_trim_arity, list(fns)))
+        self.callDuringTry = self.callDuringTry or kwargs.get("callDuringTry", False)
+        return self
+
+    def addCondition(self, *fns, **kwargs):
+        """Add a boolean predicate function to expression's list of parse actions. See 
+        L{I{setParseAction}} for function call signatures. Unlike C{setParseAction}, 
+        functions passed to C{addCondition} need to return boolean success/fail of the condition.
+
+        Optional keyword arguments:
+         - message = define a custom message to be used in the raised exception
+         - fatal   = if True, will raise ParseFatalException to stop parsing immediately; otherwise will raise ParseException
+         
+        Example::
+            integer = Word(nums).setParseAction(lambda toks: int(toks[0]))
+            year_int = integer.copy()
+            year_int.addCondition(lambda toks: toks[0] >= 2000, message="Only support years 2000 and later")
+            date_str = year_int + '/' + integer + '/' + integer
+
+            result = date_str.parseString("1999/12/31")  # -> Exception: Only support years 2000 and later (at char 0), (line:1, col:1)
+        """
+        msg = kwargs.get("message", "failed user-defined condition")
+        exc_type = ParseFatalException if kwargs.get("fatal", False) else ParseException
+        for fn in fns:
+            def pa(s,l,t):
+                if not bool(_trim_arity(fn)(s,l,t)):
+                    raise exc_type(s,l,msg)
+            self.parseAction.append(pa)
+        self.callDuringTry = self.callDuringTry or kwargs.get("callDuringTry", False)
+        return self
+
+    def setFailAction( self, fn ):
+        """Define action to perform if parsing fails at this expression.
+           Fail acton fn is a callable function that takes the arguments
+           C{fn(s,loc,expr,err)} where:
+            - s = string being parsed
+            - loc = location where expression match was attempted and failed
+            - expr = the parse expression that failed
+            - err = the exception thrown
+           The function returns no value.  It may throw C{L{ParseFatalException}}
+           if it is desired to stop parsing immediately."""
+        self.failAction = fn
+        return self
+
+    def _skipIgnorables( self, instring, loc ):
+        exprsFound = True
+        while exprsFound:
+            exprsFound = False
+            for e in self.ignoreExprs:
+                try:
+                    while 1:
+                        loc,dummy = e._parse( instring, loc )
+                        exprsFound = True
+                except ParseException:
+                    pass
+        return loc
+
+    def preParse( self, instring, loc ):
+        if self.ignoreExprs:
+            loc = self._skipIgnorables( instring, loc )
+
+        if self.skipWhitespace:
+            wt = self.whiteChars
+            instrlen = len(instring)
+            while loc < instrlen and instring[loc] in wt:
+                loc += 1
+
+        return loc
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        return loc, []
+
+    def postParse( self, instring, loc, tokenlist ):
+        return tokenlist
+
+    #~ @profile
+    def _parseNoCache( self, instring, loc, doActions=True, callPreParse=True ):
+        debugging = ( self.debug ) #and doActions )
+
+        if debugging or self.failAction:
+            #~ print ("Match",self,"at loc",loc,"(%d,%d)" % ( lineno(loc,instring), col(loc,instring) ))
+            if (self.debugActions[0] ):
+                self.debugActions[0]( instring, loc, self )
+            if callPreParse and self.callPreparse:
+                preloc = self.preParse( instring, loc )
+            else:
+                preloc = loc
+            tokensStart = preloc
+            try:
+                try:
+                    loc,tokens = self.parseImpl( instring, preloc, doActions )
+                except IndexError:
+                    raise ParseException( instring, len(instring), self.errmsg, self )
+            except ParseBaseException as err:
+                #~ print ("Exception raised:", err)
+                if self.debugActions[2]:
+                    self.debugActions[2]( instring, tokensStart, self, err )
+                if self.failAction:
+                    self.failAction( instring, tokensStart, self, err )
+                raise
+        else:
+            if callPreParse and self.callPreparse:
+                preloc = self.preParse( instring, loc )
+            else:
+                preloc = loc
+            tokensStart = preloc
+            if self.mayIndexError or preloc >= len(instring):
+                try:
+                    loc,tokens = self.parseImpl( instring, preloc, doActions )
+                except IndexError:
+                    raise ParseException( instring, len(instring), self.errmsg, self )
+            else:
+                loc,tokens = self.parseImpl( instring, preloc, doActions )
+
+        tokens = self.postParse( instring, loc, tokens )
+
+        retTokens = ParseResults( tokens, self.resultsName, asList=self.saveAsList, modal=self.modalResults )
+        if self.parseAction and (doActions or self.callDuringTry):
+            if debugging:
+                try:
+                    for fn in self.parseAction:
+                        tokens = fn( instring, tokensStart, retTokens )
+                        if tokens is not None:
+                            retTokens = ParseResults( tokens,
+                                                      self.resultsName,
+                                                      asList=self.saveAsList and isinstance(tokens,(ParseResults,list)),
+                                                      modal=self.modalResults )
+                except ParseBaseException as err:
+                    #~ print "Exception raised in user parse action:", err
+                    if (self.debugActions[2] ):
+                        self.debugActions[2]( instring, tokensStart, self, err )
+                    raise
+            else:
+                for fn in self.parseAction:
+                    tokens = fn( instring, tokensStart, retTokens )
+                    if tokens is not None:
+                        retTokens = ParseResults( tokens,
+                                                  self.resultsName,
+                                                  asList=self.saveAsList and isinstance(tokens,(ParseResults,list)),
+                                                  modal=self.modalResults )
+        if debugging:
+            #~ print ("Matched",self,"->",retTokens.asList())
+            if (self.debugActions[1] ):
+                self.debugActions[1]( instring, tokensStart, loc, self, retTokens )
+
+        return loc, retTokens
+
+    def tryParse( self, instring, loc ):
+        try:
+            return self._parse( instring, loc, doActions=False )[0]
+        except ParseFatalException:
+            raise ParseException( instring, loc, self.errmsg, self)
+    
+    def canParseNext(self, instring, loc):
+        try:
+            self.tryParse(instring, loc)
+        except (ParseException, IndexError):
+            return False
+        else:
+            return True
+
+    class _UnboundedCache(object):
+        def __init__(self):
+            cache = {}
+            self.not_in_cache = not_in_cache = object()
+
+            def get(self, key):
+                return cache.get(key, not_in_cache)
+
+            def set(self, key, value):
+                cache[key] = value
+
+            def clear(self):
+                cache.clear()
+                
+            def cache_len(self):
+                return len(cache)
+
+            self.get = types.MethodType(get, self)
+            self.set = types.MethodType(set, self)
+            self.clear = types.MethodType(clear, self)
+            self.__len__ = types.MethodType(cache_len, self)
+
+    if _OrderedDict is not None:
+        class _FifoCache(object):
+            def __init__(self, size):
+                self.not_in_cache = not_in_cache = object()
+
+                cache = _OrderedDict()
+
+                def get(self, key):
+                    return cache.get(key, not_in_cache)
+
+                def set(self, key, value):
+                    cache[key] = value
+                    while len(cache) > size:
+                        try:
+                            cache.popitem(False)
+                        except KeyError:
+                            pass
+
+                def clear(self):
+                    cache.clear()
+
+                def cache_len(self):
+                    return len(cache)
+
+                self.get = types.MethodType(get, self)
+                self.set = types.MethodType(set, self)
+                self.clear = types.MethodType(clear, self)
+                self.__len__ = types.MethodType(cache_len, self)
+
+    else:
+        class _FifoCache(object):
+            def __init__(self, size):
+                self.not_in_cache = not_in_cache = object()
+
+                cache = {}
+                key_fifo = collections.deque([], size)
+
+                def get(self, key):
+                    return cache.get(key, not_in_cache)
+
+                def set(self, key, value):
+                    cache[key] = value
+                    while len(key_fifo) > size:
+                        cache.pop(key_fifo.popleft(), None)
+                    key_fifo.append(key)
+
+                def clear(self):
+                    cache.clear()
+                    key_fifo.clear()
+
+                def cache_len(self):
+                    return len(cache)
+
+                self.get = types.MethodType(get, self)
+                self.set = types.MethodType(set, self)
+                self.clear = types.MethodType(clear, self)
+                self.__len__ = types.MethodType(cache_len, self)
+
+    # argument cache for optimizing repeated calls when backtracking through recursive expressions
+    packrat_cache = {} # this is set later by enabledPackrat(); this is here so that resetCache() doesn't fail
+    packrat_cache_lock = RLock()
+    packrat_cache_stats = [0, 0]
+
+    # this method gets repeatedly called during backtracking with the same arguments -
+    # we can cache these arguments and save ourselves the trouble of re-parsing the contained expression
+    def _parseCache( self, instring, loc, doActions=True, callPreParse=True ):
+        HIT, MISS = 0, 1
+        lookup = (self, instring, loc, callPreParse, doActions)
+        with ParserElement.packrat_cache_lock:
+            cache = ParserElement.packrat_cache
+            value = cache.get(lookup)
+            if value is cache.not_in_cache:
+                ParserElement.packrat_cache_stats[MISS] += 1
+                try:
+                    value = self._parseNoCache(instring, loc, doActions, callPreParse)
+                except ParseBaseException as pe:
+                    # cache a copy of the exception, without the traceback
+                    cache.set(lookup, pe.__class__(*pe.args))
+                    raise
+                else:
+                    cache.set(lookup, (value[0], value[1].copy()))
+                    return value
+            else:
+                ParserElement.packrat_cache_stats[HIT] += 1
+                if isinstance(value, Exception):
+                    raise value
+                return (value[0], value[1].copy())
+
+    _parse = _parseNoCache
+
+    @staticmethod
+    def resetCache():
+        ParserElement.packrat_cache.clear()
+        ParserElement.packrat_cache_stats[:] = [0] * len(ParserElement.packrat_cache_stats)
+
+    _packratEnabled = False
+    @staticmethod
+    def enablePackrat(cache_size_limit=128):
+        """Enables "packrat" parsing, which adds memoizing to the parsing logic.
+           Repeated parse attempts at the same string location (which happens
+           often in many complex grammars) can immediately return a cached value,
+           instead of re-executing parsing/validating code.  Memoizing is done of
+           both valid results and parsing exceptions.
+           
+           Parameters:
+            - cache_size_limit - (default=C{128}) - if an integer value is provided
+              will limit the size of the packrat cache; if None is passed, then
+              the cache size will be unbounded; if 0 is passed, the cache will
+              be effectively disabled.
+            
+           This speedup may break existing programs that use parse actions that
+           have side-effects.  For this reason, packrat parsing is disabled when
+           you first import pyparsing.  To activate the packrat feature, your
+           program must call the class method C{ParserElement.enablePackrat()}.  If
+           your program uses C{psyco} to "compile as you go", you must call
+           C{enablePackrat} before calling C{psyco.full()}.  If you do not do this,
+           Python will crash.  For best results, call C{enablePackrat()} immediately
+           after importing pyparsing.
+           
+           Example::
+               import pyparsing
+               pyparsing.ParserElement.enablePackrat()
+        """
+        if not ParserElement._packratEnabled:
+            ParserElement._packratEnabled = True
+            if cache_size_limit is None:
+                ParserElement.packrat_cache = ParserElement._UnboundedCache()
+            else:
+                ParserElement.packrat_cache = ParserElement._FifoCache(cache_size_limit)
+            ParserElement._parse = ParserElement._parseCache
+
+    def parseString( self, instring, parseAll=False ):
+        """
+        Execute the parse expression with the given string.
+        This is the main interface to the client code, once the complete
+        expression has been built.
+
+        If you want the grammar to require that the entire input string be
+        successfully parsed, then set C{parseAll} to True (equivalent to ending
+        the grammar with C{L{StringEnd()}}).
+
+        Note: C{parseString} implicitly calls C{expandtabs()} on the input string,
+        in order to report proper column numbers in parse actions.
+        If the input string contains tabs and
+        the grammar uses parse actions that use the C{loc} argument to index into the
+        string being parsed, you can ensure you have a consistent view of the input
+        string by:
+         - calling C{parseWithTabs} on your grammar before calling C{parseString}
+           (see L{I{parseWithTabs}})
+         - define your parse action using the full C{(s,loc,toks)} signature, and
+           reference the input string using the parse action's C{s} argument
+         - explictly expand the tabs in your input string before calling
+           C{parseString}
+        
+        Example::
+            Word('a').parseString('aaaaabaaa')  # -> ['aaaaa']
+            Word('a').parseString('aaaaabaaa', parseAll=True)  # -> Exception: Expected end of text
+        """
+        ParserElement.resetCache()
+        if not self.streamlined:
+            self.streamline()
+            #~ self.saveAsList = True
+        for e in self.ignoreExprs:
+            e.streamline()
+        if not self.keepTabs:
+            instring = instring.expandtabs()
+        try:
+            loc, tokens = self._parse( instring, 0 )
+            if parseAll:
+                loc = self.preParse( instring, loc )
+                se = Empty() + StringEnd()
+                se._parse( instring, loc )
+        except ParseBaseException as exc:
+            if ParserElement.verbose_stacktrace:
+                raise
+            else:
+                # catch and re-raise exception from here, clears out pyparsing internal stack trace
+                raise exc
+        else:
+            return tokens
+
+    def scanString( self, instring, maxMatches=_MAX_INT, overlap=False ):
+        """
+        Scan the input string for expression matches.  Each match will return the
+        matching tokens, start location, and end location.  May be called with optional
+        C{maxMatches} argument, to clip scanning after 'n' matches are found.  If
+        C{overlap} is specified, then overlapping matches will be reported.
+
+        Note that the start and end locations are reported relative to the string
+        being parsed.  See L{I{parseString}} for more information on parsing
+        strings with embedded tabs.
+
+        Example::
+            source = "sldjf123lsdjjkf345sldkjf879lkjsfd987"
+            print(source)
+            for tokens,start,end in Word(alphas).scanString(source):
+                print(' '*start + '^'*(end-start))
+                print(' '*start + tokens[0])
+        
+        prints::
+        
+            sldjf123lsdjjkf345sldkjf879lkjsfd987
+            ^^^^^
+            sldjf
+                    ^^^^^^^
+                    lsdjjkf
+                              ^^^^^^
+                              sldkjf
+                                       ^^^^^^
+                                       lkjsfd
+        """
+        if not self.streamlined:
+            self.streamline()
+        for e in self.ignoreExprs:
+            e.streamline()
+
+        if not self.keepTabs:
+            instring = _ustr(instring).expandtabs()
+        instrlen = len(instring)
+        loc = 0
+        preparseFn = self.preParse
+        parseFn = self._parse
+        ParserElement.resetCache()
+        matches = 0
+        try:
+            while loc <= instrlen and matches < maxMatches:
+                try:
+                    preloc = preparseFn( instring, loc )
+                    nextLoc,tokens = parseFn( instring, preloc, callPreParse=False )
+                except ParseException:
+                    loc = preloc+1
+                else:
+                    if nextLoc > loc:
+                        matches += 1
+                        yield tokens, preloc, nextLoc
+                        if overlap:
+                            nextloc = preparseFn( instring, loc )
+                            if nextloc > loc:
+                                loc = nextLoc
+                            else:
+                                loc += 1
+                        else:
+                            loc = nextLoc
+                    else:
+                        loc = preloc+1
+        except ParseBaseException as exc:
+            if ParserElement.verbose_stacktrace:
+                raise
+            else:
+                # catch and re-raise exception from here, clears out pyparsing internal stack trace
+                raise exc
+
+    def transformString( self, instring ):
+        """
+        Extension to C{L{scanString}}, to modify matching text with modified tokens that may
+        be returned from a parse action.  To use C{transformString}, define a grammar and
+        attach a parse action to it that modifies the returned token list.
+        Invoking C{transformString()} on a target string will then scan for matches,
+        and replace the matched text patterns according to the logic in the parse
+        action.  C{transformString()} returns the resulting transformed string.
+        
+        Example::
+            wd = Word(alphas)
+            wd.setParseAction(lambda toks: toks[0].title())
+            
+            print(wd.transformString("now is the winter of our discontent made glorious summer by this sun of york."))
+        Prints::
+            Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York.
+        """
+        out = []
+        lastE = 0
+        # force preservation of s, to minimize unwanted transformation of string, and to
+        # keep string locs straight between transformString and scanString
+        self.keepTabs = True
+        try:
+            for t,s,e in self.scanString( instring ):
+                out.append( instring[lastE:s] )
+                if t:
+                    if isinstance(t,ParseResults):
+                        out += t.asList()
+                    elif isinstance(t,list):
+                        out += t
+                    else:
+                        out.append(t)
+                lastE = e
+            out.append(instring[lastE:])
+            out = [o for o in out if o]
+            return "".join(map(_ustr,_flatten(out)))
+        except ParseBaseException as exc:
+            if ParserElement.verbose_stacktrace:
+                raise
+            else:
+                # catch and re-raise exception from here, clears out pyparsing internal stack trace
+                raise exc
+
+    def searchString( self, instring, maxMatches=_MAX_INT ):
+        """
+        Another extension to C{L{scanString}}, simplifying the access to the tokens found
+        to match the given parse expression.  May be called with optional
+        C{maxMatches} argument, to clip searching after 'n' matches are found.
+        
+        Example::
+            # a capitalized word starts with an uppercase letter, followed by zero or more lowercase letters
+            cap_word = Word(alphas.upper(), alphas.lower())
+            
+            print(cap_word.searchString("More than Iron, more than Lead, more than Gold I need Electricity"))
+
+            # the sum() builtin can be used to merge results into a single ParseResults object
+            print(sum(cap_word.searchString("More than Iron, more than Lead, more than Gold I need Electricity")))
+        prints::
+            [['More'], ['Iron'], ['Lead'], ['Gold'], ['I'], ['Electricity']]
+            ['More', 'Iron', 'Lead', 'Gold', 'I', 'Electricity']
+        """
+        try:
+            return ParseResults([ t for t,s,e in self.scanString( instring, maxMatches ) ])
+        except ParseBaseException as exc:
+            if ParserElement.verbose_stacktrace:
+                raise
+            else:
+                # catch and re-raise exception from here, clears out pyparsing internal stack trace
+                raise exc
+
+    def split(self, instring, maxsplit=_MAX_INT, includeSeparators=False):
+        """
+        Generator method to split a string using the given expression as a separator.
+        May be called with optional C{maxsplit} argument, to limit the number of splits;
+        and the optional C{includeSeparators} argument (default=C{False}), if the separating
+        matching text should be included in the split results.
+        
+        Example::        
+            punc = oneOf(list(".,;:/-!?"))
+            print(list(punc.split("This, this?, this sentence, is badly punctuated!")))
+        prints::
+            ['This', ' this', '', ' this sentence', ' is badly punctuated', '']
+        """
+        splits = 0
+        last = 0
+        for t,s,e in self.scanString(instring, maxMatches=maxsplit):
+            yield instring[last:s]
+            if includeSeparators:
+                yield t[0]
+            last = e
+        yield instring[last:]
+
+    def __add__(self, other ):
+        """
+        Implementation of + operator - returns C{L{And}}. Adding strings to a ParserElement
+        converts them to L{Literal}s by default.
+        
+        Example::
+            greet = Word(alphas) + "," + Word(alphas) + "!"
+            hello = "Hello, World!"
+            print (hello, "->", greet.parseString(hello))
+        Prints::
+            Hello, World! -> ['Hello', ',', 'World', '!']
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return And( [ self, other ] )
+
+    def __radd__(self, other ):
+        """
+        Implementation of + operator when left operand is not a C{L{ParserElement}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return other + self
+
+    def __sub__(self, other):
+        """
+        Implementation of - operator, returns C{L{And}} with error stop
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return self + And._ErrorStop() + other
+
+    def __rsub__(self, other ):
+        """
+        Implementation of - operator when left operand is not a C{L{ParserElement}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return other - self
+
+    def __mul__(self,other):
+        """
+        Implementation of * operator, allows use of C{expr * 3} in place of
+        C{expr + expr + expr}.  Expressions may also me multiplied by a 2-integer
+        tuple, similar to C{{min,max}} multipliers in regular expressions.  Tuples
+        may also include C{None} as in:
+         - C{expr*(n,None)} or C{expr*(n,)} is equivalent
+              to C{expr*n + L{ZeroOrMore}(expr)}
+              (read as "at least n instances of C{expr}")
+         - C{expr*(None,n)} is equivalent to C{expr*(0,n)}
+              (read as "0 to n instances of C{expr}")
+         - C{expr*(None,None)} is equivalent to C{L{ZeroOrMore}(expr)}
+         - C{expr*(1,None)} is equivalent to C{L{OneOrMore}(expr)}
+
+        Note that C{expr*(None,n)} does not raise an exception if
+        more than n exprs exist in the input stream; that is,
+        C{expr*(None,n)} does not enforce a maximum number of expr
+        occurrences.  If this behavior is desired, then write
+        C{expr*(None,n) + ~expr}
+        """
+        if isinstance(other,int):
+            minElements, optElements = other,0
+        elif isinstance(other,tuple):
+            other = (other + (None, None))[:2]
+            if other[0] is None:
+                other = (0, other[1])
+            if isinstance(other[0],int) and other[1] is None:
+                if other[0] == 0:
+                    return ZeroOrMore(self)
+                if other[0] == 1:
+                    return OneOrMore(self)
+                else:
+                    return self*other[0] + ZeroOrMore(self)
+            elif isinstance(other[0],int) and isinstance(other[1],int):
+                minElements, optElements = other
+                optElements -= minElements
+            else:
+                raise TypeError("cannot multiply 'ParserElement' and ('%s','%s') objects", type(other[0]),type(other[1]))
+        else:
+            raise TypeError("cannot multiply 'ParserElement' and '%s' objects", type(other))
+
+        if minElements < 0:
+            raise ValueError("cannot multiply ParserElement by negative value")
+        if optElements < 0:
+            raise ValueError("second tuple value must be greater or equal to first tuple value")
+        if minElements == optElements == 0:
+            raise ValueError("cannot multiply ParserElement by 0 or (0,0)")
+
+        if (optElements):
+            def makeOptionalList(n):
+                if n>1:
+                    return Optional(self + makeOptionalList(n-1))
+                else:
+                    return Optional(self)
+            if minElements:
+                if minElements == 1:
+                    ret = self + makeOptionalList(optElements)
+                else:
+                    ret = And([self]*minElements) + makeOptionalList(optElements)
+            else:
+                ret = makeOptionalList(optElements)
+        else:
+            if minElements == 1:
+                ret = self
+            else:
+                ret = And([self]*minElements)
+        return ret
+
+    def __rmul__(self, other):
+        return self.__mul__(other)
+
+    def __or__(self, other ):
+        """
+        Implementation of | operator - returns C{L{MatchFirst}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return MatchFirst( [ self, other ] )
+
+    def __ror__(self, other ):
+        """
+        Implementation of | operator when left operand is not a C{L{ParserElement}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return other | self
+
+    def __xor__(self, other ):
+        """
+        Implementation of ^ operator - returns C{L{Or}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return Or( [ self, other ] )
+
+    def __rxor__(self, other ):
+        """
+        Implementation of ^ operator when left operand is not a C{L{ParserElement}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return other ^ self
+
+    def __and__(self, other ):
+        """
+        Implementation of & operator - returns C{L{Each}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return Each( [ self, other ] )
+
+    def __rand__(self, other ):
+        """
+        Implementation of & operator when left operand is not a C{L{ParserElement}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return other & self
+
+    def __invert__( self ):
+        """
+        Implementation of ~ operator - returns C{L{NotAny}}
+        """
+        return NotAny( self )
+
+    def __call__(self, name=None):
+        """
+        Shortcut for C{L{setResultsName}}, with C{listAllMatches=False}.
+        
+        If C{name} is given with a trailing C{'*'} character, then C{listAllMatches} will be
+        passed as C{True}.
+           
+        If C{name} is omitted, same as calling C{L{copy}}.
+
+        Example::
+            # these are equivalent
+            userdata = Word(alphas).setResultsName("name") + Word(nums+"-").setResultsName("socsecno")
+            userdata = Word(alphas)("name") + Word(nums+"-")("socsecno")             
+        """
+        if name is not None:
+            return self.setResultsName(name)
+        else:
+            return self.copy()
+
+    def suppress( self ):
+        """
+        Suppresses the output of this C{ParserElement}; useful to keep punctuation from
+        cluttering up returned output.
+        """
+        return Suppress( self )
+
+    def leaveWhitespace( self ):
+        """
+        Disables the skipping of whitespace before matching the characters in the
+        C{ParserElement}'s defined pattern.  This is normally only used internally by
+        the pyparsing module, but may be needed in some whitespace-sensitive grammars.
+        """
+        self.skipWhitespace = False
+        return self
+
+    def setWhitespaceChars( self, chars ):
+        """
+        Overrides the default whitespace chars
+        """
+        self.skipWhitespace = True
+        self.whiteChars = chars
+        self.copyDefaultWhiteChars = False
+        return self
+
+    def parseWithTabs( self ):
+        """
+        Overrides default behavior to expand C{}s to spaces before parsing the input string.
+        Must be called before C{parseString} when the input grammar contains elements that
+        match C{} characters.
+        """
+        self.keepTabs = True
+        return self
+
+    def ignore( self, other ):
+        """
+        Define expression to be ignored (e.g., comments) while doing pattern
+        matching; may be called repeatedly, to define multiple comment or other
+        ignorable patterns.
+        
+        Example::
+            patt = OneOrMore(Word(alphas))
+            patt.parseString('ablaj /* comment */ lskjd') # -> ['ablaj']
+            
+            patt.ignore(cStyleComment)
+            patt.parseString('ablaj /* comment */ lskjd') # -> ['ablaj', 'lskjd']
+        """
+        if isinstance(other, basestring):
+            other = Suppress(other)
+
+        if isinstance( other, Suppress ):
+            if other not in self.ignoreExprs:
+                self.ignoreExprs.append(other)
+        else:
+            self.ignoreExprs.append( Suppress( other.copy() ) )
+        return self
+
+    def setDebugActions( self, startAction, successAction, exceptionAction ):
+        """
+        Enable display of debugging messages while doing pattern matching.
+        """
+        self.debugActions = (startAction or _defaultStartDebugAction,
+                             successAction or _defaultSuccessDebugAction,
+                             exceptionAction or _defaultExceptionDebugAction)
+        self.debug = True
+        return self
+
+    def setDebug( self, flag=True ):
+        """
+        Enable display of debugging messages while doing pattern matching.
+        Set C{flag} to True to enable, False to disable.
+
+        Example::
+            wd = Word(alphas).setName("alphaword")
+            integer = Word(nums).setName("numword")
+            term = wd | integer
+            
+            # turn on debugging for wd
+            wd.setDebug()
+
+            OneOrMore(term).parseString("abc 123 xyz 890")
+        
+        prints::
+            Match alphaword at loc 0(1,1)
+            Matched alphaword -> ['abc']
+            Match alphaword at loc 3(1,4)
+            Exception raised:Expected alphaword (at char 4), (line:1, col:5)
+            Match alphaword at loc 7(1,8)
+            Matched alphaword -> ['xyz']
+            Match alphaword at loc 11(1,12)
+            Exception raised:Expected alphaword (at char 12), (line:1, col:13)
+            Match alphaword at loc 15(1,16)
+            Exception raised:Expected alphaword (at char 15), (line:1, col:16)
+
+        The output shown is that produced by the default debug actions - custom debug actions can be
+        specified using L{setDebugActions}. Prior to attempting
+        to match the C{wd} expression, the debugging message C{"Match  at loc (,)"}
+        is shown. Then if the parse succeeds, a C{"Matched"} message is shown, or an C{"Exception raised"}
+        message is shown. Also note the use of L{setName} to assign a human-readable name to the expression,
+        which makes debugging and exception messages easier to understand - for instance, the default
+        name created for the C{Word} expression without calling C{setName} is C{"W:(ABCD...)"}.
+        """
+        if flag:
+            self.setDebugActions( _defaultStartDebugAction, _defaultSuccessDebugAction, _defaultExceptionDebugAction )
+        else:
+            self.debug = False
+        return self
+
+    def __str__( self ):
+        return self.name
+
+    def __repr__( self ):
+        return _ustr(self)
+
+    def streamline( self ):
+        self.streamlined = True
+        self.strRepr = None
+        return self
+
+    def checkRecursion( self, parseElementList ):
+        pass
+
+    def validate( self, validateTrace=[] ):
+        """
+        Check defined expressions for valid structure, check for infinite recursive definitions.
+        """
+        self.checkRecursion( [] )
+
+    def parseFile( self, file_or_filename, parseAll=False ):
+        """
+        Execute the parse expression on the given file or filename.
+        If a filename is specified (instead of a file object),
+        the entire file is opened, read, and closed before parsing.
+        """
+        try:
+            file_contents = file_or_filename.read()
+        except AttributeError:
+            with open(file_or_filename, "r") as f:
+                file_contents = f.read()
+        try:
+            return self.parseString(file_contents, parseAll)
+        except ParseBaseException as exc:
+            if ParserElement.verbose_stacktrace:
+                raise
+            else:
+                # catch and re-raise exception from here, clears out pyparsing internal stack trace
+                raise exc
+
+    def __eq__(self,other):
+        if isinstance(other, ParserElement):
+            return self is other or vars(self) == vars(other)
+        elif isinstance(other, basestring):
+            return self.matches(other)
+        else:
+            return super(ParserElement,self)==other
+
+    def __ne__(self,other):
+        return not (self == other)
+
+    def __hash__(self):
+        return hash(id(self))
+
+    def __req__(self,other):
+        return self == other
+
+    def __rne__(self,other):
+        return not (self == other)
+
+    def matches(self, testString, parseAll=True):
+        """
+        Method for quick testing of a parser against a test string. Good for simple 
+        inline microtests of sub expressions while building up larger parser.
+           
+        Parameters:
+         - testString - to test against this expression for a match
+         - parseAll - (default=C{True}) - flag to pass to C{L{parseString}} when running tests
+            
+        Example::
+            expr = Word(nums)
+            assert expr.matches("100")
+        """
+        try:
+            self.parseString(_ustr(testString), parseAll=parseAll)
+            return True
+        except ParseBaseException:
+            return False
+                
+    def runTests(self, tests, parseAll=True, comment='#', fullDump=True, printResults=True, failureTests=False):
+        """
+        Execute the parse expression on a series of test strings, showing each
+        test, the parsed results or where the parse failed. Quick and easy way to
+        run a parse expression against a list of sample strings.
+           
+        Parameters:
+         - tests - a list of separate test strings, or a multiline string of test strings
+         - parseAll - (default=C{True}) - flag to pass to C{L{parseString}} when running tests           
+         - comment - (default=C{'#'}) - expression for indicating embedded comments in the test 
+              string; pass None to disable comment filtering
+         - fullDump - (default=C{True}) - dump results as list followed by results names in nested outline;
+              if False, only dump nested list
+         - printResults - (default=C{True}) prints test output to stdout
+         - failureTests - (default=C{False}) indicates if these tests are expected to fail parsing
+
+        Returns: a (success, results) tuple, where success indicates that all tests succeeded
+        (or failed if C{failureTests} is True), and the results contain a list of lines of each 
+        test's output
+        
+        Example::
+            number_expr = pyparsing_common.number.copy()
+
+            result = number_expr.runTests('''
+                # unsigned integer
+                100
+                # negative integer
+                -100
+                # float with scientific notation
+                6.02e23
+                # integer with scientific notation
+                1e-12
+                ''')
+            print("Success" if result[0] else "Failed!")
+
+            result = number_expr.runTests('''
+                # stray character
+                100Z
+                # missing leading digit before '.'
+                -.100
+                # too many '.'
+                3.14.159
+                ''', failureTests=True)
+            print("Success" if result[0] else "Failed!")
+        prints::
+            # unsigned integer
+            100
+            [100]
+
+            # negative integer
+            -100
+            [-100]
+
+            # float with scientific notation
+            6.02e23
+            [6.02e+23]
+
+            # integer with scientific notation
+            1e-12
+            [1e-12]
+
+            Success
+            
+            # stray character
+            100Z
+               ^
+            FAIL: Expected end of text (at char 3), (line:1, col:4)
+
+            # missing leading digit before '.'
+            -.100
+            ^
+            FAIL: Expected {real number with scientific notation | real number | signed integer} (at char 0), (line:1, col:1)
+
+            # too many '.'
+            3.14.159
+                ^
+            FAIL: Expected end of text (at char 4), (line:1, col:5)
+
+            Success
+
+        Each test string must be on a single line. If you want to test a string that spans multiple
+        lines, create a test like this::
+
+            expr.runTest(r"this is a test\\n of strings that spans \\n 3 lines")
+        
+        (Note that this is a raw string literal, you must include the leading 'r'.)
+        """
+        if isinstance(tests, basestring):
+            tests = list(map(str.strip, tests.rstrip().splitlines()))
+        if isinstance(comment, basestring):
+            comment = Literal(comment)
+        allResults = []
+        comments = []
+        success = True
+        for t in tests:
+            if comment is not None and comment.matches(t, False) or comments and not t:
+                comments.append(t)
+                continue
+            if not t:
+                continue
+            out = ['\n'.join(comments), t]
+            comments = []
+            try:
+                t = t.replace(r'\n','\n')
+                result = self.parseString(t, parseAll=parseAll)
+                out.append(result.dump(full=fullDump))
+                success = success and not failureTests
+            except ParseBaseException as pe:
+                fatal = "(FATAL)" if isinstance(pe, ParseFatalException) else ""
+                if '\n' in t:
+                    out.append(line(pe.loc, t))
+                    out.append(' '*(col(pe.loc,t)-1) + '^' + fatal)
+                else:
+                    out.append(' '*pe.loc + '^' + fatal)
+                out.append("FAIL: " + str(pe))
+                success = success and failureTests
+                result = pe
+            except Exception as exc:
+                out.append("FAIL-EXCEPTION: " + str(exc))
+                success = success and failureTests
+                result = exc
+
+            if printResults:
+                if fullDump:
+                    out.append('')
+                print('\n'.join(out))
+
+            allResults.append((t, result))
+        
+        return success, allResults
+
+        
+class Token(ParserElement):
+    """
+    Abstract C{ParserElement} subclass, for defining atomic matching patterns.
+    """
+    def __init__( self ):
+        super(Token,self).__init__( savelist=False )
+
+
+class Empty(Token):
+    """
+    An empty token, will always match.
+    """
+    def __init__( self ):
+        super(Empty,self).__init__()
+        self.name = "Empty"
+        self.mayReturnEmpty = True
+        self.mayIndexError = False
+
+
+class NoMatch(Token):
+    """
+    A token that will never match.
+    """
+    def __init__( self ):
+        super(NoMatch,self).__init__()
+        self.name = "NoMatch"
+        self.mayReturnEmpty = True
+        self.mayIndexError = False
+        self.errmsg = "Unmatchable token"
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        raise ParseException(instring, loc, self.errmsg, self)
+
+
+class Literal(Token):
+    """
+    Token to exactly match a specified string.
+    
+    Example::
+        Literal('blah').parseString('blah')  # -> ['blah']
+        Literal('blah').parseString('blahfooblah')  # -> ['blah']
+        Literal('blah').parseString('bla')  # -> Exception: Expected "blah"
+    
+    For case-insensitive matching, use L{CaselessLiteral}.
+    
+    For keyword matching (force word break before and after the matched string),
+    use L{Keyword} or L{CaselessKeyword}.
+    """
+    def __init__( self, matchString ):
+        super(Literal,self).__init__()
+        self.match = matchString
+        self.matchLen = len(matchString)
+        try:
+            self.firstMatchChar = matchString[0]
+        except IndexError:
+            warnings.warn("null string passed to Literal; use Empty() instead",
+                            SyntaxWarning, stacklevel=2)
+            self.__class__ = Empty
+        self.name = '"%s"' % _ustr(self.match)
+        self.errmsg = "Expected " + self.name
+        self.mayReturnEmpty = False
+        self.mayIndexError = False
+
+    # Performance tuning: this routine gets called a *lot*
+    # if this is a single character match string  and the first character matches,
+    # short-circuit as quickly as possible, and avoid calling startswith
+    #~ @profile
+    def parseImpl( self, instring, loc, doActions=True ):
+        if (instring[loc] == self.firstMatchChar and
+            (self.matchLen==1 or instring.startswith(self.match,loc)) ):
+            return loc+self.matchLen, self.match
+        raise ParseException(instring, loc, self.errmsg, self)
+_L = Literal
+ParserElement._literalStringClass = Literal
+
+class Keyword(Token):
+    """
+    Token to exactly match a specified string as a keyword, that is, it must be
+    immediately followed by a non-keyword character.  Compare with C{L{Literal}}:
+     - C{Literal("if")} will match the leading C{'if'} in C{'ifAndOnlyIf'}.
+     - C{Keyword("if")} will not; it will only match the leading C{'if'} in C{'if x=1'}, or C{'if(y==2)'}
+    Accepts two optional constructor arguments in addition to the keyword string:
+     - C{identChars} is a string of characters that would be valid identifier characters,
+          defaulting to all alphanumerics + "_" and "$"
+     - C{caseless} allows case-insensitive matching, default is C{False}.
+       
+    Example::
+        Keyword("start").parseString("start")  # -> ['start']
+        Keyword("start").parseString("starting")  # -> Exception
+
+    For case-insensitive matching, use L{CaselessKeyword}.
+    """
+    DEFAULT_KEYWORD_CHARS = alphanums+"_$"
+
+    def __init__( self, matchString, identChars=None, caseless=False ):
+        super(Keyword,self).__init__()
+        if identChars is None:
+            identChars = Keyword.DEFAULT_KEYWORD_CHARS
+        self.match = matchString
+        self.matchLen = len(matchString)
+        try:
+            self.firstMatchChar = matchString[0]
+        except IndexError:
+            warnings.warn("null string passed to Keyword; use Empty() instead",
+                            SyntaxWarning, stacklevel=2)
+        self.name = '"%s"' % self.match
+        self.errmsg = "Expected " + self.name
+        self.mayReturnEmpty = False
+        self.mayIndexError = False
+        self.caseless = caseless
+        if caseless:
+            self.caselessmatch = matchString.upper()
+            identChars = identChars.upper()
+        self.identChars = set(identChars)
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if self.caseless:
+            if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and
+                 (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) and
+                 (loc == 0 or instring[loc-1].upper() not in self.identChars) ):
+                return loc+self.matchLen, self.match
+        else:
+            if (instring[loc] == self.firstMatchChar and
+                (self.matchLen==1 or instring.startswith(self.match,loc)) and
+                (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen] not in self.identChars) and
+                (loc == 0 or instring[loc-1] not in self.identChars) ):
+                return loc+self.matchLen, self.match
+        raise ParseException(instring, loc, self.errmsg, self)
+
+    def copy(self):
+        c = super(Keyword,self).copy()
+        c.identChars = Keyword.DEFAULT_KEYWORD_CHARS
+        return c
+
+    @staticmethod
+    def setDefaultKeywordChars( chars ):
+        """Overrides the default Keyword chars
+        """
+        Keyword.DEFAULT_KEYWORD_CHARS = chars
+
+class CaselessLiteral(Literal):
+    """
+    Token to match a specified string, ignoring case of letters.
+    Note: the matched results will always be in the case of the given
+    match string, NOT the case of the input text.
+
+    Example::
+        OneOrMore(CaselessLiteral("CMD")).parseString("cmd CMD Cmd10") # -> ['CMD', 'CMD', 'CMD']
+        
+    (Contrast with example for L{CaselessKeyword}.)
+    """
+    def __init__( self, matchString ):
+        super(CaselessLiteral,self).__init__( matchString.upper() )
+        # Preserve the defining literal.
+        self.returnString = matchString
+        self.name = "'%s'" % self.returnString
+        self.errmsg = "Expected " + self.name
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if instring[ loc:loc+self.matchLen ].upper() == self.match:
+            return loc+self.matchLen, self.returnString
+        raise ParseException(instring, loc, self.errmsg, self)
+
+class CaselessKeyword(Keyword):
+    """
+    Caseless version of L{Keyword}.
+
+    Example::
+        OneOrMore(CaselessKeyword("CMD")).parseString("cmd CMD Cmd10") # -> ['CMD', 'CMD']
+        
+    (Contrast with example for L{CaselessLiteral}.)
+    """
+    def __init__( self, matchString, identChars=None ):
+        super(CaselessKeyword,self).__init__( matchString, identChars, caseless=True )
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and
+             (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) ):
+            return loc+self.matchLen, self.match
+        raise ParseException(instring, loc, self.errmsg, self)
+
+class CloseMatch(Token):
+    """
+    A variation on L{Literal} which matches "close" matches, that is, 
+    strings with at most 'n' mismatching characters. C{CloseMatch} takes parameters:
+     - C{match_string} - string to be matched
+     - C{maxMismatches} - (C{default=1}) maximum number of mismatches allowed to count as a match
+    
+    The results from a successful parse will contain the matched text from the input string and the following named results:
+     - C{mismatches} - a list of the positions within the match_string where mismatches were found
+     - C{original} - the original match_string used to compare against the input string
+    
+    If C{mismatches} is an empty list, then the match was an exact match.
+    
+    Example::
+        patt = CloseMatch("ATCATCGAATGGA")
+        patt.parseString("ATCATCGAAXGGA") # -> (['ATCATCGAAXGGA'], {'mismatches': [[9]], 'original': ['ATCATCGAATGGA']})
+        patt.parseString("ATCAXCGAAXGGA") # -> Exception: Expected 'ATCATCGAATGGA' (with up to 1 mismatches) (at char 0), (line:1, col:1)
+
+        # exact match
+        patt.parseString("ATCATCGAATGGA") # -> (['ATCATCGAATGGA'], {'mismatches': [[]], 'original': ['ATCATCGAATGGA']})
+
+        # close match allowing up to 2 mismatches
+        patt = CloseMatch("ATCATCGAATGGA", maxMismatches=2)
+        patt.parseString("ATCAXCGAAXGGA") # -> (['ATCAXCGAAXGGA'], {'mismatches': [[4, 9]], 'original': ['ATCATCGAATGGA']})
+    """
+    def __init__(self, match_string, maxMismatches=1):
+        super(CloseMatch,self).__init__()
+        self.name = match_string
+        self.match_string = match_string
+        self.maxMismatches = maxMismatches
+        self.errmsg = "Expected %r (with up to %d mismatches)" % (self.match_string, self.maxMismatches)
+        self.mayIndexError = False
+        self.mayReturnEmpty = False
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        start = loc
+        instrlen = len(instring)
+        maxloc = start + len(self.match_string)
+
+        if maxloc <= instrlen:
+            match_string = self.match_string
+            match_stringloc = 0
+            mismatches = []
+            maxMismatches = self.maxMismatches
+
+            for match_stringloc,s_m in enumerate(zip(instring[loc:maxloc], self.match_string)):
+                src,mat = s_m
+                if src != mat:
+                    mismatches.append(match_stringloc)
+                    if len(mismatches) > maxMismatches:
+                        break
+            else:
+                loc = match_stringloc + 1
+                results = ParseResults([instring[start:loc]])
+                results['original'] = self.match_string
+                results['mismatches'] = mismatches
+                return loc, results
+
+        raise ParseException(instring, loc, self.errmsg, self)
+
+
+class Word(Token):
+    """
+    Token for matching words composed of allowed character sets.
+    Defined with string containing all allowed initial characters,
+    an optional string containing allowed body characters (if omitted,
+    defaults to the initial character set), and an optional minimum,
+    maximum, and/or exact length.  The default value for C{min} is 1 (a
+    minimum value < 1 is not valid); the default values for C{max} and C{exact}
+    are 0, meaning no maximum or exact length restriction. An optional
+    C{excludeChars} parameter can list characters that might be found in 
+    the input C{bodyChars} string; useful to define a word of all printables
+    except for one or two characters, for instance.
+    
+    L{srange} is useful for defining custom character set strings for defining 
+    C{Word} expressions, using range notation from regular expression character sets.
+    
+    A common mistake is to use C{Word} to match a specific literal string, as in 
+    C{Word("Address")}. Remember that C{Word} uses the string argument to define
+    I{sets} of matchable characters. This expression would match "Add", "AAA",
+    "dAred", or any other word made up of the characters 'A', 'd', 'r', 'e', and 's'.
+    To match an exact literal string, use L{Literal} or L{Keyword}.
+
+    pyparsing includes helper strings for building Words:
+     - L{alphas}
+     - L{nums}
+     - L{alphanums}
+     - L{hexnums}
+     - L{alphas8bit} (alphabetic characters in ASCII range 128-255 - accented, tilded, umlauted, etc.)
+     - L{punc8bit} (non-alphabetic characters in ASCII range 128-255 - currency, symbols, superscripts, diacriticals, etc.)
+     - L{printables} (any non-whitespace character)
+
+    Example::
+        # a word composed of digits
+        integer = Word(nums) # equivalent to Word("0123456789") or Word(srange("0-9"))
+        
+        # a word with a leading capital, and zero or more lowercase
+        capital_word = Word(alphas.upper(), alphas.lower())
+
+        # hostnames are alphanumeric, with leading alpha, and '-'
+        hostname = Word(alphas, alphanums+'-')
+        
+        # roman numeral (not a strict parser, accepts invalid mix of characters)
+        roman = Word("IVXLCDM")
+        
+        # any string of non-whitespace characters, except for ','
+        csv_value = Word(printables, excludeChars=",")
+    """
+    def __init__( self, initChars, bodyChars=None, min=1, max=0, exact=0, asKeyword=False, excludeChars=None ):
+        super(Word,self).__init__()
+        if excludeChars:
+            initChars = ''.join(c for c in initChars if c not in excludeChars)
+            if bodyChars:
+                bodyChars = ''.join(c for c in bodyChars if c not in excludeChars)
+        self.initCharsOrig = initChars
+        self.initChars = set(initChars)
+        if bodyChars :
+            self.bodyCharsOrig = bodyChars
+            self.bodyChars = set(bodyChars)
+        else:
+            self.bodyCharsOrig = initChars
+            self.bodyChars = set(initChars)
+
+        self.maxSpecified = max > 0
+
+        if min < 1:
+            raise ValueError("cannot specify a minimum length < 1; use Optional(Word()) if zero-length word is permitted")
+
+        self.minLen = min
+
+        if max > 0:
+            self.maxLen = max
+        else:
+            self.maxLen = _MAX_INT
+
+        if exact > 0:
+            self.maxLen = exact
+            self.minLen = exact
+
+        self.name = _ustr(self)
+        self.errmsg = "Expected " + self.name
+        self.mayIndexError = False
+        self.asKeyword = asKeyword
+
+        if ' ' not in self.initCharsOrig+self.bodyCharsOrig and (min==1 and max==0 and exact==0):
+            if self.bodyCharsOrig == self.initCharsOrig:
+                self.reString = "[%s]+" % _escapeRegexRangeChars(self.initCharsOrig)
+            elif len(self.initCharsOrig) == 1:
+                self.reString = "%s[%s]*" % \
+                                      (re.escape(self.initCharsOrig),
+                                      _escapeRegexRangeChars(self.bodyCharsOrig),)
+            else:
+                self.reString = "[%s][%s]*" % \
+                                      (_escapeRegexRangeChars(self.initCharsOrig),
+                                      _escapeRegexRangeChars(self.bodyCharsOrig),)
+            if self.asKeyword:
+                self.reString = r"\b"+self.reString+r"\b"
+            try:
+                self.re = re.compile( self.reString )
+            except Exception:
+                self.re = None
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if self.re:
+            result = self.re.match(instring,loc)
+            if not result:
+                raise ParseException(instring, loc, self.errmsg, self)
+
+            loc = result.end()
+            return loc, result.group()
+
+        if not(instring[ loc ] in self.initChars):
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        start = loc
+        loc += 1
+        instrlen = len(instring)
+        bodychars = self.bodyChars
+        maxloc = start + self.maxLen
+        maxloc = min( maxloc, instrlen )
+        while loc < maxloc and instring[loc] in bodychars:
+            loc += 1
+
+        throwException = False
+        if loc - start < self.minLen:
+            throwException = True
+        if self.maxSpecified and loc < instrlen and instring[loc] in bodychars:
+            throwException = True
+        if self.asKeyword:
+            if (start>0 and instring[start-1] in bodychars) or (loc4:
+                    return s[:4]+"..."
+                else:
+                    return s
+
+            if ( self.initCharsOrig != self.bodyCharsOrig ):
+                self.strRepr = "W:(%s,%s)" % ( charsAsStr(self.initCharsOrig), charsAsStr(self.bodyCharsOrig) )
+            else:
+                self.strRepr = "W:(%s)" % charsAsStr(self.initCharsOrig)
+
+        return self.strRepr
+
+
+class Regex(Token):
+    r"""
+    Token for matching strings that match a given regular expression.
+    Defined with string specifying the regular expression in a form recognized by the inbuilt Python re module.
+    If the given regex contains named groups (defined using C{(?P...)}), these will be preserved as 
+    named parse results.
+
+    Example::
+        realnum = Regex(r"[+-]?\d+\.\d*")
+        date = Regex(r'(?P\d{4})-(?P\d\d?)-(?P\d\d?)')
+        # ref: http://stackoverflow.com/questions/267399/how-do-you-match-only-valid-roman-numerals-with-a-regular-expression
+        roman = Regex(r"M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})")
+    """
+    compiledREtype = type(re.compile("[A-Z]"))
+    def __init__( self, pattern, flags=0):
+        """The parameters C{pattern} and C{flags} are passed to the C{re.compile()} function as-is. See the Python C{re} module for an explanation of the acceptable patterns and flags."""
+        super(Regex,self).__init__()
+
+        if isinstance(pattern, basestring):
+            if not pattern:
+                warnings.warn("null string passed to Regex; use Empty() instead",
+                        SyntaxWarning, stacklevel=2)
+
+            self.pattern = pattern
+            self.flags = flags
+
+            try:
+                self.re = re.compile(self.pattern, self.flags)
+                self.reString = self.pattern
+            except sre_constants.error:
+                warnings.warn("invalid pattern (%s) passed to Regex" % pattern,
+                    SyntaxWarning, stacklevel=2)
+                raise
+
+        elif isinstance(pattern, Regex.compiledREtype):
+            self.re = pattern
+            self.pattern = \
+            self.reString = str(pattern)
+            self.flags = flags
+            
+        else:
+            raise ValueError("Regex may only be constructed with a string or a compiled RE object")
+
+        self.name = _ustr(self)
+        self.errmsg = "Expected " + self.name
+        self.mayIndexError = False
+        self.mayReturnEmpty = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        result = self.re.match(instring,loc)
+        if not result:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        loc = result.end()
+        d = result.groupdict()
+        ret = ParseResults(result.group())
+        if d:
+            for k in d:
+                ret[k] = d[k]
+        return loc,ret
+
+    def __str__( self ):
+        try:
+            return super(Regex,self).__str__()
+        except Exception:
+            pass
+
+        if self.strRepr is None:
+            self.strRepr = "Re:(%s)" % repr(self.pattern)
+
+        return self.strRepr
+
+
+class QuotedString(Token):
+    r"""
+    Token for matching strings that are delimited by quoting characters.
+    
+    Defined with the following parameters:
+        - quoteChar - string of one or more characters defining the quote delimiting string
+        - escChar - character to escape quotes, typically backslash (default=C{None})
+        - escQuote - special quote sequence to escape an embedded quote string (such as SQL's "" to escape an embedded ") (default=C{None})
+        - multiline - boolean indicating whether quotes can span multiple lines (default=C{False})
+        - unquoteResults - boolean indicating whether the matched text should be unquoted (default=C{True})
+        - endQuoteChar - string of one or more characters defining the end of the quote delimited string (default=C{None} => same as quoteChar)
+        - convertWhitespaceEscapes - convert escaped whitespace (C{'\t'}, C{'\n'}, etc.) to actual whitespace (default=C{True})
+
+    Example::
+        qs = QuotedString('"')
+        print(qs.searchString('lsjdf "This is the quote" sldjf'))
+        complex_qs = QuotedString('{{', endQuoteChar='}}')
+        print(complex_qs.searchString('lsjdf {{This is the "quote"}} sldjf'))
+        sql_qs = QuotedString('"', escQuote='""')
+        print(sql_qs.searchString('lsjdf "This is the quote with ""embedded"" quotes" sldjf'))
+    prints::
+        [['This is the quote']]
+        [['This is the "quote"']]
+        [['This is the quote with "embedded" quotes']]
+    """
+    def __init__( self, quoteChar, escChar=None, escQuote=None, multiline=False, unquoteResults=True, endQuoteChar=None, convertWhitespaceEscapes=True):
+        super(QuotedString,self).__init__()
+
+        # remove white space from quote chars - wont work anyway
+        quoteChar = quoteChar.strip()
+        if not quoteChar:
+            warnings.warn("quoteChar cannot be the empty string",SyntaxWarning,stacklevel=2)
+            raise SyntaxError()
+
+        if endQuoteChar is None:
+            endQuoteChar = quoteChar
+        else:
+            endQuoteChar = endQuoteChar.strip()
+            if not endQuoteChar:
+                warnings.warn("endQuoteChar cannot be the empty string",SyntaxWarning,stacklevel=2)
+                raise SyntaxError()
+
+        self.quoteChar = quoteChar
+        self.quoteCharLen = len(quoteChar)
+        self.firstQuoteChar = quoteChar[0]
+        self.endQuoteChar = endQuoteChar
+        self.endQuoteCharLen = len(endQuoteChar)
+        self.escChar = escChar
+        self.escQuote = escQuote
+        self.unquoteResults = unquoteResults
+        self.convertWhitespaceEscapes = convertWhitespaceEscapes
+
+        if multiline:
+            self.flags = re.MULTILINE | re.DOTALL
+            self.pattern = r'%s(?:[^%s%s]' % \
+                ( re.escape(self.quoteChar),
+                  _escapeRegexRangeChars(self.endQuoteChar[0]),
+                  (escChar is not None and _escapeRegexRangeChars(escChar) or '') )
+        else:
+            self.flags = 0
+            self.pattern = r'%s(?:[^%s\n\r%s]' % \
+                ( re.escape(self.quoteChar),
+                  _escapeRegexRangeChars(self.endQuoteChar[0]),
+                  (escChar is not None and _escapeRegexRangeChars(escChar) or '') )
+        if len(self.endQuoteChar) > 1:
+            self.pattern += (
+                '|(?:' + ')|(?:'.join("%s[^%s]" % (re.escape(self.endQuoteChar[:i]),
+                                               _escapeRegexRangeChars(self.endQuoteChar[i]))
+                                    for i in range(len(self.endQuoteChar)-1,0,-1)) + ')'
+                )
+        if escQuote:
+            self.pattern += (r'|(?:%s)' % re.escape(escQuote))
+        if escChar:
+            self.pattern += (r'|(?:%s.)' % re.escape(escChar))
+            self.escCharReplacePattern = re.escape(self.escChar)+"(.)"
+        self.pattern += (r')*%s' % re.escape(self.endQuoteChar))
+
+        try:
+            self.re = re.compile(self.pattern, self.flags)
+            self.reString = self.pattern
+        except sre_constants.error:
+            warnings.warn("invalid pattern (%s) passed to Regex" % self.pattern,
+                SyntaxWarning, stacklevel=2)
+            raise
+
+        self.name = _ustr(self)
+        self.errmsg = "Expected " + self.name
+        self.mayIndexError = False
+        self.mayReturnEmpty = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        result = instring[loc] == self.firstQuoteChar and self.re.match(instring,loc) or None
+        if not result:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        loc = result.end()
+        ret = result.group()
+
+        if self.unquoteResults:
+
+            # strip off quotes
+            ret = ret[self.quoteCharLen:-self.endQuoteCharLen]
+
+            if isinstance(ret,basestring):
+                # replace escaped whitespace
+                if '\\' in ret and self.convertWhitespaceEscapes:
+                    ws_map = {
+                        r'\t' : '\t',
+                        r'\n' : '\n',
+                        r'\f' : '\f',
+                        r'\r' : '\r',
+                    }
+                    for wslit,wschar in ws_map.items():
+                        ret = ret.replace(wslit, wschar)
+
+                # replace escaped characters
+                if self.escChar:
+                    ret = re.sub(self.escCharReplacePattern, r"\g<1>", ret)
+
+                # replace escaped quotes
+                if self.escQuote:
+                    ret = ret.replace(self.escQuote, self.endQuoteChar)
+
+        return loc, ret
+
+    def __str__( self ):
+        try:
+            return super(QuotedString,self).__str__()
+        except Exception:
+            pass
+
+        if self.strRepr is None:
+            self.strRepr = "quoted string, starting with %s ending with %s" % (self.quoteChar, self.endQuoteChar)
+
+        return self.strRepr
+
+
+class CharsNotIn(Token):
+    """
+    Token for matching words composed of characters I{not} in a given set (will
+    include whitespace in matched characters if not listed in the provided exclusion set - see example).
+    Defined with string containing all disallowed characters, and an optional
+    minimum, maximum, and/or exact length.  The default value for C{min} is 1 (a
+    minimum value < 1 is not valid); the default values for C{max} and C{exact}
+    are 0, meaning no maximum or exact length restriction.
+
+    Example::
+        # define a comma-separated-value as anything that is not a ','
+        csv_value = CharsNotIn(',')
+        print(delimitedList(csv_value).parseString("dkls,lsdkjf,s12 34,@!#,213"))
+    prints::
+        ['dkls', 'lsdkjf', 's12 34', '@!#', '213']
+    """
+    def __init__( self, notChars, min=1, max=0, exact=0 ):
+        super(CharsNotIn,self).__init__()
+        self.skipWhitespace = False
+        self.notChars = notChars
+
+        if min < 1:
+            raise ValueError("cannot specify a minimum length < 1; use Optional(CharsNotIn()) if zero-length char group is permitted")
+
+        self.minLen = min
+
+        if max > 0:
+            self.maxLen = max
+        else:
+            self.maxLen = _MAX_INT
+
+        if exact > 0:
+            self.maxLen = exact
+            self.minLen = exact
+
+        self.name = _ustr(self)
+        self.errmsg = "Expected " + self.name
+        self.mayReturnEmpty = ( self.minLen == 0 )
+        self.mayIndexError = False
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if instring[loc] in self.notChars:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        start = loc
+        loc += 1
+        notchars = self.notChars
+        maxlen = min( start+self.maxLen, len(instring) )
+        while loc < maxlen and \
+              (instring[loc] not in notchars):
+            loc += 1
+
+        if loc - start < self.minLen:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        return loc, instring[start:loc]
+
+    def __str__( self ):
+        try:
+            return super(CharsNotIn, self).__str__()
+        except Exception:
+            pass
+
+        if self.strRepr is None:
+            if len(self.notChars) > 4:
+                self.strRepr = "!W:(%s...)" % self.notChars[:4]
+            else:
+                self.strRepr = "!W:(%s)" % self.notChars
+
+        return self.strRepr
+
+class White(Token):
+    """
+    Special matching class for matching whitespace.  Normally, whitespace is ignored
+    by pyparsing grammars.  This class is included when some whitespace structures
+    are significant.  Define with a string containing the whitespace characters to be
+    matched; default is C{" \\t\\r\\n"}.  Also takes optional C{min}, C{max}, and C{exact} arguments,
+    as defined for the C{L{Word}} class.
+    """
+    whiteStrs = {
+        " " : "",
+        "\t": "",
+        "\n": "",
+        "\r": "",
+        "\f": "",
+        }
+    def __init__(self, ws=" \t\r\n", min=1, max=0, exact=0):
+        super(White,self).__init__()
+        self.matchWhite = ws
+        self.setWhitespaceChars( "".join(c for c in self.whiteChars if c not in self.matchWhite) )
+        #~ self.leaveWhitespace()
+        self.name = ("".join(White.whiteStrs[c] for c in self.matchWhite))
+        self.mayReturnEmpty = True
+        self.errmsg = "Expected " + self.name
+
+        self.minLen = min
+
+        if max > 0:
+            self.maxLen = max
+        else:
+            self.maxLen = _MAX_INT
+
+        if exact > 0:
+            self.maxLen = exact
+            self.minLen = exact
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if not(instring[ loc ] in self.matchWhite):
+            raise ParseException(instring, loc, self.errmsg, self)
+        start = loc
+        loc += 1
+        maxloc = start + self.maxLen
+        maxloc = min( maxloc, len(instring) )
+        while loc < maxloc and instring[loc] in self.matchWhite:
+            loc += 1
+
+        if loc - start < self.minLen:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        return loc, instring[start:loc]
+
+
+class _PositionToken(Token):
+    def __init__( self ):
+        super(_PositionToken,self).__init__()
+        self.name=self.__class__.__name__
+        self.mayReturnEmpty = True
+        self.mayIndexError = False
+
+class GoToColumn(_PositionToken):
+    """
+    Token to advance to a specific column of input text; useful for tabular report scraping.
+    """
+    def __init__( self, colno ):
+        super(GoToColumn,self).__init__()
+        self.col = colno
+
+    def preParse( self, instring, loc ):
+        if col(loc,instring) != self.col:
+            instrlen = len(instring)
+            if self.ignoreExprs:
+                loc = self._skipIgnorables( instring, loc )
+            while loc < instrlen and instring[loc].isspace() and col( loc, instring ) != self.col :
+                loc += 1
+        return loc
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        thiscol = col( loc, instring )
+        if thiscol > self.col:
+            raise ParseException( instring, loc, "Text not in expected column", self )
+        newloc = loc + self.col - thiscol
+        ret = instring[ loc: newloc ]
+        return newloc, ret
+
+
+class LineStart(_PositionToken):
+    """
+    Matches if current position is at the beginning of a line within the parse string
+    
+    Example::
+    
+        test = '''\
+        AAA this line
+        AAA and this line
+          AAA but not this one
+        B AAA and definitely not this one
+        '''
+
+        for t in (LineStart() + 'AAA' + restOfLine).searchString(test):
+            print(t)
+    
+    Prints::
+        ['AAA', ' this line']
+        ['AAA', ' and this line']    
+
+    """
+    def __init__( self ):
+        super(LineStart,self).__init__()
+        self.errmsg = "Expected start of line"
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if col(loc, instring) == 1:
+            return loc, []
+        raise ParseException(instring, loc, self.errmsg, self)
+
+class LineEnd(_PositionToken):
+    """
+    Matches if current position is at the end of a line within the parse string
+    """
+    def __init__( self ):
+        super(LineEnd,self).__init__()
+        self.setWhitespaceChars( ParserElement.DEFAULT_WHITE_CHARS.replace("\n","") )
+        self.errmsg = "Expected end of line"
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if loc len(instring):
+            return loc, []
+        else:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+class WordStart(_PositionToken):
+    """
+    Matches if the current position is at the beginning of a Word, and
+    is not preceded by any character in a given set of C{wordChars}
+    (default=C{printables}). To emulate the C{\b} behavior of regular expressions,
+    use C{WordStart(alphanums)}. C{WordStart} will also match at the beginning of
+    the string being parsed, or at the beginning of a line.
+    """
+    def __init__(self, wordChars = printables):
+        super(WordStart,self).__init__()
+        self.wordChars = set(wordChars)
+        self.errmsg = "Not at the start of a word"
+
+    def parseImpl(self, instring, loc, doActions=True ):
+        if loc != 0:
+            if (instring[loc-1] in self.wordChars or
+                instring[loc] not in self.wordChars):
+                raise ParseException(instring, loc, self.errmsg, self)
+        return loc, []
+
+class WordEnd(_PositionToken):
+    """
+    Matches if the current position is at the end of a Word, and
+    is not followed by any character in a given set of C{wordChars}
+    (default=C{printables}). To emulate the C{\b} behavior of regular expressions,
+    use C{WordEnd(alphanums)}. C{WordEnd} will also match at the end of
+    the string being parsed, or at the end of a line.
+    """
+    def __init__(self, wordChars = printables):
+        super(WordEnd,self).__init__()
+        self.wordChars = set(wordChars)
+        self.skipWhitespace = False
+        self.errmsg = "Not at the end of a word"
+
+    def parseImpl(self, instring, loc, doActions=True ):
+        instrlen = len(instring)
+        if instrlen>0 and loc maxExcLoc:
+                    maxException = err
+                    maxExcLoc = err.loc
+            except IndexError:
+                if len(instring) > maxExcLoc:
+                    maxException = ParseException(instring,len(instring),e.errmsg,self)
+                    maxExcLoc = len(instring)
+            else:
+                # save match among all matches, to retry longest to shortest
+                matches.append((loc2, e))
+
+        if matches:
+            matches.sort(key=lambda x: -x[0])
+            for _,e in matches:
+                try:
+                    return e._parse( instring, loc, doActions )
+                except ParseException as err:
+                    err.__traceback__ = None
+                    if err.loc > maxExcLoc:
+                        maxException = err
+                        maxExcLoc = err.loc
+
+        if maxException is not None:
+            maxException.msg = self.errmsg
+            raise maxException
+        else:
+            raise ParseException(instring, loc, "no defined alternatives to match", self)
+
+
+    def __ixor__(self, other ):
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        return self.append( other ) #Or( [ self, other ] )
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "{" + " ^ ".join(_ustr(e) for e in self.exprs) + "}"
+
+        return self.strRepr
+
+    def checkRecursion( self, parseElementList ):
+        subRecCheckList = parseElementList[:] + [ self ]
+        for e in self.exprs:
+            e.checkRecursion( subRecCheckList )
+
+
+class MatchFirst(ParseExpression):
+    """
+    Requires that at least one C{ParseExpression} is found.
+    If two expressions match, the first one listed is the one that will match.
+    May be constructed using the C{'|'} operator.
+
+    Example::
+        # construct MatchFirst using '|' operator
+        
+        # watch the order of expressions to match
+        number = Word(nums) | Combine(Word(nums) + '.' + Word(nums))
+        print(number.searchString("123 3.1416 789")) #  Fail! -> [['123'], ['3'], ['1416'], ['789']]
+
+        # put more selective expression first
+        number = Combine(Word(nums) + '.' + Word(nums)) | Word(nums)
+        print(number.searchString("123 3.1416 789")) #  Better -> [['123'], ['3.1416'], ['789']]
+    """
+    def __init__( self, exprs, savelist = False ):
+        super(MatchFirst,self).__init__(exprs, savelist)
+        if self.exprs:
+            self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs)
+        else:
+            self.mayReturnEmpty = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        maxExcLoc = -1
+        maxException = None
+        for e in self.exprs:
+            try:
+                ret = e._parse( instring, loc, doActions )
+                return ret
+            except ParseException as err:
+                if err.loc > maxExcLoc:
+                    maxException = err
+                    maxExcLoc = err.loc
+            except IndexError:
+                if len(instring) > maxExcLoc:
+                    maxException = ParseException(instring,len(instring),e.errmsg,self)
+                    maxExcLoc = len(instring)
+
+        # only got here if no expression matched, raise exception for match that made it the furthest
+        else:
+            if maxException is not None:
+                maxException.msg = self.errmsg
+                raise maxException
+            else:
+                raise ParseException(instring, loc, "no defined alternatives to match", self)
+
+    def __ior__(self, other ):
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        return self.append( other ) #MatchFirst( [ self, other ] )
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "{" + " | ".join(_ustr(e) for e in self.exprs) + "}"
+
+        return self.strRepr
+
+    def checkRecursion( self, parseElementList ):
+        subRecCheckList = parseElementList[:] + [ self ]
+        for e in self.exprs:
+            e.checkRecursion( subRecCheckList )
+
+
+class Each(ParseExpression):
+    """
+    Requires all given C{ParseExpression}s to be found, but in any order.
+    Expressions may be separated by whitespace.
+    May be constructed using the C{'&'} operator.
+
+    Example::
+        color = oneOf("RED ORANGE YELLOW GREEN BLUE PURPLE BLACK WHITE BROWN")
+        shape_type = oneOf("SQUARE CIRCLE TRIANGLE STAR HEXAGON OCTAGON")
+        integer = Word(nums)
+        shape_attr = "shape:" + shape_type("shape")
+        posn_attr = "posn:" + Group(integer("x") + ',' + integer("y"))("posn")
+        color_attr = "color:" + color("color")
+        size_attr = "size:" + integer("size")
+
+        # use Each (using operator '&') to accept attributes in any order 
+        # (shape and posn are required, color and size are optional)
+        shape_spec = shape_attr & posn_attr & Optional(color_attr) & Optional(size_attr)
+
+        shape_spec.runTests('''
+            shape: SQUARE color: BLACK posn: 100, 120
+            shape: CIRCLE size: 50 color: BLUE posn: 50,80
+            color:GREEN size:20 shape:TRIANGLE posn:20,40
+            '''
+            )
+    prints::
+        shape: SQUARE color: BLACK posn: 100, 120
+        ['shape:', 'SQUARE', 'color:', 'BLACK', 'posn:', ['100', ',', '120']]
+        - color: BLACK
+        - posn: ['100', ',', '120']
+          - x: 100
+          - y: 120
+        - shape: SQUARE
+
+
+        shape: CIRCLE size: 50 color: BLUE posn: 50,80
+        ['shape:', 'CIRCLE', 'size:', '50', 'color:', 'BLUE', 'posn:', ['50', ',', '80']]
+        - color: BLUE
+        - posn: ['50', ',', '80']
+          - x: 50
+          - y: 80
+        - shape: CIRCLE
+        - size: 50
+
+
+        color: GREEN size: 20 shape: TRIANGLE posn: 20,40
+        ['color:', 'GREEN', 'size:', '20', 'shape:', 'TRIANGLE', 'posn:', ['20', ',', '40']]
+        - color: GREEN
+        - posn: ['20', ',', '40']
+          - x: 20
+          - y: 40
+        - shape: TRIANGLE
+        - size: 20
+    """
+    def __init__( self, exprs, savelist = True ):
+        super(Each,self).__init__(exprs, savelist)
+        self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs)
+        self.skipWhitespace = True
+        self.initExprGroups = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if self.initExprGroups:
+            self.opt1map = dict((id(e.expr),e) for e in self.exprs if isinstance(e,Optional))
+            opt1 = [ e.expr for e in self.exprs if isinstance(e,Optional) ]
+            opt2 = [ e for e in self.exprs if e.mayReturnEmpty and not isinstance(e,Optional)]
+            self.optionals = opt1 + opt2
+            self.multioptionals = [ e.expr for e in self.exprs if isinstance(e,ZeroOrMore) ]
+            self.multirequired = [ e.expr for e in self.exprs if isinstance(e,OneOrMore) ]
+            self.required = [ e for e in self.exprs if not isinstance(e,(Optional,ZeroOrMore,OneOrMore)) ]
+            self.required += self.multirequired
+            self.initExprGroups = False
+        tmpLoc = loc
+        tmpReqd = self.required[:]
+        tmpOpt  = self.optionals[:]
+        matchOrder = []
+
+        keepMatching = True
+        while keepMatching:
+            tmpExprs = tmpReqd + tmpOpt + self.multioptionals + self.multirequired
+            failed = []
+            for e in tmpExprs:
+                try:
+                    tmpLoc = e.tryParse( instring, tmpLoc )
+                except ParseException:
+                    failed.append(e)
+                else:
+                    matchOrder.append(self.opt1map.get(id(e),e))
+                    if e in tmpReqd:
+                        tmpReqd.remove(e)
+                    elif e in tmpOpt:
+                        tmpOpt.remove(e)
+            if len(failed) == len(tmpExprs):
+                keepMatching = False
+
+        if tmpReqd:
+            missing = ", ".join(_ustr(e) for e in tmpReqd)
+            raise ParseException(instring,loc,"Missing one or more required elements (%s)" % missing )
+
+        # add any unmatched Optionals, in case they have default values defined
+        matchOrder += [e for e in self.exprs if isinstance(e,Optional) and e.expr in tmpOpt]
+
+        resultlist = []
+        for e in matchOrder:
+            loc,results = e._parse(instring,loc,doActions)
+            resultlist.append(results)
+
+        finalResults = sum(resultlist, ParseResults([]))
+        return loc, finalResults
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "{" + " & ".join(_ustr(e) for e in self.exprs) + "}"
+
+        return self.strRepr
+
+    def checkRecursion( self, parseElementList ):
+        subRecCheckList = parseElementList[:] + [ self ]
+        for e in self.exprs:
+            e.checkRecursion( subRecCheckList )
+
+
+class ParseElementEnhance(ParserElement):
+    """
+    Abstract subclass of C{ParserElement}, for combining and post-processing parsed tokens.
+    """
+    def __init__( self, expr, savelist=False ):
+        super(ParseElementEnhance,self).__init__(savelist)
+        if isinstance( expr, basestring ):
+            if issubclass(ParserElement._literalStringClass, Token):
+                expr = ParserElement._literalStringClass(expr)
+            else:
+                expr = ParserElement._literalStringClass(Literal(expr))
+        self.expr = expr
+        self.strRepr = None
+        if expr is not None:
+            self.mayIndexError = expr.mayIndexError
+            self.mayReturnEmpty = expr.mayReturnEmpty
+            self.setWhitespaceChars( expr.whiteChars )
+            self.skipWhitespace = expr.skipWhitespace
+            self.saveAsList = expr.saveAsList
+            self.callPreparse = expr.callPreparse
+            self.ignoreExprs.extend(expr.ignoreExprs)
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if self.expr is not None:
+            return self.expr._parse( instring, loc, doActions, callPreParse=False )
+        else:
+            raise ParseException("",loc,self.errmsg,self)
+
+    def leaveWhitespace( self ):
+        self.skipWhitespace = False
+        self.expr = self.expr.copy()
+        if self.expr is not None:
+            self.expr.leaveWhitespace()
+        return self
+
+    def ignore( self, other ):
+        if isinstance( other, Suppress ):
+            if other not in self.ignoreExprs:
+                super( ParseElementEnhance, self).ignore( other )
+                if self.expr is not None:
+                    self.expr.ignore( self.ignoreExprs[-1] )
+        else:
+            super( ParseElementEnhance, self).ignore( other )
+            if self.expr is not None:
+                self.expr.ignore( self.ignoreExprs[-1] )
+        return self
+
+    def streamline( self ):
+        super(ParseElementEnhance,self).streamline()
+        if self.expr is not None:
+            self.expr.streamline()
+        return self
+
+    def checkRecursion( self, parseElementList ):
+        if self in parseElementList:
+            raise RecursiveGrammarException( parseElementList+[self] )
+        subRecCheckList = parseElementList[:] + [ self ]
+        if self.expr is not None:
+            self.expr.checkRecursion( subRecCheckList )
+
+    def validate( self, validateTrace=[] ):
+        tmp = validateTrace[:]+[self]
+        if self.expr is not None:
+            self.expr.validate(tmp)
+        self.checkRecursion( [] )
+
+    def __str__( self ):
+        try:
+            return super(ParseElementEnhance,self).__str__()
+        except Exception:
+            pass
+
+        if self.strRepr is None and self.expr is not None:
+            self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.expr) )
+        return self.strRepr
+
+
+class FollowedBy(ParseElementEnhance):
+    """
+    Lookahead matching of the given parse expression.  C{FollowedBy}
+    does I{not} advance the parsing position within the input string, it only
+    verifies that the specified parse expression matches at the current
+    position.  C{FollowedBy} always returns a null token list.
+
+    Example::
+        # use FollowedBy to match a label only if it is followed by a ':'
+        data_word = Word(alphas)
+        label = data_word + FollowedBy(':')
+        attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
+        
+        OneOrMore(attr_expr).parseString("shape: SQUARE color: BLACK posn: upper left").pprint()
+    prints::
+        [['shape', 'SQUARE'], ['color', 'BLACK'], ['posn', 'upper left']]
+    """
+    def __init__( self, expr ):
+        super(FollowedBy,self).__init__(expr)
+        self.mayReturnEmpty = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        self.expr.tryParse( instring, loc )
+        return loc, []
+
+
+class NotAny(ParseElementEnhance):
+    """
+    Lookahead to disallow matching with the given parse expression.  C{NotAny}
+    does I{not} advance the parsing position within the input string, it only
+    verifies that the specified parse expression does I{not} match at the current
+    position.  Also, C{NotAny} does I{not} skip over leading whitespace. C{NotAny}
+    always returns a null token list.  May be constructed using the '~' operator.
+
+    Example::
+        
+    """
+    def __init__( self, expr ):
+        super(NotAny,self).__init__(expr)
+        #~ self.leaveWhitespace()
+        self.skipWhitespace = False  # do NOT use self.leaveWhitespace(), don't want to propagate to exprs
+        self.mayReturnEmpty = True
+        self.errmsg = "Found unwanted token, "+_ustr(self.expr)
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if self.expr.canParseNext(instring, loc):
+            raise ParseException(instring, loc, self.errmsg, self)
+        return loc, []
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "~{" + _ustr(self.expr) + "}"
+
+        return self.strRepr
+
+class _MultipleMatch(ParseElementEnhance):
+    def __init__( self, expr, stopOn=None):
+        super(_MultipleMatch, self).__init__(expr)
+        self.saveAsList = True
+        ender = stopOn
+        if isinstance(ender, basestring):
+            ender = ParserElement._literalStringClass(ender)
+        self.not_ender = ~ender if ender is not None else None
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        self_expr_parse = self.expr._parse
+        self_skip_ignorables = self._skipIgnorables
+        check_ender = self.not_ender is not None
+        if check_ender:
+            try_not_ender = self.not_ender.tryParse
+        
+        # must be at least one (but first see if we are the stopOn sentinel;
+        # if so, fail)
+        if check_ender:
+            try_not_ender(instring, loc)
+        loc, tokens = self_expr_parse( instring, loc, doActions, callPreParse=False )
+        try:
+            hasIgnoreExprs = (not not self.ignoreExprs)
+            while 1:
+                if check_ender:
+                    try_not_ender(instring, loc)
+                if hasIgnoreExprs:
+                    preloc = self_skip_ignorables( instring, loc )
+                else:
+                    preloc = loc
+                loc, tmptokens = self_expr_parse( instring, preloc, doActions )
+                if tmptokens or tmptokens.haskeys():
+                    tokens += tmptokens
+        except (ParseException,IndexError):
+            pass
+
+        return loc, tokens
+        
+class OneOrMore(_MultipleMatch):
+    """
+    Repetition of one or more of the given expression.
+    
+    Parameters:
+     - expr - expression that must match one or more times
+     - stopOn - (default=C{None}) - expression for a terminating sentinel
+          (only required if the sentinel would ordinarily match the repetition 
+          expression)          
+
+    Example::
+        data_word = Word(alphas)
+        label = data_word + FollowedBy(':')
+        attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).setParseAction(' '.join))
+
+        text = "shape: SQUARE posn: upper left color: BLACK"
+        OneOrMore(attr_expr).parseString(text).pprint()  # Fail! read 'color' as data instead of next label -> [['shape', 'SQUARE color']]
+
+        # use stopOn attribute for OneOrMore to avoid reading label string as part of the data
+        attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
+        OneOrMore(attr_expr).parseString(text).pprint() # Better -> [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'BLACK']]
+        
+        # could also be written as
+        (attr_expr * (1,)).parseString(text).pprint()
+    """
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "{" + _ustr(self.expr) + "}..."
+
+        return self.strRepr
+
+class ZeroOrMore(_MultipleMatch):
+    """
+    Optional repetition of zero or more of the given expression.
+    
+    Parameters:
+     - expr - expression that must match zero or more times
+     - stopOn - (default=C{None}) - expression for a terminating sentinel
+          (only required if the sentinel would ordinarily match the repetition 
+          expression)          
+
+    Example: similar to L{OneOrMore}
+    """
+    def __init__( self, expr, stopOn=None):
+        super(ZeroOrMore,self).__init__(expr, stopOn=stopOn)
+        self.mayReturnEmpty = True
+        
+    def parseImpl( self, instring, loc, doActions=True ):
+        try:
+            return super(ZeroOrMore, self).parseImpl(instring, loc, doActions)
+        except (ParseException,IndexError):
+            return loc, []
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "[" + _ustr(self.expr) + "]..."
+
+        return self.strRepr
+
+class _NullToken(object):
+    def __bool__(self):
+        return False
+    __nonzero__ = __bool__
+    def __str__(self):
+        return ""
+
+_optionalNotMatched = _NullToken()
+class Optional(ParseElementEnhance):
+    """
+    Optional matching of the given expression.
+
+    Parameters:
+     - expr - expression that must match zero or more times
+     - default (optional) - value to be returned if the optional expression is not found.
+
+    Example::
+        # US postal code can be a 5-digit zip, plus optional 4-digit qualifier
+        zip = Combine(Word(nums, exact=5) + Optional('-' + Word(nums, exact=4)))
+        zip.runTests('''
+            # traditional ZIP code
+            12345
+            
+            # ZIP+4 form
+            12101-0001
+            
+            # invalid ZIP
+            98765-
+            ''')
+    prints::
+        # traditional ZIP code
+        12345
+        ['12345']
+
+        # ZIP+4 form
+        12101-0001
+        ['12101-0001']
+
+        # invalid ZIP
+        98765-
+             ^
+        FAIL: Expected end of text (at char 5), (line:1, col:6)
+    """
+    def __init__( self, expr, default=_optionalNotMatched ):
+        super(Optional,self).__init__( expr, savelist=False )
+        self.saveAsList = self.expr.saveAsList
+        self.defaultValue = default
+        self.mayReturnEmpty = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        try:
+            loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False )
+        except (ParseException,IndexError):
+            if self.defaultValue is not _optionalNotMatched:
+                if self.expr.resultsName:
+                    tokens = ParseResults([ self.defaultValue ])
+                    tokens[self.expr.resultsName] = self.defaultValue
+                else:
+                    tokens = [ self.defaultValue ]
+            else:
+                tokens = []
+        return loc, tokens
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "[" + _ustr(self.expr) + "]"
+
+        return self.strRepr
+
+class SkipTo(ParseElementEnhance):
+    """
+    Token for skipping over all undefined text until the matched expression is found.
+
+    Parameters:
+     - expr - target expression marking the end of the data to be skipped
+     - include - (default=C{False}) if True, the target expression is also parsed 
+          (the skipped text and target expression are returned as a 2-element list).
+     - ignore - (default=C{None}) used to define grammars (typically quoted strings and 
+          comments) that might contain false matches to the target expression
+     - failOn - (default=C{None}) define expressions that are not allowed to be 
+          included in the skipped test; if found before the target expression is found, 
+          the SkipTo is not a match
+
+    Example::
+        report = '''
+            Outstanding Issues Report - 1 Jan 2000
+
+               # | Severity | Description                               |  Days Open
+            -----+----------+-------------------------------------------+-----------
+             101 | Critical | Intermittent system crash                 |          6
+              94 | Cosmetic | Spelling error on Login ('log|n')         |         14
+              79 | Minor    | System slow when running too many reports |         47
+            '''
+        integer = Word(nums)
+        SEP = Suppress('|')
+        # use SkipTo to simply match everything up until the next SEP
+        # - ignore quoted strings, so that a '|' character inside a quoted string does not match
+        # - parse action will call token.strip() for each matched token, i.e., the description body
+        string_data = SkipTo(SEP, ignore=quotedString)
+        string_data.setParseAction(tokenMap(str.strip))
+        ticket_expr = (integer("issue_num") + SEP 
+                      + string_data("sev") + SEP 
+                      + string_data("desc") + SEP 
+                      + integer("days_open"))
+        
+        for tkt in ticket_expr.searchString(report):
+            print tkt.dump()
+    prints::
+        ['101', 'Critical', 'Intermittent system crash', '6']
+        - days_open: 6
+        - desc: Intermittent system crash
+        - issue_num: 101
+        - sev: Critical
+        ['94', 'Cosmetic', "Spelling error on Login ('log|n')", '14']
+        - days_open: 14
+        - desc: Spelling error on Login ('log|n')
+        - issue_num: 94
+        - sev: Cosmetic
+        ['79', 'Minor', 'System slow when running too many reports', '47']
+        - days_open: 47
+        - desc: System slow when running too many reports
+        - issue_num: 79
+        - sev: Minor
+    """
+    def __init__( self, other, include=False, ignore=None, failOn=None ):
+        super( SkipTo, self ).__init__( other )
+        self.ignoreExpr = ignore
+        self.mayReturnEmpty = True
+        self.mayIndexError = False
+        self.includeMatch = include
+        self.asList = False
+        if isinstance(failOn, basestring):
+            self.failOn = ParserElement._literalStringClass(failOn)
+        else:
+            self.failOn = failOn
+        self.errmsg = "No match found for "+_ustr(self.expr)
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        startloc = loc
+        instrlen = len(instring)
+        expr = self.expr
+        expr_parse = self.expr._parse
+        self_failOn_canParseNext = self.failOn.canParseNext if self.failOn is not None else None
+        self_ignoreExpr_tryParse = self.ignoreExpr.tryParse if self.ignoreExpr is not None else None
+        
+        tmploc = loc
+        while tmploc <= instrlen:
+            if self_failOn_canParseNext is not None:
+                # break if failOn expression matches
+                if self_failOn_canParseNext(instring, tmploc):
+                    break
+                    
+            if self_ignoreExpr_tryParse is not None:
+                # advance past ignore expressions
+                while 1:
+                    try:
+                        tmploc = self_ignoreExpr_tryParse(instring, tmploc)
+                    except ParseBaseException:
+                        break
+            
+            try:
+                expr_parse(instring, tmploc, doActions=False, callPreParse=False)
+            except (ParseException, IndexError):
+                # no match, advance loc in string
+                tmploc += 1
+            else:
+                # matched skipto expr, done
+                break
+
+        else:
+            # ran off the end of the input string without matching skipto expr, fail
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        # build up return values
+        loc = tmploc
+        skiptext = instring[startloc:loc]
+        skipresult = ParseResults(skiptext)
+        
+        if self.includeMatch:
+            loc, mat = expr_parse(instring,loc,doActions,callPreParse=False)
+            skipresult += mat
+
+        return loc, skipresult
+
+class Forward(ParseElementEnhance):
+    """
+    Forward declaration of an expression to be defined later -
+    used for recursive grammars, such as algebraic infix notation.
+    When the expression is known, it is assigned to the C{Forward} variable using the '<<' operator.
+
+    Note: take care when assigning to C{Forward} not to overlook precedence of operators.
+    Specifically, '|' has a lower precedence than '<<', so that::
+        fwdExpr << a | b | c
+    will actually be evaluated as::
+        (fwdExpr << a) | b | c
+    thereby leaving b and c out as parseable alternatives.  It is recommended that you
+    explicitly group the values inserted into the C{Forward}::
+        fwdExpr << (a | b | c)
+    Converting to use the '<<=' operator instead will avoid this problem.
+
+    See L{ParseResults.pprint} for an example of a recursive parser created using
+    C{Forward}.
+    """
+    def __init__( self, other=None ):
+        super(Forward,self).__init__( other, savelist=False )
+
+    def __lshift__( self, other ):
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass(other)
+        self.expr = other
+        self.strRepr = None
+        self.mayIndexError = self.expr.mayIndexError
+        self.mayReturnEmpty = self.expr.mayReturnEmpty
+        self.setWhitespaceChars( self.expr.whiteChars )
+        self.skipWhitespace = self.expr.skipWhitespace
+        self.saveAsList = self.expr.saveAsList
+        self.ignoreExprs.extend(self.expr.ignoreExprs)
+        return self
+        
+    def __ilshift__(self, other):
+        return self << other
+    
+    def leaveWhitespace( self ):
+        self.skipWhitespace = False
+        return self
+
+    def streamline( self ):
+        if not self.streamlined:
+            self.streamlined = True
+            if self.expr is not None:
+                self.expr.streamline()
+        return self
+
+    def validate( self, validateTrace=[] ):
+        if self not in validateTrace:
+            tmp = validateTrace[:]+[self]
+            if self.expr is not None:
+                self.expr.validate(tmp)
+        self.checkRecursion([])
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+        return self.__class__.__name__ + ": ..."
+
+        # stubbed out for now - creates awful memory and perf issues
+        self._revertClass = self.__class__
+        self.__class__ = _ForwardNoRecurse
+        try:
+            if self.expr is not None:
+                retString = _ustr(self.expr)
+            else:
+                retString = "None"
+        finally:
+            self.__class__ = self._revertClass
+        return self.__class__.__name__ + ": " + retString
+
+    def copy(self):
+        if self.expr is not None:
+            return super(Forward,self).copy()
+        else:
+            ret = Forward()
+            ret <<= self
+            return ret
+
+class _ForwardNoRecurse(Forward):
+    def __str__( self ):
+        return "..."
+
+class TokenConverter(ParseElementEnhance):
+    """
+    Abstract subclass of C{ParseExpression}, for converting parsed results.
+    """
+    def __init__( self, expr, savelist=False ):
+        super(TokenConverter,self).__init__( expr )#, savelist )
+        self.saveAsList = False
+
+class Combine(TokenConverter):
+    """
+    Converter to concatenate all matching tokens to a single string.
+    By default, the matching patterns must also be contiguous in the input string;
+    this can be disabled by specifying C{'adjacent=False'} in the constructor.
+
+    Example::
+        real = Word(nums) + '.' + Word(nums)
+        print(real.parseString('3.1416')) # -> ['3', '.', '1416']
+        # will also erroneously match the following
+        print(real.parseString('3. 1416')) # -> ['3', '.', '1416']
+
+        real = Combine(Word(nums) + '.' + Word(nums))
+        print(real.parseString('3.1416')) # -> ['3.1416']
+        # no match when there are internal spaces
+        print(real.parseString('3. 1416')) # -> Exception: Expected W:(0123...)
+    """
+    def __init__( self, expr, joinString="", adjacent=True ):
+        super(Combine,self).__init__( expr )
+        # suppress whitespace-stripping in contained parse expressions, but re-enable it on the Combine itself
+        if adjacent:
+            self.leaveWhitespace()
+        self.adjacent = adjacent
+        self.skipWhitespace = True
+        self.joinString = joinString
+        self.callPreparse = True
+
+    def ignore( self, other ):
+        if self.adjacent:
+            ParserElement.ignore(self, other)
+        else:
+            super( Combine, self).ignore( other )
+        return self
+
+    def postParse( self, instring, loc, tokenlist ):
+        retToks = tokenlist.copy()
+        del retToks[:]
+        retToks += ParseResults([ "".join(tokenlist._asStringList(self.joinString)) ], modal=self.modalResults)
+
+        if self.resultsName and retToks.haskeys():
+            return [ retToks ]
+        else:
+            return retToks
+
+class Group(TokenConverter):
+    """
+    Converter to return the matched tokens as a list - useful for returning tokens of C{L{ZeroOrMore}} and C{L{OneOrMore}} expressions.
+
+    Example::
+        ident = Word(alphas)
+        num = Word(nums)
+        term = ident | num
+        func = ident + Optional(delimitedList(term))
+        print(func.parseString("fn a,b,100"))  # -> ['fn', 'a', 'b', '100']
+
+        func = ident + Group(Optional(delimitedList(term)))
+        print(func.parseString("fn a,b,100"))  # -> ['fn', ['a', 'b', '100']]
+    """
+    def __init__( self, expr ):
+        super(Group,self).__init__( expr )
+        self.saveAsList = True
+
+    def postParse( self, instring, loc, tokenlist ):
+        return [ tokenlist ]
+
+class Dict(TokenConverter):
+    """
+    Converter to return a repetitive expression as a list, but also as a dictionary.
+    Each element can also be referenced using the first token in the expression as its key.
+    Useful for tabular report scraping when the first column can be used as a item key.
+
+    Example::
+        data_word = Word(alphas)
+        label = data_word + FollowedBy(':')
+        attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).setParseAction(' '.join))
+
+        text = "shape: SQUARE posn: upper left color: light blue texture: burlap"
+        attr_expr = (label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
+        
+        # print attributes as plain groups
+        print(OneOrMore(attr_expr).parseString(text).dump())
+        
+        # instead of OneOrMore(expr), parse using Dict(OneOrMore(Group(expr))) - Dict will auto-assign names
+        result = Dict(OneOrMore(Group(attr_expr))).parseString(text)
+        print(result.dump())
+        
+        # access named fields as dict entries, or output as dict
+        print(result['shape'])        
+        print(result.asDict())
+    prints::
+        ['shape', 'SQUARE', 'posn', 'upper left', 'color', 'light blue', 'texture', 'burlap']
+
+        [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']]
+        - color: light blue
+        - posn: upper left
+        - shape: SQUARE
+        - texture: burlap
+        SQUARE
+        {'color': 'light blue', 'posn': 'upper left', 'texture': 'burlap', 'shape': 'SQUARE'}
+    See more examples at L{ParseResults} of accessing fields by results name.
+    """
+    def __init__( self, expr ):
+        super(Dict,self).__init__( expr )
+        self.saveAsList = True
+
+    def postParse( self, instring, loc, tokenlist ):
+        for i,tok in enumerate(tokenlist):
+            if len(tok) == 0:
+                continue
+            ikey = tok[0]
+            if isinstance(ikey,int):
+                ikey = _ustr(tok[0]).strip()
+            if len(tok)==1:
+                tokenlist[ikey] = _ParseResultsWithOffset("",i)
+            elif len(tok)==2 and not isinstance(tok[1],ParseResults):
+                tokenlist[ikey] = _ParseResultsWithOffset(tok[1],i)
+            else:
+                dictvalue = tok.copy() #ParseResults(i)
+                del dictvalue[0]
+                if len(dictvalue)!= 1 or (isinstance(dictvalue,ParseResults) and dictvalue.haskeys()):
+                    tokenlist[ikey] = _ParseResultsWithOffset(dictvalue,i)
+                else:
+                    tokenlist[ikey] = _ParseResultsWithOffset(dictvalue[0],i)
+
+        if self.resultsName:
+            return [ tokenlist ]
+        else:
+            return tokenlist
+
+
+class Suppress(TokenConverter):
+    """
+    Converter for ignoring the results of a parsed expression.
+
+    Example::
+        source = "a, b, c,d"
+        wd = Word(alphas)
+        wd_list1 = wd + ZeroOrMore(',' + wd)
+        print(wd_list1.parseString(source))
+
+        # often, delimiters that are useful during parsing are just in the
+        # way afterward - use Suppress to keep them out of the parsed output
+        wd_list2 = wd + ZeroOrMore(Suppress(',') + wd)
+        print(wd_list2.parseString(source))
+    prints::
+        ['a', ',', 'b', ',', 'c', ',', 'd']
+        ['a', 'b', 'c', 'd']
+    (See also L{delimitedList}.)
+    """
+    def postParse( self, instring, loc, tokenlist ):
+        return []
+
+    def suppress( self ):
+        return self
+
+
+class OnlyOnce(object):
+    """
+    Wrapper for parse actions, to ensure they are only called once.
+    """
+    def __init__(self, methodCall):
+        self.callable = _trim_arity(methodCall)
+        self.called = False
+    def __call__(self,s,l,t):
+        if not self.called:
+            results = self.callable(s,l,t)
+            self.called = True
+            return results
+        raise ParseException(s,l,"")
+    def reset(self):
+        self.called = False
+
+def traceParseAction(f):
+    """
+    Decorator for debugging parse actions. 
+    
+    When the parse action is called, this decorator will print C{">> entering I{method-name}(line:I{current_source_line}, I{parse_location}, I{matched_tokens})".}
+    When the parse action completes, the decorator will print C{"<<"} followed by the returned value, or any exception that the parse action raised.
+
+    Example::
+        wd = Word(alphas)
+
+        @traceParseAction
+        def remove_duplicate_chars(tokens):
+            return ''.join(sorted(set(''.join(tokens))))
+
+        wds = OneOrMore(wd).setParseAction(remove_duplicate_chars)
+        print(wds.parseString("slkdjs sld sldd sdlf sdljf"))
+    prints::
+        >>entering remove_duplicate_chars(line: 'slkdjs sld sldd sdlf sdljf', 0, (['slkdjs', 'sld', 'sldd', 'sdlf', 'sdljf'], {}))
+        <3:
+            thisFunc = paArgs[0].__class__.__name__ + '.' + thisFunc
+        sys.stderr.write( ">>entering %s(line: '%s', %d, %r)\n" % (thisFunc,line(l,s),l,t) )
+        try:
+            ret = f(*paArgs)
+        except Exception as exc:
+            sys.stderr.write( "< ['aa', 'bb', 'cc']
+        delimitedList(Word(hexnums), delim=':', combine=True).parseString("AA:BB:CC:DD:EE") # -> ['AA:BB:CC:DD:EE']
+    """
+    dlName = _ustr(expr)+" ["+_ustr(delim)+" "+_ustr(expr)+"]..."
+    if combine:
+        return Combine( expr + ZeroOrMore( delim + expr ) ).setName(dlName)
+    else:
+        return ( expr + ZeroOrMore( Suppress( delim ) + expr ) ).setName(dlName)
+
+def countedArray( expr, intExpr=None ):
+    """
+    Helper to define a counted list of expressions.
+    This helper defines a pattern of the form::
+        integer expr expr expr...
+    where the leading integer tells how many expr expressions follow.
+    The matched tokens returns the array of expr tokens as a list - the leading count token is suppressed.
+    
+    If C{intExpr} is specified, it should be a pyparsing expression that produces an integer value.
+
+    Example::
+        countedArray(Word(alphas)).parseString('2 ab cd ef')  # -> ['ab', 'cd']
+
+        # in this parser, the leading integer value is given in binary,
+        # '10' indicating that 2 values are in the array
+        binaryConstant = Word('01').setParseAction(lambda t: int(t[0], 2))
+        countedArray(Word(alphas), intExpr=binaryConstant).parseString('10 ab cd ef')  # -> ['ab', 'cd']
+    """
+    arrayExpr = Forward()
+    def countFieldParseAction(s,l,t):
+        n = t[0]
+        arrayExpr << (n and Group(And([expr]*n)) or Group(empty))
+        return []
+    if intExpr is None:
+        intExpr = Word(nums).setParseAction(lambda t:int(t[0]))
+    else:
+        intExpr = intExpr.copy()
+    intExpr.setName("arrayLen")
+    intExpr.addParseAction(countFieldParseAction, callDuringTry=True)
+    return ( intExpr + arrayExpr ).setName('(len) ' + _ustr(expr) + '...')
+
+def _flatten(L):
+    ret = []
+    for i in L:
+        if isinstance(i,list):
+            ret.extend(_flatten(i))
+        else:
+            ret.append(i)
+    return ret
+
+def matchPreviousLiteral(expr):
+    """
+    Helper to define an expression that is indirectly defined from
+    the tokens matched in a previous expression, that is, it looks
+    for a 'repeat' of a previous expression.  For example::
+        first = Word(nums)
+        second = matchPreviousLiteral(first)
+        matchExpr = first + ":" + second
+    will match C{"1:1"}, but not C{"1:2"}.  Because this matches a
+    previous literal, will also match the leading C{"1:1"} in C{"1:10"}.
+    If this is not desired, use C{matchPreviousExpr}.
+    Do I{not} use with packrat parsing enabled.
+    """
+    rep = Forward()
+    def copyTokenToRepeater(s,l,t):
+        if t:
+            if len(t) == 1:
+                rep << t[0]
+            else:
+                # flatten t tokens
+                tflat = _flatten(t.asList())
+                rep << And(Literal(tt) for tt in tflat)
+        else:
+            rep << Empty()
+    expr.addParseAction(copyTokenToRepeater, callDuringTry=True)
+    rep.setName('(prev) ' + _ustr(expr))
+    return rep
+
+def matchPreviousExpr(expr):
+    """
+    Helper to define an expression that is indirectly defined from
+    the tokens matched in a previous expression, that is, it looks
+    for a 'repeat' of a previous expression.  For example::
+        first = Word(nums)
+        second = matchPreviousExpr(first)
+        matchExpr = first + ":" + second
+    will match C{"1:1"}, but not C{"1:2"}.  Because this matches by
+    expressions, will I{not} match the leading C{"1:1"} in C{"1:10"};
+    the expressions are evaluated first, and then compared, so
+    C{"1"} is compared with C{"10"}.
+    Do I{not} use with packrat parsing enabled.
+    """
+    rep = Forward()
+    e2 = expr.copy()
+    rep <<= e2
+    def copyTokenToRepeater(s,l,t):
+        matchTokens = _flatten(t.asList())
+        def mustMatchTheseTokens(s,l,t):
+            theseTokens = _flatten(t.asList())
+            if  theseTokens != matchTokens:
+                raise ParseException("",0,"")
+        rep.setParseAction( mustMatchTheseTokens, callDuringTry=True )
+    expr.addParseAction(copyTokenToRepeater, callDuringTry=True)
+    rep.setName('(prev) ' + _ustr(expr))
+    return rep
+
+def _escapeRegexRangeChars(s):
+    #~  escape these chars: ^-]
+    for c in r"\^-]":
+        s = s.replace(c,_bslash+c)
+    s = s.replace("\n",r"\n")
+    s = s.replace("\t",r"\t")
+    return _ustr(s)
+
+def oneOf( strs, caseless=False, useRegex=True ):
+    """
+    Helper to quickly define a set of alternative Literals, and makes sure to do
+    longest-first testing when there is a conflict, regardless of the input order,
+    but returns a C{L{MatchFirst}} for best performance.
+
+    Parameters:
+     - strs - a string of space-delimited literals, or a collection of string literals
+     - caseless - (default=C{False}) - treat all literals as caseless
+     - useRegex - (default=C{True}) - as an optimization, will generate a Regex
+          object; otherwise, will generate a C{MatchFirst} object (if C{caseless=True}, or
+          if creating a C{Regex} raises an exception)
+
+    Example::
+        comp_oper = oneOf("< = > <= >= !=")
+        var = Word(alphas)
+        number = Word(nums)
+        term = var | number
+        comparison_expr = term + comp_oper + term
+        print(comparison_expr.searchString("B = 12  AA=23 B<=AA AA>12"))
+    prints::
+        [['B', '=', '12'], ['AA', '=', '23'], ['B', '<=', 'AA'], ['AA', '>', '12']]
+    """
+    if caseless:
+        isequal = ( lambda a,b: a.upper() == b.upper() )
+        masks = ( lambda a,b: b.upper().startswith(a.upper()) )
+        parseElementClass = CaselessLiteral
+    else:
+        isequal = ( lambda a,b: a == b )
+        masks = ( lambda a,b: b.startswith(a) )
+        parseElementClass = Literal
+
+    symbols = []
+    if isinstance(strs,basestring):
+        symbols = strs.split()
+    elif isinstance(strs, Iterable):
+        symbols = list(strs)
+    else:
+        warnings.warn("Invalid argument to oneOf, expected string or iterable",
+                SyntaxWarning, stacklevel=2)
+    if not symbols:
+        return NoMatch()
+
+    i = 0
+    while i < len(symbols)-1:
+        cur = symbols[i]
+        for j,other in enumerate(symbols[i+1:]):
+            if ( isequal(other, cur) ):
+                del symbols[i+j+1]
+                break
+            elif ( masks(cur, other) ):
+                del symbols[i+j+1]
+                symbols.insert(i,other)
+                cur = other
+                break
+        else:
+            i += 1
+
+    if not caseless and useRegex:
+        #~ print (strs,"->", "|".join( [ _escapeRegexChars(sym) for sym in symbols] ))
+        try:
+            if len(symbols)==len("".join(symbols)):
+                return Regex( "[%s]" % "".join(_escapeRegexRangeChars(sym) for sym in symbols) ).setName(' | '.join(symbols))
+            else:
+                return Regex( "|".join(re.escape(sym) for sym in symbols) ).setName(' | '.join(symbols))
+        except Exception:
+            warnings.warn("Exception creating Regex for oneOf, building MatchFirst",
+                    SyntaxWarning, stacklevel=2)
+
+
+    # last resort, just use MatchFirst
+    return MatchFirst(parseElementClass(sym) for sym in symbols).setName(' | '.join(symbols))
+
+def dictOf( key, value ):
+    """
+    Helper to easily and clearly define a dictionary by specifying the respective patterns
+    for the key and value.  Takes care of defining the C{L{Dict}}, C{L{ZeroOrMore}}, and C{L{Group}} tokens
+    in the proper order.  The key pattern can include delimiting markers or punctuation,
+    as long as they are suppressed, thereby leaving the significant key text.  The value
+    pattern can include named results, so that the C{Dict} results can include named token
+    fields.
+
+    Example::
+        text = "shape: SQUARE posn: upper left color: light blue texture: burlap"
+        attr_expr = (label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
+        print(OneOrMore(attr_expr).parseString(text).dump())
+        
+        attr_label = label
+        attr_value = Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join)
+
+        # similar to Dict, but simpler call format
+        result = dictOf(attr_label, attr_value).parseString(text)
+        print(result.dump())
+        print(result['shape'])
+        print(result.shape)  # object attribute access works too
+        print(result.asDict())
+    prints::
+        [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']]
+        - color: light blue
+        - posn: upper left
+        - shape: SQUARE
+        - texture: burlap
+        SQUARE
+        SQUARE
+        {'color': 'light blue', 'shape': 'SQUARE', 'posn': 'upper left', 'texture': 'burlap'}
+    """
+    return Dict( ZeroOrMore( Group ( key + value ) ) )
+
+def originalTextFor(expr, asString=True):
+    """
+    Helper to return the original, untokenized text for a given expression.  Useful to
+    restore the parsed fields of an HTML start tag into the raw tag text itself, or to
+    revert separate tokens with intervening whitespace back to the original matching
+    input text. By default, returns astring containing the original parsed text.  
+       
+    If the optional C{asString} argument is passed as C{False}, then the return value is a 
+    C{L{ParseResults}} containing any results names that were originally matched, and a 
+    single token containing the original matched text from the input string.  So if 
+    the expression passed to C{L{originalTextFor}} contains expressions with defined
+    results names, you must set C{asString} to C{False} if you want to preserve those
+    results name values.
+
+    Example::
+        src = "this is test  bold text  normal text "
+        for tag in ("b","i"):
+            opener,closer = makeHTMLTags(tag)
+            patt = originalTextFor(opener + SkipTo(closer) + closer)
+            print(patt.searchString(src)[0])
+    prints::
+        [' bold text ']
+        ['text']
+    """
+    locMarker = Empty().setParseAction(lambda s,loc,t: loc)
+    endlocMarker = locMarker.copy()
+    endlocMarker.callPreparse = False
+    matchExpr = locMarker("_original_start") + expr + endlocMarker("_original_end")
+    if asString:
+        extractText = lambda s,l,t: s[t._original_start:t._original_end]
+    else:
+        def extractText(s,l,t):
+            t[:] = [s[t.pop('_original_start'):t.pop('_original_end')]]
+    matchExpr.setParseAction(extractText)
+    matchExpr.ignoreExprs = expr.ignoreExprs
+    return matchExpr
+
+def ungroup(expr): 
+    """
+    Helper to undo pyparsing's default grouping of And expressions, even
+    if all but one are non-empty.
+    """
+    return TokenConverter(expr).setParseAction(lambda t:t[0])
+
+def locatedExpr(expr):
+    """
+    Helper to decorate a returned token with its starting and ending locations in the input string.
+    This helper adds the following results names:
+     - locn_start = location where matched expression begins
+     - locn_end = location where matched expression ends
+     - value = the actual parsed results
+
+    Be careful if the input text contains C{} characters, you may want to call
+    C{L{ParserElement.parseWithTabs}}
+
+    Example::
+        wd = Word(alphas)
+        for match in locatedExpr(wd).searchString("ljsdf123lksdjjf123lkkjj1222"):
+            print(match)
+    prints::
+        [[0, 'ljsdf', 5]]
+        [[8, 'lksdjjf', 15]]
+        [[18, 'lkkjj', 23]]
+    """
+    locator = Empty().setParseAction(lambda s,l,t: l)
+    return Group(locator("locn_start") + expr("value") + locator.copy().leaveWhitespace()("locn_end"))
+
+
+# convenience constants for positional expressions
+empty       = Empty().setName("empty")
+lineStart   = LineStart().setName("lineStart")
+lineEnd     = LineEnd().setName("lineEnd")
+stringStart = StringStart().setName("stringStart")
+stringEnd   = StringEnd().setName("stringEnd")
+
+_escapedPunc = Word( _bslash, r"\[]-*.$+^?()~ ", exact=2 ).setParseAction(lambda s,l,t:t[0][1])
+_escapedHexChar = Regex(r"\\0?[xX][0-9a-fA-F]+").setParseAction(lambda s,l,t:unichr(int(t[0].lstrip(r'\0x'),16)))
+_escapedOctChar = Regex(r"\\0[0-7]+").setParseAction(lambda s,l,t:unichr(int(t[0][1:],8)))
+_singleChar = _escapedPunc | _escapedHexChar | _escapedOctChar | CharsNotIn(r'\]', exact=1)
+_charRange = Group(_singleChar + Suppress("-") + _singleChar)
+_reBracketExpr = Literal("[") + Optional("^").setResultsName("negate") + Group( OneOrMore( _charRange | _singleChar ) ).setResultsName("body") + "]"
+
+def srange(s):
+    r"""
+    Helper to easily define string ranges for use in Word construction.  Borrows
+    syntax from regexp '[]' string range definitions::
+        srange("[0-9]")   -> "0123456789"
+        srange("[a-z]")   -> "abcdefghijklmnopqrstuvwxyz"
+        srange("[a-z$_]") -> "abcdefghijklmnopqrstuvwxyz$_"
+    The input string must be enclosed in []'s, and the returned string is the expanded
+    character set joined into a single string.
+    The values enclosed in the []'s may be:
+     - a single character
+     - an escaped character with a leading backslash (such as C{\-} or C{\]})
+     - an escaped hex character with a leading C{'\x'} (C{\x21}, which is a C{'!'} character) 
+         (C{\0x##} is also supported for backwards compatibility) 
+     - an escaped octal character with a leading C{'\0'} (C{\041}, which is a C{'!'} character)
+     - a range of any of the above, separated by a dash (C{'a-z'}, etc.)
+     - any combination of the above (C{'aeiouy'}, C{'a-zA-Z0-9_$'}, etc.)
+    """
+    _expanded = lambda p: p if not isinstance(p,ParseResults) else ''.join(unichr(c) for c in range(ord(p[0]),ord(p[1])+1))
+    try:
+        return "".join(_expanded(part) for part in _reBracketExpr.parseString(s).body)
+    except Exception:
+        return ""
+
+def matchOnlyAtCol(n):
+    """
+    Helper method for defining parse actions that require matching at a specific
+    column in the input text.
+    """
+    def verifyCol(strg,locn,toks):
+        if col(locn,strg) != n:
+            raise ParseException(strg,locn,"matched token not at column %d" % n)
+    return verifyCol
+
+def replaceWith(replStr):
+    """
+    Helper method for common parse actions that simply return a literal value.  Especially
+    useful when used with C{L{transformString}()}.
+
+    Example::
+        num = Word(nums).setParseAction(lambda toks: int(toks[0]))
+        na = oneOf("N/A NA").setParseAction(replaceWith(math.nan))
+        term = na | num
+        
+        OneOrMore(term).parseString("324 234 N/A 234") # -> [324, 234, nan, 234]
+    """
+    return lambda s,l,t: [replStr]
+
+def removeQuotes(s,l,t):
+    """
+    Helper parse action for removing quotation marks from parsed quoted strings.
+
+    Example::
+        # by default, quotation marks are included in parsed results
+        quotedString.parseString("'Now is the Winter of our Discontent'") # -> ["'Now is the Winter of our Discontent'"]
+
+        # use removeQuotes to strip quotation marks from parsed results
+        quotedString.setParseAction(removeQuotes)
+        quotedString.parseString("'Now is the Winter of our Discontent'") # -> ["Now is the Winter of our Discontent"]
+    """
+    return t[0][1:-1]
+
+def tokenMap(func, *args):
+    """
+    Helper to define a parse action by mapping a function to all elements of a ParseResults list.If any additional 
+    args are passed, they are forwarded to the given function as additional arguments after
+    the token, as in C{hex_integer = Word(hexnums).setParseAction(tokenMap(int, 16))}, which will convert the
+    parsed data to an integer using base 16.
+
+    Example (compare the last to example in L{ParserElement.transformString}::
+        hex_ints = OneOrMore(Word(hexnums)).setParseAction(tokenMap(int, 16))
+        hex_ints.runTests('''
+            00 11 22 aa FF 0a 0d 1a
+            ''')
+        
+        upperword = Word(alphas).setParseAction(tokenMap(str.upper))
+        OneOrMore(upperword).runTests('''
+            my kingdom for a horse
+            ''')
+
+        wd = Word(alphas).setParseAction(tokenMap(str.title))
+        OneOrMore(wd).setParseAction(' '.join).runTests('''
+            now is the winter of our discontent made glorious summer by this sun of york
+            ''')
+    prints::
+        00 11 22 aa FF 0a 0d 1a
+        [0, 17, 34, 170, 255, 10, 13, 26]
+
+        my kingdom for a horse
+        ['MY', 'KINGDOM', 'FOR', 'A', 'HORSE']
+
+        now is the winter of our discontent made glorious summer by this sun of york
+        ['Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York']
+    """
+    def pa(s,l,t):
+        return [func(tokn, *args) for tokn in t]
+
+    try:
+        func_name = getattr(func, '__name__', 
+                            getattr(func, '__class__').__name__)
+    except Exception:
+        func_name = str(func)
+    pa.__name__ = func_name
+
+    return pa
+
+upcaseTokens = tokenMap(lambda t: _ustr(t).upper())
+"""(Deprecated) Helper parse action to convert tokens to upper case. Deprecated in favor of L{pyparsing_common.upcaseTokens}"""
+
+downcaseTokens = tokenMap(lambda t: _ustr(t).lower())
+"""(Deprecated) Helper parse action to convert tokens to lower case. Deprecated in favor of L{pyparsing_common.downcaseTokens}"""
+    
+def _makeTags(tagStr, xml):
+    """Internal helper to construct opening and closing tag expressions, given a tag name"""
+    if isinstance(tagStr,basestring):
+        resname = tagStr
+        tagStr = Keyword(tagStr, caseless=not xml)
+    else:
+        resname = tagStr.name
+
+    tagAttrName = Word(alphas,alphanums+"_-:")
+    if (xml):
+        tagAttrValue = dblQuotedString.copy().setParseAction( removeQuotes )
+        openTag = Suppress("<") + tagStr("tag") + \
+                Dict(ZeroOrMore(Group( tagAttrName + Suppress("=") + tagAttrValue ))) + \
+                Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">")
+    else:
+        printablesLessRAbrack = "".join(c for c in printables if c not in ">")
+        tagAttrValue = quotedString.copy().setParseAction( removeQuotes ) | Word(printablesLessRAbrack)
+        openTag = Suppress("<") + tagStr("tag") + \
+                Dict(ZeroOrMore(Group( tagAttrName.setParseAction(downcaseTokens) + \
+                Optional( Suppress("=") + tagAttrValue ) ))) + \
+                Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">")
+    closeTag = Combine(_L("")
+
+    openTag = openTag.setResultsName("start"+"".join(resname.replace(":"," ").title().split())).setName("<%s>" % resname)
+    closeTag = closeTag.setResultsName("end"+"".join(resname.replace(":"," ").title().split())).setName("" % resname)
+    openTag.tag = resname
+    closeTag.tag = resname
+    return openTag, closeTag
+
+def makeHTMLTags(tagStr):
+    """
+    Helper to construct opening and closing tag expressions for HTML, given a tag name. Matches
+    tags in either upper or lower case, attributes with namespaces and with quoted or unquoted values.
+
+    Example::
+        text = 'More info at the pyparsing wiki page'
+        # makeHTMLTags returns pyparsing expressions for the opening and closing tags as a 2-tuple
+        a,a_end = makeHTMLTags("A")
+        link_expr = a + SkipTo(a_end)("link_text") + a_end
+        
+        for link in link_expr.searchString(text):
+            # attributes in the  tag (like "href" shown here) are also accessible as named results
+            print(link.link_text, '->', link.href)
+    prints::
+        pyparsing -> http://pyparsing.wikispaces.com
+    """
+    return _makeTags( tagStr, False )
+
+def makeXMLTags(tagStr):
+    """
+    Helper to construct opening and closing tag expressions for XML, given a tag name. Matches
+    tags only in the given upper/lower case.
+
+    Example: similar to L{makeHTMLTags}
+    """
+    return _makeTags( tagStr, True )
+
+def withAttribute(*args,**attrDict):
+    """
+    Helper to create a validating parse action to be used with start tags created
+    with C{L{makeXMLTags}} or C{L{makeHTMLTags}}. Use C{withAttribute} to qualify a starting tag
+    with a required attribute value, to avoid false matches on common tags such as
+    C{} or C{
}. + + Call C{withAttribute} with a series of attribute names and values. Specify the list + of filter attributes names and values as: + - keyword arguments, as in C{(align="right")}, or + - as an explicit dict with C{**} operator, when an attribute name is also a Python + reserved word, as in C{**{"class":"Customer", "align":"right"}} + - a list of name-value tuples, as in ( ("ns1:class", "Customer"), ("ns2:align","right") ) + For attribute names with a namespace prefix, you must use the second form. Attribute + names are matched insensitive to upper/lower case. + + If just testing for C{class} (with or without a namespace), use C{L{withClass}}. + + To verify that the attribute exists, but without specifying a value, pass + C{withAttribute.ANY_VALUE} as the value. + + Example:: + html = ''' +
+ Some text +
1 4 0 1 0
+
1,3 2,3 1,1
+
this has no type
+
+ + ''' + div,div_end = makeHTMLTags("div") + + # only match div tag having a type attribute with value "grid" + div_grid = div().setParseAction(withAttribute(type="grid")) + grid_expr = div_grid + SkipTo(div | div_end)("body") + for grid_header in grid_expr.searchString(html): + print(grid_header.body) + + # construct a match with any div tag having a type attribute, regardless of the value + div_any_type = div().setParseAction(withAttribute(type=withAttribute.ANY_VALUE)) + div_expr = div_any_type + SkipTo(div | div_end)("body") + for div_header in div_expr.searchString(html): + print(div_header.body) + prints:: + 1 4 0 1 0 + + 1 4 0 1 0 + 1,3 2,3 1,1 + """ + if args: + attrs = args[:] + else: + attrs = attrDict.items() + attrs = [(k,v) for k,v in attrs] + def pa(s,l,tokens): + for attrName,attrValue in attrs: + if attrName not in tokens: + raise ParseException(s,l,"no matching attribute " + attrName) + if attrValue != withAttribute.ANY_VALUE and tokens[attrName] != attrValue: + raise ParseException(s,l,"attribute '%s' has value '%s', must be '%s'" % + (attrName, tokens[attrName], attrValue)) + return pa +withAttribute.ANY_VALUE = object() + +def withClass(classname, namespace=''): + """ + Simplified version of C{L{withAttribute}} when matching on a div class - made + difficult because C{class} is a reserved word in Python. + + Example:: + html = ''' +
+ Some text +
1 4 0 1 0
+
1,3 2,3 1,1
+
this <div> has no class
+
+ + ''' + div,div_end = makeHTMLTags("div") + div_grid = div().setParseAction(withClass("grid")) + + grid_expr = div_grid + SkipTo(div | div_end)("body") + for grid_header in grid_expr.searchString(html): + print(grid_header.body) + + div_any_type = div().setParseAction(withClass(withAttribute.ANY_VALUE)) + div_expr = div_any_type + SkipTo(div | div_end)("body") + for div_header in div_expr.searchString(html): + print(div_header.body) + prints:: + 1 4 0 1 0 + + 1 4 0 1 0 + 1,3 2,3 1,1 + """ + classattr = "%s:class" % namespace if namespace else "class" + return withAttribute(**{classattr : classname}) + +opAssoc = _Constants() +opAssoc.LEFT = object() +opAssoc.RIGHT = object() + +def infixNotation( baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')') ): + """ + Helper method for constructing grammars of expressions made up of + operators working in a precedence hierarchy. Operators may be unary or + binary, left- or right-associative. Parse actions can also be attached + to operator expressions. The generated parser will also recognize the use + of parentheses to override operator precedences (see example below). + + Note: if you define a deep operator list, you may see performance issues + when using infixNotation. See L{ParserElement.enablePackrat} for a + mechanism to potentially improve your parser performance. + + Parameters: + - baseExpr - expression representing the most basic element for the nested + - opList - list of tuples, one for each operator precedence level in the + expression grammar; each tuple is of the form + (opExpr, numTerms, rightLeftAssoc, parseAction), where: + - opExpr is the pyparsing expression for the operator; + may also be a string, which will be converted to a Literal; + if numTerms is 3, opExpr is a tuple of two expressions, for the + two operators separating the 3 terms + - numTerms is the number of terms for this operator (must + be 1, 2, or 3) + - rightLeftAssoc is the indicator whether the operator is + right or left associative, using the pyparsing-defined + constants C{opAssoc.RIGHT} and C{opAssoc.LEFT}. + - parseAction is the parse action to be associated with + expressions matching this operator expression (the + parse action tuple member may be omitted); if the parse action + is passed a tuple or list of functions, this is equivalent to + calling C{setParseAction(*fn)} (L{ParserElement.setParseAction}) + - lpar - expression for matching left-parentheses (default=C{Suppress('(')}) + - rpar - expression for matching right-parentheses (default=C{Suppress(')')}) + + Example:: + # simple example of four-function arithmetic with ints and variable names + integer = pyparsing_common.signed_integer + varname = pyparsing_common.identifier + + arith_expr = infixNotation(integer | varname, + [ + ('-', 1, opAssoc.RIGHT), + (oneOf('* /'), 2, opAssoc.LEFT), + (oneOf('+ -'), 2, opAssoc.LEFT), + ]) + + arith_expr.runTests(''' + 5+3*6 + (5+3)*6 + -2--11 + ''', fullDump=False) + prints:: + 5+3*6 + [[5, '+', [3, '*', 6]]] + + (5+3)*6 + [[[5, '+', 3], '*', 6]] + + -2--11 + [[['-', 2], '-', ['-', 11]]] + """ + ret = Forward() + lastExpr = baseExpr | ( lpar + ret + rpar ) + for i,operDef in enumerate(opList): + opExpr,arity,rightLeftAssoc,pa = (operDef + (None,))[:4] + termName = "%s term" % opExpr if arity < 3 else "%s%s term" % opExpr + if arity == 3: + if opExpr is None or len(opExpr) != 2: + raise ValueError("if numterms=3, opExpr must be a tuple or list of two expressions") + opExpr1, opExpr2 = opExpr + thisExpr = Forward().setName(termName) + if rightLeftAssoc == opAssoc.LEFT: + if arity == 1: + matchExpr = FollowedBy(lastExpr + opExpr) + Group( lastExpr + OneOrMore( opExpr ) ) + elif arity == 2: + if opExpr is not None: + matchExpr = FollowedBy(lastExpr + opExpr + lastExpr) + Group( lastExpr + OneOrMore( opExpr + lastExpr ) ) + else: + matchExpr = FollowedBy(lastExpr+lastExpr) + Group( lastExpr + OneOrMore(lastExpr) ) + elif arity == 3: + matchExpr = FollowedBy(lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr) + \ + Group( lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr ) + else: + raise ValueError("operator must be unary (1), binary (2), or ternary (3)") + elif rightLeftAssoc == opAssoc.RIGHT: + if arity == 1: + # try to avoid LR with this extra test + if not isinstance(opExpr, Optional): + opExpr = Optional(opExpr) + matchExpr = FollowedBy(opExpr.expr + thisExpr) + Group( opExpr + thisExpr ) + elif arity == 2: + if opExpr is not None: + matchExpr = FollowedBy(lastExpr + opExpr + thisExpr) + Group( lastExpr + OneOrMore( opExpr + thisExpr ) ) + else: + matchExpr = FollowedBy(lastExpr + thisExpr) + Group( lastExpr + OneOrMore( thisExpr ) ) + elif arity == 3: + matchExpr = FollowedBy(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr) + \ + Group( lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr ) + else: + raise ValueError("operator must be unary (1), binary (2), or ternary (3)") + else: + raise ValueError("operator must indicate right or left associativity") + if pa: + if isinstance(pa, (tuple, list)): + matchExpr.setParseAction(*pa) + else: + matchExpr.setParseAction(pa) + thisExpr <<= ( matchExpr.setName(termName) | lastExpr ) + lastExpr = thisExpr + ret <<= lastExpr + return ret + +operatorPrecedence = infixNotation +"""(Deprecated) Former name of C{L{infixNotation}}, will be dropped in a future release.""" + +dblQuotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*')+'"').setName("string enclosed in double quotes") +sglQuotedString = Combine(Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*")+"'").setName("string enclosed in single quotes") +quotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*')+'"'| + Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*")+"'").setName("quotedString using single or double quotes") +unicodeString = Combine(_L('u') + quotedString.copy()).setName("unicode string literal") + +def nestedExpr(opener="(", closer=")", content=None, ignoreExpr=quotedString.copy()): + """ + Helper method for defining nested lists enclosed in opening and closing + delimiters ("(" and ")" are the default). + + Parameters: + - opener - opening character for a nested list (default=C{"("}); can also be a pyparsing expression + - closer - closing character for a nested list (default=C{")"}); can also be a pyparsing expression + - content - expression for items within the nested lists (default=C{None}) + - ignoreExpr - expression for ignoring opening and closing delimiters (default=C{quotedString}) + + If an expression is not provided for the content argument, the nested + expression will capture all whitespace-delimited content between delimiters + as a list of separate values. + + Use the C{ignoreExpr} argument to define expressions that may contain + opening or closing characters that should not be treated as opening + or closing characters for nesting, such as quotedString or a comment + expression. Specify multiple expressions using an C{L{Or}} or C{L{MatchFirst}}. + The default is L{quotedString}, but if no expressions are to be ignored, + then pass C{None} for this argument. + + Example:: + data_type = oneOf("void int short long char float double") + decl_data_type = Combine(data_type + Optional(Word('*'))) + ident = Word(alphas+'_', alphanums+'_') + number = pyparsing_common.number + arg = Group(decl_data_type + ident) + LPAR,RPAR = map(Suppress, "()") + + code_body = nestedExpr('{', '}', ignoreExpr=(quotedString | cStyleComment)) + + c_function = (decl_data_type("type") + + ident("name") + + LPAR + Optional(delimitedList(arg), [])("args") + RPAR + + code_body("body")) + c_function.ignore(cStyleComment) + + source_code = ''' + int is_odd(int x) { + return (x%2); + } + + int dec_to_hex(char hchar) { + if (hchar >= '0' && hchar <= '9') { + return (ord(hchar)-ord('0')); + } else { + return (10+ord(hchar)-ord('A')); + } + } + ''' + for func in c_function.searchString(source_code): + print("%(name)s (%(type)s) args: %(args)s" % func) + + prints:: + is_odd (int) args: [['int', 'x']] + dec_to_hex (int) args: [['char', 'hchar']] + """ + if opener == closer: + raise ValueError("opening and closing strings cannot be the same") + if content is None: + if isinstance(opener,basestring) and isinstance(closer,basestring): + if len(opener) == 1 and len(closer)==1: + if ignoreExpr is not None: + content = (Combine(OneOrMore(~ignoreExpr + + CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + content = (empty.copy()+CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS + ).setParseAction(lambda t:t[0].strip())) + else: + if ignoreExpr is not None: + content = (Combine(OneOrMore(~ignoreExpr + + ~Literal(opener) + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + content = (Combine(OneOrMore(~Literal(opener) + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + raise ValueError("opening and closing arguments must be strings if no content expression is given") + ret = Forward() + if ignoreExpr is not None: + ret <<= Group( Suppress(opener) + ZeroOrMore( ignoreExpr | ret | content ) + Suppress(closer) ) + else: + ret <<= Group( Suppress(opener) + ZeroOrMore( ret | content ) + Suppress(closer) ) + ret.setName('nested %s%s expression' % (opener,closer)) + return ret + +def indentedBlock(blockStatementExpr, indentStack, indent=True): + """ + Helper method for defining space-delimited indentation blocks, such as + those used to define block statements in Python source code. + + Parameters: + - blockStatementExpr - expression defining syntax of statement that + is repeated within the indented block + - indentStack - list created by caller to manage indentation stack + (multiple statementWithIndentedBlock expressions within a single grammar + should share a common indentStack) + - indent - boolean indicating whether block must be indented beyond the + the current level; set to False for block of left-most statements + (default=C{True}) + + A valid block must contain at least one C{blockStatement}. + + Example:: + data = ''' + def A(z): + A1 + B = 100 + G = A2 + A2 + A3 + B + def BB(a,b,c): + BB1 + def BBA(): + bba1 + bba2 + bba3 + C + D + def spam(x,y): + def eggs(z): + pass + ''' + + + indentStack = [1] + stmt = Forward() + + identifier = Word(alphas, alphanums) + funcDecl = ("def" + identifier + Group( "(" + Optional( delimitedList(identifier) ) + ")" ) + ":") + func_body = indentedBlock(stmt, indentStack) + funcDef = Group( funcDecl + func_body ) + + rvalue = Forward() + funcCall = Group(identifier + "(" + Optional(delimitedList(rvalue)) + ")") + rvalue << (funcCall | identifier | Word(nums)) + assignment = Group(identifier + "=" + rvalue) + stmt << ( funcDef | assignment | identifier ) + + module_body = OneOrMore(stmt) + + parseTree = module_body.parseString(data) + parseTree.pprint() + prints:: + [['def', + 'A', + ['(', 'z', ')'], + ':', + [['A1'], [['B', '=', '100']], [['G', '=', 'A2']], ['A2'], ['A3']]], + 'B', + ['def', + 'BB', + ['(', 'a', 'b', 'c', ')'], + ':', + [['BB1'], [['def', 'BBA', ['(', ')'], ':', [['bba1'], ['bba2'], ['bba3']]]]]], + 'C', + 'D', + ['def', + 'spam', + ['(', 'x', 'y', ')'], + ':', + [[['def', 'eggs', ['(', 'z', ')'], ':', [['pass']]]]]]] + """ + def checkPeerIndent(s,l,t): + if l >= len(s): return + curCol = col(l,s) + if curCol != indentStack[-1]: + if curCol > indentStack[-1]: + raise ParseFatalException(s,l,"illegal nesting") + raise ParseException(s,l,"not a peer entry") + + def checkSubIndent(s,l,t): + curCol = col(l,s) + if curCol > indentStack[-1]: + indentStack.append( curCol ) + else: + raise ParseException(s,l,"not a subentry") + + def checkUnindent(s,l,t): + if l >= len(s): return + curCol = col(l,s) + if not(indentStack and curCol < indentStack[-1] and curCol <= indentStack[-2]): + raise ParseException(s,l,"not an unindent") + indentStack.pop() + + NL = OneOrMore(LineEnd().setWhitespaceChars("\t ").suppress()) + INDENT = (Empty() + Empty().setParseAction(checkSubIndent)).setName('INDENT') + PEER = Empty().setParseAction(checkPeerIndent).setName('') + UNDENT = Empty().setParseAction(checkUnindent).setName('UNINDENT') + if indent: + smExpr = Group( Optional(NL) + + #~ FollowedBy(blockStatementExpr) + + INDENT + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) + UNDENT) + else: + smExpr = Group( Optional(NL) + + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) ) + blockStatementExpr.ignore(_bslash + LineEnd()) + return smExpr.setName('indented block') + +alphas8bit = srange(r"[\0xc0-\0xd6\0xd8-\0xf6\0xf8-\0xff]") +punc8bit = srange(r"[\0xa1-\0xbf\0xd7\0xf7]") + +anyOpenTag,anyCloseTag = makeHTMLTags(Word(alphas,alphanums+"_:").setName('any tag')) +_htmlEntityMap = dict(zip("gt lt amp nbsp quot apos".split(),'><& "\'')) +commonHTMLEntity = Regex('&(?P' + '|'.join(_htmlEntityMap.keys()) +");").setName("common HTML entity") +def replaceHTMLEntity(t): + """Helper parser action to replace common HTML entities with their special characters""" + return _htmlEntityMap.get(t.entity) + +# it's easy to get these comment structures wrong - they're very common, so may as well make them available +cStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/').setName("C style comment") +"Comment of the form C{/* ... */}" + +htmlComment = Regex(r"").setName("HTML comment") +"Comment of the form C{}" + +restOfLine = Regex(r".*").leaveWhitespace().setName("rest of line") +dblSlashComment = Regex(r"//(?:\\\n|[^\n])*").setName("// comment") +"Comment of the form C{// ... (to end of line)}" + +cppStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/'| dblSlashComment).setName("C++ style comment") +"Comment of either form C{L{cStyleComment}} or C{L{dblSlashComment}}" + +javaStyleComment = cppStyleComment +"Same as C{L{cppStyleComment}}" + +pythonStyleComment = Regex(r"#.*").setName("Python style comment") +"Comment of the form C{# ... (to end of line)}" + +_commasepitem = Combine(OneOrMore(Word(printables, excludeChars=',') + + Optional( Word(" \t") + + ~Literal(",") + ~LineEnd() ) ) ).streamline().setName("commaItem") +commaSeparatedList = delimitedList( Optional( quotedString.copy() | _commasepitem, default="") ).setName("commaSeparatedList") +"""(Deprecated) Predefined expression of 1 or more printable words or quoted strings, separated by commas. + This expression is deprecated in favor of L{pyparsing_common.comma_separated_list}.""" + +# some other useful expressions - using lower-case class name since we are really using this as a namespace +class pyparsing_common: + """ + Here are some common low-level expressions that may be useful in jump-starting parser development: + - numeric forms (L{integers}, L{reals}, L{scientific notation}) + - common L{programming identifiers} + - network addresses (L{MAC}, L{IPv4}, L{IPv6}) + - ISO8601 L{dates} and L{datetime} + - L{UUID} + - L{comma-separated list} + Parse actions: + - C{L{convertToInteger}} + - C{L{convertToFloat}} + - C{L{convertToDate}} + - C{L{convertToDatetime}} + - C{L{stripHTMLTags}} + - C{L{upcaseTokens}} + - C{L{downcaseTokens}} + + Example:: + pyparsing_common.number.runTests(''' + # any int or real number, returned as the appropriate type + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + ''') + + pyparsing_common.fnumber.runTests(''' + # any int or real number, returned as float + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + ''') + + pyparsing_common.hex_integer.runTests(''' + # hex numbers + 100 + FF + ''') + + pyparsing_common.fraction.runTests(''' + # fractions + 1/2 + -3/4 + ''') + + pyparsing_common.mixed_integer.runTests(''' + # mixed fractions + 1 + 1/2 + -3/4 + 1-3/4 + ''') + + import uuid + pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID)) + pyparsing_common.uuid.runTests(''' + # uuid + 12345678-1234-5678-1234-567812345678 + ''') + prints:: + # any int or real number, returned as the appropriate type + 100 + [100] + + -100 + [-100] + + +100 + [100] + + 3.14159 + [3.14159] + + 6.02e23 + [6.02e+23] + + 1e-12 + [1e-12] + + # any int or real number, returned as float + 100 + [100.0] + + -100 + [-100.0] + + +100 + [100.0] + + 3.14159 + [3.14159] + + 6.02e23 + [6.02e+23] + + 1e-12 + [1e-12] + + # hex numbers + 100 + [256] + + FF + [255] + + # fractions + 1/2 + [0.5] + + -3/4 + [-0.75] + + # mixed fractions + 1 + [1] + + 1/2 + [0.5] + + -3/4 + [-0.75] + + 1-3/4 + [1.75] + + # uuid + 12345678-1234-5678-1234-567812345678 + [UUID('12345678-1234-5678-1234-567812345678')] + """ + + convertToInteger = tokenMap(int) + """ + Parse action for converting parsed integers to Python int + """ + + convertToFloat = tokenMap(float) + """ + Parse action for converting parsed numbers to Python float + """ + + integer = Word(nums).setName("integer").setParseAction(convertToInteger) + """expression that parses an unsigned integer, returns an int""" + + hex_integer = Word(hexnums).setName("hex integer").setParseAction(tokenMap(int,16)) + """expression that parses a hexadecimal integer, returns an int""" + + signed_integer = Regex(r'[+-]?\d+').setName("signed integer").setParseAction(convertToInteger) + """expression that parses an integer with optional leading sign, returns an int""" + + fraction = (signed_integer().setParseAction(convertToFloat) + '/' + signed_integer().setParseAction(convertToFloat)).setName("fraction") + """fractional expression of an integer divided by an integer, returns a float""" + fraction.addParseAction(lambda t: t[0]/t[-1]) + + mixed_integer = (fraction | signed_integer + Optional(Optional('-').suppress() + fraction)).setName("fraction or mixed integer-fraction") + """mixed integer of the form 'integer - fraction', with optional leading integer, returns float""" + mixed_integer.addParseAction(sum) + + real = Regex(r'[+-]?\d+\.\d*').setName("real number").setParseAction(convertToFloat) + """expression that parses a floating point number and returns a float""" + + sci_real = Regex(r'[+-]?\d+([eE][+-]?\d+|\.\d*([eE][+-]?\d+)?)').setName("real number with scientific notation").setParseAction(convertToFloat) + """expression that parses a floating point number with optional scientific notation and returns a float""" + + # streamlining this expression makes the docs nicer-looking + number = (sci_real | real | signed_integer).streamline() + """any numeric expression, returns the corresponding Python type""" + + fnumber = Regex(r'[+-]?\d+\.?\d*([eE][+-]?\d+)?').setName("fnumber").setParseAction(convertToFloat) + """any int or real number, returned as float""" + + identifier = Word(alphas+'_', alphanums+'_').setName("identifier") + """typical code identifier (leading alpha or '_', followed by 0 or more alphas, nums, or '_')""" + + ipv4_address = Regex(r'(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})){3}').setName("IPv4 address") + "IPv4 address (C{0.0.0.0 - 255.255.255.255})" + + _ipv6_part = Regex(r'[0-9a-fA-F]{1,4}').setName("hex_integer") + _full_ipv6_address = (_ipv6_part + (':' + _ipv6_part)*7).setName("full IPv6 address") + _short_ipv6_address = (Optional(_ipv6_part + (':' + _ipv6_part)*(0,6)) + "::" + Optional(_ipv6_part + (':' + _ipv6_part)*(0,6))).setName("short IPv6 address") + _short_ipv6_address.addCondition(lambda t: sum(1 for tt in t if pyparsing_common._ipv6_part.matches(tt)) < 8) + _mixed_ipv6_address = ("::ffff:" + ipv4_address).setName("mixed IPv6 address") + ipv6_address = Combine((_full_ipv6_address | _mixed_ipv6_address | _short_ipv6_address).setName("IPv6 address")).setName("IPv6 address") + "IPv6 address (long, short, or mixed form)" + + mac_address = Regex(r'[0-9a-fA-F]{2}([:.-])[0-9a-fA-F]{2}(?:\1[0-9a-fA-F]{2}){4}').setName("MAC address") + "MAC address xx:xx:xx:xx:xx (may also have '-' or '.' delimiters)" + + @staticmethod + def convertToDate(fmt="%Y-%m-%d"): + """ + Helper to create a parse action for converting parsed date string to Python datetime.date + + Params - + - fmt - format to be passed to datetime.strptime (default=C{"%Y-%m-%d"}) + + Example:: + date_expr = pyparsing_common.iso8601_date.copy() + date_expr.setParseAction(pyparsing_common.convertToDate()) + print(date_expr.parseString("1999-12-31")) + prints:: + [datetime.date(1999, 12, 31)] + """ + def cvt_fn(s,l,t): + try: + return datetime.strptime(t[0], fmt).date() + except ValueError as ve: + raise ParseException(s, l, str(ve)) + return cvt_fn + + @staticmethod + def convertToDatetime(fmt="%Y-%m-%dT%H:%M:%S.%f"): + """ + Helper to create a parse action for converting parsed datetime string to Python datetime.datetime + + Params - + - fmt - format to be passed to datetime.strptime (default=C{"%Y-%m-%dT%H:%M:%S.%f"}) + + Example:: + dt_expr = pyparsing_common.iso8601_datetime.copy() + dt_expr.setParseAction(pyparsing_common.convertToDatetime()) + print(dt_expr.parseString("1999-12-31T23:59:59.999")) + prints:: + [datetime.datetime(1999, 12, 31, 23, 59, 59, 999000)] + """ + def cvt_fn(s,l,t): + try: + return datetime.strptime(t[0], fmt) + except ValueError as ve: + raise ParseException(s, l, str(ve)) + return cvt_fn + + iso8601_date = Regex(r'(?P\d{4})(?:-(?P\d\d)(?:-(?P\d\d))?)?').setName("ISO8601 date") + "ISO8601 date (C{yyyy-mm-dd})" + + iso8601_datetime = Regex(r'(?P\d{4})-(?P\d\d)-(?P\d\d)[T ](?P\d\d):(?P\d\d)(:(?P\d\d(\.\d*)?)?)?(?PZ|[+-]\d\d:?\d\d)?').setName("ISO8601 datetime") + "ISO8601 datetime (C{yyyy-mm-ddThh:mm:ss.s(Z|+-00:00)}) - trailing seconds, milliseconds, and timezone optional; accepts separating C{'T'} or C{' '}" + + uuid = Regex(r'[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}').setName("UUID") + "UUID (C{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx})" + + _html_stripper = anyOpenTag.suppress() | anyCloseTag.suppress() + @staticmethod + def stripHTMLTags(s, l, tokens): + """ + Parse action to remove HTML tags from web page HTML source + + Example:: + # strip HTML links from normal text + text = 'More info at the
pyparsing wiki page' + td,td_end = makeHTMLTags("TD") + table_text = td + SkipTo(td_end).setParseAction(pyparsing_common.stripHTMLTags)("body") + td_end + + print(table_text.parseString(text).body) # -> 'More info at the pyparsing wiki page' + """ + return pyparsing_common._html_stripper.transformString(tokens[0]) + + _commasepitem = Combine(OneOrMore(~Literal(",") + ~LineEnd() + Word(printables, excludeChars=',') + + Optional( White(" \t") ) ) ).streamline().setName("commaItem") + comma_separated_list = delimitedList( Optional( quotedString.copy() | _commasepitem, default="") ).setName("comma separated list") + """Predefined expression of 1 or more printable words or quoted strings, separated by commas.""" + + upcaseTokens = staticmethod(tokenMap(lambda t: _ustr(t).upper())) + """Parse action to convert tokens to upper case.""" + + downcaseTokens = staticmethod(tokenMap(lambda t: _ustr(t).lower())) + """Parse action to convert tokens to lower case.""" + + +if __name__ == "__main__": + + selectToken = CaselessLiteral("select") + fromToken = CaselessLiteral("from") + + ident = Word(alphas, alphanums + "_$") + + columnName = delimitedList(ident, ".", combine=True).setParseAction(upcaseTokens) + columnNameList = Group(delimitedList(columnName)).setName("columns") + columnSpec = ('*' | columnNameList) + + tableName = delimitedList(ident, ".", combine=True).setParseAction(upcaseTokens) + tableNameList = Group(delimitedList(tableName)).setName("tables") + + simpleSQL = selectToken("command") + columnSpec("columns") + fromToken + tableNameList("tables") + + # demo runTests method, including embedded comments in test string + simpleSQL.runTests(""" + # '*' as column list and dotted table name + select * from SYS.XYZZY + + # caseless match on "SELECT", and casts back to "select" + SELECT * from XYZZY, ABC + + # list of column names, and mixed case SELECT keyword + Select AA,BB,CC from Sys.dual + + # multiple tables + Select A, B, C from Sys.dual, Table2 + + # invalid SELECT keyword - should fail + Xelect A, B, C from Sys.dual + + # incomplete command - should fail + Select + + # invalid column name - should fail + Select ^^^ frox Sys.dual + + """) + + pyparsing_common.number.runTests(""" + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + """) + + # any int or real number, returned as float + pyparsing_common.fnumber.runTests(""" + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + """) + + pyparsing_common.hex_integer.runTests(""" + 100 + FF + """) + + import uuid + pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID)) + pyparsing_common.uuid.runTests(""" + 12345678-1234-5678-1234-567812345678 + """) diff --git a/venv/lib/python3.10/site-packages/setuptools/archive_util.py b/venv/lib/python3.10/site-packages/setuptools/archive_util.py new file mode 100644 index 00000000..0f702848 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/archive_util.py @@ -0,0 +1,205 @@ +"""Utilities for extracting common archive formats""" + +import zipfile +import tarfile +import os +import shutil +import posixpath +import contextlib +from distutils.errors import DistutilsError + +from pkg_resources import ensure_directory + +__all__ = [ + "unpack_archive", "unpack_zipfile", "unpack_tarfile", "default_filter", + "UnrecognizedFormat", "extraction_drivers", "unpack_directory", +] + + +class UnrecognizedFormat(DistutilsError): + """Couldn't recognize the archive type""" + + +def default_filter(src, dst): + """The default progress/filter callback; returns True for all files""" + return dst + + +def unpack_archive( + filename, extract_dir, progress_filter=default_filter, + drivers=None): + """Unpack `filename` to `extract_dir`, or raise ``UnrecognizedFormat`` + + `progress_filter` is a function taking two arguments: a source path + internal to the archive ('/'-separated), and a filesystem path where it + will be extracted. The callback must return the desired extract path + (which may be the same as the one passed in), or else ``None`` to skip + that file or directory. The callback can thus be used to report on the + progress of the extraction, as well as to filter the items extracted or + alter their extraction paths. + + `drivers`, if supplied, must be a non-empty sequence of functions with the + same signature as this function (minus the `drivers` argument), that raise + ``UnrecognizedFormat`` if they do not support extracting the designated + archive type. The `drivers` are tried in sequence until one is found that + does not raise an error, or until all are exhausted (in which case + ``UnrecognizedFormat`` is raised). If you do not supply a sequence of + drivers, the module's ``extraction_drivers`` constant will be used, which + means that ``unpack_zipfile`` and ``unpack_tarfile`` will be tried, in that + order. + """ + for driver in drivers or extraction_drivers: + try: + driver(filename, extract_dir, progress_filter) + except UnrecognizedFormat: + continue + else: + return + else: + raise UnrecognizedFormat( + "Not a recognized archive type: %s" % filename + ) + + +def unpack_directory(filename, extract_dir, progress_filter=default_filter): + """"Unpack" a directory, using the same interface as for archives + + Raises ``UnrecognizedFormat`` if `filename` is not a directory + """ + if not os.path.isdir(filename): + raise UnrecognizedFormat("%s is not a directory" % filename) + + paths = { + filename: ('', extract_dir), + } + for base, dirs, files in os.walk(filename): + src, dst = paths[base] + for d in dirs: + paths[os.path.join(base, d)] = src + d + '/', os.path.join(dst, d) + for f in files: + target = os.path.join(dst, f) + target = progress_filter(src + f, target) + if not target: + # skip non-files + continue + ensure_directory(target) + f = os.path.join(base, f) + shutil.copyfile(f, target) + shutil.copystat(f, target) + + +def unpack_zipfile(filename, extract_dir, progress_filter=default_filter): + """Unpack zip `filename` to `extract_dir` + + Raises ``UnrecognizedFormat`` if `filename` is not a zipfile (as determined + by ``zipfile.is_zipfile()``). See ``unpack_archive()`` for an explanation + of the `progress_filter` argument. + """ + + if not zipfile.is_zipfile(filename): + raise UnrecognizedFormat("%s is not a zip file" % (filename,)) + + with zipfile.ZipFile(filename) as z: + for info in z.infolist(): + name = info.filename + + # don't extract absolute paths or ones with .. in them + if name.startswith('/') or '..' in name.split('/'): + continue + + target = os.path.join(extract_dir, *name.split('/')) + target = progress_filter(name, target) + if not target: + continue + if name.endswith('/'): + # directory + ensure_directory(target) + else: + # file + ensure_directory(target) + data = z.read(info.filename) + with open(target, 'wb') as f: + f.write(data) + unix_attributes = info.external_attr >> 16 + if unix_attributes: + os.chmod(target, unix_attributes) + + +def _resolve_tar_file_or_dir(tar_obj, tar_member_obj): + """Resolve any links and extract link targets as normal files.""" + while tar_member_obj is not None and ( + tar_member_obj.islnk() or tar_member_obj.issym()): + linkpath = tar_member_obj.linkname + if tar_member_obj.issym(): + base = posixpath.dirname(tar_member_obj.name) + linkpath = posixpath.join(base, linkpath) + linkpath = posixpath.normpath(linkpath) + tar_member_obj = tar_obj._getmember(linkpath) + + is_file_or_dir = ( + tar_member_obj is not None and + (tar_member_obj.isfile() or tar_member_obj.isdir()) + ) + if is_file_or_dir: + return tar_member_obj + + raise LookupError('Got unknown file type') + + +def _iter_open_tar(tar_obj, extract_dir, progress_filter): + """Emit member-destination pairs from a tar archive.""" + # don't do any chowning! + tar_obj.chown = lambda *args: None + + with contextlib.closing(tar_obj): + for member in tar_obj: + name = member.name + # don't extract absolute paths or ones with .. in them + if name.startswith('/') or '..' in name.split('/'): + continue + + prelim_dst = os.path.join(extract_dir, *name.split('/')) + + try: + member = _resolve_tar_file_or_dir(tar_obj, member) + except LookupError: + continue + + final_dst = progress_filter(name, prelim_dst) + if not final_dst: + continue + + if final_dst.endswith(os.sep): + final_dst = final_dst[:-1] + + yield member, final_dst + + +def unpack_tarfile(filename, extract_dir, progress_filter=default_filter): + """Unpack tar/tar.gz/tar.bz2 `filename` to `extract_dir` + + Raises ``UnrecognizedFormat`` if `filename` is not a tarfile (as determined + by ``tarfile.open()``). See ``unpack_archive()`` for an explanation + of the `progress_filter` argument. + """ + try: + tarobj = tarfile.open(filename) + except tarfile.TarError as e: + raise UnrecognizedFormat( + "%s is not a compressed or uncompressed tar file" % (filename,) + ) from e + + for member, final_dst in _iter_open_tar( + tarobj, extract_dir, progress_filter, + ): + try: + # XXX Ugh + tarobj._extract_member(member, final_dst) + except tarfile.ExtractError: + # chown/chmod/mkfifo/mknode/makedev failed + pass + + return True + + +extraction_drivers = unpack_directory, unpack_zipfile, unpack_tarfile diff --git a/venv/lib/python3.10/site-packages/setuptools/build_meta.py b/venv/lib/python3.10/site-packages/setuptools/build_meta.py new file mode 100644 index 00000000..d0ac613b --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/build_meta.py @@ -0,0 +1,290 @@ +"""A PEP 517 interface to setuptools + +Previously, when a user or a command line tool (let's call it a "frontend") +needed to make a request of setuptools to take a certain action, for +example, generating a list of installation requirements, the frontend would +would call "setup.py egg_info" or "setup.py bdist_wheel" on the command line. + +PEP 517 defines a different method of interfacing with setuptools. Rather +than calling "setup.py" directly, the frontend should: + + 1. Set the current directory to the directory with a setup.py file + 2. Import this module into a safe python interpreter (one in which + setuptools can potentially set global variables or crash hard). + 3. Call one of the functions defined in PEP 517. + +What each function does is defined in PEP 517. However, here is a "casual" +definition of the functions (this definition should not be relied on for +bug reports or API stability): + + - `build_wheel`: build a wheel in the folder and return the basename + - `get_requires_for_build_wheel`: get the `setup_requires` to build + - `prepare_metadata_for_build_wheel`: get the `install_requires` + - `build_sdist`: build an sdist in the folder and return the basename + - `get_requires_for_build_sdist`: get the `setup_requires` to build + +Again, this is not a formal definition! Just a "taste" of the module. +""" + +import io +import os +import sys +import tokenize +import shutil +import contextlib +import tempfile +import warnings + +import setuptools +import distutils + +from pkg_resources import parse_requirements + +__all__ = ['get_requires_for_build_sdist', + 'get_requires_for_build_wheel', + 'prepare_metadata_for_build_wheel', + 'build_wheel', + 'build_sdist', + '__legacy__', + 'SetupRequirementsError'] + + +class SetupRequirementsError(BaseException): + def __init__(self, specifiers): + self.specifiers = specifiers + + +class Distribution(setuptools.dist.Distribution): + def fetch_build_eggs(self, specifiers): + specifier_list = list(map(str, parse_requirements(specifiers))) + + raise SetupRequirementsError(specifier_list) + + @classmethod + @contextlib.contextmanager + def patch(cls): + """ + Replace + distutils.dist.Distribution with this class + for the duration of this context. + """ + orig = distutils.core.Distribution + distutils.core.Distribution = cls + try: + yield + finally: + distutils.core.Distribution = orig + + +@contextlib.contextmanager +def no_install_setup_requires(): + """Temporarily disable installing setup_requires + + Under PEP 517, the backend reports build dependencies to the frontend, + and the frontend is responsible for ensuring they're installed. + So setuptools (acting as a backend) should not try to install them. + """ + orig = setuptools._install_setup_requires + setuptools._install_setup_requires = lambda attrs: None + try: + yield + finally: + setuptools._install_setup_requires = orig + + +def _get_immediate_subdirectories(a_dir): + return [name for name in os.listdir(a_dir) + if os.path.isdir(os.path.join(a_dir, name))] + + +def _file_with_extension(directory, extension): + matching = ( + f for f in os.listdir(directory) + if f.endswith(extension) + ) + try: + file, = matching + except ValueError: + raise ValueError( + 'No distribution was found. Ensure that `setup.py` ' + 'is not empty and that it calls `setup()`.') + return file + + +def _open_setup_script(setup_script): + if not os.path.exists(setup_script): + # Supply a default setup.py + return io.StringIO(u"from setuptools import setup; setup()") + + return getattr(tokenize, 'open', open)(setup_script) + + +@contextlib.contextmanager +def suppress_known_deprecation(): + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', 'setup.py install is deprecated') + yield + + +class _BuildMetaBackend(object): + + def _fix_config(self, config_settings): + config_settings = config_settings or {} + config_settings.setdefault('--global-option', []) + return config_settings + + def _get_build_requires(self, config_settings, requirements): + config_settings = self._fix_config(config_settings) + + sys.argv = sys.argv[:1] + ['egg_info'] + \ + config_settings["--global-option"] + try: + with Distribution.patch(): + self.run_setup() + except SetupRequirementsError as e: + requirements += e.specifiers + + return requirements + + def run_setup(self, setup_script='setup.py'): + # Note that we can reuse our build directory between calls + # Correctness comes first, then optimization later + __file__ = setup_script + __name__ = '__main__' + + with _open_setup_script(__file__) as f: + code = f.read().replace(r'\r\n', r'\n') + + exec(compile(code, __file__, 'exec'), locals()) + + def get_requires_for_build_wheel(self, config_settings=None): + config_settings = self._fix_config(config_settings) + return self._get_build_requires( + config_settings, requirements=['wheel']) + + def get_requires_for_build_sdist(self, config_settings=None): + config_settings = self._fix_config(config_settings) + return self._get_build_requires(config_settings, requirements=[]) + + def prepare_metadata_for_build_wheel(self, metadata_directory, + config_settings=None): + sys.argv = sys.argv[:1] + [ + 'dist_info', '--egg-base', metadata_directory] + with no_install_setup_requires(): + self.run_setup() + + dist_info_directory = metadata_directory + while True: + dist_infos = [f for f in os.listdir(dist_info_directory) + if f.endswith('.dist-info')] + + if ( + len(dist_infos) == 0 and + len(_get_immediate_subdirectories(dist_info_directory)) == 1 + ): + + dist_info_directory = os.path.join( + dist_info_directory, os.listdir(dist_info_directory)[0]) + continue + + assert len(dist_infos) == 1 + break + + # PEP 517 requires that the .dist-info directory be placed in the + # metadata_directory. To comply, we MUST copy the directory to the root + if dist_info_directory != metadata_directory: + shutil.move( + os.path.join(dist_info_directory, dist_infos[0]), + metadata_directory) + shutil.rmtree(dist_info_directory, ignore_errors=True) + + return dist_infos[0] + + def _build_with_temp_dir(self, setup_command, result_extension, + result_directory, config_settings): + config_settings = self._fix_config(config_settings) + result_directory = os.path.abspath(result_directory) + + # Build in a temporary directory, then copy to the target. + os.makedirs(result_directory, exist_ok=True) + with tempfile.TemporaryDirectory(dir=result_directory) as tmp_dist_dir: + sys.argv = (sys.argv[:1] + setup_command + + ['--dist-dir', tmp_dist_dir] + + config_settings["--global-option"]) + with no_install_setup_requires(): + self.run_setup() + + result_basename = _file_with_extension( + tmp_dist_dir, result_extension) + result_path = os.path.join(result_directory, result_basename) + if os.path.exists(result_path): + # os.rename will fail overwriting on non-Unix. + os.remove(result_path) + os.rename(os.path.join(tmp_dist_dir, result_basename), result_path) + + return result_basename + + def build_wheel(self, wheel_directory, config_settings=None, + metadata_directory=None): + with suppress_known_deprecation(): + return self._build_with_temp_dir(['bdist_wheel'], '.whl', + wheel_directory, config_settings) + + def build_sdist(self, sdist_directory, config_settings=None): + return self._build_with_temp_dir(['sdist', '--formats', 'gztar'], + '.tar.gz', sdist_directory, + config_settings) + + +class _BuildMetaLegacyBackend(_BuildMetaBackend): + """Compatibility backend for setuptools + + This is a version of setuptools.build_meta that endeavors + to maintain backwards + compatibility with pre-PEP 517 modes of invocation. It + exists as a temporary + bridge between the old packaging mechanism and the new + packaging mechanism, + and will eventually be removed. + """ + def run_setup(self, setup_script='setup.py'): + # In order to maintain compatibility with scripts assuming that + # the setup.py script is in a directory on the PYTHONPATH, inject + # '' into sys.path. (pypa/setuptools#1642) + sys_path = list(sys.path) # Save the original path + + script_dir = os.path.dirname(os.path.abspath(setup_script)) + if script_dir not in sys.path: + sys.path.insert(0, script_dir) + + # Some setup.py scripts (e.g. in pygame and numpy) use sys.argv[0] to + # get the directory of the source code. They expect it to refer to the + # setup.py script. + sys_argv_0 = sys.argv[0] + sys.argv[0] = setup_script + + try: + super(_BuildMetaLegacyBackend, + self).run_setup(setup_script=setup_script) + finally: + # While PEP 517 frontends should be calling each hook in a fresh + # subprocess according to the standard (and thus it should not be + # strictly necessary to restore the old sys.path), we'll restore + # the original path so that the path manipulation does not persist + # within the hook after run_setup is called. + sys.path[:] = sys_path + sys.argv[0] = sys_argv_0 + + +# The primary backend +_BACKEND = _BuildMetaBackend() + +get_requires_for_build_wheel = _BACKEND.get_requires_for_build_wheel +get_requires_for_build_sdist = _BACKEND.get_requires_for_build_sdist +prepare_metadata_for_build_wheel = _BACKEND.prepare_metadata_for_build_wheel +build_wheel = _BACKEND.build_wheel +build_sdist = _BACKEND.build_sdist + + +# The legacy backend +__legacy__ = _BuildMetaLegacyBackend() diff --git a/venv/lib/python3.10/site-packages/setuptools/cli-32.exe b/venv/lib/python3.10/site-packages/setuptools/cli-32.exe new file mode 100644 index 0000000000000000000000000000000000000000..b1487b7819e7286577a043c7726fbe0ca1543083 GIT binary patch literal 65536 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

@|$jDp4t7maBT@by!vG1&J_?=DS4W3Hu6w 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[c}iI#RErv3nvcIClH@!{vM)zJ_weD zu_-L8NU*GlC{d0L!!VW10^+~>qmNB~Y8H+F}!P8_d(PpvjzMJQmr z)FkX;2B~<|3JfJeWv@IXo~nTtp$}Gjie> zs8UDG*kid(%i5QCBp~MA;#I186PI-nZ&k7!k8BiLJSuR>h7ArSYHD~B0I z=T6L{zqglekt0JjG5z&|GWb4?+B5+{p^fgTufl_KesA{@I&g7rNq==^SGc5GcM%$N zDBG2)qExz*Z;jGN_-iD-y8i2BCq)p}2lKcspLg>w-;qwg(()HXrZa3jd!}spuwBVX zwmX!iwU?#7uoQnunw|OlU~+c z^L5Ak3zWhaA4B^FhMMboO0k*O2GL)lD9_<$5b>czbCvKcSt+u*gA*=%dH>Q-Bc11h zzO7jbXN)&5mBf=w2anK6P$YcJZQoWa2#E!v{hFKxxm7Fc)Fc9iC35{|Lp7bIDjrhC zgMiGf4r2yquH{U7WdMio;XS4Y%Ry{q7#kv#gZ07i`7eo#MMh_o68E*Fd_#nrri^4b zX+slbsv>+8pmck%oLDUL()8NRJ#Z z8DReF_eq2zsjEXGs)yS{k}ykS1B!ZrY0f6O65^lslJv3g&wfpDg-&EwF8wrc=hSwm zPlV&n%%yE_@onOwK?)`GNJ6MQ0drMuBYWCH5dkD)uErh@*k}#GcFl<-;;TN+5vb|b zctkCv;*zL7f)A;QuO%(81r0)&aUz4EQu;kA!k@7i8RZ)koMaWW`5cC6n@{w!!J$5d zx}l)4VP4xL=BKi&c^{n_Qi`q@G{vimblcVR53b#*X$FUOQFm!A8JKahNSiBdY+x3bJZfD8n{--FLUM4+Mx@{vM_ep zkk)U=K8R(rhU(X_faI*ZO}cn`5t*O}lx^j8|0rt-)o=Axn^DGcQTi!#7hxLTq?|HQ zB;T6(nrsCeYK0_o%)IO+CP{n#+|;w1ZmvD2c-J{i88bp63RjyKOE!B!D3U{RCs*Zh z&^%65VM(J34230U4bHS}M@SYS9TEK}c%)2<$h1|T;##zRtjRt@#1T%J=kAhOiw+Z% z7DpyWVK@6%9K^uVD9LDKj)dR^aZK6$@Lt)l;sj@`QSzBm{TlLG{JKM_^60Zr2w~nr zr>P-BaV8OjjWm?hQ3$ZCx+lyD%q`~4iNF9xWKi$t&pzBhwN9Dq-o^v9@=abLR#|

KZqkLal4YCRR9VNhIM|rBqmzzcImvcx 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/setuptools/cli-64.exe b/venv/lib/python3.10/site-packages/setuptools/cli-64.exe new file mode 100644 index 0000000000000000000000000000000000000000..675e6bf3743f3d3011c238657e7128ee9960ef7f GIT binary patch literal 74752 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/setuptools/cli-arm64.exe b/venv/lib/python3.10/site-packages/setuptools/cli-arm64.exe new file mode 100644 index 0000000000000000000000000000000000000000..7a87ce48093d2c984b2ceb7b1f8e1ba6f5fc94f1 GIT binary patch literal 137216 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

x5%&be+vRm^Y>egOSbwaL4{e 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/setuptools/cli.exe b/venv/lib/python3.10/site-packages/setuptools/cli.exe new file mode 100644 index 0000000000000000000000000000000000000000..b1487b7819e7286577a043c7726fbe0ca1543083 GIT binary patch literal 65536 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

@|$jDp4t7maBT@by!vG1&J_?=DS4W3Hu6w zu^D>0gT`DfGs$gel^vGnqMFm{Sbi<)U=^ovM}T{v_J7pCAK-2wQGBXnZ^mrGc?bvo8MSvz1spgD`Uk!U$&1RXiB ziRLDk1WeoL$6{zZ(?vgjfdRksQ|J|JABy`ECh`m*He~nmN52(q!R-kxq=%5#(KIn} zL~My()Fw7fH;>;rMA{+(1;m2|oZ);nqGU6zokoKJN)7dKi3EIEij9ciXht zv8{BCA-qf{#{6gCkKc>mtqAa$FGGaMK#t4K@nbN(oBm8cIMe$S7UyjwVs!oZt(d7| zb7u36v2AI6Mx7gFOt#8!i!#n&PTXIHyGV1R3^>@om0y9&buceznv`%ftx7WsYkJ68 z{~S5%M*=IvZ_I!|FZ|~vJF-4R!5u?^u^+US9nODKzmT%6BDOV&Lb4ea3U_`R1vJAA zm;KzPN&FU+$qq-ZTw&O#+%e=Ff|CJ>;X`W~@D#>A8Uzz08Hu~S8w&sUN9CSW zMaZFqcBaJ7AbD{0QyR{S8-5R)eFl}o|Dq<3+(O(~@Q@@qUI8rpFf@R7YtXnVW*CkLFO;bNc&1^Q&q^imS5H5D_u)|n@dtbATexLU{scQ8K z{0foM_$;z`D{_?w{|y0C%Z20&&Dpt&zQ4BJpWKci^kI?7NTNTQzcmF_o`V!e;%S6F zJS-FAa39pi-)sRKso=2>!1=vs8dX%H8Dv@R(LV%#G#~Sxxe+^nk zsF9cd2PUF0g@!sqqHC~&(nUH^^o|=R5a~Cl2D*y$vd2Tp+J6RX39$y8jC@|dM``>3 zErhERybREN)Ngz)K(XBinxhZ?z-DtnP*59RErJ3Uc=n_hba%dh+}n%wo{lYr=q9UE zNAnjagDSo7TKZ!=T~H-1s4|QE+%D-??CRk+dI9(x8jC{;Ek6>v6A|F|MDKC@eYBn%UGK26~-S zGl-TwzX2rlBrtR0_pr!G^)Di+J$6S2j0<80!7u-pfeRop27#nBXiP?;sZB=^zi}n7 zAr7(_6R7j)KmsR<{*jkNW#yot?{0$VS<-$1guRjcj<>k{(o9F*Uje);_sb@7}A zvkP7}TkuPvgR*;^=>84a4Ul{9rG1P|boI`dV;+7?wu*naOZ0FxRS61_^r9v-4);#E zY5N&2uGCzxSQS4)Wsa|*9KaGF6Q$mfW3*gX-Hq_MK4Yyrgnj; zodHzA?*st-l3xx)@D%p)2KtC|_(x0A0EZx^o>Z#NH$cMe}d z@9X(O5%utS;+@BD5bx>y8u6aNFBk8be3E$2;$y@+mn-63$kWAp4mbZdVdyhA`}jEo z&CR9!jChyx)8f6DpAzo?|ATnn!e1Bf75tERui`I>_Zt43c(3KphQlxqvE}R zKP28N-znZ(d82r52O7VD8!^xClk+M0@JA1uI3G#eO>Bk1M4dD+9c}&Na7W~x4 z^W9I2X`?aIn(tqUC}u^N3E@Iznw~oF3u^DPqlM#C$AYCAxt@OBJiKYxf-=kv?Mt<@ z@X&POMyy+@81d_RUncfmaw-S2oM7@C!T;0Vxd290UWlV^B$Ei%bK85*z2}~RmA&`>e*f!VYyE3s2}W2t*mRDL+r|C9 z-BHe;*vF%45dPr)Anr&THpVEgmMG^A`}nF4xLvr{9lmX$=(*rPy-;UNcrz=pvd2^n zSL)zXy(+bgPpeXY3}em*(8-p1R3Xtv6xu5|ZyY%94b*Ei^$HB@{&XygzSZ$vqKpY~r}R4}Ze^cBgxPX`g{_}Sgj z;{Nz*KOU0)AzWJ|{oj-ROTOmlKz&%Al>X0?;}_&#p&K`I^QR^C95bfVxkWI_+D`>} zt>jK%J**<`M(5?Cj?edJXX?3IZ!;XX-nOD`GBoXw3DKcgA;t75cZw>n{P>CB`0p+K zcAB=$-}-B*tgp>p$pu-PZ65}AingU;cc-aP{CS#uZd=cv$ANvoIBDKk^!U`zi)x%3 zO}h2-qJ1qkU#m*}V0Y?_%kHo$RFtnJ+SeK_Wq7hX)HW*&_EV*V7;VM3zT1~HZlWN` zKoT$!a07{e3vdAbjBlN4$hhwmPm`y~^EA)XJllD;^X%Z+!LyTRCr|jI_jNVdg@vQp z+HIYo=I{rl(xt$9;9f}^>G<1FMlUsve79;Ja*=r%*&;MYIBb)C4ZNt7u23h8@9Bhr zpMU&B7x}i|PcFf;Z_?6_@=99aKKaz@lS$Gi9h8L-5_p@PKNA5D&^XsN?nwPSo9_eF zdLOFR`$a_3QnpZ-p1%4Z+V`RAh5Cq)+akhI18NxRvkz>(52a_FTXLDI5iv;namw&C z@GIa&U@veGcnx?Tpsh#J)+2c)@=WBJz%zlTizmXO--_pnfa#>Dr^J1SBolnyV}9RqJggkQ8*+(SQV0ZRd4+J6-wAV;j}bDG zv%Io9W*{f53OE^I*<~OQmV|J^>++U~gs?uqU)AONpuecLv!SalJPu)+X(BJ{f_@Sb zzO^&8k7HQx#X)yd+Fi7lCizq9=a15F?HhL8a-u~!iV24Y#T^QU!{ zzy%a@KNyVRv@S+2W^M_82|+%>&P54kmL$+nE{9_yh&RjZ#d!=%aOw5)#$eD|pOKzl zro`tR4>7@@#^heAX)EMxiF)EM$opT5EPsMOt83~$^A}r{yuZuunYhI78Nb9#po4sS z9bXXlmrD%Xd|2k;BD{-CLiQf4p4jVY!aTfX$$?N4 z@HW_`44C#^9PeKepR(9t^ix+E_T()7&373PfdQcx5d zW6?^fPSE2)R)C9OLM|7oMi*QJXFi0yOtBOB^24%Q{IIMghjK zzr7ECJkUUM1NN;M!~Gh^%nP*Ee0G%)c zCt3Vlio;UG%JAx0$gewJc0L!s@JzE^cQ}9hvac;EFoH{5-zKgHecr=pD6z7x@U|5~UW$gZvHPc0`w^an11p`i85cF8iVrFY$?WJRB(CCI_ao25US9JC2K$r@F#Bi9TUS4RZ?!KMRv9o(o zPU$Cx$&J{e^&=Q?X!rREbDV+EOBaQpQGbW?%0`C$h0ZJXAAtLYapTDIO5#5%+&Dq} z!I2;2bK6AzECtpB-Di+5JFiIU;IrLf&wpM~Ww_vZC6vZz~pxcpd=9 z{X3jjBr|_dDm@aI2+R_f|Ly0MM}H{!s`HA6*9)9i9;YmFq9Me#U-5nn(D(?SG0uBl zk!+AwA^9P^d@AJSu;JCPi z`{r*suPE$5&KG&P=1Z_&gjTD2wu{9r-#M_eGc`i>i!uiI&P5v|&!lC*8wa(xpP(gC zDA#L{I2=Uuk-28IymRPqfSIt[c}iI#RErv3nvcIClH@!{vM)zJ_weD zu_-L8NU*GlC{d0L!!VW10^+~>qmNB~Y8H+F}!P8_d(PpvjzMJQmr z)FkX;2B~<|3JfJeWv@IXo~nTtp$}Gjie> zs8UDG*kid(%i5QCBp~MA;#I186PI-nZ&k7!k8BiLJSuR>h7ArSYHD~B0I z=T6L{zqglekt0JjG5z&|GWb4?+B5+{p^fgTufl_KesA{@I&g7rNq==^SGc5GcM%$N zDBG2)qExz*Z;jGN_-iD-y8i2BCq)p}2lKcspLg>w-;qwg(()HXrZa3jd!}spuwBVX zwmX!iwU?#7uoQnunw|OlU~+c z^L5Ak3zWhaA4B^FhMMboO0k*O2GL)lD9_<$5b>czbCvKcSt+u*gA*=%dH>Q-Bc11h zzO7jbXN)&5mBf=w2anK6P$YcJZQoWa2#E!v{hFKxxm7Fc)Fc9iC35{|Lp7bIDjrhC zgMiGf4r2yquH{U7WdMio;XS4Y%Ry{q7#kv#gZ07i`7eo#MMh_o68E*Fd_#nrri^4b zX+slbsv>+8pmck%oLDUL()8NRJ#Z z8DReF_eq2zsjEXGs)yS{k}ykS1B!ZrY0f6O65^lslJv3g&wfpDg-&EwF8wrc=hSwm zPlV&n%%yE_@onOwK?)`GNJ6MQ0drMuBYWCH5dkD)uErh@*k}#GcFl<-;;TN+5vb|b zctkCv;*zL7f)A;QuO%(81r0)&aUz4EQu;kA!k@7i8RZ)koMaWW`5cC6n@{w!!J$5d zx}l)4VP4xL=BKi&c^{n_Qi`q@G{vimblcVR53b#*X$FUOQFm!A8JKahNSiBdY+x3bJZfD8n{--FLUM4+Mx@{vM_ep zkk)U=K8R(rhU(X_faI*ZO}cn`5t*O}lx^j8|0rt-)o=Axn^DGcQTi!#7hxLTq?|HQ zB;T6(nrsCeYK0_o%)IO+CP{n#+|;w1ZmvD2c-J{i88bp63RjyKOE!B!D3U{RCs*Zh z&^%65VM(J34230U4bHS}M@SYS9TEK}c%)2<$h1|T;##zRtjRt@#1T%J=kAhOiw+Z% z7DpyWVK@6%9K^uVD9LDKj)dR^aZK6$@Lt)l;sj@`QSzBm{TlLG{JKM_^60Zr2w~nr zr>P-BaV8OjjWm?hQ3$ZCx+lyD%q`~4iNF9xWKi$t&pzBhwN9Dq-o^v9@=abLR#|

KZqkLal4YCRR9VNhIM|rBqmzzcImvcx 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/setuptools/command/__init__.py b/venv/lib/python3.10/site-packages/setuptools/command/__init__.py new file mode 100644 index 00000000..b966dcea --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/command/__init__.py @@ -0,0 +1,8 @@ +from distutils.command.bdist import bdist +import sys + +if 'egg' not in bdist.format_commands: + bdist.format_command['egg'] = ('bdist_egg', "Python .egg file") + bdist.format_commands.append('egg') + +del bdist, sys diff --git a/venv/lib/python3.10/site-packages/setuptools/command/alias.py b/venv/lib/python3.10/site-packages/setuptools/command/alias.py new file mode 100644 index 00000000..452a9244 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/command/alias.py @@ -0,0 +1,78 @@ +from distutils.errors import DistutilsOptionError + +from setuptools.command.setopt import edit_config, option_base, config_file + + +def shquote(arg): + """Quote an argument for later parsing by shlex.split()""" + for c in '"', "'", "\\", "#": + if c in arg: + return repr(arg) + if arg.split() != [arg]: + return repr(arg) + return arg + + +class alias(option_base): + """Define a shortcut that invokes one or more commands""" + + description = "define a shortcut to invoke one or more commands" + command_consumes_arguments = True + + user_options = [ + ('remove', 'r', 'remove (unset) the alias'), + ] + option_base.user_options + + boolean_options = option_base.boolean_options + ['remove'] + + def initialize_options(self): + option_base.initialize_options(self) + self.args = None + self.remove = None + + def finalize_options(self): + option_base.finalize_options(self) + if self.remove and len(self.args) != 1: + raise DistutilsOptionError( + "Must specify exactly one argument (the alias name) when " + "using --remove" + ) + + def run(self): + aliases = self.distribution.get_option_dict('aliases') + + if not self.args: + print("Command Aliases") + print("---------------") + for alias in aliases: + print("setup.py alias", format_alias(alias, aliases)) + return + + elif len(self.args) == 1: + alias, = self.args + if self.remove: + command = None + elif alias in aliases: + print("setup.py alias", format_alias(alias, aliases)) + return + else: + print("No alias definition found for %r" % alias) + return + else: + alias = self.args[0] + command = ' '.join(map(shquote, self.args[1:])) + + edit_config(self.filename, {'aliases': {alias: command}}, self.dry_run) + + +def format_alias(name, aliases): + source, command = aliases[name] + if source == config_file('global'): + source = '--global-config ' + elif source == config_file('user'): + source = '--user-config ' + elif source == config_file('local'): + source = '' + else: + source = '--filename=%r' % source + return source + name + ' ' + command diff --git a/venv/lib/python3.10/site-packages/setuptools/command/bdist_egg.py b/venv/lib/python3.10/site-packages/setuptools/command/bdist_egg.py new file mode 100644 index 00000000..e6b1609f --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/command/bdist_egg.py @@ -0,0 +1,456 @@ +"""setuptools.command.bdist_egg + +Build .egg distributions""" + +from distutils.dir_util import remove_tree, mkpath +from distutils import log +from types import CodeType +import sys +import os +import re +import textwrap +import marshal + +from pkg_resources import get_build_platform, Distribution, ensure_directory +from setuptools.extension import Library +from setuptools import Command + +from sysconfig import get_path, get_python_version + + +def _get_purelib(): + return get_path("purelib") + + +def strip_module(filename): + if '.' in filename: + filename = os.path.splitext(filename)[0] + if filename.endswith('module'): + filename = filename[:-6] + return filename + + +def sorted_walk(dir): + """Do os.walk in a reproducible way, + independent of indeterministic filesystem readdir order + """ + for base, dirs, files in os.walk(dir): + dirs.sort() + files.sort() + yield base, dirs, files + + +def write_stub(resource, pyfile): + _stub_template = textwrap.dedent(""" + def __bootstrap__(): + global __bootstrap__, __loader__, __file__ + import sys, pkg_resources, importlib.util + __file__ = pkg_resources.resource_filename(__name__, %r) + __loader__ = None; del __bootstrap__, __loader__ + spec = importlib.util.spec_from_file_location(__name__,__file__) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + __bootstrap__() + """).lstrip() + with open(pyfile, 'w') as f: + f.write(_stub_template % resource) + + +class bdist_egg(Command): + description = "create an \"egg\" distribution" + + user_options = [ + ('bdist-dir=', 'b', + "temporary directory for creating the distribution"), + ('plat-name=', 'p', "platform name to embed in generated filenames " + "(default: %s)" % get_build_platform()), + ('exclude-source-files', None, + "remove all .py files from the generated egg"), + ('keep-temp', 'k', + "keep the pseudo-installation tree around after " + + "creating the distribution archive"), + ('dist-dir=', 'd', + "directory to put final built distributions in"), + ('skip-build', None, + "skip rebuilding everything (for testing/debugging)"), + ] + + boolean_options = [ + 'keep-temp', 'skip-build', 'exclude-source-files' + ] + + def initialize_options(self): + self.bdist_dir = None + self.plat_name = None + self.keep_temp = 0 + self.dist_dir = None + self.skip_build = 0 + self.egg_output = None + self.exclude_source_files = None + + def finalize_options(self): + ei_cmd = self.ei_cmd = self.get_finalized_command("egg_info") + self.egg_info = ei_cmd.egg_info + + if self.bdist_dir is None: + bdist_base = self.get_finalized_command('bdist').bdist_base + self.bdist_dir = os.path.join(bdist_base, 'egg') + + if self.plat_name is None: + self.plat_name = get_build_platform() + + self.set_undefined_options('bdist', ('dist_dir', 'dist_dir')) + + if self.egg_output is None: + + # Compute filename of the output egg + basename = Distribution( + None, None, ei_cmd.egg_name, ei_cmd.egg_version, + get_python_version(), + self.distribution.has_ext_modules() and self.plat_name + ).egg_name() + + self.egg_output = os.path.join(self.dist_dir, basename + '.egg') + + def do_install_data(self): + # Hack for packages that install data to install's --install-lib + self.get_finalized_command('install').install_lib = self.bdist_dir + + site_packages = os.path.normcase(os.path.realpath(_get_purelib())) + old, self.distribution.data_files = self.distribution.data_files, [] + + for item in old: + if isinstance(item, tuple) and len(item) == 2: + if os.path.isabs(item[0]): + realpath = os.path.realpath(item[0]) + normalized = os.path.normcase(realpath) + if normalized == site_packages or normalized.startswith( + site_packages + os.sep + ): + item = realpath[len(site_packages) + 1:], item[1] + # XXX else: raise ??? + self.distribution.data_files.append(item) + + try: + log.info("installing package data to %s", self.bdist_dir) + self.call_command('install_data', force=0, root=None) + finally: + self.distribution.data_files = old + + def get_outputs(self): + return [self.egg_output] + + def call_command(self, cmdname, **kw): + """Invoke reinitialized command `cmdname` with keyword args""" + for dirname in INSTALL_DIRECTORY_ATTRS: + kw.setdefault(dirname, self.bdist_dir) + kw.setdefault('skip_build', self.skip_build) + kw.setdefault('dry_run', self.dry_run) + cmd = self.reinitialize_command(cmdname, **kw) + self.run_command(cmdname) + return cmd + + def run(self): # noqa: C901 # is too complex (14) # FIXME + # Generate metadata first + self.run_command("egg_info") + # We run install_lib before install_data, because some data hacks + # pull their data path from the install_lib command. + log.info("installing library code to %s", self.bdist_dir) + instcmd = self.get_finalized_command('install') + old_root = instcmd.root + instcmd.root = None + if self.distribution.has_c_libraries() and not self.skip_build: + self.run_command('build_clib') + cmd = self.call_command('install_lib', warn_dir=0) + instcmd.root = old_root + + all_outputs, ext_outputs = self.get_ext_outputs() + self.stubs = [] + to_compile = [] + for (p, ext_name) in enumerate(ext_outputs): + filename, ext = os.path.splitext(ext_name) + pyfile = os.path.join(self.bdist_dir, strip_module(filename) + + '.py') + self.stubs.append(pyfile) + log.info("creating stub loader for %s", ext_name) + if not self.dry_run: + write_stub(os.path.basename(ext_name), pyfile) + to_compile.append(pyfile) + ext_outputs[p] = ext_name.replace(os.sep, '/') + + if to_compile: + cmd.byte_compile(to_compile) + if self.distribution.data_files: + self.do_install_data() + + # Make the EGG-INFO directory + archive_root = self.bdist_dir + egg_info = os.path.join(archive_root, 'EGG-INFO') + self.mkpath(egg_info) + if self.distribution.scripts: + script_dir = os.path.join(egg_info, 'scripts') + log.info("installing scripts to %s", script_dir) + self.call_command('install_scripts', install_dir=script_dir, + no_ep=1) + + self.copy_metadata_to(egg_info) + native_libs = os.path.join(egg_info, "native_libs.txt") + if all_outputs: + log.info("writing %s", native_libs) + if not self.dry_run: + ensure_directory(native_libs) + libs_file = open(native_libs, 'wt') + libs_file.write('\n'.join(all_outputs)) + libs_file.write('\n') + libs_file.close() + elif os.path.isfile(native_libs): + log.info("removing %s", native_libs) + if not self.dry_run: + os.unlink(native_libs) + + write_safety_flag( + os.path.join(archive_root, 'EGG-INFO'), self.zip_safe() + ) + + if os.path.exists(os.path.join(self.egg_info, 'depends.txt')): + log.warn( + "WARNING: 'depends.txt' will not be used by setuptools 0.6!\n" + "Use the install_requires/extras_require setup() args instead." + ) + + if self.exclude_source_files: + self.zap_pyfiles() + + # Make the archive + make_zipfile(self.egg_output, archive_root, verbose=self.verbose, + dry_run=self.dry_run, mode=self.gen_header()) + if not self.keep_temp: + remove_tree(self.bdist_dir, dry_run=self.dry_run) + + # Add to 'Distribution.dist_files' so that the "upload" command works + getattr(self.distribution, 'dist_files', []).append( + ('bdist_egg', get_python_version(), self.egg_output)) + + def zap_pyfiles(self): + log.info("Removing .py files from temporary directory") + for base, dirs, files in walk_egg(self.bdist_dir): + for name in files: + path = os.path.join(base, name) + + if name.endswith('.py'): + log.debug("Deleting %s", path) + os.unlink(path) + + if base.endswith('__pycache__'): + path_old = path + + pattern = r'(?P.+)\.(?P[^.]+)\.pyc' + m = re.match(pattern, name) + path_new = os.path.join( + base, os.pardir, m.group('name') + '.pyc') + log.info( + "Renaming file from [%s] to [%s]" + % (path_old, path_new)) + try: + os.remove(path_new) + except OSError: + pass + os.rename(path_old, path_new) + + def zip_safe(self): + safe = getattr(self.distribution, 'zip_safe', None) + if safe is not None: + return safe + log.warn("zip_safe flag not set; analyzing archive contents...") + return analyze_egg(self.bdist_dir, self.stubs) + + def gen_header(self): + return 'w' + + def copy_metadata_to(self, target_dir): + "Copy metadata (egg info) to the target_dir" + # normalize the path (so that a forward-slash in egg_info will + # match using startswith below) + norm_egg_info = os.path.normpath(self.egg_info) + prefix = os.path.join(norm_egg_info, '') + for path in self.ei_cmd.filelist.files: + if path.startswith(prefix): + target = os.path.join(target_dir, path[len(prefix):]) + ensure_directory(target) + self.copy_file(path, target) + + def get_ext_outputs(self): + """Get a list of relative paths to C extensions in the output distro""" + + all_outputs = [] + ext_outputs = [] + + paths = {self.bdist_dir: ''} + for base, dirs, files in sorted_walk(self.bdist_dir): + for filename in files: + if os.path.splitext(filename)[1].lower() in NATIVE_EXTENSIONS: + all_outputs.append(paths[base] + filename) + for filename in dirs: + paths[os.path.join(base, filename)] = (paths[base] + + filename + '/') + + if self.distribution.has_ext_modules(): + build_cmd = self.get_finalized_command('build_ext') + for ext in build_cmd.extensions: + if isinstance(ext, Library): + continue + fullname = build_cmd.get_ext_fullname(ext.name) + filename = build_cmd.get_ext_filename(fullname) + if not os.path.basename(filename).startswith('dl-'): + if os.path.exists(os.path.join(self.bdist_dir, filename)): + ext_outputs.append(filename) + + return all_outputs, ext_outputs + + +NATIVE_EXTENSIONS = dict.fromkeys('.dll .so .dylib .pyd'.split()) + + +def walk_egg(egg_dir): + """Walk an unpacked egg's contents, skipping the metadata directory""" + walker = sorted_walk(egg_dir) + base, dirs, files = next(walker) + if 'EGG-INFO' in dirs: + dirs.remove('EGG-INFO') + yield base, dirs, files + for bdf in walker: + yield bdf + + +def analyze_egg(egg_dir, stubs): + # check for existing flag in EGG-INFO + for flag, fn in safety_flags.items(): + if os.path.exists(os.path.join(egg_dir, 'EGG-INFO', fn)): + return flag + if not can_scan(): + return False + safe = True + for base, dirs, files in walk_egg(egg_dir): + for name in files: + if name.endswith('.py') or name.endswith('.pyw'): + continue + elif name.endswith('.pyc') or name.endswith('.pyo'): + # always scan, even if we already know we're not safe + safe = scan_module(egg_dir, base, name, stubs) and safe + return safe + + +def write_safety_flag(egg_dir, safe): + # Write or remove zip safety flag file(s) + for flag, fn in safety_flags.items(): + fn = os.path.join(egg_dir, fn) + if os.path.exists(fn): + if safe is None or bool(safe) != flag: + os.unlink(fn) + elif safe is not None and bool(safe) == flag: + f = open(fn, 'wt') + f.write('\n') + f.close() + + +safety_flags = { + True: 'zip-safe', + False: 'not-zip-safe', +} + + +def scan_module(egg_dir, base, name, stubs): + """Check whether module possibly uses unsafe-for-zipfile stuff""" + + filename = os.path.join(base, name) + if filename[:-1] in stubs: + return True # Extension module + pkg = base[len(egg_dir) + 1:].replace(os.sep, '.') + module = pkg + (pkg and '.' or '') + os.path.splitext(name)[0] + if sys.version_info < (3, 7): + skip = 12 # skip magic & date & file size + else: + skip = 16 # skip magic & reserved? & date & file size + f = open(filename, 'rb') + f.read(skip) + code = marshal.load(f) + f.close() + safe = True + symbols = dict.fromkeys(iter_symbols(code)) + for bad in ['__file__', '__path__']: + if bad in symbols: + log.warn("%s: module references %s", module, bad) + safe = False + if 'inspect' in symbols: + for bad in [ + 'getsource', 'getabsfile', 'getsourcefile', 'getfile' + 'getsourcelines', 'findsource', 'getcomments', 'getframeinfo', + 'getinnerframes', 'getouterframes', 'stack', 'trace' + ]: + if bad in symbols: + log.warn("%s: module MAY be using inspect.%s", module, bad) + safe = False + return safe + + +def iter_symbols(code): + """Yield names and strings used by `code` and its nested code objects""" + for name in code.co_names: + yield name + for const in code.co_consts: + if isinstance(const, str): + yield const + elif isinstance(const, CodeType): + for name in iter_symbols(const): + yield name + + +def can_scan(): + if not sys.platform.startswith('java') and sys.platform != 'cli': + # CPython, PyPy, etc. + return True + log.warn("Unable to analyze compiled code on this platform.") + log.warn("Please ask the author to include a 'zip_safe'" + " setting (either True or False) in the package's setup.py") + + +# Attribute names of options for commands that might need to be convinced to +# install to the egg build directory + +INSTALL_DIRECTORY_ATTRS = [ + 'install_lib', 'install_dir', 'install_data', 'install_base' +] + + +def make_zipfile(zip_filename, base_dir, verbose=0, dry_run=0, compress=True, + mode='w'): + """Create a zip file from all the files under 'base_dir'. The output + zip file will be named 'base_dir' + ".zip". Uses either the "zipfile" + Python module (if available) or the InfoZIP "zip" utility (if installed + and found on the default search path). If neither tool is available, + raises DistutilsExecError. Returns the name of the output zip file. + """ + import zipfile + + mkpath(os.path.dirname(zip_filename), dry_run=dry_run) + log.info("creating '%s' and adding '%s' to it", zip_filename, base_dir) + + def visit(z, dirname, names): + for name in names: + path = os.path.normpath(os.path.join(dirname, name)) + if os.path.isfile(path): + p = path[len(base_dir) + 1:] + if not dry_run: + z.write(path, p) + log.debug("adding '%s'", p) + + compression = zipfile.ZIP_DEFLATED if compress else zipfile.ZIP_STORED + if not dry_run: + z = zipfile.ZipFile(zip_filename, mode, compression=compression) + for dirname, dirs, files in sorted_walk(base_dir): + visit(z, dirname, files) + z.close() + else: + for dirname, dirs, files in sorted_walk(base_dir): + visit(None, dirname, files) + return zip_filename diff --git a/venv/lib/python3.10/site-packages/setuptools/command/bdist_rpm.py b/venv/lib/python3.10/site-packages/setuptools/command/bdist_rpm.py new file mode 100644 index 00000000..98bf5dea --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/command/bdist_rpm.py @@ -0,0 +1,40 @@ +import distutils.command.bdist_rpm as orig +import warnings + +from setuptools import SetuptoolsDeprecationWarning + + +class bdist_rpm(orig.bdist_rpm): + """ + Override the default bdist_rpm behavior to do the following: + + 1. Run egg_info to ensure the name and version are properly calculated. + 2. Always run 'install' using --single-version-externally-managed to + disable eggs in RPM distributions. + """ + + def run(self): + warnings.warn( + "bdist_rpm is deprecated and will be removed in a future " + "version. Use bdist_wheel (wheel packages) instead.", + SetuptoolsDeprecationWarning, + ) + + # ensure distro name is up-to-date + self.run_command('egg_info') + + orig.bdist_rpm.run(self) + + def _make_spec_file(self): + spec = orig.bdist_rpm._make_spec_file(self) + spec = [ + line.replace( + "setup.py install ", + "setup.py install --single-version-externally-managed " + ).replace( + "%setup", + "%setup -n %{name}-%{unmangled_version}" + ) + for line in spec + ] + return spec diff --git a/venv/lib/python3.10/site-packages/setuptools/command/build_clib.py b/venv/lib/python3.10/site-packages/setuptools/command/build_clib.py new file mode 100644 index 00000000..67ce2444 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/command/build_clib.py @@ -0,0 +1,101 @@ +import distutils.command.build_clib as orig +from distutils.errors import DistutilsSetupError +from distutils import log +from setuptools.dep_util import newer_pairwise_group + + +class build_clib(orig.build_clib): + """ + Override the default build_clib behaviour to do the following: + + 1. Implement a rudimentary timestamp-based dependency system + so 'compile()' doesn't run every time. + 2. Add more keys to the 'build_info' dictionary: + * obj_deps - specify dependencies for each object compiled. + this should be a dictionary mapping a key + with the source filename to a list of + dependencies. Use an empty string for global + dependencies. + * cflags - specify a list of additional flags to pass to + the compiler. + """ + + def build_libraries(self, libraries): + for (lib_name, build_info) in libraries: + sources = build_info.get('sources') + if sources is None or not isinstance(sources, (list, tuple)): + raise DistutilsSetupError( + "in 'libraries' option (library '%s'), " + "'sources' must be present and must be " + "a list of source filenames" % lib_name) + sources = list(sources) + + log.info("building '%s' library", lib_name) + + # Make sure everything is the correct type. + # obj_deps should be a dictionary of keys as sources + # and a list/tuple of files that are its dependencies. + obj_deps = build_info.get('obj_deps', dict()) + if not isinstance(obj_deps, dict): + raise DistutilsSetupError( + "in 'libraries' option (library '%s'), " + "'obj_deps' must be a dictionary of " + "type 'source: list'" % lib_name) + dependencies = [] + + # Get the global dependencies that are specified by the '' key. + # These will go into every source's dependency list. + global_deps = obj_deps.get('', list()) + if not isinstance(global_deps, (list, tuple)): + raise DistutilsSetupError( + "in 'libraries' option (library '%s'), " + "'obj_deps' must be a dictionary of " + "type 'source: list'" % lib_name) + + # Build the list to be used by newer_pairwise_group + # each source will be auto-added to its dependencies. + for source in sources: + src_deps = [source] + src_deps.extend(global_deps) + extra_deps = obj_deps.get(source, list()) + if not isinstance(extra_deps, (list, tuple)): + raise DistutilsSetupError( + "in 'libraries' option (library '%s'), " + "'obj_deps' must be a dictionary of " + "type 'source: list'" % lib_name) + src_deps.extend(extra_deps) + dependencies.append(src_deps) + + expected_objects = self.compiler.object_filenames( + sources, + output_dir=self.build_temp, + ) + + if ( + newer_pairwise_group(dependencies, expected_objects) + != ([], []) + ): + # First, compile the source code to object files in the library + # directory. (This should probably change to putting object + # files in a temporary build directory.) + macros = build_info.get('macros') + include_dirs = build_info.get('include_dirs') + cflags = build_info.get('cflags') + self.compiler.compile( + sources, + output_dir=self.build_temp, + macros=macros, + include_dirs=include_dirs, + extra_postargs=cflags, + debug=self.debug + ) + + # Now "link" the object files together into a static library. + # (On Unix at least, this isn't really linking -- it just + # builds an archive. Whatever.) + self.compiler.create_static_lib( + expected_objects, + lib_name, + output_dir=self.build_clib, + debug=self.debug + ) diff --git a/venv/lib/python3.10/site-packages/setuptools/command/build_ext.py b/venv/lib/python3.10/site-packages/setuptools/command/build_ext.py new file mode 100644 index 00000000..c59eff8b --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/command/build_ext.py @@ -0,0 +1,328 @@ +import os +import sys +import itertools +from importlib.machinery import EXTENSION_SUFFIXES +from distutils.command.build_ext import build_ext as _du_build_ext +from distutils.file_util import copy_file +from distutils.ccompiler import new_compiler +from distutils.sysconfig import customize_compiler, get_config_var +from distutils.errors import DistutilsError +from distutils import log + +from setuptools.extension import Library + +try: + # Attempt to use Cython for building extensions, if available + from Cython.Distutils.build_ext import build_ext as _build_ext + # Additionally, assert that the compiler module will load + # also. Ref #1229. + __import__('Cython.Compiler.Main') +except ImportError: + _build_ext = _du_build_ext + +# make sure _config_vars is initialized +get_config_var("LDSHARED") +from distutils.sysconfig import _config_vars as _CONFIG_VARS # noqa + + +def _customize_compiler_for_shlib(compiler): + if sys.platform == "darwin": + # building .dylib requires additional compiler flags on OSX; here we + # temporarily substitute the pyconfig.h variables so that distutils' + # 'customize_compiler' uses them before we build the shared libraries. + tmp = _CONFIG_VARS.copy() + try: + # XXX Help! I don't have any idea whether these are right... + _CONFIG_VARS['LDSHARED'] = ( + "gcc -Wl,-x -dynamiclib -undefined dynamic_lookup") + _CONFIG_VARS['CCSHARED'] = " -dynamiclib" + _CONFIG_VARS['SO'] = ".dylib" + customize_compiler(compiler) + finally: + _CONFIG_VARS.clear() + _CONFIG_VARS.update(tmp) + else: + customize_compiler(compiler) + + +have_rtld = False +use_stubs = False +libtype = 'shared' + +if sys.platform == "darwin": + use_stubs = True +elif os.name != 'nt': + try: + import dl + use_stubs = have_rtld = hasattr(dl, 'RTLD_NOW') + except ImportError: + pass + + +def if_dl(s): + return s if have_rtld else '' + + +def get_abi3_suffix(): + """Return the file extension for an abi3-compliant Extension()""" + for suffix in EXTENSION_SUFFIXES: + if '.abi3' in suffix: # Unix + return suffix + elif suffix == '.pyd': # Windows + return suffix + + +class build_ext(_build_ext): + def run(self): + """Build extensions in build directory, then copy if --inplace""" + old_inplace, self.inplace = self.inplace, 0 + _build_ext.run(self) + self.inplace = old_inplace + if old_inplace: + self.copy_extensions_to_source() + + def copy_extensions_to_source(self): + build_py = self.get_finalized_command('build_py') + for ext in self.extensions: + fullname = self.get_ext_fullname(ext.name) + filename = self.get_ext_filename(fullname) + modpath = fullname.split('.') + package = '.'.join(modpath[:-1]) + package_dir = build_py.get_package_dir(package) + dest_filename = os.path.join(package_dir, + os.path.basename(filename)) + src_filename = os.path.join(self.build_lib, filename) + + # Always copy, even if source is older than destination, to ensure + # that the right extensions for the current Python/platform are + # used. + copy_file( + src_filename, dest_filename, verbose=self.verbose, + dry_run=self.dry_run + ) + if ext._needs_stub: + self.write_stub(package_dir or os.curdir, ext, True) + + def get_ext_filename(self, fullname): + so_ext = os.getenv('SETUPTOOLS_EXT_SUFFIX') + if so_ext: + filename = os.path.join(*fullname.split('.')) + so_ext + else: + filename = _build_ext.get_ext_filename(self, fullname) + so_ext = get_config_var('EXT_SUFFIX') + + if fullname in self.ext_map: + ext = self.ext_map[fullname] + use_abi3 = getattr(ext, 'py_limited_api') and get_abi3_suffix() + if use_abi3: + filename = filename[:-len(so_ext)] + so_ext = get_abi3_suffix() + filename = filename + so_ext + if isinstance(ext, Library): + fn, ext = os.path.splitext(filename) + return self.shlib_compiler.library_filename(fn, libtype) + elif use_stubs and ext._links_to_dynamic: + d, fn = os.path.split(filename) + return os.path.join(d, 'dl-' + fn) + return filename + + def initialize_options(self): + _build_ext.initialize_options(self) + self.shlib_compiler = None + self.shlibs = [] + self.ext_map = {} + + def finalize_options(self): + _build_ext.finalize_options(self) + self.extensions = self.extensions or [] + self.check_extensions_list(self.extensions) + self.shlibs = [ext for ext in self.extensions + if isinstance(ext, Library)] + if self.shlibs: + self.setup_shlib_compiler() + for ext in self.extensions: + ext._full_name = self.get_ext_fullname(ext.name) + for ext in self.extensions: + fullname = ext._full_name + self.ext_map[fullname] = ext + + # distutils 3.1 will also ask for module names + # XXX what to do with conflicts? + self.ext_map[fullname.split('.')[-1]] = ext + + ltd = self.shlibs and self.links_to_dynamic(ext) or False + ns = ltd and use_stubs and not isinstance(ext, Library) + ext._links_to_dynamic = ltd + ext._needs_stub = ns + filename = ext._file_name = self.get_ext_filename(fullname) + libdir = os.path.dirname(os.path.join(self.build_lib, filename)) + if ltd and libdir not in ext.library_dirs: + ext.library_dirs.append(libdir) + if ltd and use_stubs and os.curdir not in ext.runtime_library_dirs: + ext.runtime_library_dirs.append(os.curdir) + + def setup_shlib_compiler(self): + compiler = self.shlib_compiler = new_compiler( + compiler=self.compiler, dry_run=self.dry_run, force=self.force + ) + _customize_compiler_for_shlib(compiler) + + if self.include_dirs is not None: + compiler.set_include_dirs(self.include_dirs) + if self.define is not None: + # 'define' option is a list of (name,value) tuples + for (name, value) in self.define: + compiler.define_macro(name, value) + if self.undef is not None: + for macro in self.undef: + compiler.undefine_macro(macro) + if self.libraries is not None: + compiler.set_libraries(self.libraries) + if self.library_dirs is not None: + compiler.set_library_dirs(self.library_dirs) + if self.rpath is not None: + compiler.set_runtime_library_dirs(self.rpath) + if self.link_objects is not None: + compiler.set_link_objects(self.link_objects) + + # hack so distutils' build_extension() builds a library instead + compiler.link_shared_object = link_shared_object.__get__(compiler) + + def get_export_symbols(self, ext): + if isinstance(ext, Library): + return ext.export_symbols + return _build_ext.get_export_symbols(self, ext) + + def build_extension(self, ext): + ext._convert_pyx_sources_to_lang() + _compiler = self.compiler + try: + if isinstance(ext, Library): + self.compiler = self.shlib_compiler + _build_ext.build_extension(self, ext) + if ext._needs_stub: + cmd = self.get_finalized_command('build_py').build_lib + self.write_stub(cmd, ext) + finally: + self.compiler = _compiler + + def links_to_dynamic(self, ext): + """Return true if 'ext' links to a dynamic lib in the same package""" + # XXX this should check to ensure the lib is actually being built + # XXX as dynamic, and not just using a locally-found version or a + # XXX static-compiled version + libnames = dict.fromkeys([lib._full_name for lib in self.shlibs]) + pkg = '.'.join(ext._full_name.split('.')[:-1] + ['']) + return any(pkg + libname in libnames for libname in ext.libraries) + + def get_outputs(self): + return _build_ext.get_outputs(self) + self.__get_stubs_outputs() + + def __get_stubs_outputs(self): + # assemble the base name for each extension that needs a stub + ns_ext_bases = ( + os.path.join(self.build_lib, *ext._full_name.split('.')) + for ext in self.extensions + if ext._needs_stub + ) + # pair each base with the extension + pairs = itertools.product(ns_ext_bases, self.__get_output_extensions()) + return list(base + fnext for base, fnext in pairs) + + def __get_output_extensions(self): + yield '.py' + yield '.pyc' + if self.get_finalized_command('build_py').optimize: + yield '.pyo' + + def write_stub(self, output_dir, ext, compile=False): + log.info("writing stub loader for %s to %s", ext._full_name, + output_dir) + stub_file = (os.path.join(output_dir, *ext._full_name.split('.')) + + '.py') + if compile and os.path.exists(stub_file): + raise DistutilsError(stub_file + " already exists! Please delete.") + if not self.dry_run: + f = open(stub_file, 'w') + f.write( + '\n'.join([ + "def __bootstrap__():", + " global __bootstrap__, __file__, __loader__", + " import sys, os, pkg_resources, importlib.util" + + if_dl(", dl"), + " __file__ = pkg_resources.resource_filename" + "(__name__,%r)" + % os.path.basename(ext._file_name), + " del __bootstrap__", + " if '__loader__' in globals():", + " del __loader__", + if_dl(" old_flags = sys.getdlopenflags()"), + " old_dir = os.getcwd()", + " try:", + " os.chdir(os.path.dirname(__file__))", + if_dl(" sys.setdlopenflags(dl.RTLD_NOW)"), + " spec = importlib.util.spec_from_file_location(", + " __name__, __file__)", + " mod = importlib.util.module_from_spec(spec)", + " spec.loader.exec_module(mod)", + " finally:", + if_dl(" sys.setdlopenflags(old_flags)"), + " os.chdir(old_dir)", + "__bootstrap__()", + "" # terminal \n + ]) + ) + f.close() + if compile: + from distutils.util import byte_compile + + byte_compile([stub_file], optimize=0, + force=True, dry_run=self.dry_run) + optimize = self.get_finalized_command('install_lib').optimize + if optimize > 0: + byte_compile([stub_file], optimize=optimize, + force=True, dry_run=self.dry_run) + if os.path.exists(stub_file) and not self.dry_run: + os.unlink(stub_file) + + +if use_stubs or os.name == 'nt': + # Build shared libraries + # + def link_shared_object( + self, objects, output_libname, output_dir=None, libraries=None, + library_dirs=None, runtime_library_dirs=None, export_symbols=None, + debug=0, extra_preargs=None, extra_postargs=None, build_temp=None, + target_lang=None): + self.link( + self.SHARED_LIBRARY, objects, output_libname, + output_dir, libraries, library_dirs, runtime_library_dirs, + export_symbols, debug, extra_preargs, extra_postargs, + build_temp, target_lang + ) +else: + # Build static libraries everywhere else + libtype = 'static' + + def link_shared_object( + self, objects, output_libname, output_dir=None, libraries=None, + library_dirs=None, runtime_library_dirs=None, export_symbols=None, + debug=0, extra_preargs=None, extra_postargs=None, build_temp=None, + target_lang=None): + # XXX we need to either disallow these attrs on Library instances, + # or warn/abort here if set, or something... + # libraries=None, library_dirs=None, runtime_library_dirs=None, + # export_symbols=None, extra_preargs=None, extra_postargs=None, + # build_temp=None + + assert output_dir is None # distutils build_ext doesn't pass this + output_dir, filename = os.path.split(output_libname) + basename, ext = os.path.splitext(filename) + if self.library_filename("x").startswith('lib'): + # strip 'lib' prefix; this is kludgy if some platform uses + # a different prefix + basename = basename[3:] + + self.create_static_lib( + objects, basename, output_dir, debug, target_lang + ) diff --git a/venv/lib/python3.10/site-packages/setuptools/command/build_py.py b/venv/lib/python3.10/site-packages/setuptools/command/build_py.py new file mode 100644 index 00000000..c3fdc092 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/command/build_py.py @@ -0,0 +1,242 @@ +from glob import glob +from distutils.util import convert_path +import distutils.command.build_py as orig +import os +import fnmatch +import textwrap +import io +import distutils.errors +import itertools +import stat +from setuptools.extern.more_itertools import unique_everseen + + +def make_writable(target): + os.chmod(target, os.stat(target).st_mode | stat.S_IWRITE) + + +class build_py(orig.build_py): + """Enhanced 'build_py' command that includes data files with packages + + The data files are specified via a 'package_data' argument to 'setup()'. + See 'setuptools.dist.Distribution' for more details. + + Also, this version of the 'build_py' command allows you to specify both + 'py_modules' and 'packages' in the same setup operation. + """ + + def finalize_options(self): + orig.build_py.finalize_options(self) + self.package_data = self.distribution.package_data + self.exclude_package_data = self.distribution.exclude_package_data or {} + if 'data_files' in self.__dict__: + del self.__dict__['data_files'] + self.__updated_files = [] + + def run(self): + """Build modules, packages, and copy data files to build directory""" + if not self.py_modules and not self.packages: + return + + if self.py_modules: + self.build_modules() + + if self.packages: + self.build_packages() + self.build_package_data() + + # Only compile actual .py files, using our base class' idea of what our + # output files are. + self.byte_compile(orig.build_py.get_outputs(self, include_bytecode=0)) + + def __getattr__(self, attr): + "lazily compute data files" + if attr == 'data_files': + self.data_files = self._get_data_files() + return self.data_files + return orig.build_py.__getattr__(self, attr) + + def build_module(self, module, module_file, package): + outfile, copied = orig.build_py.build_module(self, module, module_file, package) + if copied: + self.__updated_files.append(outfile) + return outfile, copied + + def _get_data_files(self): + """Generate list of '(package,src_dir,build_dir,filenames)' tuples""" + self.analyze_manifest() + return list(map(self._get_pkg_data_files, self.packages or ())) + + def get_data_files_without_manifest(self): + """ + Generate list of ``(package,src_dir,build_dir,filenames)`` tuples, + but without triggering any attempt to analyze or build the manifest. + """ + # Prevent eventual errors from unset `manifest_files` + # (that would otherwise be set by `analyze_manifest`) + self.__dict__.setdefault('manifest_files', {}) + return list(map(self._get_pkg_data_files, self.packages or ())) + + def _get_pkg_data_files(self, package): + # Locate package source directory + src_dir = self.get_package_dir(package) + + # Compute package build directory + build_dir = os.path.join(*([self.build_lib] + package.split('.'))) + + # Strip directory from globbed filenames + filenames = [ + os.path.relpath(file, src_dir) + for file in self.find_data_files(package, src_dir) + ] + return package, src_dir, build_dir, filenames + + def find_data_files(self, package, src_dir): + """Return filenames for package's data files in 'src_dir'""" + patterns = self._get_platform_patterns( + self.package_data, + package, + src_dir, + ) + globs_expanded = map(glob, patterns) + # flatten the expanded globs into an iterable of matches + globs_matches = itertools.chain.from_iterable(globs_expanded) + glob_files = filter(os.path.isfile, globs_matches) + files = itertools.chain( + self.manifest_files.get(package, []), + glob_files, + ) + return self.exclude_data_files(package, src_dir, files) + + def build_package_data(self): + """Copy data files into build directory""" + for package, src_dir, build_dir, filenames in self.data_files: + for filename in filenames: + target = os.path.join(build_dir, filename) + self.mkpath(os.path.dirname(target)) + srcfile = os.path.join(src_dir, filename) + outf, copied = self.copy_file(srcfile, target) + make_writable(target) + srcfile = os.path.abspath(srcfile) + + def analyze_manifest(self): + self.manifest_files = mf = {} + if not self.distribution.include_package_data: + return + src_dirs = {} + for package in self.packages or (): + # Locate package source directory + src_dirs[assert_relative(self.get_package_dir(package))] = package + + self.run_command('egg_info') + ei_cmd = self.get_finalized_command('egg_info') + for path in ei_cmd.filelist.files: + d, f = os.path.split(assert_relative(path)) + prev = None + oldf = f + while d and d != prev and d not in src_dirs: + prev = d + d, df = os.path.split(d) + f = os.path.join(df, f) + if d in src_dirs: + if path.endswith('.py') and f == oldf: + continue # it's a module, not data + mf.setdefault(src_dirs[d], []).append(path) + + def get_data_files(self): + pass # Lazily compute data files in _get_data_files() function. + + def check_package(self, package, package_dir): + """Check namespace packages' __init__ for declare_namespace""" + try: + return self.packages_checked[package] + except KeyError: + pass + + init_py = orig.build_py.check_package(self, package, package_dir) + self.packages_checked[package] = init_py + + if not init_py or not self.distribution.namespace_packages: + return init_py + + for pkg in self.distribution.namespace_packages: + if pkg == package or pkg.startswith(package + '.'): + break + else: + return init_py + + with io.open(init_py, 'rb') as f: + contents = f.read() + if b'declare_namespace' not in contents: + raise distutils.errors.DistutilsError( + "Namespace package problem: %s is a namespace package, but " + "its\n__init__.py does not call declare_namespace()! Please " + 'fix it.\n(See the setuptools manual under ' + '"Namespace Packages" for details.)\n"' % (package,) + ) + return init_py + + def initialize_options(self): + self.packages_checked = {} + orig.build_py.initialize_options(self) + + def get_package_dir(self, package): + res = orig.build_py.get_package_dir(self, package) + if self.distribution.src_root is not None: + return os.path.join(self.distribution.src_root, res) + return res + + def exclude_data_files(self, package, src_dir, files): + """Filter filenames for package's data files in 'src_dir'""" + files = list(files) + patterns = self._get_platform_patterns( + self.exclude_package_data, + package, + src_dir, + ) + match_groups = (fnmatch.filter(files, pattern) for pattern in patterns) + # flatten the groups of matches into an iterable of matches + matches = itertools.chain.from_iterable(match_groups) + bad = set(matches) + keepers = (fn for fn in files if fn not in bad) + # ditch dupes + return list(unique_everseen(keepers)) + + @staticmethod + def _get_platform_patterns(spec, package, src_dir): + """ + yield platform-specific path patterns (suitable for glob + or fn_match) from a glob-based spec (such as + self.package_data or self.exclude_package_data) + matching package in src_dir. + """ + raw_patterns = itertools.chain( + spec.get('', []), + spec.get(package, []), + ) + return ( + # Each pattern has to be converted to a platform-specific path + os.path.join(src_dir, convert_path(pattern)) + for pattern in raw_patterns + ) + + +def assert_relative(path): + if not os.path.isabs(path): + return path + from distutils.errors import DistutilsSetupError + + msg = ( + textwrap.dedent( + """ + Error: setup script specifies an absolute path: + + %s + + setup() arguments must *always* be /-separated paths relative to the + setup.py directory, *never* absolute paths. + """ + ).lstrip() + % path + ) + raise DistutilsSetupError(msg) diff --git a/venv/lib/python3.10/site-packages/setuptools/command/develop.py b/venv/lib/python3.10/site-packages/setuptools/command/develop.py new file mode 100644 index 00000000..24fb0a7c --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/command/develop.py @@ -0,0 +1,193 @@ +from distutils.util import convert_path +from distutils import log +from distutils.errors import DistutilsError, DistutilsOptionError +import os +import glob +import io + +import pkg_resources +from setuptools.command.easy_install import easy_install +from setuptools import namespaces +import setuptools + + +class develop(namespaces.DevelopInstaller, easy_install): + """Set up package for development""" + + description = "install package in 'development mode'" + + user_options = easy_install.user_options + [ + ("uninstall", "u", "Uninstall this source package"), + ("egg-path=", None, "Set the path to be used in the .egg-link file"), + ] + + boolean_options = easy_install.boolean_options + ['uninstall'] + + command_consumes_arguments = False # override base + + def run(self): + if self.uninstall: + self.multi_version = True + self.uninstall_link() + self.uninstall_namespaces() + else: + self.install_for_development() + self.warn_deprecated_options() + + def initialize_options(self): + self.uninstall = None + self.egg_path = None + easy_install.initialize_options(self) + self.setup_path = None + self.always_copy_from = '.' # always copy eggs installed in curdir + + def finalize_options(self): + ei = self.get_finalized_command("egg_info") + if ei.broken_egg_info: + template = "Please rename %r to %r before using 'develop'" + args = ei.egg_info, ei.broken_egg_info + raise DistutilsError(template % args) + self.args = [ei.egg_name] + + easy_install.finalize_options(self) + self.expand_basedirs() + self.expand_dirs() + # pick up setup-dir .egg files only: no .egg-info + self.package_index.scan(glob.glob('*.egg')) + + egg_link_fn = ei.egg_name + '.egg-link' + self.egg_link = os.path.join(self.install_dir, egg_link_fn) + self.egg_base = ei.egg_base + if self.egg_path is None: + self.egg_path = os.path.abspath(ei.egg_base) + + target = pkg_resources.normalize_path(self.egg_base) + egg_path = pkg_resources.normalize_path( + os.path.join(self.install_dir, self.egg_path) + ) + if egg_path != target: + raise DistutilsOptionError( + "--egg-path must be a relative path from the install" + " directory to " + target + ) + + # Make a distribution for the package's source + self.dist = pkg_resources.Distribution( + target, + pkg_resources.PathMetadata(target, os.path.abspath(ei.egg_info)), + project_name=ei.egg_name, + ) + + self.setup_path = self._resolve_setup_path( + self.egg_base, + self.install_dir, + self.egg_path, + ) + + @staticmethod + def _resolve_setup_path(egg_base, install_dir, egg_path): + """ + Generate a path from egg_base back to '.' where the + setup script resides and ensure that path points to the + setup path from $install_dir/$egg_path. + """ + path_to_setup = egg_base.replace(os.sep, '/').rstrip('/') + if path_to_setup != os.curdir: + path_to_setup = '../' * (path_to_setup.count('/') + 1) + resolved = pkg_resources.normalize_path( + os.path.join(install_dir, egg_path, path_to_setup) + ) + if resolved != pkg_resources.normalize_path(os.curdir): + raise DistutilsOptionError( + "Can't get a consistent path to setup script from" + " installation directory", + resolved, + pkg_resources.normalize_path(os.curdir), + ) + return path_to_setup + + def install_for_development(self): + self.run_command('egg_info') + + # Build extensions in-place + self.reinitialize_command('build_ext', inplace=1) + self.run_command('build_ext') + + if setuptools.bootstrap_install_from: + self.easy_install(setuptools.bootstrap_install_from) + setuptools.bootstrap_install_from = None + + self.install_namespaces() + + # create an .egg-link in the installation dir, pointing to our egg + log.info("Creating %s (link to %s)", self.egg_link, self.egg_base) + if not self.dry_run: + with open(self.egg_link, "w") as f: + f.write(self.egg_path + "\n" + self.setup_path) + # postprocess the installed distro, fixing up .pth, installing scripts, + # and handling requirements + self.process_distribution(None, self.dist, not self.no_deps) + + def uninstall_link(self): + if os.path.exists(self.egg_link): + log.info("Removing %s (link to %s)", self.egg_link, self.egg_base) + egg_link_file = open(self.egg_link) + contents = [line.rstrip() for line in egg_link_file] + egg_link_file.close() + if contents not in ([self.egg_path], [self.egg_path, self.setup_path]): + log.warn("Link points to %s: uninstall aborted", contents) + return + if not self.dry_run: + os.unlink(self.egg_link) + if not self.dry_run: + self.update_pth(self.dist) # remove any .pth link to us + if self.distribution.scripts: + # XXX should also check for entry point scripts! + log.warn("Note: you must uninstall or replace scripts manually!") + + def install_egg_scripts(self, dist): + if dist is not self.dist: + # Installing a dependency, so fall back to normal behavior + return easy_install.install_egg_scripts(self, dist) + + # create wrapper scripts in the script dir, pointing to dist.scripts + + # new-style... + self.install_wrapper_scripts(dist) + + # ...and old-style + for script_name in self.distribution.scripts or []: + script_path = os.path.abspath(convert_path(script_name)) + script_name = os.path.basename(script_path) + with io.open(script_path) as strm: + script_text = strm.read() + self.install_script(dist, script_name, script_text, script_path) + + def install_wrapper_scripts(self, dist): + dist = VersionlessRequirement(dist) + return easy_install.install_wrapper_scripts(self, dist) + + +class VersionlessRequirement: + """ + Adapt a pkg_resources.Distribution to simply return the project + name as the 'requirement' so that scripts will work across + multiple versions. + + >>> from pkg_resources import Distribution + >>> dist = Distribution(project_name='foo', version='1.0') + >>> str(dist.as_requirement()) + 'foo==1.0' + >>> adapted_dist = VersionlessRequirement(dist) + >>> str(adapted_dist.as_requirement()) + 'foo' + """ + + def __init__(self, dist): + self.__dist = dist + + def __getattr__(self, name): + return getattr(self.__dist, name) + + def as_requirement(self): + return self.project_name diff --git a/venv/lib/python3.10/site-packages/setuptools/command/dist_info.py b/venv/lib/python3.10/site-packages/setuptools/command/dist_info.py new file mode 100644 index 00000000..c45258fa --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/command/dist_info.py @@ -0,0 +1,36 @@ +""" +Create a dist_info directory +As defined in the wheel specification +""" + +import os + +from distutils.core import Command +from distutils import log + + +class dist_info(Command): + + description = 'create a .dist-info directory' + + user_options = [ + ('egg-base=', 'e', "directory containing .egg-info directories" + " (default: top of the source tree)"), + ] + + def initialize_options(self): + self.egg_base = None + + def finalize_options(self): + pass + + def run(self): + egg_info = self.get_finalized_command('egg_info') + egg_info.egg_base = self.egg_base + egg_info.finalize_options() + egg_info.run() + dist_info_dir = egg_info.egg_info[:-len('.egg-info')] + '.dist-info' + log.info("creating '{}'".format(os.path.abspath(dist_info_dir))) + + bdist_wheel = self.get_finalized_command('bdist_wheel') + bdist_wheel.egg2dist(egg_info.egg_info, dist_info_dir) diff --git a/venv/lib/python3.10/site-packages/setuptools/command/easy_install.py b/venv/lib/python3.10/site-packages/setuptools/command/easy_install.py new file mode 100644 index 00000000..2ebf9e58 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/command/easy_install.py @@ -0,0 +1,2354 @@ +""" +Easy Install +------------ + +A tool for doing automatic download/extract/build of distutils-based Python +packages. For detailed documentation, see the accompanying EasyInstall.txt +file, or visit the `EasyInstall home page`__. + +__ https://setuptools.pypa.io/en/latest/deprecated/easy_install.html + +""" + +from glob import glob +from distutils.util import get_platform +from distutils.util import convert_path, subst_vars +from distutils.errors import ( + DistutilsArgError, DistutilsOptionError, + DistutilsError, DistutilsPlatformError, +) +from distutils.command.install import INSTALL_SCHEMES, SCHEME_KEYS +from distutils import log, dir_util +from distutils.command.build_scripts import first_line_re +from distutils.spawn import find_executable +import sys +import os +import zipimport +import shutil +import tempfile +import zipfile +import re +import stat +import random +import textwrap +import warnings +import site +import struct +import contextlib +import subprocess +import shlex +import io +import configparser + + +from sysconfig import get_config_vars, get_path + +from setuptools import SetuptoolsDeprecationWarning + +from setuptools import Command +from setuptools.sandbox import run_setup +from setuptools.command import setopt +from setuptools.archive_util import unpack_archive +from setuptools.package_index import ( + PackageIndex, parse_requirement_arg, URL_SCHEME, +) +from setuptools.command import bdist_egg, egg_info +from setuptools.wheel import Wheel +from pkg_resources import ( + yield_lines, normalize_path, resource_string, ensure_directory, + get_distribution, find_distributions, Environment, Requirement, + Distribution, PathMetadata, EggMetadata, WorkingSet, DistributionNotFound, + VersionConflict, DEVELOP_DIST, +) +import pkg_resources + +# Turn on PEP440Warnings +warnings.filterwarnings("default", category=pkg_resources.PEP440Warning) + +__all__ = [ + 'samefile', 'easy_install', 'PthDistributions', 'extract_wininst_cfg', + 'get_exe_prefixes', +] + + +def is_64bit(): + return struct.calcsize("P") == 8 + + +def samefile(p1, p2): + """ + Determine if two paths reference the same file. + + Augments os.path.samefile to work on Windows and + suppresses errors if the path doesn't exist. + """ + both_exist = os.path.exists(p1) and os.path.exists(p2) + use_samefile = hasattr(os.path, 'samefile') and both_exist + if use_samefile: + return os.path.samefile(p1, p2) + norm_p1 = os.path.normpath(os.path.normcase(p1)) + norm_p2 = os.path.normpath(os.path.normcase(p2)) + return norm_p1 == norm_p2 + + +def _to_bytes(s): + return s.encode('utf8') + + +def isascii(s): + try: + s.encode('ascii') + return True + except UnicodeError: + return False + + +def _one_liner(text): + return textwrap.dedent(text).strip().replace('\n', '; ') + + +class easy_install(Command): + """Manage a download/build/install process""" + description = "Find/get/install Python packages" + command_consumes_arguments = True + + user_options = [ + ('prefix=', None, "installation prefix"), + ("zip-ok", "z", "install package as a zipfile"), + ("multi-version", "m", "make apps have to require() a version"), + ("upgrade", "U", "force upgrade (searches PyPI for latest versions)"), + ("install-dir=", "d", "install package to DIR"), + ("script-dir=", "s", "install scripts to DIR"), + ("exclude-scripts", "x", "Don't install scripts"), + ("always-copy", "a", "Copy all needed packages to install dir"), + ("index-url=", "i", "base URL of Python Package Index"), + ("find-links=", "f", "additional URL(s) to search for packages"), + ("build-directory=", "b", + "download/extract/build in DIR; keep the results"), + ('optimize=', 'O', + "also compile with optimization: -O1 for \"python -O\", " + "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"), + ('record=', None, + "filename in which to record list of installed files"), + ('always-unzip', 'Z', "don't install as a zipfile, no matter what"), + ('site-dirs=', 'S', "list of directories where .pth files work"), + ('editable', 'e', "Install specified packages in editable form"), + ('no-deps', 'N', "don't install dependencies"), + ('allow-hosts=', 'H', "pattern(s) that hostnames must match"), + ('local-snapshots-ok', 'l', + "allow building eggs from local checkouts"), + ('version', None, "print version information and exit"), + ('install-layout=', None, "installation layout to choose (known values: deb)"), + ('force-installation-into-system-dir', '0', "force installation into /usr"), + ('no-find-links', None, + "Don't load find-links defined in packages being installed"), + ('user', None, "install in user site-package '%s'" % site.USER_SITE) + ] + boolean_options = [ + 'zip-ok', 'multi-version', 'exclude-scripts', 'upgrade', 'always-copy', + 'editable', + 'no-deps', 'local-snapshots-ok', 'version', 'force-installation-into-system-dir' + 'user' + ] + + negative_opt = {'always-unzip': 'zip-ok'} + create_index = PackageIndex + + def initialize_options(self): + warnings.warn( + "easy_install command is deprecated. " + "Use build and pip and other standards-based tools.", + EasyInstallDeprecationWarning, + ) + + # the --user option seems to be an opt-in one, + # so the default should be False. + self.user = 0 + self.zip_ok = self.local_snapshots_ok = None + self.install_dir = self.script_dir = self.exclude_scripts = None + self.index_url = None + self.find_links = None + self.build_directory = None + self.args = None + self.optimize = self.record = None + self.upgrade = self.always_copy = self.multi_version = None + self.editable = self.no_deps = self.allow_hosts = None + self.root = self.prefix = self.no_report = None + self.version = None + self.install_purelib = None # for pure module distributions + self.install_platlib = None # non-pure (dists w/ extensions) + self.install_headers = None # for C/C++ headers + self.install_lib = None # set to either purelib or platlib + self.install_scripts = None + self.install_data = None + self.install_base = None + self.install_platbase = None + if site.ENABLE_USER_SITE: + self.install_userbase = site.USER_BASE + self.install_usersite = site.USER_SITE + else: + self.install_userbase = None + self.install_usersite = None + self.no_find_links = None + + # Options not specifiable via command line + self.package_index = None + self.pth_file = self.always_copy_from = None + self.site_dirs = None + self.installed_projects = {} + # enable custom installation, known values: deb + self.install_layout = None + self.force_installation_into_system_dir = None + self.multiarch = None + + # Always read easy_install options, even if we are subclassed, or have + # an independent instance created. This ensures that defaults will + # always come from the standard configuration file(s)' "easy_install" + # section, even if this is a "develop" or "install" command, or some + # other embedding. + self._dry_run = None + self.verbose = self.distribution.verbose + self.distribution._set_command_options( + self, self.distribution.get_option_dict('easy_install') + ) + + def delete_blockers(self, blockers): + extant_blockers = ( + filename for filename in blockers + if os.path.exists(filename) or os.path.islink(filename) + ) + list(map(self._delete_path, extant_blockers)) + + def _delete_path(self, path): + log.info("Deleting %s", path) + if self.dry_run: + return + + is_tree = os.path.isdir(path) and not os.path.islink(path) + remover = rmtree if is_tree else os.unlink + remover(path) + + @staticmethod + def _render_version(): + """ + Render the Setuptools version and installation details, then exit. + """ + ver = '{}.{}'.format(*sys.version_info) + dist = get_distribution('setuptools') + tmpl = 'setuptools {dist.version} from {dist.location} (Python {ver})' + print(tmpl.format(**locals())) + raise SystemExit() + + def finalize_options(self): # noqa: C901 # is too complex (25) # FIXME + self.version and self._render_version() + + py_version = sys.version.split()[0] + prefix, exec_prefix = get_config_vars('prefix', 'exec_prefix') + + self.config_vars = { + 'dist_name': self.distribution.get_name(), + 'dist_version': self.distribution.get_version(), + 'dist_fullname': self.distribution.get_fullname(), + 'py_version': py_version, + 'py_version_short': py_version[0:3], + 'py_version_nodot': py_version[0] + py_version[2], + 'sys_prefix': prefix, + 'prefix': prefix, + 'sys_exec_prefix': exec_prefix, + 'exec_prefix': exec_prefix, + # Only python 3.2+ has abiflags + 'abiflags': getattr(sys, 'abiflags', ''), + } + + if site.ENABLE_USER_SITE: + self.config_vars['userbase'] = self.install_userbase + self.config_vars['usersite'] = self.install_usersite + + elif self.user: + log.warn("WARNING: The user site-packages directory is disabled.") + + self._fix_install_dir_for_user_site() + + self.expand_basedirs() + self.expand_dirs() + + if self.install_layout: + if not self.install_layout.lower() in ['deb']: + raise DistutilsOptionError("unknown value for --install-layout") + self.install_layout = self.install_layout.lower() + + import sysconfig + if sys.version_info[:2] >= (3, 3): + self.multiarch = sysconfig.get_config_var('MULTIARCH') + + self._expand( + 'install_dir', 'script_dir', 'build_directory', + 'site_dirs', + ) + # If a non-default installation directory was specified, default the + # script directory to match it. + if self.script_dir is None: + self.script_dir = self.install_dir + + if self.no_find_links is None: + self.no_find_links = False + + # Let install_dir get set by install_lib command, which in turn + # gets its info from the install command, and takes into account + # --prefix and --home and all that other crud. + self.set_undefined_options( + 'install_lib', ('install_dir', 'install_dir') + ) + # Likewise, set default script_dir from 'install_scripts.install_dir' + self.set_undefined_options( + 'install_scripts', ('install_dir', 'script_dir') + ) + + if self.user and self.install_purelib: + self.install_dir = self.install_purelib + self.script_dir = self.install_scripts + + if self.prefix == '/usr' and not self.force_installation_into_system_dir: + raise DistutilsOptionError("""installation into /usr + +Trying to install into the system managed parts of the file system. Please +consider to install to another location, or use the option +--force-installation-into-system-dir to overwrite this warning. +""") + + # default --record from the install command + self.set_undefined_options('install', ('record', 'record')) + # Should this be moved to the if statement below? It's not used + # elsewhere + normpath = map(normalize_path, sys.path) + self.all_site_dirs = get_site_dirs() + if self.site_dirs is not None: + site_dirs = [ + os.path.expanduser(s.strip()) for s in + self.site_dirs.split(',') + ] + for d in site_dirs: + if not os.path.isdir(d): + log.warn("%s (in --site-dirs) does not exist", d) + elif normalize_path(d) not in normpath: + raise DistutilsOptionError( + d + " (in --site-dirs) is not on sys.path" + ) + else: + self.all_site_dirs.append(normalize_path(d)) + if not self.editable: + self.check_site_dir() + self.index_url = self.index_url or "https://pypi.org/simple/" + self.shadow_path = self.all_site_dirs[:] + for path_item in self.install_dir, normalize_path(self.script_dir): + if path_item not in self.shadow_path: + self.shadow_path.insert(0, path_item) + + if self.allow_hosts is not None: + hosts = [s.strip() for s in self.allow_hosts.split(',')] + else: + hosts = ['*'] + if self.package_index is None: + self.package_index = self.create_index( + self.index_url, search_path=self.shadow_path, hosts=hosts, + ) + self.local_index = Environment(self.shadow_path + sys.path) + + if self.find_links is not None: + if isinstance(self.find_links, str): + self.find_links = self.find_links.split() + else: + self.find_links = [] + if self.local_snapshots_ok: + self.package_index.scan_egg_links(self.shadow_path + sys.path) + if not self.no_find_links: + self.package_index.add_find_links(self.find_links) + self.set_undefined_options('install_lib', ('optimize', 'optimize')) + if not isinstance(self.optimize, int): + try: + self.optimize = int(self.optimize) + if not (0 <= self.optimize <= 2): + raise ValueError + except ValueError as e: + raise DistutilsOptionError( + "--optimize must be 0, 1, or 2" + ) from e + + if self.editable and not self.build_directory: + raise DistutilsArgError( + "Must specify a build directory (-b) when using --editable" + ) + if not self.args: + raise DistutilsArgError( + "No urls, filenames, or requirements specified (see --help)") + + self.outputs = [] + + def _fix_install_dir_for_user_site(self): + """ + Fix the install_dir if "--user" was used. + """ + if not self.user or not site.ENABLE_USER_SITE: + return + + self.create_home_path() + if self.install_userbase is None: + msg = "User base directory is not specified" + raise DistutilsPlatformError(msg) + self.install_base = self.install_platbase = self.install_userbase + scheme_name = os.name.replace('posix', 'unix') + '_user' + self.select_scheme(scheme_name) + + def _expand_attrs(self, attrs): + for attr in attrs: + val = getattr(self, attr) + if val is not None: + if os.name == 'posix' or os.name == 'nt': + val = os.path.expanduser(val) + val = subst_vars(val, self.config_vars) + setattr(self, attr, val) + + def expand_basedirs(self): + """Calls `os.path.expanduser` on install_base, install_platbase and + root.""" + self._expand_attrs(['install_base', 'install_platbase', 'root']) + + def expand_dirs(self): + """Calls `os.path.expanduser` on install dirs.""" + dirs = [ + 'install_purelib', + 'install_platlib', + 'install_lib', + 'install_headers', + 'install_scripts', + 'install_data', + ] + self._expand_attrs(dirs) + + def run(self, show_deprecation=True): + if show_deprecation: + self.announce( + "WARNING: The easy_install command is deprecated " + "and will be removed in a future version.", + log.WARN, + ) + if self.verbose != self.distribution.verbose: + log.set_verbosity(self.verbose) + try: + for spec in self.args: + self.easy_install(spec, not self.no_deps) + if self.record: + outputs = list(sorted(self.outputs)) + if self.root: # strip any package prefix + root_len = len(self.root) + for counter in range(len(outputs)): + outputs[counter] = outputs[counter][root_len:] + from distutils import file_util + + self.execute( + file_util.write_file, (self.record, outputs), + "writing list of installed files to '%s'" % + self.record + ) + self.warn_deprecated_options() + finally: + log.set_verbosity(self.distribution.verbose) + + def pseudo_tempname(self): + """Return a pseudo-tempname base in the install directory. + This code is intentionally naive; if a malicious party can write to + the target directory you're already in deep doodoo. + """ + try: + pid = os.getpid() + except Exception: + pid = random.randint(0, sys.maxsize) + return os.path.join(self.install_dir, "test-easy-install-%s" % pid) + + def warn_deprecated_options(self): + pass + + def check_site_dir(self): # noqa: C901 # is too complex (12) # FIXME + """Verify that self.install_dir is .pth-capable dir, if needed""" + + instdir = normalize_path(self.install_dir) + pth_file = os.path.join(instdir, 'easy-install.pth') + + if not os.path.exists(instdir): + try: + os.makedirs(instdir) + except (OSError, IOError): + self.cant_write_to_target() + + # Is it a configured, PYTHONPATH, implicit, or explicit site dir? + is_site_dir = instdir in self.all_site_dirs + + if not is_site_dir and not self.multi_version: + # No? Then directly test whether it does .pth file processing + is_site_dir = self.check_pth_processing() + else: + # make sure we can write to target dir + testfile = self.pseudo_tempname() + '.write-test' + test_exists = os.path.exists(testfile) + try: + if test_exists: + os.unlink(testfile) + open(testfile, 'w').close() + os.unlink(testfile) + except (OSError, IOError): + self.cant_write_to_target() + + if not is_site_dir and not self.multi_version: + # Can't install non-multi to non-site dir with easy_install + pythonpath = os.environ.get('PYTHONPATH', '') + log.warn(self.__no_default_msg, self.install_dir, pythonpath) + + if is_site_dir: + if self.pth_file is None: + self.pth_file = PthDistributions(pth_file, self.all_site_dirs) + else: + self.pth_file = None + + if self.multi_version and not os.path.exists(pth_file): + self.pth_file = None # don't create a .pth file + self.install_dir = instdir + + __cant_write_msg = textwrap.dedent(""" + can't create or remove files in install directory + + The following error occurred while trying to add or remove files in the + installation directory: + + %s + + The installation directory you specified (via --install-dir, --prefix, or + the distutils default setting) was: + + %s + """).lstrip() # noqa + + __not_exists_id = textwrap.dedent(""" + This directory does not currently exist. Please create it and try again, or + choose a different installation directory (using the -d or --install-dir + option). + """).lstrip() # noqa + + __access_msg = textwrap.dedent(""" + Perhaps your account does not have write access to this directory? If the + installation directory is a system-owned directory, you may need to sign in + as the administrator or "root" account. If you do not have administrative + access to this machine, you may wish to choose a different installation + directory, preferably one that is listed in your PYTHONPATH environment + variable. + + For information on other options, you may wish to consult the + documentation at: + + https://setuptools.pypa.io/en/latest/deprecated/easy_install.html + + Please make the appropriate changes for your system and try again. + """).lstrip() # noqa + + def cant_write_to_target(self): + msg = self.__cant_write_msg % (sys.exc_info()[1], self.install_dir,) + + if not os.path.exists(self.install_dir): + msg += '\n' + self.__not_exists_id + else: + msg += '\n' + self.__access_msg + raise DistutilsError(msg) + + def check_pth_processing(self): + """Empirically verify whether .pth files are supported in inst. dir""" + instdir = self.install_dir + log.info("Checking .pth file support in %s", instdir) + pth_file = self.pseudo_tempname() + ".pth" + ok_file = pth_file + '.ok' + ok_exists = os.path.exists(ok_file) + tmpl = _one_liner(""" + import os + f = open({ok_file!r}, 'w') + f.write('OK') + f.close() + """) + '\n' + try: + if ok_exists: + os.unlink(ok_file) + dirname = os.path.dirname(ok_file) + os.makedirs(dirname, exist_ok=True) + f = open(pth_file, 'w') + except (OSError, IOError): + self.cant_write_to_target() + else: + try: + f.write(tmpl.format(**locals())) + f.close() + f = None + executable = sys.executable + if os.name == 'nt': + dirname, basename = os.path.split(executable) + alt = os.path.join(dirname, 'pythonw.exe') + use_alt = ( + basename.lower() == 'python.exe' and + os.path.exists(alt) + ) + if use_alt: + # use pythonw.exe to avoid opening a console window + executable = alt + + from distutils.spawn import spawn + + spawn([executable, '-E', '-c', 'pass'], 0) + + if os.path.exists(ok_file): + log.info( + "TEST PASSED: %s appears to support .pth files", + instdir + ) + return True + finally: + if f: + f.close() + if os.path.exists(ok_file): + os.unlink(ok_file) + if os.path.exists(pth_file): + os.unlink(pth_file) + if not self.multi_version: + log.warn("TEST FAILED: %s does NOT support .pth files", instdir) + return False + + def install_egg_scripts(self, dist): + """Write all the scripts for `dist`, unless scripts are excluded""" + if not self.exclude_scripts and dist.metadata_isdir('scripts'): + for script_name in dist.metadata_listdir('scripts'): + if dist.metadata_isdir('scripts/' + script_name): + # The "script" is a directory, likely a Python 3 + # __pycache__ directory, so skip it. + continue + self.install_script( + dist, script_name, + dist.get_metadata('scripts/' + script_name) + ) + self.install_wrapper_scripts(dist) + + def add_output(self, path): + if os.path.isdir(path): + for base, dirs, files in os.walk(path): + for filename in files: + self.outputs.append(os.path.join(base, filename)) + else: + self.outputs.append(path) + + def not_editable(self, spec): + if self.editable: + raise DistutilsArgError( + "Invalid argument %r: you can't use filenames or URLs " + "with --editable (except via the --find-links option)." + % (spec,) + ) + + def check_editable(self, spec): + if not self.editable: + return + + if os.path.exists(os.path.join(self.build_directory, spec.key)): + raise DistutilsArgError( + "%r already exists in %s; can't do a checkout there" % + (spec.key, self.build_directory) + ) + + @contextlib.contextmanager + def _tmpdir(self): + tmpdir = tempfile.mkdtemp(prefix=u"easy_install-") + try: + # cast to str as workaround for #709 and #710 and #712 + yield str(tmpdir) + finally: + os.path.exists(tmpdir) and rmtree(tmpdir) + + def easy_install(self, spec, deps=False): + with self._tmpdir() as tmpdir: + if not isinstance(spec, Requirement): + if URL_SCHEME(spec): + # It's a url, download it to tmpdir and process + self.not_editable(spec) + dl = self.package_index.download(spec, tmpdir) + return self.install_item(None, dl, tmpdir, deps, True) + + elif os.path.exists(spec): + # Existing file or directory, just process it directly + self.not_editable(spec) + return self.install_item(None, spec, tmpdir, deps, True) + else: + spec = parse_requirement_arg(spec) + + self.check_editable(spec) + dist = self.package_index.fetch_distribution( + spec, tmpdir, self.upgrade, self.editable, + not self.always_copy, self.local_index + ) + if dist is None: + msg = "Could not find suitable distribution for %r" % spec + if self.always_copy: + msg += " (--always-copy skips system and development eggs)" + raise DistutilsError(msg) + elif dist.precedence == DEVELOP_DIST: + # .egg-info dists don't need installing, just process deps + self.process_distribution(spec, dist, deps, "Using") + return dist + else: + return self.install_item(spec, dist.location, tmpdir, deps) + + def install_item(self, spec, download, tmpdir, deps, install_needed=False): + + # Installation is also needed if file in tmpdir or is not an egg + install_needed = install_needed or self.always_copy + install_needed = install_needed or os.path.dirname(download) == tmpdir + install_needed = install_needed or not download.endswith('.egg') + install_needed = install_needed or ( + self.always_copy_from is not None and + os.path.dirname(normalize_path(download)) == + normalize_path(self.always_copy_from) + ) + + if spec and not install_needed: + # at this point, we know it's a local .egg, we just don't know if + # it's already installed. + for dist in self.local_index[spec.project_name]: + if dist.location == download: + break + else: + install_needed = True # it's not in the local index + + log.info("Processing %s", os.path.basename(download)) + + if install_needed: + dists = self.install_eggs(spec, download, tmpdir) + for dist in dists: + self.process_distribution(spec, dist, deps) + else: + dists = [self.egg_distribution(download)] + self.process_distribution(spec, dists[0], deps, "Using") + + if spec is not None: + for dist in dists: + if dist in spec: + return dist + + def select_scheme(self, name): + """Sets the install directories by applying the install schemes.""" + # it's the caller's problem if they supply a bad name! + scheme = INSTALL_SCHEMES[name] + for key in SCHEME_KEYS: + attrname = 'install_' + key + if getattr(self, attrname) is None: + setattr(self, attrname, scheme[key]) + + # FIXME: 'easy_install.process_distribution' is too complex (12) + def process_distribution( # noqa: C901 + self, requirement, dist, deps=True, *info, + ): + self.update_pth(dist) + self.package_index.add(dist) + if dist in self.local_index[dist.key]: + self.local_index.remove(dist) + self.local_index.add(dist) + self.install_egg_scripts(dist) + self.installed_projects[dist.key] = dist + log.info(self.installation_report(requirement, dist, *info)) + if (dist.has_metadata('dependency_links.txt') and + not self.no_find_links): + self.package_index.add_find_links( + dist.get_metadata_lines('dependency_links.txt') + ) + if not deps and not self.always_copy: + return + elif requirement is not None and dist.key != requirement.key: + log.warn("Skipping dependencies for %s", dist) + return # XXX this is not the distribution we were looking for + elif requirement is None or dist not in requirement: + # if we wound up with a different version, resolve what we've got + distreq = dist.as_requirement() + requirement = Requirement(str(distreq)) + log.info("Processing dependencies for %s", requirement) + try: + distros = WorkingSet([]).resolve( + [requirement], self.local_index, self.easy_install + ) + except DistributionNotFound as e: + raise DistutilsError(str(e)) from e + except VersionConflict as e: + raise DistutilsError(e.report()) from e + if self.always_copy or self.always_copy_from: + # Force all the relevant distros to be copied or activated + for dist in distros: + if dist.key not in self.installed_projects: + self.easy_install(dist.as_requirement()) + log.info("Finished processing dependencies for %s", requirement) + + def should_unzip(self, dist): + if self.zip_ok is not None: + return not self.zip_ok + if dist.has_metadata('not-zip-safe'): + return True + if not dist.has_metadata('zip-safe'): + return True + return False + + def maybe_move(self, spec, dist_filename, setup_base): + dst = os.path.join(self.build_directory, spec.key) + if os.path.exists(dst): + msg = ( + "%r already exists in %s; build directory %s will not be kept" + ) + log.warn(msg, spec.key, self.build_directory, setup_base) + return setup_base + if os.path.isdir(dist_filename): + setup_base = dist_filename + else: + if os.path.dirname(dist_filename) == setup_base: + os.unlink(dist_filename) # get it out of the tmp dir + contents = os.listdir(setup_base) + if len(contents) == 1: + dist_filename = os.path.join(setup_base, contents[0]) + if os.path.isdir(dist_filename): + # if the only thing there is a directory, move it instead + setup_base = dist_filename + ensure_directory(dst) + shutil.move(setup_base, dst) + return dst + + def install_wrapper_scripts(self, dist): + if self.exclude_scripts: + return + for args in ScriptWriter.best().get_args(dist): + self.write_script(*args) + + def install_script(self, dist, script_name, script_text, dev_path=None): + """Generate a legacy script wrapper and install it""" + spec = str(dist.as_requirement()) + is_script = is_python_script(script_text, script_name) + + if is_script: + body = self._load_template(dev_path) % locals() + script_text = ScriptWriter.get_header(script_text) + body + self.write_script(script_name, _to_bytes(script_text), 'b') + + @staticmethod + def _load_template(dev_path): + """ + There are a couple of template scripts in the package. This + function loads one of them and prepares it for use. + """ + # See https://github.com/pypa/setuptools/issues/134 for info + # on script file naming and downstream issues with SVR4 + name = 'script.tmpl' + if dev_path: + name = name.replace('.tmpl', ' (dev).tmpl') + + raw_bytes = resource_string('setuptools', name) + return raw_bytes.decode('utf-8') + + def write_script(self, script_name, contents, mode="t", blockers=()): + """Write an executable file to the scripts directory""" + self.delete_blockers( # clean up old .py/.pyw w/o a script + [os.path.join(self.script_dir, x) for x in blockers] + ) + log.info("Installing %s script to %s", script_name, self.script_dir) + target = os.path.join(self.script_dir, script_name) + self.add_output(target) + + if self.dry_run: + return + + mask = current_umask() + ensure_directory(target) + if os.path.exists(target): + os.unlink(target) + with open(target, "w" + mode) as f: + f.write(contents) + chmod(target, 0o777 - mask) + + def install_eggs(self, spec, dist_filename, tmpdir): + # .egg dirs or files are already built, so just return them + installer_map = { + '.egg': self.install_egg, + '.exe': self.install_exe, + '.whl': self.install_wheel, + } + try: + install_dist = installer_map[ + dist_filename.lower()[-4:] + ] + except KeyError: + pass + else: + return [install_dist(dist_filename, tmpdir)] + + # Anything else, try to extract and build + setup_base = tmpdir + if os.path.isfile(dist_filename) and not dist_filename.endswith('.py'): + unpack_archive(dist_filename, tmpdir, self.unpack_progress) + elif os.path.isdir(dist_filename): + setup_base = os.path.abspath(dist_filename) + + if (setup_base.startswith(tmpdir) # something we downloaded + and self.build_directory and spec is not None): + setup_base = self.maybe_move(spec, dist_filename, setup_base) + + # Find the setup.py file + setup_script = os.path.join(setup_base, 'setup.py') + + if not os.path.exists(setup_script): + setups = glob(os.path.join(setup_base, '*', 'setup.py')) + if not setups: + raise DistutilsError( + "Couldn't find a setup script in %s" % + os.path.abspath(dist_filename) + ) + if len(setups) > 1: + raise DistutilsError( + "Multiple setup scripts in %s" % + os.path.abspath(dist_filename) + ) + setup_script = setups[0] + + # Now run it, and return the result + if self.editable: + log.info(self.report_editable(spec, setup_script)) + return [] + else: + return self.build_and_install(setup_script, setup_base) + + def egg_distribution(self, egg_path): + if os.path.isdir(egg_path): + metadata = PathMetadata(egg_path, os.path.join(egg_path, + 'EGG-INFO')) + else: + metadata = EggMetadata(zipimport.zipimporter(egg_path)) + return Distribution.from_filename(egg_path, metadata=metadata) + + # FIXME: 'easy_install.install_egg' is too complex (11) + def install_egg(self, egg_path, tmpdir): # noqa: C901 + destination = os.path.join( + self.install_dir, + os.path.basename(egg_path), + ) + destination = os.path.abspath(destination) + if not self.dry_run: + ensure_directory(destination) + + dist = self.egg_distribution(egg_path) + if not samefile(egg_path, destination): + if os.path.isdir(destination) and not os.path.islink(destination): + dir_util.remove_tree(destination, dry_run=self.dry_run) + elif os.path.exists(destination): + self.execute( + os.unlink, + (destination,), + "Removing " + destination, + ) + try: + new_dist_is_zipped = False + if os.path.isdir(egg_path): + if egg_path.startswith(tmpdir): + f, m = shutil.move, "Moving" + else: + f, m = shutil.copytree, "Copying" + elif self.should_unzip(dist): + self.mkpath(destination) + f, m = self.unpack_and_compile, "Extracting" + else: + new_dist_is_zipped = True + if egg_path.startswith(tmpdir): + f, m = shutil.move, "Moving" + else: + f, m = shutil.copy2, "Copying" + self.execute( + f, + (egg_path, destination), + (m + " %s to %s") % ( + os.path.basename(egg_path), + os.path.dirname(destination) + ), + ) + update_dist_caches( + destination, + fix_zipimporter_caches=new_dist_is_zipped, + ) + except Exception: + update_dist_caches(destination, fix_zipimporter_caches=False) + raise + + self.add_output(destination) + return self.egg_distribution(destination) + + def install_exe(self, dist_filename, tmpdir): + # See if it's valid, get data + cfg = extract_wininst_cfg(dist_filename) + if cfg is None: + raise DistutilsError( + "%s is not a valid distutils Windows .exe" % dist_filename + ) + # Create a dummy distribution object until we build the real distro + dist = Distribution( + None, + project_name=cfg.get('metadata', 'name'), + version=cfg.get('metadata', 'version'), platform=get_platform(), + ) + + # Convert the .exe to an unpacked egg + egg_path = os.path.join(tmpdir, dist.egg_name() + '.egg') + dist.location = egg_path + egg_tmp = egg_path + '.tmp' + _egg_info = os.path.join(egg_tmp, 'EGG-INFO') + pkg_inf = os.path.join(_egg_info, 'PKG-INFO') + ensure_directory(pkg_inf) # make sure EGG-INFO dir exists + dist._provider = PathMetadata(egg_tmp, _egg_info) # XXX + self.exe_to_egg(dist_filename, egg_tmp) + + # Write EGG-INFO/PKG-INFO + if not os.path.exists(pkg_inf): + f = open(pkg_inf, 'w') + f.write('Metadata-Version: 1.0\n') + for k, v in cfg.items('metadata'): + if k != 'target_version': + f.write('%s: %s\n' % (k.replace('_', '-').title(), v)) + f.close() + script_dir = os.path.join(_egg_info, 'scripts') + # delete entry-point scripts to avoid duping + self.delete_blockers([ + os.path.join(script_dir, args[0]) + for args in ScriptWriter.get_args(dist) + ]) + # Build .egg file from tmpdir + bdist_egg.make_zipfile( + egg_path, egg_tmp, verbose=self.verbose, dry_run=self.dry_run, + ) + # install the .egg + return self.install_egg(egg_path, tmpdir) + + # FIXME: 'easy_install.exe_to_egg' is too complex (12) + def exe_to_egg(self, dist_filename, egg_tmp): # noqa: C901 + """Extract a bdist_wininst to the directories an egg would use""" + # Check for .pth file and set up prefix translations + prefixes = get_exe_prefixes(dist_filename) + to_compile = [] + native_libs = [] + top_level = {} + + def process(src, dst): + s = src.lower() + for old, new in prefixes: + if s.startswith(old): + src = new + src[len(old):] + parts = src.split('/') + dst = os.path.join(egg_tmp, *parts) + dl = dst.lower() + if dl.endswith('.pyd') or dl.endswith('.dll'): + parts[-1] = bdist_egg.strip_module(parts[-1]) + top_level[os.path.splitext(parts[0])[0]] = 1 + native_libs.append(src) + elif dl.endswith('.py') and old != 'SCRIPTS/': + top_level[os.path.splitext(parts[0])[0]] = 1 + to_compile.append(dst) + return dst + if not src.endswith('.pth'): + log.warn("WARNING: can't process %s", src) + return None + + # extract, tracking .pyd/.dll->native_libs and .py -> to_compile + unpack_archive(dist_filename, egg_tmp, process) + stubs = [] + for res in native_libs: + if res.lower().endswith('.pyd'): # create stubs for .pyd's + parts = res.split('/') + resource = parts[-1] + parts[-1] = bdist_egg.strip_module(parts[-1]) + '.py' + pyfile = os.path.join(egg_tmp, *parts) + to_compile.append(pyfile) + stubs.append(pyfile) + bdist_egg.write_stub(resource, pyfile) + self.byte_compile(to_compile) # compile .py's + bdist_egg.write_safety_flag( + os.path.join(egg_tmp, 'EGG-INFO'), + bdist_egg.analyze_egg(egg_tmp, stubs)) # write zip-safety flag + + for name in 'top_level', 'native_libs': + if locals()[name]: + txt = os.path.join(egg_tmp, 'EGG-INFO', name + '.txt') + if not os.path.exists(txt): + f = open(txt, 'w') + f.write('\n'.join(locals()[name]) + '\n') + f.close() + + def install_wheel(self, wheel_path, tmpdir): + wheel = Wheel(wheel_path) + assert wheel.is_compatible() + destination = os.path.join(self.install_dir, wheel.egg_name()) + destination = os.path.abspath(destination) + if not self.dry_run: + ensure_directory(destination) + if os.path.isdir(destination) and not os.path.islink(destination): + dir_util.remove_tree(destination, dry_run=self.dry_run) + elif os.path.exists(destination): + self.execute( + os.unlink, + (destination,), + "Removing " + destination, + ) + try: + self.execute( + wheel.install_as_egg, + (destination,), + ("Installing %s to %s") % ( + os.path.basename(wheel_path), + os.path.dirname(destination) + ), + ) + finally: + update_dist_caches(destination, fix_zipimporter_caches=False) + self.add_output(destination) + return self.egg_distribution(destination) + + __mv_warning = textwrap.dedent(""" + Because this distribution was installed --multi-version, before you can + import modules from this package in an application, you will need to + 'import pkg_resources' and then use a 'require()' call similar to one of + these examples, in order to select the desired version: + + pkg_resources.require("%(name)s") # latest installed version + pkg_resources.require("%(name)s==%(version)s") # this exact version + pkg_resources.require("%(name)s>=%(version)s") # this version or higher + """).lstrip() # noqa + + __id_warning = textwrap.dedent(""" + Note also that the installation directory must be on sys.path at runtime for + this to work. (e.g. by being the application's script directory, by being on + PYTHONPATH, or by being added to sys.path by your code.) + """) # noqa + + def installation_report(self, req, dist, what="Installed"): + """Helpful installation message for display to package users""" + msg = "\n%(what)s %(eggloc)s%(extras)s" + if self.multi_version and not self.no_report: + msg += '\n' + self.__mv_warning + if self.install_dir not in map(normalize_path, sys.path): + msg += '\n' + self.__id_warning + + eggloc = dist.location + name = dist.project_name + version = dist.version + extras = '' # TODO: self.report_extras(req, dist) + return msg % locals() + + __editable_msg = textwrap.dedent(""" + Extracted editable version of %(spec)s to %(dirname)s + + If it uses setuptools in its setup script, you can activate it in + "development" mode by going to that directory and running:: + + %(python)s setup.py develop + + See the setuptools documentation for the "develop" command for more info. + """).lstrip() # noqa + + def report_editable(self, spec, setup_script): + dirname = os.path.dirname(setup_script) + python = sys.executable + return '\n' + self.__editable_msg % locals() + + def run_setup(self, setup_script, setup_base, args): + sys.modules.setdefault('distutils.command.bdist_egg', bdist_egg) + sys.modules.setdefault('distutils.command.egg_info', egg_info) + + args = list(args) + if self.verbose > 2: + v = 'v' * (self.verbose - 1) + args.insert(0, '-' + v) + elif self.verbose < 2: + args.insert(0, '-q') + if self.dry_run: + args.insert(0, '-n') + log.info( + "Running %s %s", setup_script[len(setup_base) + 1:], ' '.join(args) + ) + try: + run_setup(setup_script, args) + except SystemExit as v: + raise DistutilsError( + "Setup script exited with %s" % (v.args[0],) + ) from v + + def build_and_install(self, setup_script, setup_base): + args = ['bdist_egg', '--dist-dir'] + + dist_dir = tempfile.mkdtemp( + prefix='egg-dist-tmp-', dir=os.path.dirname(setup_script) + ) + try: + self._set_fetcher_options(os.path.dirname(setup_script)) + args.append(dist_dir) + + self.run_setup(setup_script, setup_base, args) + all_eggs = Environment([dist_dir]) + eggs = [] + for key in all_eggs: + for dist in all_eggs[key]: + eggs.append(self.install_egg(dist.location, setup_base)) + if not eggs and not self.dry_run: + log.warn("No eggs found in %s (setup script problem?)", + dist_dir) + return eggs + finally: + rmtree(dist_dir) + log.set_verbosity(self.verbose) # restore our log verbosity + + def _set_fetcher_options(self, base): + """ + When easy_install is about to run bdist_egg on a source dist, that + source dist might have 'setup_requires' directives, requiring + additional fetching. Ensure the fetcher options given to easy_install + are available to that command as well. + """ + # find the fetch options from easy_install and write them out + # to the setup.cfg file. + ei_opts = self.distribution.get_option_dict('easy_install').copy() + fetch_directives = ( + 'find_links', 'site_dirs', 'index_url', 'optimize', 'allow_hosts', + ) + fetch_options = {} + for key, val in ei_opts.items(): + if key not in fetch_directives: + continue + fetch_options[key] = val[1] + # create a settings dictionary suitable for `edit_config` + settings = dict(easy_install=fetch_options) + cfg_filename = os.path.join(base, 'setup.cfg') + setopt.edit_config(cfg_filename, settings) + + def update_pth(self, dist): # noqa: C901 # is too complex (11) # FIXME + if self.pth_file is None: + return + + for d in self.pth_file[dist.key]: # drop old entries + if not self.multi_version and d.location == dist.location: + continue + + log.info("Removing %s from easy-install.pth file", d) + self.pth_file.remove(d) + if d.location in self.shadow_path: + self.shadow_path.remove(d.location) + + if not self.multi_version: + if dist.location in self.pth_file.paths: + log.info( + "%s is already the active version in easy-install.pth", + dist, + ) + else: + log.info("Adding %s to easy-install.pth file", dist) + self.pth_file.add(dist) # add new entry + if dist.location not in self.shadow_path: + self.shadow_path.append(dist.location) + + if self.dry_run: + return + + self.pth_file.save() + + if dist.key != 'setuptools': + return + + # Ensure that setuptools itself never becomes unavailable! + # XXX should this check for latest version? + filename = os.path.join(self.install_dir, 'setuptools.pth') + if os.path.islink(filename): + os.unlink(filename) + with open(filename, 'wt') as f: + f.write(self.pth_file.make_relative(dist.location) + '\n') + + def unpack_progress(self, src, dst): + # Progress filter for unpacking + log.debug("Unpacking %s to %s", src, dst) + return dst # only unpack-and-compile skips files for dry run + + def unpack_and_compile(self, egg_path, destination): + to_compile = [] + to_chmod = [] + + def pf(src, dst): + if dst.endswith('.py') and not src.startswith('EGG-INFO/'): + to_compile.append(dst) + elif dst.endswith('.dll') or dst.endswith('.so'): + to_chmod.append(dst) + self.unpack_progress(src, dst) + return not self.dry_run and dst or None + + unpack_archive(egg_path, destination, pf) + self.byte_compile(to_compile) + if not self.dry_run: + for f in to_chmod: + mode = ((os.stat(f)[stat.ST_MODE]) | 0o555) & 0o7755 + chmod(f, mode) + + def byte_compile(self, to_compile): + if sys.dont_write_bytecode: + return + + from distutils.util import byte_compile + + try: + # try to make the byte compile messages quieter + log.set_verbosity(self.verbose - 1) + + byte_compile(to_compile, optimize=0, force=1, dry_run=self.dry_run) + if self.optimize: + byte_compile( + to_compile, optimize=self.optimize, force=1, + dry_run=self.dry_run, + ) + finally: + log.set_verbosity(self.verbose) # restore original verbosity + + __no_default_msg = textwrap.dedent(""" + bad install directory or PYTHONPATH + + You are attempting to install a package to a directory that is not + on PYTHONPATH and which Python does not read ".pth" files from. The + installation directory you specified (via --install-dir, --prefix, or + the distutils default setting) was: + + %s + + and your PYTHONPATH environment variable currently contains: + + %r + + Here are some of your options for correcting the problem: + + * You can choose a different installation directory, i.e., one that is + on PYTHONPATH or supports .pth files + + * You can add the installation directory to the PYTHONPATH environment + variable. (It must then also be on PYTHONPATH whenever you run + Python and want to use the package(s) you are installing.) + + * You can set up the installation directory to support ".pth" files by + using one of the approaches described here: + + https://setuptools.pypa.io/en/latest/deprecated/easy_install.html#custom-installation-locations + + + Please make the appropriate changes for your system and try again. + """).strip() + + def create_home_path(self): + """Create directories under ~.""" + if not self.user: + return + home = convert_path(os.path.expanduser("~")) + for name, path in self.config_vars.items(): + if path.startswith(home) and not os.path.isdir(path): + self.debug_print("os.makedirs('%s', 0o700)" % path) + os.makedirs(path, 0o700) + + if sys.version[:3] in ('2.3', '2.4', '2.5') or 'real_prefix' in sys.__dict__: + sitedir_name = 'site-packages' + else: + sitedir_name = 'dist-packages' + + INSTALL_SCHEMES = dict( + posix=dict( + install_dir='$base/lib/python$py_version_short/site-packages', + script_dir='$base/bin', + ), + unix_local = dict( + install_dir = '$base/local/lib/python$py_version_short/%s' % sitedir_name, + script_dir = '$base/local/bin', + ), + posix_local = dict( + install_dir = '$base/local/lib/python$py_version_short/%s' % sitedir_name, + script_dir = '$base/local/bin', + ), + deb_system = dict( + install_dir = '$base/lib/python3/%s' % sitedir_name, + script_dir = '$base/bin', + ), + ) + + DEFAULT_SCHEME = dict( + install_dir='$base/Lib/site-packages', + script_dir='$base/Scripts', + ) + + def _expand(self, *attrs): + config_vars = self.get_finalized_command('install').config_vars + + if self.prefix or self.install_layout: + if self.install_layout and self.install_layout in ['deb']: + scheme_name = "deb_system" + self.prefix = '/usr' + elif self.prefix or 'real_prefix' in sys.__dict__: + scheme_name = os.name + else: + scheme_name = "posix_local" + # Set default install_dir/scripts from --prefix + config_vars = config_vars.copy() + config_vars['base'] = self.prefix + scheme = self.INSTALL_SCHEMES.get(scheme_name,self.DEFAULT_SCHEME) + for attr, val in scheme.items(): + if getattr(self, attr, None) is None: + setattr(self, attr, val) + + from distutils.util import subst_vars + + for attr in attrs: + val = getattr(self, attr) + if val is not None: + val = subst_vars(val, config_vars) + if os.name == 'posix': + val = os.path.expanduser(val) + setattr(self, attr, val) + + +def _pythonpath(): + items = os.environ.get('PYTHONPATH', '').split(os.pathsep) + return filter(None, items) + + +def get_site_dirs(): + """ + Return a list of 'site' dirs + """ + + sitedirs = [] + + # start with PYTHONPATH + sitedirs.extend(_pythonpath()) + + prefixes = [sys.prefix] + if sys.exec_prefix != sys.prefix: + prefixes.append(sys.exec_prefix) + for prefix in prefixes: + if not prefix: + continue + + if sys.platform in ('os2emx', 'riscos'): + sitedirs.append(os.path.join(prefix, "Lib", "site-packages")) + elif os.sep == '/': + sitedirs.extend([ + os.path.join( + prefix, + "local/lib", + "python" + sys.version[:3], + "dist-packages", + ), + os.path.join( + prefix, + "lib", + "python{}.{}".format(*sys.version_info), + "dist-packages", + ), + os.path.join(prefix, "lib", "site-python"), + ]) + else: + sitedirs.extend([ + prefix, + os.path.join(prefix, "lib", "site-packages"), + ]) + if sys.platform != 'darwin': + continue + + # for framework builds *only* we add the standard Apple + # locations. Currently only per-user, but /Library and + # /Network/Library could be added too + if 'Python.framework' not in prefix: + continue + + home = os.environ.get('HOME') + if not home: + continue + + home_sp = os.path.join( + home, + 'Library', + 'Python', + '{}.{}'.format(*sys.version_info), + 'site-packages', + ) + sitedirs.append(home_sp) + lib_paths = get_path('purelib'), get_path('platlib') + + sitedirs.extend(s for s in lib_paths if s not in sitedirs) + + if site.ENABLE_USER_SITE: + sitedirs.append(site.USER_SITE) + + with contextlib.suppress(AttributeError): + sitedirs.extend(site.getsitepackages()) + + sitedirs = list(map(normalize_path, sitedirs)) + + return sitedirs + + +def expand_paths(inputs): # noqa: C901 # is too complex (11) # FIXME + """Yield sys.path directories that might contain "old-style" packages""" + + seen = {} + + for dirname in inputs: + dirname = normalize_path(dirname) + if dirname in seen: + continue + + seen[dirname] = 1 + if not os.path.isdir(dirname): + continue + + files = os.listdir(dirname) + yield dirname, files + + for name in files: + if not name.endswith('.pth'): + # We only care about the .pth files + continue + if name in ('easy-install.pth', 'setuptools.pth'): + # Ignore .pth files that we control + continue + + # Read the .pth file + f = open(os.path.join(dirname, name)) + lines = list(yield_lines(f)) + f.close() + + # Yield existing non-dupe, non-import directory lines from it + for line in lines: + if line.startswith("import"): + continue + + line = normalize_path(line.rstrip()) + if line in seen: + continue + + seen[line] = 1 + if not os.path.isdir(line): + continue + + yield line, os.listdir(line) + + +def extract_wininst_cfg(dist_filename): + """Extract configuration data from a bdist_wininst .exe + + Returns a configparser.RawConfigParser, or None + """ + f = open(dist_filename, 'rb') + try: + endrec = zipfile._EndRecData(f) + if endrec is None: + return None + + prepended = (endrec[9] - endrec[5]) - endrec[6] + if prepended < 12: # no wininst data here + return None + f.seek(prepended - 12) + + tag, cfglen, bmlen = struct.unpack("egg path translations for a given .exe file""" + + prefixes = [ + ('PURELIB/', ''), + ('PLATLIB/pywin32_system32', ''), + ('PLATLIB/', ''), + ('SCRIPTS/', 'EGG-INFO/scripts/'), + ('DATA/lib/site-packages', ''), + ] + z = zipfile.ZipFile(exe_filename) + try: + for info in z.infolist(): + name = info.filename + parts = name.split('/') + if len(parts) == 3 and parts[2] == 'PKG-INFO': + if parts[1].endswith('.egg-info'): + prefixes.insert(0, ('/'.join(parts[:2]), 'EGG-INFO/')) + break + if len(parts) != 2 or not name.endswith('.pth'): + continue + if name.endswith('-nspkg.pth'): + continue + if parts[0].upper() in ('PURELIB', 'PLATLIB'): + contents = z.read(name).decode() + for pth in yield_lines(contents): + pth = pth.strip().replace('\\', '/') + if not pth.startswith('import'): + prefixes.append((('%s/%s/' % (parts[0], pth)), '')) + finally: + z.close() + prefixes = [(x.lower(), y) for x, y in prefixes] + prefixes.sort() + prefixes.reverse() + return prefixes + + +class PthDistributions(Environment): + """A .pth file with Distribution paths in it""" + + dirty = False + + def __init__(self, filename, sitedirs=()): + self.filename = filename + self.sitedirs = list(map(normalize_path, sitedirs)) + self.basedir = normalize_path(os.path.dirname(self.filename)) + self._load() + Environment.__init__(self, [], None, None) + for path in yield_lines(self.paths): + list(map(self.add, find_distributions(path, True))) + + def _load(self): + self.paths = [] + saw_import = False + seen = dict.fromkeys(self.sitedirs) + if os.path.isfile(self.filename): + f = open(self.filename, 'rt') + for line in f: + if line.startswith('import'): + saw_import = True + continue + path = line.rstrip() + self.paths.append(path) + if not path.strip() or path.strip().startswith('#'): + continue + # skip non-existent paths, in case somebody deleted a package + # manually, and duplicate paths as well + path = self.paths[-1] = normalize_path( + os.path.join(self.basedir, path) + ) + if not os.path.exists(path) or path in seen: + self.paths.pop() # skip it + self.dirty = True # we cleaned up, so we're dirty now :) + continue + seen[path] = 1 + f.close() + + if self.paths and not saw_import: + self.dirty = True # ensure anything we touch has import wrappers + while self.paths and not self.paths[-1].strip(): + self.paths.pop() + + def save(self): + """Write changed .pth file back to disk""" + if not self.dirty: + return + + rel_paths = list(map(self.make_relative, self.paths)) + if rel_paths: + log.debug("Saving %s", self.filename) + lines = self._wrap_lines(rel_paths) + data = '\n'.join(lines) + '\n' + + if os.path.islink(self.filename): + os.unlink(self.filename) + with open(self.filename, 'wt') as f: + f.write(data) + + elif os.path.exists(self.filename): + log.debug("Deleting empty %s", self.filename) + os.unlink(self.filename) + + self.dirty = False + + @staticmethod + def _wrap_lines(lines): + return lines + + def add(self, dist): + """Add `dist` to the distribution map""" + new_path = ( + dist.location not in self.paths and ( + dist.location not in self.sitedirs or + # account for '.' being in PYTHONPATH + dist.location == os.getcwd() + ) + ) + if new_path: + self.paths.append(dist.location) + self.dirty = True + Environment.add(self, dist) + + def remove(self, dist): + """Remove `dist` from the distribution map""" + while dist.location in self.paths: + self.paths.remove(dist.location) + self.dirty = True + Environment.remove(self, dist) + + def make_relative(self, path): + npath, last = os.path.split(normalize_path(path)) + baselen = len(self.basedir) + parts = [last] + sep = os.altsep == '/' and '/' or os.sep + while len(npath) >= baselen: + if npath == self.basedir: + parts.append(os.curdir) + parts.reverse() + return sep.join(parts) + npath, last = os.path.split(npath) + parts.append(last) + else: + return path + + +class RewritePthDistributions(PthDistributions): + @classmethod + def _wrap_lines(cls, lines): + yield cls.prelude + for line in lines: + yield line + yield cls.postlude + + prelude = _one_liner(""" + import sys + sys.__plen = len(sys.path) + """) + postlude = _one_liner(""" + import sys + new = sys.path[sys.__plen:] + del sys.path[sys.__plen:] + p = getattr(sys, '__egginsert', 0) + sys.path[p:p] = new + sys.__egginsert = p + len(new) + """) + + +if os.environ.get('SETUPTOOLS_SYS_PATH_TECHNIQUE', 'raw') == 'rewrite': + PthDistributions = RewritePthDistributions + + +def _first_line_re(): + """ + Return a regular expression based on first_line_re suitable for matching + strings. + """ + if isinstance(first_line_re.pattern, str): + return first_line_re + + # first_line_re in Python >=3.1.4 and >=3.2.1 is a bytes pattern. + return re.compile(first_line_re.pattern.decode()) + + +def auto_chmod(func, arg, exc): + if func in [os.unlink, os.remove] and os.name == 'nt': + chmod(arg, stat.S_IWRITE) + return func(arg) + et, ev, _ = sys.exc_info() + # TODO: This code doesn't make sense. What is it trying to do? + raise (ev[0], ev[1] + (" %s %s" % (func, arg))) + + +def update_dist_caches(dist_path, fix_zipimporter_caches): + """ + Fix any globally cached `dist_path` related data + + `dist_path` should be a path of a newly installed egg distribution (zipped + or unzipped). + + sys.path_importer_cache contains finder objects that have been cached when + importing data from the original distribution. Any such finders need to be + cleared since the replacement distribution might be packaged differently, + e.g. a zipped egg distribution might get replaced with an unzipped egg + folder or vice versa. Having the old finders cached may then cause Python + to attempt loading modules from the replacement distribution using an + incorrect loader. + + zipimport.zipimporter objects are Python loaders charged with importing + data packaged inside zip archives. If stale loaders referencing the + original distribution, are left behind, they can fail to load modules from + the replacement distribution. E.g. if an old zipimport.zipimporter instance + is used to load data from a new zipped egg archive, it may cause the + operation to attempt to locate the requested data in the wrong location - + one indicated by the original distribution's zip archive directory + information. Such an operation may then fail outright, e.g. report having + read a 'bad local file header', or even worse, it may fail silently & + return invalid data. + + zipimport._zip_directory_cache contains cached zip archive directory + information for all existing zipimport.zipimporter instances and all such + instances connected to the same archive share the same cached directory + information. + + If asked, and the underlying Python implementation allows it, we can fix + all existing zipimport.zipimporter instances instead of having to track + them down and remove them one by one, by updating their shared cached zip + archive directory information. This, of course, assumes that the + replacement distribution is packaged as a zipped egg. + + If not asked to fix existing zipimport.zipimporter instances, we still do + our best to clear any remaining zipimport.zipimporter related cached data + that might somehow later get used when attempting to load data from the new + distribution and thus cause such load operations to fail. Note that when + tracking down such remaining stale data, we can not catch every conceivable + usage from here, and we clear only those that we know of and have found to + cause problems if left alive. Any remaining caches should be updated by + whomever is in charge of maintaining them, i.e. they should be ready to + handle us replacing their zip archives with new distributions at runtime. + + """ + # There are several other known sources of stale zipimport.zipimporter + # instances that we do not clear here, but might if ever given a reason to + # do so: + # * Global setuptools pkg_resources.working_set (a.k.a. 'master working + # set') may contain distributions which may in turn contain their + # zipimport.zipimporter loaders. + # * Several zipimport.zipimporter loaders held by local variables further + # up the function call stack when running the setuptools installation. + # * Already loaded modules may have their __loader__ attribute set to the + # exact loader instance used when importing them. Python 3.4 docs state + # that this information is intended mostly for introspection and so is + # not expected to cause us problems. + normalized_path = normalize_path(dist_path) + _uncache(normalized_path, sys.path_importer_cache) + if fix_zipimporter_caches: + _replace_zip_directory_cache_data(normalized_path) + else: + # Here, even though we do not want to fix existing and now stale + # zipimporter cache information, we still want to remove it. Related to + # Python's zip archive directory information cache, we clear each of + # its stale entries in two phases: + # 1. Clear the entry so attempting to access zip archive information + # via any existing stale zipimport.zipimporter instances fails. + # 2. Remove the entry from the cache so any newly constructed + # zipimport.zipimporter instances do not end up using old stale + # zip archive directory information. + # This whole stale data removal step does not seem strictly necessary, + # but has been left in because it was done before we started replacing + # the zip archive directory information cache content if possible, and + # there are no relevant unit tests that we can depend on to tell us if + # this is really needed. + _remove_and_clear_zip_directory_cache_data(normalized_path) + + +def _collect_zipimporter_cache_entries(normalized_path, cache): + """ + Return zipimporter cache entry keys related to a given normalized path. + + Alternative path spellings (e.g. those using different character case or + those using alternative path separators) related to the same path are + included. Any sub-path entries are included as well, i.e. those + corresponding to zip archives embedded in other zip archives. + + """ + result = [] + prefix_len = len(normalized_path) + for p in cache: + np = normalize_path(p) + if (np.startswith(normalized_path) and + np[prefix_len:prefix_len + 1] in (os.sep, '')): + result.append(p) + return result + + +def _update_zipimporter_cache(normalized_path, cache, updater=None): + """ + Update zipimporter cache data for a given normalized path. + + Any sub-path entries are processed as well, i.e. those corresponding to zip + archives embedded in other zip archives. + + Given updater is a callable taking a cache entry key and the original entry + (after already removing the entry from the cache), and expected to update + the entry and possibly return a new one to be inserted in its place. + Returning None indicates that the entry should not be replaced with a new + one. If no updater is given, the cache entries are simply removed without + any additional processing, the same as if the updater simply returned None. + + """ + for p in _collect_zipimporter_cache_entries(normalized_path, cache): + # N.B. pypy's custom zipimport._zip_directory_cache implementation does + # not support the complete dict interface: + # * Does not support item assignment, thus not allowing this function + # to be used only for removing existing cache entries. + # * Does not support the dict.pop() method, forcing us to use the + # get/del patterns instead. For more detailed information see the + # following links: + # https://github.com/pypa/setuptools/issues/202#issuecomment-202913420 + # http://bit.ly/2h9itJX + old_entry = cache[p] + del cache[p] + new_entry = updater and updater(p, old_entry) + if new_entry is not None: + cache[p] = new_entry + + +def _uncache(normalized_path, cache): + _update_zipimporter_cache(normalized_path, cache) + + +def _remove_and_clear_zip_directory_cache_data(normalized_path): + def clear_and_remove_cached_zip_archive_directory_data(path, old_entry): + old_entry.clear() + + _update_zipimporter_cache( + normalized_path, zipimport._zip_directory_cache, + updater=clear_and_remove_cached_zip_archive_directory_data) + + +# PyPy Python implementation does not allow directly writing to the +# zipimport._zip_directory_cache and so prevents us from attempting to correct +# its content. The best we can do there is clear the problematic cache content +# and have PyPy repopulate it as needed. The downside is that if there are any +# stale zipimport.zipimporter instances laying around, attempting to use them +# will fail due to not having its zip archive directory information available +# instead of being automatically corrected to use the new correct zip archive +# directory information. +if '__pypy__' in sys.builtin_module_names: + _replace_zip_directory_cache_data = \ + _remove_and_clear_zip_directory_cache_data +else: + + def _replace_zip_directory_cache_data(normalized_path): + def replace_cached_zip_archive_directory_data(path, old_entry): + # N.B. In theory, we could load the zip directory information just + # once for all updated path spellings, and then copy it locally and + # update its contained path strings to contain the correct + # spelling, but that seems like a way too invasive move (this cache + # structure is not officially documented anywhere and could in + # theory change with new Python releases) for no significant + # benefit. + old_entry.clear() + zipimport.zipimporter(path) + old_entry.update(zipimport._zip_directory_cache[path]) + return old_entry + + _update_zipimporter_cache( + normalized_path, zipimport._zip_directory_cache, + updater=replace_cached_zip_archive_directory_data) + + +def is_python(text, filename=''): + "Is this string a valid Python script?" + try: + compile(text, filename, 'exec') + except (SyntaxError, TypeError): + return False + else: + return True + + +def is_sh(executable): + """Determine if the specified executable is a .sh (contains a #! line)""" + try: + with io.open(executable, encoding='latin-1') as fp: + magic = fp.read(2) + except (OSError, IOError): + return executable + return magic == '#!' + + +def nt_quote_arg(arg): + """Quote a command line argument according to Windows parsing rules""" + return subprocess.list2cmdline([arg]) + + +def is_python_script(script_text, filename): + """Is this text, as a whole, a Python script? (as opposed to shell/bat/etc. + """ + if filename.endswith('.py') or filename.endswith('.pyw'): + return True # extension says it's Python + if is_python(script_text, filename): + return True # it's syntactically valid Python + if script_text.startswith('#!'): + # It begins with a '#!' line, so check if 'python' is in it somewhere + return 'python' in script_text.splitlines()[0].lower() + + return False # Not any Python I can recognize + + +try: + from os import chmod as _chmod +except ImportError: + # Jython compatibility + def _chmod(*args): + pass + + +def chmod(path, mode): + log.debug("changing mode of %s to %o", path, mode) + try: + _chmod(path, mode) + except os.error as e: + log.debug("chmod failed: %s", e) + + +class CommandSpec(list): + """ + A command spec for a #! header, specified as a list of arguments akin to + those passed to Popen. + """ + + options = [] + split_args = dict() + + @classmethod + def best(cls): + """ + Choose the best CommandSpec class based on environmental conditions. + """ + return cls + + @classmethod + def _sys_executable(cls): + _default = os.path.normpath(sys.executable) + return os.environ.get('__PYVENV_LAUNCHER__', _default) + + @classmethod + def from_param(cls, param): + """ + Construct a CommandSpec from a parameter to build_scripts, which may + be None. + """ + if isinstance(param, cls): + return param + if isinstance(param, list): + return cls(param) + if param is None: + return cls.from_environment() + # otherwise, assume it's a string. + return cls.from_string(param) + + @classmethod + def from_environment(cls): + return cls([cls._sys_executable()]) + + @classmethod + def from_string(cls, string): + """ + Construct a command spec from a simple string representing a command + line parseable by shlex.split. + """ + items = shlex.split(string, **cls.split_args) + return cls(items) + + def install_options(self, script_text): + self.options = shlex.split(self._extract_options(script_text)) + cmdline = subprocess.list2cmdline(self) + if not isascii(cmdline): + self.options[:0] = ['-x'] + + @staticmethod + def _extract_options(orig_script): + """ + Extract any options from the first line of the script. + """ + first = (orig_script + '\n').splitlines()[0] + match = _first_line_re().match(first) + options = match.group(1) or '' if match else '' + return options.strip() + + def as_header(self): + return self._render(self + list(self.options)) + + @staticmethod + def _strip_quotes(item): + _QUOTES = '"\'' + for q in _QUOTES: + if item.startswith(q) and item.endswith(q): + return item[1:-1] + return item + + @staticmethod + def _render(items): + cmdline = subprocess.list2cmdline( + CommandSpec._strip_quotes(item.strip()) for item in items) + return '#!' + cmdline + '\n' + + +# For pbr compat; will be removed in a future version. +sys_executable = CommandSpec._sys_executable() + + +class WindowsCommandSpec(CommandSpec): + split_args = dict(posix=False) + + +class ScriptWriter: + """ + Encapsulates behavior around writing entry point scripts for console and + gui apps. + """ + + template = textwrap.dedent(r""" + # EASY-INSTALL-ENTRY-SCRIPT: %(spec)r,%(group)r,%(name)r + import re + import sys + + # for compatibility with easy_install; see #2198 + __requires__ = %(spec)r + + try: + from importlib.metadata import distribution + except ImportError: + try: + from importlib_metadata import distribution + except ImportError: + from pkg_resources import load_entry_point + + + def importlib_load_entry_point(spec, group, name): + dist_name, _, _ = spec.partition('==') + matches = ( + entry_point + for entry_point in distribution(dist_name).entry_points + if entry_point.group == group and entry_point.name == name + ) + return next(matches).load() + + + globals().setdefault('load_entry_point', importlib_load_entry_point) + + + if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(load_entry_point(%(spec)r, %(group)r, %(name)r)()) + """).lstrip() + + command_spec_class = CommandSpec + + @classmethod + def get_script_args(cls, dist, executable=None, wininst=False): + # for backward compatibility + warnings.warn("Use get_args", EasyInstallDeprecationWarning) + writer = (WindowsScriptWriter if wininst else ScriptWriter).best() + header = cls.get_script_header("", executable, wininst) + return writer.get_args(dist, header) + + @classmethod + def get_script_header(cls, script_text, executable=None, wininst=False): + # for backward compatibility + warnings.warn( + "Use get_header", EasyInstallDeprecationWarning, stacklevel=2) + if wininst: + executable = "python.exe" + return cls.get_header(script_text, executable) + + @classmethod + def get_args(cls, dist, header=None): + """ + Yield write_script() argument tuples for a distribution's + console_scripts and gui_scripts entry points. + """ + if header is None: + header = cls.get_header() + spec = str(dist.as_requirement()) + for type_ in 'console', 'gui': + group = type_ + '_scripts' + for name, ep in dist.get_entry_map(group).items(): + cls._ensure_safe_name(name) + script_text = cls.template % locals() + args = cls._get_script_args(type_, name, header, script_text) + for res in args: + yield res + + @staticmethod + def _ensure_safe_name(name): + """ + Prevent paths in *_scripts entry point names. + """ + has_path_sep = re.search(r'[\\/]', name) + if has_path_sep: + raise ValueError("Path separators not allowed in script names") + + @classmethod + def get_writer(cls, force_windows): + # for backward compatibility + warnings.warn("Use best", EasyInstallDeprecationWarning) + return WindowsScriptWriter.best() if force_windows else cls.best() + + @classmethod + def best(cls): + """ + Select the best ScriptWriter for this environment. + """ + if sys.platform == 'win32' or (os.name == 'java' and os._name == 'nt'): + return WindowsScriptWriter.best() + else: + return cls + + @classmethod + def _get_script_args(cls, type_, name, header, script_text): + # Simply write the stub with no extension. + yield (name, header + script_text) + + @classmethod + def get_header(cls, script_text="", executable=None): + """Create a #! line, getting options (if any) from script_text""" + cmd = cls.command_spec_class.best().from_param(executable) + cmd.install_options(script_text) + return cmd.as_header() + + +class WindowsScriptWriter(ScriptWriter): + command_spec_class = WindowsCommandSpec + + @classmethod + def get_writer(cls): + # for backward compatibility + warnings.warn("Use best", EasyInstallDeprecationWarning) + return cls.best() + + @classmethod + def best(cls): + """ + Select the best ScriptWriter suitable for Windows + """ + writer_lookup = dict( + executable=WindowsExecutableLauncherWriter, + natural=cls, + ) + # for compatibility, use the executable launcher by default + launcher = os.environ.get('SETUPTOOLS_LAUNCHER', 'executable') + return writer_lookup[launcher] + + @classmethod + def _get_script_args(cls, type_, name, header, script_text): + "For Windows, add a .py extension" + ext = dict(console='.pya', gui='.pyw')[type_] + if ext not in os.environ['PATHEXT'].lower().split(';'): + msg = ( + "{ext} not listed in PATHEXT; scripts will not be " + "recognized as executables." + ).format(**locals()) + warnings.warn(msg, UserWarning) + old = ['.pya', '.py', '-script.py', '.pyc', '.pyo', '.pyw', '.exe'] + old.remove(ext) + header = cls._adjust_header(type_, header) + blockers = [name + x for x in old] + yield name + ext, header + script_text, 't', blockers + + @classmethod + def _adjust_header(cls, type_, orig_header): + """ + Make sure 'pythonw' is used for gui and 'python' is used for + console (regardless of what sys.executable is). + """ + pattern = 'pythonw.exe' + repl = 'python.exe' + if type_ == 'gui': + pattern, repl = repl, pattern + pattern_ob = re.compile(re.escape(pattern), re.IGNORECASE) + new_header = pattern_ob.sub(string=orig_header, repl=repl) + return new_header if cls._use_header(new_header) else orig_header + + @staticmethod + def _use_header(new_header): + """ + Should _adjust_header use the replaced header? + + On non-windows systems, always use. On + Windows systems, only use the replaced header if it resolves + to an executable on the system. + """ + clean_header = new_header[2:-1].strip('"') + return sys.platform != 'win32' or find_executable(clean_header) + + +class WindowsExecutableLauncherWriter(WindowsScriptWriter): + @classmethod + def _get_script_args(cls, type_, name, header, script_text): + """ + For Windows, add a .py extension and an .exe launcher + """ + if type_ == 'gui': + launcher_type = 'gui' + ext = '-script.pyw' + old = ['.pyw'] + else: + launcher_type = 'cli' + ext = '-script.py' + old = ['.py', '.pyc', '.pyo'] + hdr = cls._adjust_header(type_, header) + blockers = [name + x for x in old] + yield (name + ext, hdr + script_text, 't', blockers) + yield ( + name + '.exe', get_win_launcher(launcher_type), + 'b' # write in binary mode + ) + if not is_64bit(): + # install a manifest for the launcher to prevent Windows + # from detecting it as an installer (which it will for + # launchers like easy_install.exe). Consider only + # adding a manifest for launchers detected as installers. + # See Distribute #143 for details. + m_name = name + '.exe.manifest' + yield (m_name, load_launcher_manifest(name), 't') + + +# for backward-compatibility +get_script_args = ScriptWriter.get_script_args +get_script_header = ScriptWriter.get_script_header + + +def get_win_launcher(type): + """ + Load the Windows launcher (executable) suitable for launching a script. + + `type` should be either 'cli' or 'gui' + + Returns the executable as a byte string. + """ + launcher_fn = '%s.exe' % type + if is_64bit(): + if get_platform() == "win-arm64": + launcher_fn = launcher_fn.replace(".", "-arm64.") + else: + launcher_fn = launcher_fn.replace(".", "-64.") + else: + launcher_fn = launcher_fn.replace(".", "-32.") + return resource_string('setuptools', launcher_fn) + + +def load_launcher_manifest(name): + manifest = pkg_resources.resource_string(__name__, 'launcher manifest.xml') + return manifest.decode('utf-8') % vars() + + +def rmtree(path, ignore_errors=False, onerror=auto_chmod): + return shutil.rmtree(path, ignore_errors, onerror) + + +def current_umask(): + tmp = os.umask(0o022) + os.umask(tmp) + return tmp + + +class EasyInstallDeprecationWarning(SetuptoolsDeprecationWarning): + """ + Warning for EasyInstall deprecations, bypassing suppression. + """ diff --git a/venv/lib/python3.10/site-packages/setuptools/command/egg_info.py b/venv/lib/python3.10/site-packages/setuptools/command/egg_info.py new file mode 100644 index 00000000..37f59a2b --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/command/egg_info.py @@ -0,0 +1,755 @@ +"""setuptools.command.egg_info + +Create a distribution's .egg-info directory and contents""" + +from distutils.filelist import FileList as _FileList +from distutils.errors import DistutilsInternalError +from distutils.util import convert_path +from distutils import log +import distutils.errors +import distutils.filelist +import functools +import os +import re +import sys +import io +import warnings +import time +import collections + +from setuptools import Command +from setuptools.command.sdist import sdist +from setuptools.command.sdist import walk_revctrl +from setuptools.command.setopt import edit_config +from setuptools.command import bdist_egg +from pkg_resources import ( + parse_requirements, safe_name, parse_version, + safe_version, yield_lines, EntryPoint, iter_entry_points, to_filename) +import setuptools.unicode_utils as unicode_utils +from setuptools.glob import glob + +from setuptools.extern import packaging +from setuptools import SetuptoolsDeprecationWarning + + +def translate_pattern(glob): # noqa: C901 # is too complex (14) # FIXME + """ + Translate a file path glob like '*.txt' in to a regular expression. + This differs from fnmatch.translate which allows wildcards to match + directory separators. It also knows about '**/' which matches any number of + directories. + """ + pat = '' + + # This will split on '/' within [character classes]. This is deliberate. + chunks = glob.split(os.path.sep) + + sep = re.escape(os.sep) + valid_char = '[^%s]' % (sep,) + + for c, chunk in enumerate(chunks): + last_chunk = c == len(chunks) - 1 + + # Chunks that are a literal ** are globstars. They match anything. + if chunk == '**': + if last_chunk: + # Match anything if this is the last component + pat += '.*' + else: + # Match '(name/)*' + pat += '(?:%s+%s)*' % (valid_char, sep) + continue # Break here as the whole path component has been handled + + # Find any special characters in the remainder + i = 0 + chunk_len = len(chunk) + while i < chunk_len: + char = chunk[i] + if char == '*': + # Match any number of name characters + pat += valid_char + '*' + elif char == '?': + # Match a name character + pat += valid_char + elif char == '[': + # Character class + inner_i = i + 1 + # Skip initial !/] chars + if inner_i < chunk_len and chunk[inner_i] == '!': + inner_i = inner_i + 1 + if inner_i < chunk_len and chunk[inner_i] == ']': + inner_i = inner_i + 1 + + # Loop till the closing ] is found + while inner_i < chunk_len and chunk[inner_i] != ']': + inner_i = inner_i + 1 + + if inner_i >= chunk_len: + # Got to the end of the string without finding a closing ] + # Do not treat this as a matching group, but as a literal [ + pat += re.escape(char) + else: + # Grab the insides of the [brackets] + inner = chunk[i + 1:inner_i] + char_class = '' + + # Class negation + if inner[0] == '!': + char_class = '^' + inner = inner[1:] + + char_class += re.escape(inner) + pat += '[%s]' % (char_class,) + + # Skip to the end ] + i = inner_i + else: + pat += re.escape(char) + i += 1 + + # Join each chunk with the dir separator + if not last_chunk: + pat += sep + + pat += r'\Z' + return re.compile(pat, flags=re.MULTILINE | re.DOTALL) + + +class InfoCommon: + tag_build = None + tag_date = None + + @property + def name(self): + return safe_name(self.distribution.get_name()) + + def tagged_version(self): + return safe_version(self._maybe_tag(self.distribution.get_version())) + + def _maybe_tag(self, version): + """ + egg_info may be called more than once for a distribution, + in which case the version string already contains all tags. + """ + return ( + version if self.vtags and version.endswith(self.vtags) + else version + self.vtags + ) + + def tags(self): + version = '' + if self.tag_build: + version += self.tag_build + if self.tag_date: + version += time.strftime("-%Y%m%d") + return version + vtags = property(tags) + + +class egg_info(InfoCommon, Command): + description = "create a distribution's .egg-info directory" + + user_options = [ + ('egg-base=', 'e', "directory containing .egg-info directories" + " (default: top of the source tree)"), + ('tag-date', 'd', "Add date stamp (e.g. 20050528) to version number"), + ('tag-build=', 'b', "Specify explicit tag to add to version number"), + ('no-date', 'D', "Don't include date stamp [default]"), + ] + + boolean_options = ['tag-date'] + negative_opt = { + 'no-date': 'tag-date', + } + + def initialize_options(self): + self.egg_base = None + self.egg_name = None + self.egg_info = None + self.egg_version = None + self.broken_egg_info = False + + #################################### + # allow the 'tag_svn_revision' to be detected and + # set, supporting sdists built on older Setuptools. + @property + def tag_svn_revision(self): + pass + + @tag_svn_revision.setter + def tag_svn_revision(self, value): + pass + #################################### + + def save_version_info(self, filename): + """ + Materialize the value of date into the + build tag. Install build keys in a deterministic order + to avoid arbitrary reordering on subsequent builds. + """ + egg_info = collections.OrderedDict() + # follow the order these keys would have been added + # when PYTHONHASHSEED=0 + egg_info['tag_build'] = self.tags() + egg_info['tag_date'] = 0 + edit_config(filename, dict(egg_info=egg_info)) + + def finalize_options(self): + # Note: we need to capture the current value returned + # by `self.tagged_version()`, so we can later update + # `self.distribution.metadata.version` without + # repercussions. + self.egg_name = self.name + self.egg_version = self.tagged_version() + parsed_version = parse_version(self.egg_version) + + try: + is_version = isinstance(parsed_version, packaging.version.Version) + spec = ( + "%s==%s" if is_version else "%s===%s" + ) + list( + parse_requirements(spec % (self.egg_name, self.egg_version)) + ) + except ValueError as e: + raise distutils.errors.DistutilsOptionError( + "Invalid distribution name or version syntax: %s-%s" % + (self.egg_name, self.egg_version) + ) from e + + if self.egg_base is None: + dirs = self.distribution.package_dir + self.egg_base = (dirs or {}).get('', os.curdir) + + self.ensure_dirname('egg_base') + self.egg_info = to_filename(self.egg_name) + '.egg-info' + if self.egg_base != os.curdir: + self.egg_info = os.path.join(self.egg_base, self.egg_info) + if '-' in self.egg_name: + self.check_broken_egg_info() + + # Set package version for the benefit of dumber commands + # (e.g. sdist, bdist_wininst, etc.) + # + self.distribution.metadata.version = self.egg_version + + # If we bootstrapped around the lack of a PKG-INFO, as might be the + # case in a fresh checkout, make sure that any special tags get added + # to the version info + # + pd = self.distribution._patched_dist + if pd is not None and pd.key == self.egg_name.lower(): + pd._version = self.egg_version + pd._parsed_version = parse_version(self.egg_version) + self.distribution._patched_dist = None + + def write_or_delete_file(self, what, filename, data, force=False): + """Write `data` to `filename` or delete if empty + + If `data` is non-empty, this routine is the same as ``write_file()``. + If `data` is empty but not ``None``, this is the same as calling + ``delete_file(filename)`. If `data` is ``None``, then this is a no-op + unless `filename` exists, in which case a warning is issued about the + orphaned file (if `force` is false), or deleted (if `force` is true). + """ + if data: + self.write_file(what, filename, data) + elif os.path.exists(filename): + if data is None and not force: + log.warn( + "%s not set in setup(), but %s exists", what, filename + ) + return + else: + self.delete_file(filename) + + def write_file(self, what, filename, data): + """Write `data` to `filename` (if not a dry run) after announcing it + + `what` is used in a log message to identify what is being written + to the file. + """ + log.info("writing %s to %s", what, filename) + data = data.encode("utf-8") + if not self.dry_run: + f = open(filename, 'wb') + f.write(data) + f.close() + + def delete_file(self, filename): + """Delete `filename` (if not a dry run) after announcing it""" + log.info("deleting %s", filename) + if not self.dry_run: + os.unlink(filename) + + def run(self): + self.mkpath(self.egg_info) + os.utime(self.egg_info, None) + installer = self.distribution.fetch_build_egg + for ep in iter_entry_points('egg_info.writers'): + ep.require(installer=installer) + writer = ep.resolve() + writer(self, ep.name, os.path.join(self.egg_info, ep.name)) + + # Get rid of native_libs.txt if it was put there by older bdist_egg + nl = os.path.join(self.egg_info, "native_libs.txt") + if os.path.exists(nl): + self.delete_file(nl) + + self.find_sources() + + def find_sources(self): + """Generate SOURCES.txt manifest file""" + manifest_filename = os.path.join(self.egg_info, "SOURCES.txt") + mm = manifest_maker(self.distribution) + mm.manifest = manifest_filename + mm.run() + self.filelist = mm.filelist + + def check_broken_egg_info(self): + bei = self.egg_name + '.egg-info' + if self.egg_base != os.curdir: + bei = os.path.join(self.egg_base, bei) + if os.path.exists(bei): + log.warn( + "-" * 78 + '\n' + "Note: Your current .egg-info directory has a '-' in its name;" + '\nthis will not work correctly with "setup.py develop".\n\n' + 'Please rename %s to %s to correct this problem.\n' + '-' * 78, + bei, self.egg_info + ) + self.broken_egg_info = self.egg_info + self.egg_info = bei # make it work for now + + +class FileList(_FileList): + # Implementations of the various MANIFEST.in commands + + def process_template_line(self, line): + # Parse the line: split it up, make sure the right number of words + # is there, and return the relevant words. 'action' is always + # defined: it's the first word of the line. Which of the other + # three are defined depends on the action; it'll be either + # patterns, (dir and patterns), or (dir_pattern). + (action, patterns, dir, dir_pattern) = self._parse_template_line(line) + + action_map = { + 'include': self.include, + 'exclude': self.exclude, + 'global-include': self.global_include, + 'global-exclude': self.global_exclude, + 'recursive-include': functools.partial( + self.recursive_include, dir, + ), + 'recursive-exclude': functools.partial( + self.recursive_exclude, dir, + ), + 'graft': self.graft, + 'prune': self.prune, + } + log_map = { + 'include': "warning: no files found matching '%s'", + 'exclude': ( + "warning: no previously-included files found " + "matching '%s'" + ), + 'global-include': ( + "warning: no files found matching '%s' " + "anywhere in distribution" + ), + 'global-exclude': ( + "warning: no previously-included files matching " + "'%s' found anywhere in distribution" + ), + 'recursive-include': ( + "warning: no files found matching '%s' " + "under directory '%s'" + ), + 'recursive-exclude': ( + "warning: no previously-included files matching " + "'%s' found under directory '%s'" + ), + 'graft': "warning: no directories found matching '%s'", + 'prune': "no previously-included directories found matching '%s'", + } + + try: + process_action = action_map[action] + except KeyError: + raise DistutilsInternalError( + "this cannot happen: invalid action '{action!s}'". + format(action=action), + ) + + # OK, now we know that the action is valid and we have the + # right number of words on the line for that action -- so we + # can proceed with minimal error-checking. + + action_is_recursive = action.startswith('recursive-') + if action in {'graft', 'prune'}: + patterns = [dir_pattern] + extra_log_args = (dir, ) if action_is_recursive else () + log_tmpl = log_map[action] + + self.debug_print( + ' '.join( + [action] + + ([dir] if action_is_recursive else []) + + patterns, + ) + ) + for pattern in patterns: + if not process_action(pattern): + log.warn(log_tmpl, pattern, *extra_log_args) + + def _remove_files(self, predicate): + """ + Remove all files from the file list that match the predicate. + Return True if any matching files were removed + """ + found = False + for i in range(len(self.files) - 1, -1, -1): + if predicate(self.files[i]): + self.debug_print(" removing " + self.files[i]) + del self.files[i] + found = True + return found + + def include(self, pattern): + """Include files that match 'pattern'.""" + found = [f for f in glob(pattern) if not os.path.isdir(f)] + self.extend(found) + return bool(found) + + def exclude(self, pattern): + """Exclude files that match 'pattern'.""" + match = translate_pattern(pattern) + return self._remove_files(match.match) + + def recursive_include(self, dir, pattern): + """ + Include all files anywhere in 'dir/' that match the pattern. + """ + full_pattern = os.path.join(dir, '**', pattern) + found = [f for f in glob(full_pattern, recursive=True) + if not os.path.isdir(f)] + self.extend(found) + return bool(found) + + def recursive_exclude(self, dir, pattern): + """ + Exclude any file anywhere in 'dir/' that match the pattern. + """ + match = translate_pattern(os.path.join(dir, '**', pattern)) + return self._remove_files(match.match) + + def graft(self, dir): + """Include all files from 'dir/'.""" + found = [ + item + for match_dir in glob(dir) + for item in distutils.filelist.findall(match_dir) + ] + self.extend(found) + return bool(found) + + def prune(self, dir): + """Filter out files from 'dir/'.""" + match = translate_pattern(os.path.join(dir, '**')) + return self._remove_files(match.match) + + def global_include(self, pattern): + """ + Include all files anywhere in the current directory that match the + pattern. This is very inefficient on large file trees. + """ + if self.allfiles is None: + self.findall() + match = translate_pattern(os.path.join('**', pattern)) + found = [f for f in self.allfiles if match.match(f)] + self.extend(found) + return bool(found) + + def global_exclude(self, pattern): + """ + Exclude all files anywhere that match the pattern. + """ + match = translate_pattern(os.path.join('**', pattern)) + return self._remove_files(match.match) + + def append(self, item): + if item.endswith('\r'): # Fix older sdists built on Windows + item = item[:-1] + path = convert_path(item) + + if self._safe_path(path): + self.files.append(path) + + def extend(self, paths): + self.files.extend(filter(self._safe_path, paths)) + + def _repair(self): + """ + Replace self.files with only safe paths + + Because some owners of FileList manipulate the underlying + ``files`` attribute directly, this method must be called to + repair those paths. + """ + self.files = list(filter(self._safe_path, self.files)) + + def _safe_path(self, path): + enc_warn = "'%s' not %s encodable -- skipping" + + # To avoid accidental trans-codings errors, first to unicode + u_path = unicode_utils.filesys_decode(path) + if u_path is None: + log.warn("'%s' in unexpected encoding -- skipping" % path) + return False + + # Must ensure utf-8 encodability + utf8_path = unicode_utils.try_encode(u_path, "utf-8") + if utf8_path is None: + log.warn(enc_warn, path, 'utf-8') + return False + + try: + # accept is either way checks out + if os.path.exists(u_path) or os.path.exists(utf8_path): + return True + # this will catch any encode errors decoding u_path + except UnicodeEncodeError: + log.warn(enc_warn, path, sys.getfilesystemencoding()) + + +class manifest_maker(sdist): + template = "MANIFEST.in" + + def initialize_options(self): + self.use_defaults = 1 + self.prune = 1 + self.manifest_only = 1 + self.force_manifest = 1 + + def finalize_options(self): + pass + + def run(self): + self.filelist = FileList() + if not os.path.exists(self.manifest): + self.write_manifest() # it must exist so it'll get in the list + self.add_defaults() + if os.path.exists(self.template): + self.read_template() + self.add_license_files() + self.prune_file_list() + self.filelist.sort() + self.filelist.remove_duplicates() + self.write_manifest() + + def _manifest_normalize(self, path): + path = unicode_utils.filesys_decode(path) + return path.replace(os.sep, '/') + + def write_manifest(self): + """ + Write the file list in 'self.filelist' to the manifest file + named by 'self.manifest'. + """ + self.filelist._repair() + + # Now _repairs should encodability, but not unicode + files = [self._manifest_normalize(f) for f in self.filelist.files] + msg = "writing manifest file '%s'" % self.manifest + self.execute(write_file, (self.manifest, files), msg) + + def warn(self, msg): + if not self._should_suppress_warning(msg): + sdist.warn(self, msg) + + @staticmethod + def _should_suppress_warning(msg): + """ + suppress missing-file warnings from sdist + """ + return re.match(r"standard file .*not found", msg) + + def add_defaults(self): + sdist.add_defaults(self) + self.filelist.append(self.template) + self.filelist.append(self.manifest) + rcfiles = list(walk_revctrl()) + if rcfiles: + self.filelist.extend(rcfiles) + elif os.path.exists(self.manifest): + self.read_manifest() + + if os.path.exists("setup.py"): + # setup.py should be included by default, even if it's not + # the script called to create the sdist + self.filelist.append("setup.py") + + ei_cmd = self.get_finalized_command('egg_info') + self.filelist.graft(ei_cmd.egg_info) + + def add_license_files(self): + license_files = self.distribution.metadata.license_files or [] + for lf in license_files: + log.info("adding license file '%s'", lf) + pass + self.filelist.extend(license_files) + + def prune_file_list(self): + build = self.get_finalized_command('build') + base_dir = self.distribution.get_fullname() + self.filelist.prune(build.build_base) + self.filelist.prune(base_dir) + sep = re.escape(os.sep) + self.filelist.exclude_pattern(r'(^|' + sep + r')(RCS|CVS|\.svn)' + sep, + is_regex=1) + + def _safe_data_files(self, build_py): + """ + The parent class implementation of this method + (``sdist``) will try to include data files, which + might cause recursion problems when + ``include_package_data=True``. + + Therefore, avoid triggering any attempt of + analyzing/building the manifest again. + """ + if hasattr(build_py, 'get_data_files_without_manifest'): + return build_py.get_data_files_without_manifest() + + warnings.warn( + "Custom 'build_py' does not implement " + "'get_data_files_without_manifest'.\nPlease extend command classes" + " from setuptools instead of distutils.", + SetuptoolsDeprecationWarning + ) + return build_py.get_data_files() + + +def write_file(filename, contents): + """Create a file with the specified name and write 'contents' (a + sequence of strings without line terminators) to it. + """ + contents = "\n".join(contents) + + # assuming the contents has been vetted for utf-8 encoding + contents = contents.encode("utf-8") + + with open(filename, "wb") as f: # always write POSIX-style manifest + f.write(contents) + + +def write_pkg_info(cmd, basename, filename): + log.info("writing %s", filename) + if not cmd.dry_run: + metadata = cmd.distribution.metadata + metadata.version, oldver = cmd.egg_version, metadata.version + metadata.name, oldname = cmd.egg_name, metadata.name + + try: + # write unescaped data to PKG-INFO, so older pkg_resources + # can still parse it + metadata.write_pkg_info(cmd.egg_info) + finally: + metadata.name, metadata.version = oldname, oldver + + safe = getattr(cmd.distribution, 'zip_safe', None) + + bdist_egg.write_safety_flag(cmd.egg_info, safe) + + +def warn_depends_obsolete(cmd, basename, filename): + if os.path.exists(filename): + log.warn( + "WARNING: 'depends.txt' is not used by setuptools 0.6!\n" + "Use the install_requires/extras_require setup() args instead." + ) + + +def _write_requirements(stream, reqs): + lines = yield_lines(reqs or ()) + + def append_cr(line): + return line + '\n' + lines = map(append_cr, sorted(lines)) + stream.writelines(lines) + + +def write_requirements(cmd, basename, filename): + dist = cmd.distribution + data = io.StringIO() + _write_requirements(data, dist.install_requires) + extras_require = dist.extras_require or {} + for extra in sorted(extras_require): + data.write('\n[{extra}]\n'.format(**vars())) + _write_requirements(data, extras_require[extra]) + cmd.write_or_delete_file("requirements", filename, data.getvalue()) + + +def write_setup_requirements(cmd, basename, filename): + data = io.StringIO() + _write_requirements(data, cmd.distribution.setup_requires) + cmd.write_or_delete_file("setup-requirements", filename, data.getvalue()) + + +def write_toplevel_names(cmd, basename, filename): + pkgs = dict.fromkeys( + [ + k.split('.', 1)[0] + for k in cmd.distribution.iter_distribution_names() + ] + ) + cmd.write_file("top-level names", filename, '\n'.join(sorted(pkgs)) + '\n') + + +def overwrite_arg(cmd, basename, filename): + write_arg(cmd, basename, filename, True) + + +def write_arg(cmd, basename, filename, force=False): + argname = os.path.splitext(basename)[0] + value = getattr(cmd.distribution, argname, None) + if value is not None: + value = '\n'.join(value) + '\n' + cmd.write_or_delete_file(argname, filename, value, force) + + +def write_entries(cmd, basename, filename): + ep = cmd.distribution.entry_points + + if isinstance(ep, str) or ep is None: + data = ep + elif ep is not None: + data = [] + for section, contents in sorted(ep.items()): + if not isinstance(contents, str): + contents = EntryPoint.parse_group(section, contents) + contents = '\n'.join(sorted(map(str, contents.values()))) + data.append('[%s]\n%s\n\n' % (section, contents)) + data = ''.join(data) + + cmd.write_or_delete_file('entry points', filename, data, True) + + +def get_pkg_info_revision(): + """ + Get a -r### off of PKG-INFO Version in case this is an sdist of + a subversion revision. + """ + warnings.warn( + "get_pkg_info_revision is deprecated.", EggInfoDeprecationWarning) + if os.path.exists('PKG-INFO'): + with io.open('PKG-INFO') as f: + for line in f: + match = re.match(r"Version:.*-r(\d+)\s*$", line) + if match: + return int(match.group(1)) + return 0 + + +class EggInfoDeprecationWarning(SetuptoolsDeprecationWarning): + """Deprecated behavior warning for EggInfo, bypassing suppression.""" diff --git a/venv/lib/python3.10/site-packages/setuptools/command/install.py b/venv/lib/python3.10/site-packages/setuptools/command/install.py new file mode 100644 index 00000000..35e54d20 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/command/install.py @@ -0,0 +1,132 @@ +from distutils.errors import DistutilsArgError +import inspect +import glob +import warnings +import platform +import distutils.command.install as orig + +import setuptools + +# Prior to numpy 1.9, NumPy relies on the '_install' name, so provide it for +# now. See https://github.com/pypa/setuptools/issues/199/ +_install = orig.install + + +class install(orig.install): + """Use easy_install to install the package, w/dependencies""" + + user_options = orig.install.user_options + [ + ('old-and-unmanageable', None, "Try not to use this!"), + ('single-version-externally-managed', None, + "used by system package builders to create 'flat' eggs"), + ] + boolean_options = orig.install.boolean_options + [ + 'old-and-unmanageable', 'single-version-externally-managed', + ] + new_commands = [ + ('install_egg_info', lambda self: True), + ('install_scripts', lambda self: True), + ] + _nc = dict(new_commands) + + def initialize_options(self): + + warnings.warn( + "setup.py install is deprecated. " + "Use build and pip and other standards-based tools.", + setuptools.SetuptoolsDeprecationWarning, + ) + + orig.install.initialize_options(self) + self.old_and_unmanageable = None + self.single_version_externally_managed = None + + def finalize_options(self): + orig.install.finalize_options(self) + if self.root: + self.single_version_externally_managed = True + elif self.single_version_externally_managed: + if not self.root and not self.record: + raise DistutilsArgError( + "You must specify --record or --root when building system" + " packages" + ) + + def handle_extra_path(self): + if self.root or self.single_version_externally_managed: + # explicit backward-compatibility mode, allow extra_path to work + return orig.install.handle_extra_path(self) + + # Ignore extra_path when installing an egg (or being run by another + # command without --root or --single-version-externally-managed + self.path_file = None + self.extra_dirs = '' + + def run(self): + # Explicit request for old-style install? Just do it + if self.old_and_unmanageable or self.single_version_externally_managed: + return orig.install.run(self) + + if not self._called_from_setup(inspect.currentframe()): + # Run in backward-compatibility mode to support bdist_* commands. + orig.install.run(self) + else: + self.do_egg_install() + + @staticmethod + def _called_from_setup(run_frame): + """ + Attempt to detect whether run() was called from setup() or by another + command. If called by setup(), the parent caller will be the + 'run_command' method in 'distutils.dist', and *its* caller will be + the 'run_commands' method. If called any other way, the + immediate caller *might* be 'run_command', but it won't have been + called by 'run_commands'. Return True in that case or if a call stack + is unavailable. Return False otherwise. + """ + if run_frame is None: + msg = "Call stack not available. bdist_* commands may fail." + warnings.warn(msg) + if platform.python_implementation() == 'IronPython': + msg = "For best results, pass -X:Frames to enable call stack." + warnings.warn(msg) + return True + res = inspect.getouterframes(run_frame)[2] + caller, = res[:1] + info = inspect.getframeinfo(caller) + caller_module = caller.f_globals.get('__name__', '') + return ( + caller_module == 'distutils.dist' + and info.function == 'run_commands' + ) + + def do_egg_install(self): + + easy_install = self.distribution.get_command_class('easy_install') + + cmd = easy_install( + self.distribution, args="x", root=self.root, record=self.record, + ) + cmd.ensure_finalized() # finalize before bdist_egg munges install cmd + cmd.always_copy_from = '.' # make sure local-dir eggs get installed + + # pick up setup-dir .egg files only: no .egg-info + cmd.package_index.scan(glob.glob('*.egg')) + + self.run_command('bdist_egg') + args = [self.distribution.get_command_obj('bdist_egg').egg_output] + + if setuptools.bootstrap_install_from: + # Bootstrap self-installation of setuptools + args.insert(0, setuptools.bootstrap_install_from) + + cmd.args = args + cmd.run(show_deprecation=False) + setuptools.bootstrap_install_from = None + + +# XXX Python 3.1 doesn't see _nc if this is inside the class +install.sub_commands = ( + [cmd for cmd in orig.install.sub_commands if cmd[0] not in install._nc] + + install.new_commands +) diff --git a/venv/lib/python3.10/site-packages/setuptools/command/install_egg_info.py b/venv/lib/python3.10/site-packages/setuptools/command/install_egg_info.py new file mode 100644 index 00000000..5f405bca --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/command/install_egg_info.py @@ -0,0 +1,82 @@ +from distutils import log, dir_util +import os, sys + +from setuptools import Command +from setuptools import namespaces +from setuptools.archive_util import unpack_archive +import pkg_resources + + +class install_egg_info(namespaces.Installer, Command): + """Install an .egg-info directory for the package""" + + description = "Install an .egg-info directory for the package" + + user_options = [ + ('install-dir=', 'd', "directory to install to"), + ] + + def initialize_options(self): + self.install_dir = None + self.install_layout = None + self.prefix_option = None + + def finalize_options(self): + self.set_undefined_options('install_lib', + ('install_dir', 'install_dir')) + self.set_undefined_options('install',('install_layout','install_layout')) + if sys.hexversion > 0x2060000: + self.set_undefined_options('install',('prefix_option','prefix_option')) + ei_cmd = self.get_finalized_command("egg_info") + basename = pkg_resources.Distribution( + None, None, ei_cmd.egg_name, ei_cmd.egg_version + ).egg_name() + '.egg-info' + + if self.install_layout: + if not self.install_layout.lower() in ['deb']: + raise DistutilsOptionError("unknown value for --install-layout") + self.install_layout = self.install_layout.lower() + basename = basename.replace('-py%s' % pkg_resources.PY_MAJOR, '') + elif self.prefix_option or 'real_prefix' in sys.__dict__: + # don't modify for virtualenv + pass + else: + basename = basename.replace('-py%s' % pkg_resources.PY_MAJOR, '') + + self.source = ei_cmd.egg_info + self.target = os.path.join(self.install_dir, basename) + self.outputs = [] + + def run(self): + self.run_command('egg_info') + if os.path.isdir(self.target) and not os.path.islink(self.target): + dir_util.remove_tree(self.target, dry_run=self.dry_run) + elif os.path.exists(self.target): + self.execute(os.unlink, (self.target,), "Removing " + self.target) + if not self.dry_run: + pkg_resources.ensure_directory(self.target) + self.execute( + self.copytree, (), "Copying %s to %s" % (self.source, self.target) + ) + self.install_namespaces() + + def get_outputs(self): + return self.outputs + + def copytree(self): + # Copy the .egg-info tree to site-packages + def skimmer(src, dst): + # filter out source-control directories; note that 'src' is always + # a '/'-separated path, regardless of platform. 'dst' is a + # platform-specific path. + for skip in '.svn/', 'CVS/': + if src.startswith(skip) or '/' + skip in src: + return None + if self.install_layout and self.install_layout in ['deb'] and src.startswith('SOURCES.txt'): + log.info("Skipping SOURCES.txt") + return None + self.outputs.append(dst) + log.debug("Copying %s to %s", src, dst) + return dst + + unpack_archive(self.source, self.target, skimmer) diff --git a/venv/lib/python3.10/site-packages/setuptools/command/install_lib.py b/venv/lib/python3.10/site-packages/setuptools/command/install_lib.py new file mode 100644 index 00000000..65e318eb --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/command/install_lib.py @@ -0,0 +1,148 @@ +import os +import sys +from itertools import product, starmap +import distutils.command.install_lib as orig + + +class install_lib(orig.install_lib): + """Don't add compiled flags to filenames of non-Python files""" + + def initialize_options(self): + orig.install_lib.initialize_options(self) + self.multiarch = None + self.install_layout = None + + def finalize_options(self): + orig.install_lib.finalize_options(self) + self.set_undefined_options('install',('install_layout','install_layout')) + if self.install_layout == 'deb' and sys.version_info[:2] >= (3, 3): + import sysconfig + self.multiarch = sysconfig.get_config_var('MULTIARCH') + + def run(self): + self.build() + outfiles = self.install() + if outfiles is not None: + # always compile, in case we have any extension stubs to deal with + self.byte_compile(outfiles) + + def get_exclusions(self): + """ + Return a collections.Sized collections.Container of paths to be + excluded for single_version_externally_managed installations. + """ + all_packages = ( + pkg + for ns_pkg in self._get_SVEM_NSPs() + for pkg in self._all_packages(ns_pkg) + ) + + excl_specs = product(all_packages, self._gen_exclusion_paths()) + return set(starmap(self._exclude_pkg_path, excl_specs)) + + def _exclude_pkg_path(self, pkg, exclusion_path): + """ + Given a package name and exclusion path within that package, + compute the full exclusion path. + """ + parts = pkg.split('.') + [exclusion_path] + return os.path.join(self.install_dir, *parts) + + @staticmethod + def _all_packages(pkg_name): + """ + >>> list(install_lib._all_packages('foo.bar.baz')) + ['foo.bar.baz', 'foo.bar', 'foo'] + """ + while pkg_name: + yield pkg_name + pkg_name, sep, child = pkg_name.rpartition('.') + + def _get_SVEM_NSPs(self): + """ + Get namespace packages (list) but only for + single_version_externally_managed installations and empty otherwise. + """ + # TODO: is it necessary to short-circuit here? i.e. what's the cost + # if get_finalized_command is called even when namespace_packages is + # False? + if not self.distribution.namespace_packages: + return [] + + install_cmd = self.get_finalized_command('install') + svem = install_cmd.single_version_externally_managed + + return self.distribution.namespace_packages if svem else [] + + @staticmethod + def _gen_exclusion_paths(): + """ + Generate file paths to be excluded for namespace packages (bytecode + cache files). + """ + # always exclude the package module itself + yield '__init__.py' + + yield '__init__.pyc' + yield '__init__.pyo' + + if not hasattr(sys, 'implementation'): + return + + base = os.path.join( + '__pycache__', '__init__.' + sys.implementation.cache_tag) + yield base + '.pyc' + yield base + '.pyo' + yield base + '.opt-1.pyc' + yield base + '.opt-2.pyc' + + def copy_tree( + self, infile, outfile, + preserve_mode=1, preserve_times=1, preserve_symlinks=0, level=1 + ): + assert preserve_mode and preserve_times and not preserve_symlinks + exclude = self.get_exclusions() + + if not exclude: + import distutils.dir_util + distutils.dir_util._multiarch = self.multiarch + return orig.install_lib.copy_tree(self, infile, outfile) + + # Exclude namespace package __init__.py* files from the output + + from setuptools.archive_util import unpack_directory + from distutils import log + + outfiles = [] + + if self.multiarch: + import sysconfig + ext_suffix = sysconfig.get_config_var ('EXT_SUFFIX') + if ext_suffix.endswith(self.multiarch + ext_suffix[-3:]): + new_suffix = None + else: + new_suffix = "%s-%s%s" % (ext_suffix[:-3], self.multiarch, ext_suffix[-3:]) + + def pf(src, dst): + if dst in exclude: + log.warn("Skipping installation of %s (namespace package)", + dst) + return False + + if self.multiarch and new_suffix and dst.endswith(ext_suffix) and not dst.endswith(new_suffix): + dst = dst.replace(ext_suffix, new_suffix) + log.info("renaming extension to %s", os.path.basename(dst)) + + log.info("copying %s -> %s", src, os.path.dirname(dst)) + outfiles.append(dst) + return dst + + unpack_directory(infile, outfile, pf) + return outfiles + + def get_outputs(self): + outputs = orig.install_lib.get_outputs(self) + exclude = self.get_exclusions() + if exclude: + return [f for f in outputs if f not in exclude] + return outputs diff --git a/venv/lib/python3.10/site-packages/setuptools/command/install_scripts.py b/venv/lib/python3.10/site-packages/setuptools/command/install_scripts.py new file mode 100644 index 00000000..9cd8eb06 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/command/install_scripts.py @@ -0,0 +1,69 @@ +from distutils import log +import distutils.command.install_scripts as orig +from distutils.errors import DistutilsModuleError +import os +import sys + +from pkg_resources import Distribution, PathMetadata, ensure_directory + + +class install_scripts(orig.install_scripts): + """Do normal script install, plus any egg_info wrapper scripts""" + + def initialize_options(self): + orig.install_scripts.initialize_options(self) + self.no_ep = False + + def run(self): + import setuptools.command.easy_install as ei + + self.run_command("egg_info") + if self.distribution.scripts: + orig.install_scripts.run(self) # run first to set up self.outfiles + else: + self.outfiles = [] + if self.no_ep: + # don't install entry point scripts into .egg file! + return + + ei_cmd = self.get_finalized_command("egg_info") + dist = Distribution( + ei_cmd.egg_base, PathMetadata(ei_cmd.egg_base, ei_cmd.egg_info), + ei_cmd.egg_name, ei_cmd.egg_version, + ) + bs_cmd = self.get_finalized_command('build_scripts') + exec_param = getattr(bs_cmd, 'executable', None) + try: + bw_cmd = self.get_finalized_command("bdist_wininst") + is_wininst = getattr(bw_cmd, '_is_running', False) + except (ImportError, DistutilsModuleError): + is_wininst = False + writer = ei.ScriptWriter + if is_wininst: + exec_param = "python.exe" + writer = ei.WindowsScriptWriter + if exec_param == sys.executable: + # In case the path to the Python executable contains a space, wrap + # it so it's not split up. + exec_param = [exec_param] + # resolve the writer to the environment + writer = writer.best() + cmd = writer.command_spec_class.best().from_param(exec_param) + for args in writer.get_args(dist, cmd.as_header()): + self.write_script(*args) + + def write_script(self, script_name, contents, mode="t", *ignored): + """Write an executable file to the scripts directory""" + from setuptools.command.easy_install import chmod, current_umask + + log.info("Installing %s script to %s", script_name, self.install_dir) + target = os.path.join(self.install_dir, script_name) + self.outfiles.append(target) + + mask = current_umask() + if not self.dry_run: + ensure_directory(target) + f = open(target, "w" + mode) + f.write(contents) + f.close() + chmod(target, 0o777 - mask) diff --git a/venv/lib/python3.10/site-packages/setuptools/command/launcher manifest.xml b/venv/lib/python3.10/site-packages/setuptools/command/launcher manifest.xml new file mode 100644 index 00000000..5972a96d --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/command/launcher manifest.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/venv/lib/python3.10/site-packages/setuptools/command/py36compat.py b/venv/lib/python3.10/site-packages/setuptools/command/py36compat.py new file mode 100644 index 00000000..343547a4 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/command/py36compat.py @@ -0,0 +1,134 @@ +import os +from glob import glob +from distutils.util import convert_path +from distutils.command import sdist + + +class sdist_add_defaults: + """ + Mix-in providing forward-compatibility for functionality as found in + distutils on Python 3.7. + + Do not edit the code in this class except to update functionality + as implemented in distutils. Instead, override in the subclass. + """ + + def add_defaults(self): + """Add all the default files to self.filelist: + - README or README.txt + - setup.py + - test/test*.py + - all pure Python modules mentioned in setup script + - all files pointed by package_data (build_py) + - all files defined in data_files. + - all files defined as scripts. + - all C sources listed as part of extensions or C libraries + in the setup script (doesn't catch C headers!) + Warns if (README or README.txt) or setup.py are missing; everything + else is optional. + """ + self._add_defaults_standards() + self._add_defaults_optional() + self._add_defaults_python() + self._add_defaults_data_files() + self._add_defaults_ext() + self._add_defaults_c_libs() + self._add_defaults_scripts() + + @staticmethod + def _cs_path_exists(fspath): + """ + Case-sensitive path existence check + + >>> sdist_add_defaults._cs_path_exists(__file__) + True + >>> sdist_add_defaults._cs_path_exists(__file__.upper()) + False + """ + if not os.path.exists(fspath): + return False + # make absolute so we always have a directory + abspath = os.path.abspath(fspath) + directory, filename = os.path.split(abspath) + return filename in os.listdir(directory) + + def _add_defaults_standards(self): + standards = [self.READMES, self.distribution.script_name] + for fn in standards: + if isinstance(fn, tuple): + alts = fn + got_it = False + for fn in alts: + if self._cs_path_exists(fn): + got_it = True + self.filelist.append(fn) + break + + if not got_it: + self.warn("standard file not found: should have one of " + + ', '.join(alts)) + else: + if self._cs_path_exists(fn): + self.filelist.append(fn) + else: + self.warn("standard file '%s' not found" % fn) + + def _add_defaults_optional(self): + optional = ['test/test*.py', 'setup.cfg'] + for pattern in optional: + files = filter(os.path.isfile, glob(pattern)) + self.filelist.extend(files) + + def _add_defaults_python(self): + # build_py is used to get: + # - python modules + # - files defined in package_data + build_py = self.get_finalized_command('build_py') + + # getting python files + if self.distribution.has_pure_modules(): + self.filelist.extend(build_py.get_source_files()) + + # getting package_data files + # (computed in build_py.data_files by build_py.finalize_options) + for pkg, src_dir, build_dir, filenames in build_py.data_files: + for filename in filenames: + self.filelist.append(os.path.join(src_dir, filename)) + + def _add_defaults_data_files(self): + # getting distribution.data_files + if self.distribution.has_data_files(): + for item in self.distribution.data_files: + if isinstance(item, str): + # plain file + item = convert_path(item) + if os.path.isfile(item): + self.filelist.append(item) + else: + # a (dirname, filenames) tuple + dirname, filenames = item + for f in filenames: + f = convert_path(f) + if os.path.isfile(f): + self.filelist.append(f) + + def _add_defaults_ext(self): + if self.distribution.has_ext_modules(): + build_ext = self.get_finalized_command('build_ext') + self.filelist.extend(build_ext.get_source_files()) + + def _add_defaults_c_libs(self): + if self.distribution.has_c_libraries(): + build_clib = self.get_finalized_command('build_clib') + self.filelist.extend(build_clib.get_source_files()) + + def _add_defaults_scripts(self): + if self.distribution.has_scripts(): + build_scripts = self.get_finalized_command('build_scripts') + self.filelist.extend(build_scripts.get_source_files()) + + +if hasattr(sdist.sdist, '_add_defaults_standards'): + # disable the functionality already available upstream + class sdist_add_defaults: # noqa + pass diff --git a/venv/lib/python3.10/site-packages/setuptools/command/register.py b/venv/lib/python3.10/site-packages/setuptools/command/register.py new file mode 100644 index 00000000..b8266b9a --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/command/register.py @@ -0,0 +1,18 @@ +from distutils import log +import distutils.command.register as orig + +from setuptools.errors import RemovedCommandError + + +class register(orig.register): + """Formerly used to register packages on PyPI.""" + + def run(self): + msg = ( + "The register command has been removed, use twine to upload " + + "instead (https://pypi.org/p/twine)" + ) + + self.announce("ERROR: " + msg, log.ERROR) + + raise RemovedCommandError(msg) diff --git a/venv/lib/python3.10/site-packages/setuptools/command/rotate.py b/venv/lib/python3.10/site-packages/setuptools/command/rotate.py new file mode 100644 index 00000000..74795ba9 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/command/rotate.py @@ -0,0 +1,64 @@ +from distutils.util import convert_path +from distutils import log +from distutils.errors import DistutilsOptionError +import os +import shutil + +from setuptools import Command + + +class rotate(Command): + """Delete older distributions""" + + description = "delete older distributions, keeping N newest files" + user_options = [ + ('match=', 'm', "patterns to match (required)"), + ('dist-dir=', 'd', "directory where the distributions are"), + ('keep=', 'k', "number of matching distributions to keep"), + ] + + boolean_options = [] + + def initialize_options(self): + self.match = None + self.dist_dir = None + self.keep = None + + def finalize_options(self): + if self.match is None: + raise DistutilsOptionError( + "Must specify one or more (comma-separated) match patterns " + "(e.g. '.zip' or '.egg')" + ) + if self.keep is None: + raise DistutilsOptionError("Must specify number of files to keep") + try: + self.keep = int(self.keep) + except ValueError as e: + raise DistutilsOptionError("--keep must be an integer") from e + if isinstance(self.match, str): + self.match = [ + convert_path(p.strip()) for p in self.match.split(',') + ] + self.set_undefined_options('bdist', ('dist_dir', 'dist_dir')) + + def run(self): + self.run_command("egg_info") + from glob import glob + + for pattern in self.match: + pattern = self.distribution.get_name() + '*' + pattern + files = glob(os.path.join(self.dist_dir, pattern)) + files = [(os.path.getmtime(f), f) for f in files] + files.sort() + files.reverse() + + log.info("%d file(s) matching %s", len(files), pattern) + files = files[self.keep:] + for (t, f) in files: + log.info("Deleting %s", f) + if not self.dry_run: + if os.path.isdir(f): + shutil.rmtree(f) + else: + os.unlink(f) diff --git a/venv/lib/python3.10/site-packages/setuptools/command/saveopts.py b/venv/lib/python3.10/site-packages/setuptools/command/saveopts.py new file mode 100644 index 00000000..611cec55 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/command/saveopts.py @@ -0,0 +1,22 @@ +from setuptools.command.setopt import edit_config, option_base + + +class saveopts(option_base): + """Save command-line options to a file""" + + description = "save supplied options to setup.cfg or other config file" + + def run(self): + dist = self.distribution + settings = {} + + for cmd in dist.command_options: + + if cmd == 'saveopts': + continue # don't save our own options! + + for opt, (src, val) in dist.get_option_dict(cmd).items(): + if src == "command line": + settings.setdefault(cmd, {})[opt] = val + + edit_config(self.filename, settings, self.dry_run) diff --git a/venv/lib/python3.10/site-packages/setuptools/command/sdist.py b/venv/lib/python3.10/site-packages/setuptools/command/sdist.py new file mode 100644 index 00000000..0285b690 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/command/sdist.py @@ -0,0 +1,196 @@ +from distutils import log +import distutils.command.sdist as orig +import os +import sys +import io +import contextlib + +from .py36compat import sdist_add_defaults + +import pkg_resources + +_default_revctrl = list + + +def walk_revctrl(dirname=''): + """Find all files under revision control""" + for ep in pkg_resources.iter_entry_points('setuptools.file_finders'): + for item in ep.load()(dirname): + yield item + + +class sdist(sdist_add_defaults, orig.sdist): + """Smart sdist that finds anything supported by revision control""" + + user_options = [ + ('formats=', None, + "formats for source distribution (comma-separated list)"), + ('keep-temp', 'k', + "keep the distribution tree around after creating " + + "archive file(s)"), + ('dist-dir=', 'd', + "directory to put the source distribution archive(s) in " + "[default: dist]"), + ('owner=', 'u', + "Owner name used when creating a tar file [default: current user]"), + ('group=', 'g', + "Group name used when creating a tar file [default: current group]"), + ] + + negative_opt = {} + + README_EXTENSIONS = ['', '.rst', '.txt', '.md'] + READMES = tuple('README{0}'.format(ext) for ext in README_EXTENSIONS) + + def run(self): + self.run_command('egg_info') + ei_cmd = self.get_finalized_command('egg_info') + self.filelist = ei_cmd.filelist + self.filelist.append(os.path.join(ei_cmd.egg_info, 'SOURCES.txt')) + self.check_readme() + + # Run sub commands + for cmd_name in self.get_sub_commands(): + self.run_command(cmd_name) + + self.make_distribution() + + dist_files = getattr(self.distribution, 'dist_files', []) + for file in self.archive_files: + data = ('sdist', '', file) + if data not in dist_files: + dist_files.append(data) + + def initialize_options(self): + orig.sdist.initialize_options(self) + + self._default_to_gztar() + + def _default_to_gztar(self): + # only needed on Python prior to 3.6. + if sys.version_info >= (3, 6, 0, 'beta', 1): + return + self.formats = ['gztar'] + + def make_distribution(self): + """ + Workaround for #516 + """ + with self._remove_os_link(): + orig.sdist.make_distribution(self) + + @staticmethod + @contextlib.contextmanager + def _remove_os_link(): + """ + In a context, remove and restore os.link if it exists + """ + + class NoValue: + pass + + orig_val = getattr(os, 'link', NoValue) + try: + del os.link + except Exception: + pass + try: + yield + finally: + if orig_val is not NoValue: + setattr(os, 'link', orig_val) + + def _add_defaults_optional(self): + super()._add_defaults_optional() + if os.path.isfile('pyproject.toml'): + self.filelist.append('pyproject.toml') + + def _add_defaults_python(self): + """getting python files""" + if self.distribution.has_pure_modules(): + build_py = self.get_finalized_command('build_py') + self.filelist.extend(build_py.get_source_files()) + self._add_data_files(self._safe_data_files(build_py)) + + def _safe_data_files(self, build_py): + """ + Since the ``sdist`` class is also used to compute the MANIFEST + (via :obj:`setuptools.command.egg_info.manifest_maker`), + there might be recursion problems when trying to obtain the list of + data_files and ``include_package_data=True`` (which in turn depends on + the files included in the MANIFEST). + + To avoid that, ``manifest_maker`` should be able to overwrite this + method and avoid recursive attempts to build/analyze the MANIFEST. + """ + return build_py.data_files + + def _add_data_files(self, data_files): + """ + Add data files as found in build_py.data_files. + """ + self.filelist.extend( + os.path.join(src_dir, name) + for _, src_dir, _, filenames in data_files + for name in filenames + ) + + def _add_defaults_data_files(self): + try: + super()._add_defaults_data_files() + except TypeError: + log.warn("data_files contains unexpected objects") + + def check_readme(self): + for f in self.READMES: + if os.path.exists(f): + return + else: + self.warn( + "standard file not found: should have one of " + + ', '.join(self.READMES) + ) + + def make_release_tree(self, base_dir, files): + orig.sdist.make_release_tree(self, base_dir, files) + + # Save any egg_info command line options used to create this sdist + dest = os.path.join(base_dir, 'setup.cfg') + if hasattr(os, 'link') and os.path.exists(dest): + # unlink and re-copy, since it might be hard-linked, and + # we don't want to change the source version + os.unlink(dest) + self.copy_file('setup.cfg', dest) + + self.get_finalized_command('egg_info').save_version_info(dest) + + def _manifest_is_not_generated(self): + # check for special comment used in 2.7.1 and higher + if not os.path.isfile(self.manifest): + return False + + with io.open(self.manifest, 'rb') as fp: + first_line = fp.readline() + return (first_line != + '# file GENERATED by distutils, do NOT edit\n'.encode()) + + def read_manifest(self): + """Read the manifest file (named by 'self.manifest') and use it to + fill in 'self.filelist', the list of files to include in the source + distribution. + """ + log.info("reading manifest file '%s'", self.manifest) + manifest = open(self.manifest, 'rb') + for line in manifest: + # The manifest must contain UTF-8. See #303. + try: + line = line.decode('UTF-8') + except UnicodeDecodeError: + log.warn("%r not UTF-8 decodable -- skipping" % line) + continue + # ignore comments and blank lines + line = line.strip() + if line.startswith('#') or not line: + continue + self.filelist.append(line) + manifest.close() diff --git a/venv/lib/python3.10/site-packages/setuptools/command/setopt.py b/venv/lib/python3.10/site-packages/setuptools/command/setopt.py new file mode 100644 index 00000000..6358c045 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/command/setopt.py @@ -0,0 +1,149 @@ +from distutils.util import convert_path +from distutils import log +from distutils.errors import DistutilsOptionError +import distutils +import os +import configparser + +from setuptools import Command + +__all__ = ['config_file', 'edit_config', 'option_base', 'setopt'] + + +def config_file(kind="local"): + """Get the filename of the distutils, local, global, or per-user config + + `kind` must be one of "local", "global", or "user" + """ + if kind == 'local': + return 'setup.cfg' + if kind == 'global': + return os.path.join( + os.path.dirname(distutils.__file__), 'distutils.cfg' + ) + if kind == 'user': + dot = os.name == 'posix' and '.' or '' + return os.path.expanduser(convert_path("~/%spydistutils.cfg" % dot)) + raise ValueError( + "config_file() type must be 'local', 'global', or 'user'", kind + ) + + +def edit_config(filename, settings, dry_run=False): + """Edit a configuration file to include `settings` + + `settings` is a dictionary of dictionaries or ``None`` values, keyed by + command/section name. A ``None`` value means to delete the entire section, + while a dictionary lists settings to be changed or deleted in that section. + A setting of ``None`` means to delete that setting. + """ + log.debug("Reading configuration from %s", filename) + opts = configparser.RawConfigParser() + opts.optionxform = lambda x: x + opts.read([filename]) + for section, options in settings.items(): + if options is None: + log.info("Deleting section [%s] from %s", section, filename) + opts.remove_section(section) + else: + if not opts.has_section(section): + log.debug("Adding new section [%s] to %s", section, filename) + opts.add_section(section) + for option, value in options.items(): + if value is None: + log.debug( + "Deleting %s.%s from %s", + section, option, filename + ) + opts.remove_option(section, option) + if not opts.options(section): + log.info("Deleting empty [%s] section from %s", + section, filename) + opts.remove_section(section) + else: + log.debug( + "Setting %s.%s to %r in %s", + section, option, value, filename + ) + opts.set(section, option, value) + + log.info("Writing %s", filename) + if not dry_run: + with open(filename, 'w') as f: + opts.write(f) + + +class option_base(Command): + """Abstract base class for commands that mess with config files""" + + user_options = [ + ('global-config', 'g', + "save options to the site-wide distutils.cfg file"), + ('user-config', 'u', + "save options to the current user's pydistutils.cfg file"), + ('filename=', 'f', + "configuration file to use (default=setup.cfg)"), + ] + + boolean_options = [ + 'global-config', 'user-config', + ] + + def initialize_options(self): + self.global_config = None + self.user_config = None + self.filename = None + + def finalize_options(self): + filenames = [] + if self.global_config: + filenames.append(config_file('global')) + if self.user_config: + filenames.append(config_file('user')) + if self.filename is not None: + filenames.append(self.filename) + if not filenames: + filenames.append(config_file('local')) + if len(filenames) > 1: + raise DistutilsOptionError( + "Must specify only one configuration file option", + filenames + ) + self.filename, = filenames + + +class setopt(option_base): + """Save command-line options to a file""" + + description = "set an option in setup.cfg or another config file" + + user_options = [ + ('command=', 'c', 'command to set an option for'), + ('option=', 'o', 'option to set'), + ('set-value=', 's', 'value of the option'), + ('remove', 'r', 'remove (unset) the value'), + ] + option_base.user_options + + boolean_options = option_base.boolean_options + ['remove'] + + def initialize_options(self): + option_base.initialize_options(self) + self.command = None + self.option = None + self.set_value = None + self.remove = None + + def finalize_options(self): + option_base.finalize_options(self) + if self.command is None or self.option is None: + raise DistutilsOptionError("Must specify --command *and* --option") + if self.set_value is None and not self.remove: + raise DistutilsOptionError("Must specify --set-value or --remove") + + def run(self): + edit_config( + self.filename, { + self.command: {self.option.replace('-', '_'): self.set_value} + }, + self.dry_run + ) diff --git a/venv/lib/python3.10/site-packages/setuptools/command/test.py b/venv/lib/python3.10/site-packages/setuptools/command/test.py new file mode 100644 index 00000000..4a389e4d --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/command/test.py @@ -0,0 +1,252 @@ +import os +import operator +import sys +import contextlib +import itertools +import unittest +from distutils.errors import DistutilsError, DistutilsOptionError +from distutils import log +from unittest import TestLoader + +from pkg_resources import ( + resource_listdir, + resource_exists, + normalize_path, + working_set, + evaluate_marker, + add_activation_listener, + require, + EntryPoint, +) +from setuptools import Command +from setuptools.extern.more_itertools import unique_everseen + + +class ScanningLoader(TestLoader): + def __init__(self): + TestLoader.__init__(self) + self._visited = set() + + def loadTestsFromModule(self, module, pattern=None): + """Return a suite of all tests cases contained in the given module + + If the module is a package, load tests from all the modules in it. + If the module has an ``additional_tests`` function, call it and add + the return value to the tests. + """ + if module in self._visited: + return None + self._visited.add(module) + + tests = [] + tests.append(TestLoader.loadTestsFromModule(self, module)) + + if hasattr(module, "additional_tests"): + tests.append(module.additional_tests()) + + if hasattr(module, '__path__'): + for file in resource_listdir(module.__name__, ''): + if file.endswith('.py') and file != '__init__.py': + submodule = module.__name__ + '.' + file[:-3] + else: + if resource_exists(module.__name__, file + '/__init__.py'): + submodule = module.__name__ + '.' + file + else: + continue + tests.append(self.loadTestsFromName(submodule)) + + if len(tests) != 1: + return self.suiteClass(tests) + else: + return tests[0] # don't create a nested suite for only one return + + +# adapted from jaraco.classes.properties:NonDataProperty +class NonDataProperty: + def __init__(self, fget): + self.fget = fget + + def __get__(self, obj, objtype=None): + if obj is None: + return self + return self.fget(obj) + + +class test(Command): + """Command to run unit tests after in-place build""" + + description = "run unit tests after in-place build (deprecated)" + + user_options = [ + ('test-module=', 'm', "Run 'test_suite' in specified module"), + ( + 'test-suite=', + 's', + "Run single test, case or suite (e.g. 'module.test_suite')", + ), + ('test-runner=', 'r', "Test runner to use"), + ] + + def initialize_options(self): + self.test_suite = None + self.test_module = None + self.test_loader = None + self.test_runner = None + + def finalize_options(self): + + if self.test_suite and self.test_module: + msg = "You may specify a module or a suite, but not both" + raise DistutilsOptionError(msg) + + if self.test_suite is None: + if self.test_module is None: + self.test_suite = self.distribution.test_suite + else: + self.test_suite = self.test_module + ".test_suite" + + if self.test_loader is None: + self.test_loader = getattr(self.distribution, 'test_loader', None) + if self.test_loader is None: + self.test_loader = "setuptools.command.test:ScanningLoader" + if self.test_runner is None: + self.test_runner = getattr(self.distribution, 'test_runner', None) + + @NonDataProperty + def test_args(self): + return list(self._test_args()) + + def _test_args(self): + if not self.test_suite and sys.version_info >= (2, 7): + yield 'discover' + if self.verbose: + yield '--verbose' + if self.test_suite: + yield self.test_suite + + def with_project_on_sys_path(self, func): + """ + Backward compatibility for project_on_sys_path context. + """ + with self.project_on_sys_path(): + func() + + @contextlib.contextmanager + def project_on_sys_path(self, include_dists=[]): + self.run_command('egg_info') + + # Build extensions in-place + self.reinitialize_command('build_ext', inplace=1) + self.run_command('build_ext') + + ei_cmd = self.get_finalized_command("egg_info") + + old_path = sys.path[:] + old_modules = sys.modules.copy() + + try: + project_path = normalize_path(ei_cmd.egg_base) + sys.path.insert(0, project_path) + working_set.__init__() + add_activation_listener(lambda dist: dist.activate()) + require('%s==%s' % (ei_cmd.egg_name, ei_cmd.egg_version)) + with self.paths_on_pythonpath([project_path]): + yield + finally: + sys.path[:] = old_path + sys.modules.clear() + sys.modules.update(old_modules) + working_set.__init__() + + @staticmethod + @contextlib.contextmanager + def paths_on_pythonpath(paths): + """ + Add the indicated paths to the head of the PYTHONPATH environment + variable so that subprocesses will also see the packages at + these paths. + + Do this in a context that restores the value on exit. + """ + nothing = object() + orig_pythonpath = os.environ.get('PYTHONPATH', nothing) + current_pythonpath = os.environ.get('PYTHONPATH', '') + try: + prefix = os.pathsep.join(unique_everseen(paths)) + to_join = filter(None, [prefix, current_pythonpath]) + new_path = os.pathsep.join(to_join) + if new_path: + os.environ['PYTHONPATH'] = new_path + yield + finally: + if orig_pythonpath is nothing: + os.environ.pop('PYTHONPATH', None) + else: + os.environ['PYTHONPATH'] = orig_pythonpath + + @staticmethod + def install_dists(dist): + """ + Install the requirements indicated by self.distribution and + return an iterable of the dists that were built. + """ + ir_d = dist.fetch_build_eggs(dist.install_requires) + tr_d = dist.fetch_build_eggs(dist.tests_require or []) + er_d = dist.fetch_build_eggs( + v + for k, v in dist.extras_require.items() + if k.startswith(':') and evaluate_marker(k[1:]) + ) + return itertools.chain(ir_d, tr_d, er_d) + + def run(self): + self.announce( + "WARNING: Testing via this command is deprecated and will be " + "removed in a future version. Users looking for a generic test " + "entry point independent of test runner are encouraged to use " + "tox.", + log.WARN, + ) + + installed_dists = self.install_dists(self.distribution) + + cmd = ' '.join(self._argv) + if self.dry_run: + self.announce('skipping "%s" (dry run)' % cmd) + return + + self.announce('running "%s"' % cmd) + + paths = map(operator.attrgetter('location'), installed_dists) + with self.paths_on_pythonpath(paths): + with self.project_on_sys_path(): + self.run_tests() + + def run_tests(self): + test = unittest.main( + None, + None, + self._argv, + testLoader=self._resolve_as_ep(self.test_loader), + testRunner=self._resolve_as_ep(self.test_runner), + exit=False, + ) + if not test.result.wasSuccessful(): + msg = 'Test failed: %s' % test.result + self.announce(msg, log.ERROR) + raise DistutilsError(msg) + + @property + def _argv(self): + return ['unittest'] + self.test_args + + @staticmethod + def _resolve_as_ep(val): + """ + Load the indicated attribute value, called, as a as if it were + specified as an entry point. + """ + if val is None: + return + parsed = EntryPoint.parse("x=" + val) + return parsed.resolve()() diff --git a/venv/lib/python3.10/site-packages/setuptools/command/upload.py b/venv/lib/python3.10/site-packages/setuptools/command/upload.py new file mode 100644 index 00000000..ec7f81e2 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/command/upload.py @@ -0,0 +1,17 @@ +from distutils import log +from distutils.command import upload as orig + +from setuptools.errors import RemovedCommandError + + +class upload(orig.upload): + """Formerly used to upload packages to PyPI.""" + + def run(self): + msg = ( + "The upload command has been removed, use twine to upload " + + "instead (https://pypi.org/p/twine)" + ) + + self.announce("ERROR: " + msg, log.ERROR) + raise RemovedCommandError(msg) diff --git a/venv/lib/python3.10/site-packages/setuptools/command/upload_docs.py b/venv/lib/python3.10/site-packages/setuptools/command/upload_docs.py new file mode 100644 index 00000000..845bff44 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/command/upload_docs.py @@ -0,0 +1,202 @@ +# -*- coding: utf-8 -*- +"""upload_docs + +Implements a Distutils 'upload_docs' subcommand (upload documentation to +sites other than PyPi such as devpi). +""" + +from base64 import standard_b64encode +from distutils import log +from distutils.errors import DistutilsOptionError +import os +import socket +import zipfile +import tempfile +import shutil +import itertools +import functools +import http.client +import urllib.parse + +from pkg_resources import iter_entry_points +from .upload import upload + + +def _encode(s): + return s.encode('utf-8', 'surrogateescape') + + +class upload_docs(upload): + # override the default repository as upload_docs isn't + # supported by Warehouse (and won't be). + DEFAULT_REPOSITORY = 'https://pypi.python.org/pypi/' + + description = 'Upload documentation to sites other than PyPi such as devpi' + + user_options = [ + ('repository=', 'r', + "url of repository [default: %s]" % upload.DEFAULT_REPOSITORY), + ('show-response', None, + 'display full response text from server'), + ('upload-dir=', None, 'directory to upload'), + ] + boolean_options = upload.boolean_options + + def has_sphinx(self): + if self.upload_dir is None: + for ep in iter_entry_points('distutils.commands', 'build_sphinx'): + return True + + sub_commands = [('build_sphinx', has_sphinx)] + + def initialize_options(self): + upload.initialize_options(self) + self.upload_dir = None + self.target_dir = None + + def finalize_options(self): + upload.finalize_options(self) + if self.upload_dir is None: + if self.has_sphinx(): + build_sphinx = self.get_finalized_command('build_sphinx') + self.target_dir = dict(build_sphinx.builder_target_dirs)['html'] + else: + build = self.get_finalized_command('build') + self.target_dir = os.path.join(build.build_base, 'docs') + else: + self.ensure_dirname('upload_dir') + self.target_dir = self.upload_dir + if 'pypi.python.org' in self.repository: + log.warn("Upload_docs command is deprecated for PyPi. Use RTD instead.") + self.announce('Using upload directory %s' % self.target_dir) + + def create_zipfile(self, filename): + zip_file = zipfile.ZipFile(filename, "w") + try: + self.mkpath(self.target_dir) # just in case + for root, dirs, files in os.walk(self.target_dir): + if root == self.target_dir and not files: + tmpl = "no files found in upload directory '%s'" + raise DistutilsOptionError(tmpl % self.target_dir) + for name in files: + full = os.path.join(root, name) + relative = root[len(self.target_dir):].lstrip(os.path.sep) + dest = os.path.join(relative, name) + zip_file.write(full, dest) + finally: + zip_file.close() + + def run(self): + # Run sub commands + for cmd_name in self.get_sub_commands(): + self.run_command(cmd_name) + + tmp_dir = tempfile.mkdtemp() + name = self.distribution.metadata.get_name() + zip_file = os.path.join(tmp_dir, "%s.zip" % name) + try: + self.create_zipfile(zip_file) + self.upload_file(zip_file) + finally: + shutil.rmtree(tmp_dir) + + @staticmethod + def _build_part(item, sep_boundary): + key, values = item + title = '\nContent-Disposition: form-data; name="%s"' % key + # handle multiple entries for the same name + if not isinstance(values, list): + values = [values] + for value in values: + if isinstance(value, tuple): + title += '; filename="%s"' % value[0] + value = value[1] + else: + value = _encode(value) + yield sep_boundary + yield _encode(title) + yield b"\n\n" + yield value + if value and value[-1:] == b'\r': + yield b'\n' # write an extra newline (lurve Macs) + + @classmethod + def _build_multipart(cls, data): + """ + Build up the MIME payload for the POST data + """ + boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' + sep_boundary = b'\n--' + boundary.encode('ascii') + end_boundary = sep_boundary + b'--' + end_items = end_boundary, b"\n", + builder = functools.partial( + cls._build_part, + sep_boundary=sep_boundary, + ) + part_groups = map(builder, data.items()) + parts = itertools.chain.from_iterable(part_groups) + body_items = itertools.chain(parts, end_items) + content_type = 'multipart/form-data; boundary=%s' % boundary + return b''.join(body_items), content_type + + def upload_file(self, filename): + with open(filename, 'rb') as f: + content = f.read() + meta = self.distribution.metadata + data = { + ':action': 'doc_upload', + 'name': meta.get_name(), + 'content': (os.path.basename(filename), content), + } + # set up the authentication + credentials = _encode(self.username + ':' + self.password) + credentials = standard_b64encode(credentials).decode('ascii') + auth = "Basic " + credentials + + body, ct = self._build_multipart(data) + + msg = "Submitting documentation to %s" % (self.repository) + self.announce(msg, log.INFO) + + # build the Request + # We can't use urllib2 since we need to send the Basic + # auth right with the first request + schema, netloc, url, params, query, fragments = \ + urllib.parse.urlparse(self.repository) + assert not params and not query and not fragments + if schema == 'http': + conn = http.client.HTTPConnection(netloc) + elif schema == 'https': + conn = http.client.HTTPSConnection(netloc) + else: + raise AssertionError("unsupported schema " + schema) + + data = '' + try: + conn.connect() + conn.putrequest("POST", url) + content_type = ct + conn.putheader('Content-type', content_type) + conn.putheader('Content-length', str(len(body))) + conn.putheader('Authorization', auth) + conn.endheaders() + conn.send(body) + except socket.error as e: + self.announce(str(e), log.ERROR) + return + + r = conn.getresponse() + if r.status == 200: + msg = 'Server response (%s): %s' % (r.status, r.reason) + self.announce(msg, log.INFO) + elif r.status == 301: + location = r.getheader('Location') + if location is None: + location = 'https://pythonhosted.org/%s/' % meta.get_name() + msg = 'Upload successful. Visit %s' % location + self.announce(msg, log.INFO) + else: + msg = 'Upload failed (%s): %s' % (r.status, r.reason) + self.announce(msg, log.ERROR) + if self.show_response: + print('-' * 75, r.read(), '-' * 75) diff --git a/venv/lib/python3.10/site-packages/setuptools/config.py b/venv/lib/python3.10/site-packages/setuptools/config.py new file mode 100644 index 00000000..b4e968e5 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/config.py @@ -0,0 +1,751 @@ +import ast +import io +import os +import sys + +import warnings +import functools +import importlib +from collections import defaultdict +from functools import partial +from functools import wraps +from glob import iglob +import contextlib + +from distutils.errors import DistutilsOptionError, DistutilsFileError +from setuptools.extern.packaging.version import Version, InvalidVersion +from setuptools.extern.packaging.specifiers import SpecifierSet + + +class StaticModule: + """ + Attempt to load the module by the name + """ + + def __init__(self, name): + spec = importlib.util.find_spec(name) + with open(spec.origin) as strm: + src = strm.read() + module = ast.parse(src) + vars(self).update(locals()) + del self.self + + def __getattr__(self, attr): + try: + return next( + ast.literal_eval(statement.value) + for statement in self.module.body + if isinstance(statement, ast.Assign) + for target in statement.targets + if isinstance(target, ast.Name) and target.id == attr + ) + except Exception as e: + raise AttributeError( + "{self.name} has no attribute {attr}".format(**locals()) + ) from e + + +@contextlib.contextmanager +def patch_path(path): + """ + Add path to front of sys.path for the duration of the context. + """ + try: + sys.path.insert(0, path) + yield + finally: + sys.path.remove(path) + + +def read_configuration(filepath, find_others=False, ignore_option_errors=False): + """Read given configuration file and returns options from it as a dict. + + :param str|unicode filepath: Path to configuration file + to get options from. + + :param bool find_others: Whether to search for other configuration files + which could be on in various places. + + :param bool ignore_option_errors: Whether to silently ignore + options, values of which could not be resolved (e.g. due to exceptions + in directives such as file:, attr:, etc.). + If False exceptions are propagated as expected. + + :rtype: dict + """ + from setuptools.dist import Distribution, _Distribution + + filepath = os.path.abspath(filepath) + + if not os.path.isfile(filepath): + raise DistutilsFileError('Configuration file %s does not exist.' % filepath) + + current_directory = os.getcwd() + os.chdir(os.path.dirname(filepath)) + + try: + dist = Distribution() + + filenames = dist.find_config_files() if find_others else [] + if filepath not in filenames: + filenames.append(filepath) + + _Distribution.parse_config_files(dist, filenames=filenames) + + handlers = parse_configuration( + dist, dist.command_options, ignore_option_errors=ignore_option_errors + ) + + finally: + os.chdir(current_directory) + + return configuration_to_dict(handlers) + + +def _get_option(target_obj, key): + """ + Given a target object and option key, get that option from + the target object, either through a get_{key} method or + from an attribute directly. + """ + getter_name = 'get_{key}'.format(**locals()) + by_attribute = functools.partial(getattr, target_obj, key) + getter = getattr(target_obj, getter_name, by_attribute) + return getter() + + +def configuration_to_dict(handlers): + """Returns configuration data gathered by given handlers as a dict. + + :param list[ConfigHandler] handlers: Handlers list, + usually from parse_configuration() + + :rtype: dict + """ + config_dict = defaultdict(dict) + + for handler in handlers: + for option in handler.set_options: + value = _get_option(handler.target_obj, option) + config_dict[handler.section_prefix][option] = value + + return config_dict + + +def parse_configuration(distribution, command_options, ignore_option_errors=False): + """Performs additional parsing of configuration options + for a distribution. + + Returns a list of used option handlers. + + :param Distribution distribution: + :param dict command_options: + :param bool ignore_option_errors: Whether to silently ignore + options, values of which could not be resolved (e.g. due to exceptions + in directives such as file:, attr:, etc.). + If False exceptions are propagated as expected. + :rtype: list + """ + options = ConfigOptionsHandler(distribution, command_options, ignore_option_errors) + options.parse() + + meta = ConfigMetadataHandler( + distribution.metadata, + command_options, + ignore_option_errors, + distribution.package_dir, + ) + meta.parse() + + return meta, options + + +class ConfigHandler: + """Handles metadata supplied in configuration files.""" + + section_prefix = None + """Prefix for config sections handled by this handler. + Must be provided by class heirs. + + """ + + aliases = {} + """Options aliases. + For compatibility with various packages. E.g.: d2to1 and pbr. + Note: `-` in keys is replaced with `_` by config parser. + + """ + + def __init__(self, target_obj, options, ignore_option_errors=False): + sections = {} + + section_prefix = self.section_prefix + for section_name, section_options in options.items(): + if not section_name.startswith(section_prefix): + continue + + section_name = section_name.replace(section_prefix, '').strip('.') + sections[section_name] = section_options + + self.ignore_option_errors = ignore_option_errors + self.target_obj = target_obj + self.sections = sections + self.set_options = [] + + @property + def parsers(self): + """Metadata item name to parser function mapping.""" + raise NotImplementedError( + '%s must provide .parsers property' % self.__class__.__name__ + ) + + def __setitem__(self, option_name, value): + unknown = tuple() + target_obj = self.target_obj + + # Translate alias into real name. + option_name = self.aliases.get(option_name, option_name) + + current_value = getattr(target_obj, option_name, unknown) + + if current_value is unknown: + raise KeyError(option_name) + + if current_value: + # Already inhabited. Skipping. + return + + skip_option = False + parser = self.parsers.get(option_name) + if parser: + try: + value = parser(value) + + except Exception: + skip_option = True + if not self.ignore_option_errors: + raise + + if skip_option: + return + + setter = getattr(target_obj, 'set_%s' % option_name, None) + if setter is None: + setattr(target_obj, option_name, value) + else: + setter(value) + + self.set_options.append(option_name) + + @classmethod + def _parse_list(cls, value, separator=','): + """Represents value as a list. + + Value is split either by separator (defaults to comma) or by lines. + + :param value: + :param separator: List items separator character. + :rtype: list + """ + if isinstance(value, list): # _get_parser_compound case + return value + + if '\n' in value: + value = value.splitlines() + else: + value = value.split(separator) + + return [chunk.strip() for chunk in value if chunk.strip()] + + @classmethod + def _parse_list_glob(cls, value, separator=','): + """Equivalent to _parse_list() but expands any glob patterns using glob(). + + However, unlike with glob() calls, the results remain relative paths. + + :param value: + :param separator: List items separator character. + :rtype: list + """ + glob_characters = ('*', '?', '[', ']', '{', '}') + values = cls._parse_list(value, separator=separator) + expanded_values = [] + for value in values: + + # Has globby characters? + if any(char in value for char in glob_characters): + # then expand the glob pattern while keeping paths *relative*: + expanded_values.extend(sorted( + os.path.relpath(path, os.getcwd()) + for path in iglob(os.path.abspath(value)))) + + else: + # take the value as-is: + expanded_values.append(value) + + return expanded_values + + @classmethod + def _parse_dict(cls, value): + """Represents value as a dict. + + :param value: + :rtype: dict + """ + separator = '=' + result = {} + for line in cls._parse_list(value): + key, sep, val = line.partition(separator) + if sep != separator: + raise DistutilsOptionError( + 'Unable to parse option value to dict: %s' % value + ) + result[key.strip()] = val.strip() + + return result + + @classmethod + def _parse_bool(cls, value): + """Represents value as boolean. + + :param value: + :rtype: bool + """ + value = value.lower() + return value in ('1', 'true', 'yes') + + @classmethod + def _exclude_files_parser(cls, key): + """Returns a parser function to make sure field inputs + are not files. + + Parses a value after getting the key so error messages are + more informative. + + :param key: + :rtype: callable + """ + + def parser(value): + exclude_directive = 'file:' + if value.startswith(exclude_directive): + raise ValueError( + 'Only strings are accepted for the {0} field, ' + 'files are not accepted'.format(key) + ) + return value + + return parser + + @classmethod + def _parse_file(cls, value): + """Represents value as a string, allowing including text + from nearest files using `file:` directive. + + Directive is sandboxed and won't reach anything outside + directory with setup.py. + + Examples: + file: README.rst, CHANGELOG.md, src/file.txt + + :param str value: + :rtype: str + """ + include_directive = 'file:' + + if not isinstance(value, str): + return value + + if not value.startswith(include_directive): + return value + + spec = value[len(include_directive) :] + filepaths = (os.path.abspath(path.strip()) for path in spec.split(',')) + return '\n'.join( + cls._read_file(path) + for path in filepaths + if (cls._assert_local(path) or True) and os.path.isfile(path) + ) + + @staticmethod + def _assert_local(filepath): + if not filepath.startswith(os.getcwd()): + raise DistutilsOptionError('`file:` directive can not access %s' % filepath) + + @staticmethod + def _read_file(filepath): + with io.open(filepath, encoding='utf-8') as f: + return f.read() + + @classmethod + def _parse_attr(cls, value, package_dir=None): + """Represents value as a module attribute. + + Examples: + attr: package.attr + attr: package.module.attr + + :param str value: + :rtype: str + """ + attr_directive = 'attr:' + if not value.startswith(attr_directive): + return value + + attrs_path = value.replace(attr_directive, '').strip().split('.') + attr_name = attrs_path.pop() + + module_name = '.'.join(attrs_path) + module_name = module_name or '__init__' + + parent_path = os.getcwd() + if package_dir: + if attrs_path[0] in package_dir: + # A custom path was specified for the module we want to import + custom_path = package_dir[attrs_path[0]] + parts = custom_path.rsplit('/', 1) + if len(parts) > 1: + parent_path = os.path.join(os.getcwd(), parts[0]) + module_name = parts[1] + else: + module_name = custom_path + elif '' in package_dir: + # A custom parent directory was specified for all root modules + parent_path = os.path.join(os.getcwd(), package_dir['']) + + with patch_path(parent_path): + try: + # attempt to load value statically + return getattr(StaticModule(module_name), attr_name) + except Exception: + # fallback to simple import + module = importlib.import_module(module_name) + + return getattr(module, attr_name) + + @classmethod + def _get_parser_compound(cls, *parse_methods): + """Returns parser function to represents value as a list. + + Parses a value applying given methods one after another. + + :param parse_methods: + :rtype: callable + """ + + def parse(value): + parsed = value + + for method in parse_methods: + parsed = method(parsed) + + return parsed + + return parse + + @classmethod + def _parse_section_to_dict(cls, section_options, values_parser=None): + """Parses section options into a dictionary. + + Optionally applies a given parser to values. + + :param dict section_options: + :param callable values_parser: + :rtype: dict + """ + value = {} + values_parser = values_parser or (lambda val: val) + for key, (_, val) in section_options.items(): + value[key] = values_parser(val) + return value + + def parse_section(self, section_options): + """Parses configuration file section. + + :param dict section_options: + """ + for (name, (_, value)) in section_options.items(): + try: + self[name] = value + + except KeyError: + pass # Keep silent for a new option may appear anytime. + + def parse(self): + """Parses configuration file items from one + or more related sections. + + """ + for section_name, section_options in self.sections.items(): + + method_postfix = '' + if section_name: # [section.option] variant + method_postfix = '_%s' % section_name + + section_parser_method = getattr( + self, + # Dots in section names are translated into dunderscores. + ('parse_section%s' % method_postfix).replace('.', '__'), + None, + ) + + if section_parser_method is None: + raise DistutilsOptionError( + 'Unsupported distribution option section: [%s.%s]' + % (self.section_prefix, section_name) + ) + + section_parser_method(section_options) + + def _deprecated_config_handler(self, func, msg, warning_class): + """this function will wrap around parameters that are deprecated + + :param msg: deprecation message + :param warning_class: class of warning exception to be raised + :param func: function to be wrapped around + """ + + @wraps(func) + def config_handler(*args, **kwargs): + warnings.warn(msg, warning_class) + return func(*args, **kwargs) + + return config_handler + + +class ConfigMetadataHandler(ConfigHandler): + + section_prefix = 'metadata' + + aliases = { + 'home_page': 'url', + 'summary': 'description', + 'classifier': 'classifiers', + 'platform': 'platforms', + } + + strict_mode = False + """We need to keep it loose, to be partially compatible with + `pbr` and `d2to1` packages which also uses `metadata` section. + + """ + + def __init__( + self, target_obj, options, ignore_option_errors=False, package_dir=None + ): + super(ConfigMetadataHandler, self).__init__( + target_obj, options, ignore_option_errors + ) + self.package_dir = package_dir + + @property + def parsers(self): + """Metadata item name to parser function mapping.""" + parse_list = self._parse_list + parse_file = self._parse_file + parse_dict = self._parse_dict + exclude_files_parser = self._exclude_files_parser + + return { + 'platforms': parse_list, + 'keywords': parse_list, + 'provides': parse_list, + 'requires': self._deprecated_config_handler( + parse_list, + "The requires parameter is deprecated, please use " + "install_requires for runtime dependencies.", + DeprecationWarning, + ), + 'obsoletes': parse_list, + 'classifiers': self._get_parser_compound(parse_file, parse_list), + 'license': exclude_files_parser('license'), + 'license_file': self._deprecated_config_handler( + exclude_files_parser('license_file'), + "The license_file parameter is deprecated, " + "use license_files instead.", + DeprecationWarning, + ), + 'license_files': parse_list, + 'description': parse_file, + 'long_description': parse_file, + 'version': self._parse_version, + 'project_urls': parse_dict, + } + + def _parse_version(self, value): + """Parses `version` option value. + + :param value: + :rtype: str + + """ + version = self._parse_file(value) + + if version != value: + version = version.strip() + # Be strict about versions loaded from file because it's easy to + # accidentally include newlines and other unintended content + try: + Version(version) + except InvalidVersion: + tmpl = ( + 'Version loaded from {value} does not ' + 'comply with PEP 440: {version}' + ) + raise DistutilsOptionError(tmpl.format(**locals())) + + return version + + version = self._parse_attr(value, self.package_dir) + + if callable(version): + version = version() + + if not isinstance(version, str): + if hasattr(version, '__iter__'): + version = '.'.join(map(str, version)) + else: + version = '%s' % version + + return version + + +class ConfigOptionsHandler(ConfigHandler): + + section_prefix = 'options' + + @property + def parsers(self): + """Metadata item name to parser function mapping.""" + parse_list = self._parse_list + parse_list_semicolon = partial(self._parse_list, separator=';') + parse_bool = self._parse_bool + parse_dict = self._parse_dict + parse_cmdclass = self._parse_cmdclass + + return { + 'zip_safe': parse_bool, + 'include_package_data': parse_bool, + 'package_dir': parse_dict, + 'scripts': parse_list, + 'eager_resources': parse_list, + 'dependency_links': parse_list, + 'namespace_packages': parse_list, + 'install_requires': parse_list_semicolon, + 'setup_requires': parse_list_semicolon, + 'tests_require': parse_list_semicolon, + 'packages': self._parse_packages, + 'entry_points': self._parse_file, + 'py_modules': parse_list, + 'python_requires': SpecifierSet, + 'cmdclass': parse_cmdclass, + } + + def _parse_cmdclass(self, value): + def resolve_class(qualified_class_name): + idx = qualified_class_name.rfind('.') + class_name = qualified_class_name[idx + 1 :] + pkg_name = qualified_class_name[:idx] + + module = __import__(pkg_name) + + return getattr(module, class_name) + + return {k: resolve_class(v) for k, v in self._parse_dict(value).items()} + + def _parse_packages(self, value): + """Parses `packages` option value. + + :param value: + :rtype: list + """ + find_directives = ['find:', 'find_namespace:'] + trimmed_value = value.strip() + + if trimmed_value not in find_directives: + return self._parse_list(value) + + findns = trimmed_value == find_directives[1] + + # Read function arguments from a dedicated section. + find_kwargs = self.parse_section_packages__find( + self.sections.get('packages.find', {}) + ) + + if findns: + from setuptools import find_namespace_packages as find_packages + else: + from setuptools import find_packages + + return find_packages(**find_kwargs) + + def parse_section_packages__find(self, section_options): + """Parses `packages.find` configuration file section. + + To be used in conjunction with _parse_packages(). + + :param dict section_options: + """ + section_data = self._parse_section_to_dict(section_options, self._parse_list) + + valid_keys = ['where', 'include', 'exclude'] + + find_kwargs = dict( + [(k, v) for k, v in section_data.items() if k in valid_keys and v] + ) + + where = find_kwargs.get('where') + if where is not None: + find_kwargs['where'] = where[0] # cast list to single val + + return find_kwargs + + def parse_section_entry_points(self, section_options): + """Parses `entry_points` configuration file section. + + :param dict section_options: + """ + parsed = self._parse_section_to_dict(section_options, self._parse_list) + self['entry_points'] = parsed + + def _parse_package_data(self, section_options): + parsed = self._parse_section_to_dict(section_options, self._parse_list) + + root = parsed.get('*') + if root: + parsed[''] = root + del parsed['*'] + + return parsed + + def parse_section_package_data(self, section_options): + """Parses `package_data` configuration file section. + + :param dict section_options: + """ + self['package_data'] = self._parse_package_data(section_options) + + def parse_section_exclude_package_data(self, section_options): + """Parses `exclude_package_data` configuration file section. + + :param dict section_options: + """ + self['exclude_package_data'] = self._parse_package_data(section_options) + + def parse_section_extras_require(self, section_options): + """Parses `extras_require` configuration file section. + + :param dict section_options: + """ + parse_list = partial(self._parse_list, separator=';') + self['extras_require'] = self._parse_section_to_dict( + section_options, parse_list + ) + + def parse_section_data_files(self, section_options): + """Parses `data_files` configuration file section. + + :param dict section_options: + """ + parsed = self._parse_section_to_dict(section_options, self._parse_list_glob) + self['data_files'] = [(k, v) for k, v in parsed.items()] diff --git a/venv/lib/python3.10/site-packages/setuptools/dep_util.py b/venv/lib/python3.10/site-packages/setuptools/dep_util.py new file mode 100644 index 00000000..521eb716 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/dep_util.py @@ -0,0 +1,25 @@ +from distutils.dep_util import newer_group + + +# yes, this is was almost entirely copy-pasted from +# 'newer_pairwise()', this is just another convenience +# function. +def newer_pairwise_group(sources_groups, targets): + """Walk both arguments in parallel, testing if each source group is newer + than its corresponding target. Returns a pair of lists (sources_groups, + targets) where sources is newer than target, according to the semantics + of 'newer_group()'. + """ + if len(sources_groups) != len(targets): + raise ValueError( + "'sources_group' and 'targets' must be the same length") + + # build a pair of lists (sources_groups, targets) where source is newer + n_sources = [] + n_targets = [] + for i in range(len(sources_groups)): + if newer_group(sources_groups[i], targets[i]): + n_sources.append(sources_groups[i]) + n_targets.append(targets[i]) + + return n_sources, n_targets diff --git a/venv/lib/python3.10/site-packages/setuptools/depends.py b/venv/lib/python3.10/site-packages/setuptools/depends.py new file mode 100644 index 00000000..adffd12d --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/depends.py @@ -0,0 +1,176 @@ +import sys +import marshal +import contextlib +import dis + +from setuptools.extern.packaging import version + +from ._imp import find_module, PY_COMPILED, PY_FROZEN, PY_SOURCE +from . import _imp + + +__all__ = [ + 'Require', 'find_module', 'get_module_constant', 'extract_constant' +] + + +class Require: + """A prerequisite to building or installing a distribution""" + + def __init__( + self, name, requested_version, module, homepage='', + attribute=None, format=None): + + if format is None and requested_version is not None: + format = version.Version + + if format is not None: + requested_version = format(requested_version) + if attribute is None: + attribute = '__version__' + + self.__dict__.update(locals()) + del self.self + + def full_name(self): + """Return full package/distribution name, w/version""" + if self.requested_version is not None: + return '%s-%s' % (self.name, self.requested_version) + return self.name + + def version_ok(self, version): + """Is 'version' sufficiently up-to-date?""" + return self.attribute is None or self.format is None or \ + str(version) != "unknown" and self.format(version) >= self.requested_version + + def get_version(self, paths=None, default="unknown"): + """Get version number of installed module, 'None', or 'default' + + Search 'paths' for module. If not found, return 'None'. If found, + return the extracted version attribute, or 'default' if no version + attribute was specified, or the value cannot be determined without + importing the module. The version is formatted according to the + requirement's version format (if any), unless it is 'None' or the + supplied 'default'. + """ + + if self.attribute is None: + try: + f, p, i = find_module(self.module, paths) + if f: + f.close() + return default + except ImportError: + return None + + v = get_module_constant(self.module, self.attribute, default, paths) + + if v is not None and v is not default and self.format is not None: + return self.format(v) + + return v + + def is_present(self, paths=None): + """Return true if dependency is present on 'paths'""" + return self.get_version(paths) is not None + + def is_current(self, paths=None): + """Return true if dependency is present and up-to-date on 'paths'""" + version = self.get_version(paths) + if version is None: + return False + return self.version_ok(str(version)) + + +def maybe_close(f): + @contextlib.contextmanager + def empty(): + yield + return + if not f: + return empty() + + return contextlib.closing(f) + + +def get_module_constant(module, symbol, default=-1, paths=None): + """Find 'module' by searching 'paths', and extract 'symbol' + + Return 'None' if 'module' does not exist on 'paths', or it does not define + 'symbol'. If the module defines 'symbol' as a constant, return the + constant. Otherwise, return 'default'.""" + + try: + f, path, (suffix, mode, kind) = info = find_module(module, paths) + except ImportError: + # Module doesn't exist + return None + + with maybe_close(f): + if kind == PY_COMPILED: + f.read(8) # skip magic & date + code = marshal.load(f) + elif kind == PY_FROZEN: + code = _imp.get_frozen_object(module, paths) + elif kind == PY_SOURCE: + code = compile(f.read(), path, 'exec') + else: + # Not something we can parse; we'll have to import it. :( + imported = _imp.get_module(module, paths, info) + return getattr(imported, symbol, None) + + return extract_constant(code, symbol, default) + + +def extract_constant(code, symbol, default=-1): + """Extract the constant value of 'symbol' from 'code' + + If the name 'symbol' is bound to a constant value by the Python code + object 'code', return that value. If 'symbol' is bound to an expression, + return 'default'. Otherwise, return 'None'. + + Return value is based on the first assignment to 'symbol'. 'symbol' must + be a global, or at least a non-"fast" local in the code block. That is, + only 'STORE_NAME' and 'STORE_GLOBAL' opcodes are checked, and 'symbol' + must be present in 'code.co_names'. + """ + if symbol not in code.co_names: + # name's not there, can't possibly be an assignment + return None + + name_idx = list(code.co_names).index(symbol) + + STORE_NAME = 90 + STORE_GLOBAL = 97 + LOAD_CONST = 100 + + const = default + + for byte_code in dis.Bytecode(code): + op = byte_code.opcode + arg = byte_code.arg + + if op == LOAD_CONST: + const = code.co_consts[arg] + elif arg == name_idx and (op == STORE_NAME or op == STORE_GLOBAL): + return const + else: + const = default + + +def _update_globals(): + """ + Patch the globals to remove the objects not available on some platforms. + + XXX it'd be better to test assertions about bytecode instead. + """ + + if not sys.platform.startswith('java') and sys.platform != 'cli': + return + incompatible = 'extract_constant', 'get_module_constant' + for name in incompatible: + del globals()[name] + __all__.remove(name) + + +_update_globals() diff --git a/venv/lib/python3.10/site-packages/setuptools/dist.py b/venv/lib/python3.10/site-packages/setuptools/dist.py new file mode 100644 index 00000000..4adb2d26 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/dist.py @@ -0,0 +1,1156 @@ +# -*- coding: utf-8 -*- +__all__ = ['Distribution'] + +import io +import sys +import re +import os +import warnings +import numbers +import distutils.log +import distutils.core +import distutils.cmd +import distutils.dist +import distutils.command +from distutils.util import strtobool +from distutils.debug import DEBUG +from distutils.fancy_getopt import translate_longopt +from glob import iglob +import itertools +import textwrap +from typing import List, Optional, TYPE_CHECKING + +from collections import defaultdict +from email import message_from_file + +from distutils.errors import DistutilsOptionError, DistutilsSetupError +from distutils.util import rfc822_escape + +from setuptools.extern import packaging +from setuptools.extern import ordered_set +from setuptools.extern.more_itertools import unique_everseen + +from . import SetuptoolsDeprecationWarning + +import setuptools +import setuptools.command +from setuptools import windows_support +from setuptools.monkey import get_unpatched +from setuptools.config import parse_configuration +import pkg_resources +from setuptools.extern.packaging import version + +if TYPE_CHECKING: + from email.message import Message + +__import__('setuptools.extern.packaging.specifiers') +__import__('setuptools.extern.packaging.version') + + +def _get_unpatched(cls): + warnings.warn("Do not call this function", DistDeprecationWarning) + return get_unpatched(cls) + + +def get_metadata_version(self): + mv = getattr(self, 'metadata_version', None) + if mv is None: + mv = version.Version('2.1') + self.metadata_version = mv + return mv + + +def rfc822_unescape(content: str) -> str: + """Reverse RFC-822 escaping by removing leading whitespaces from content.""" + lines = content.splitlines() + if len(lines) == 1: + return lines[0].lstrip() + return '\n'.join((lines[0].lstrip(), textwrap.dedent('\n'.join(lines[1:])))) + + +def _read_field_from_msg(msg: "Message", field: str) -> Optional[str]: + """Read Message header field.""" + value = msg[field] + if value == 'UNKNOWN': + return None + return value + + +def _read_field_unescaped_from_msg(msg: "Message", field: str) -> Optional[str]: + """Read Message header field and apply rfc822_unescape.""" + value = _read_field_from_msg(msg, field) + if value is None: + return value + return rfc822_unescape(value) + + +def _read_list_from_msg(msg: "Message", field: str) -> Optional[List[str]]: + """Read Message header field and return all results as list.""" + values = msg.get_all(field, None) + if values == []: + return None + return values + + +def _read_payload_from_msg(msg: "Message") -> Optional[str]: + value = msg.get_payload().strip() + if value == 'UNKNOWN': + return None + return value + + +def read_pkg_file(self, file): + """Reads the metadata values from a file object.""" + msg = message_from_file(file) + + self.metadata_version = version.Version(msg['metadata-version']) + self.name = _read_field_from_msg(msg, 'name') + self.version = _read_field_from_msg(msg, 'version') + self.description = _read_field_from_msg(msg, 'summary') + # we are filling author only. + self.author = _read_field_from_msg(msg, 'author') + self.maintainer = None + self.author_email = _read_field_from_msg(msg, 'author-email') + self.maintainer_email = None + self.url = _read_field_from_msg(msg, 'home-page') + self.license = _read_field_unescaped_from_msg(msg, 'license') + + if 'download-url' in msg: + self.download_url = _read_field_from_msg(msg, 'download-url') + else: + self.download_url = None + + self.long_description = _read_field_unescaped_from_msg(msg, 'description') + if ( + self.long_description is None and + self.metadata_version >= version.Version('2.1') + ): + self.long_description = _read_payload_from_msg(msg) + self.description = _read_field_from_msg(msg, 'summary') + + if 'keywords' in msg: + self.keywords = _read_field_from_msg(msg, 'keywords').split(',') + + self.platforms = _read_list_from_msg(msg, 'platform') + self.classifiers = _read_list_from_msg(msg, 'classifier') + + # PEP 314 - these fields only exist in 1.1 + if self.metadata_version == version.Version('1.1'): + self.requires = _read_list_from_msg(msg, 'requires') + self.provides = _read_list_from_msg(msg, 'provides') + self.obsoletes = _read_list_from_msg(msg, 'obsoletes') + else: + self.requires = None + self.provides = None + self.obsoletes = None + + self.license_files = _read_list_from_msg(msg, 'license-file') + + +def single_line(val): + """ + Quick and dirty validation for Summary pypa/setuptools#1390. + """ + if '\n' in val: + # TODO: Replace with `raise ValueError("newlines not allowed")` + # after reviewing #2893. + warnings.warn("newlines not allowed and will break in the future") + val = val.strip().split('\n')[0] + return val + + +# Based on Python 3.5 version +def write_pkg_file(self, file): # noqa: C901 # is too complex (14) # FIXME + """Write the PKG-INFO format data to a file object.""" + version = self.get_metadata_version() + + def write_field(key, value): + file.write("%s: %s\n" % (key, value)) + + write_field('Metadata-Version', str(version)) + write_field('Name', self.get_name()) + write_field('Version', self.get_version()) + write_field('Summary', single_line(self.get_description())) + write_field('Home-page', self.get_url()) + + optional_fields = ( + ('Author', 'author'), + ('Author-email', 'author_email'), + ('Maintainer', 'maintainer'), + ('Maintainer-email', 'maintainer_email'), + ) + + for field, attr in optional_fields: + attr_val = getattr(self, attr, None) + if attr_val is not None: + write_field(field, attr_val) + + license = rfc822_escape(self.get_license()) + write_field('License', license) + if self.download_url: + write_field('Download-URL', self.download_url) + for project_url in self.project_urls.items(): + write_field('Project-URL', '%s, %s' % project_url) + + keywords = ','.join(self.get_keywords()) + if keywords: + write_field('Keywords', keywords) + + for platform in self.get_platforms(): + write_field('Platform', platform) + + self._write_list(file, 'Classifier', self.get_classifiers()) + + # PEP 314 + self._write_list(file, 'Requires', self.get_requires()) + self._write_list(file, 'Provides', self.get_provides()) + self._write_list(file, 'Obsoletes', self.get_obsoletes()) + + # Setuptools specific for PEP 345 + if hasattr(self, 'python_requires'): + write_field('Requires-Python', self.python_requires) + + # PEP 566 + if self.long_description_content_type: + write_field('Description-Content-Type', self.long_description_content_type) + if self.provides_extras: + for extra in sorted(self.provides_extras): + write_field('Provides-Extra', extra) + + self._write_list(file, 'License-File', self.license_files or []) + + file.write("\n%s\n\n" % self.get_long_description()) + + +sequence = tuple, list + + +def check_importable(dist, attr, value): + try: + ep = pkg_resources.EntryPoint.parse('x=' + value) + assert not ep.extras + except (TypeError, ValueError, AttributeError, AssertionError) as e: + raise DistutilsSetupError( + "%r must be importable 'module:attrs' string (got %r)" % (attr, value) + ) from e + + +def assert_string_list(dist, attr, value): + """Verify that value is a string list""" + try: + # verify that value is a list or tuple to exclude unordered + # or single-use iterables + assert isinstance(value, (list, tuple)) + # verify that elements of value are strings + assert ''.join(value) != value + except (TypeError, ValueError, AttributeError, AssertionError) as e: + raise DistutilsSetupError( + "%r must be a list of strings (got %r)" % (attr, value) + ) from e + + +def check_nsp(dist, attr, value): + """Verify that namespace packages are valid""" + ns_packages = value + assert_string_list(dist, attr, ns_packages) + for nsp in ns_packages: + if not dist.has_contents_for(nsp): + raise DistutilsSetupError( + "Distribution contains no modules or packages for " + + "namespace package %r" % nsp + ) + parent, sep, child = nsp.rpartition('.') + if parent and parent not in ns_packages: + distutils.log.warn( + "WARNING: %r is declared as a package namespace, but %r" + " is not: please correct this in setup.py", + nsp, + parent, + ) + + +def check_extras(dist, attr, value): + """Verify that extras_require mapping is valid""" + try: + list(itertools.starmap(_check_extra, value.items())) + except (TypeError, ValueError, AttributeError) as e: + raise DistutilsSetupError( + "'extras_require' must be a dictionary whose values are " + "strings or lists of strings containing valid project/version " + "requirement specifiers." + ) from e + + +def _check_extra(extra, reqs): + name, sep, marker = extra.partition(':') + if marker and pkg_resources.invalid_marker(marker): + raise DistutilsSetupError("Invalid environment marker: " + marker) + list(pkg_resources.parse_requirements(reqs)) + + +def assert_bool(dist, attr, value): + """Verify that value is True, False, 0, or 1""" + if bool(value) != value: + tmpl = "{attr!r} must be a boolean value (got {value!r})" + raise DistutilsSetupError(tmpl.format(attr=attr, value=value)) + + +def invalid_unless_false(dist, attr, value): + if not value: + warnings.warn(f"{attr} is ignored.", DistDeprecationWarning) + return + raise DistutilsSetupError(f"{attr} is invalid.") + + +def check_requirements(dist, attr, value): + """Verify that install_requires is a valid requirements list""" + try: + list(pkg_resources.parse_requirements(value)) + if isinstance(value, (dict, set)): + raise TypeError("Unordered types are not allowed") + except (TypeError, ValueError) as error: + tmpl = ( + "{attr!r} must be a string or list of strings " + "containing valid project/version requirement specifiers; {error}" + ) + raise DistutilsSetupError(tmpl.format(attr=attr, error=error)) from error + + +def check_specifier(dist, attr, value): + """Verify that value is a valid version specifier""" + try: + packaging.specifiers.SpecifierSet(value) + except (packaging.specifiers.InvalidSpecifier, AttributeError) as error: + tmpl = ( + "{attr!r} must be a string " "containing valid version specifiers; {error}" + ) + raise DistutilsSetupError(tmpl.format(attr=attr, error=error)) from error + + +def check_entry_points(dist, attr, value): + """Verify that entry_points map is parseable""" + try: + pkg_resources.EntryPoint.parse_map(value) + except ValueError as e: + raise DistutilsSetupError(e) from e + + +def check_test_suite(dist, attr, value): + if not isinstance(value, str): + raise DistutilsSetupError("test_suite must be a string") + + +def check_package_data(dist, attr, value): + """Verify that value is a dictionary of package names to glob lists""" + if not isinstance(value, dict): + raise DistutilsSetupError( + "{!r} must be a dictionary mapping package names to lists of " + "string wildcard patterns".format(attr) + ) + for k, v in value.items(): + if not isinstance(k, str): + raise DistutilsSetupError( + "keys of {!r} dict must be strings (got {!r})".format(attr, k) + ) + assert_string_list(dist, 'values of {!r} dict'.format(attr), v) + + +def check_packages(dist, attr, value): + for pkgname in value: + if not re.match(r'\w+(\.\w+)*', pkgname): + distutils.log.warn( + "WARNING: %r not a valid package name; please use only " + ".-separated package names in setup.py", + pkgname, + ) + + +_Distribution = get_unpatched(distutils.core.Distribution) + + +class Distribution(_Distribution): + """Distribution with support for tests and package data + + This is an enhanced version of 'distutils.dist.Distribution' that + effectively adds the following new optional keyword arguments to 'setup()': + + 'install_requires' -- a string or sequence of strings specifying project + versions that the distribution requires when installed, in the format + used by 'pkg_resources.require()'. They will be installed + automatically when the package is installed. If you wish to use + packages that are not available in PyPI, or want to give your users an + alternate download location, you can add a 'find_links' option to the + '[easy_install]' section of your project's 'setup.cfg' file, and then + setuptools will scan the listed web pages for links that satisfy the + requirements. + + 'extras_require' -- a dictionary mapping names of optional "extras" to the + additional requirement(s) that using those extras incurs. For example, + this:: + + extras_require = dict(reST = ["docutils>=0.3", "reSTedit"]) + + indicates that the distribution can optionally provide an extra + capability called "reST", but it can only be used if docutils and + reSTedit are installed. If the user installs your package using + EasyInstall and requests one of your extras, the corresponding + additional requirements will be installed if needed. + + 'test_suite' -- the name of a test suite to run for the 'test' command. + If the user runs 'python setup.py test', the package will be installed, + and the named test suite will be run. The format is the same as + would be used on a 'unittest.py' command line. That is, it is the + dotted name of an object to import and call to generate a test suite. + + 'package_data' -- a dictionary mapping package names to lists of filenames + or globs to use to find data files contained in the named packages. + If the dictionary has filenames or globs listed under '""' (the empty + string), those names will be searched for in every package, in addition + to any names for the specific package. Data files found using these + names/globs will be installed along with the package, in the same + location as the package. Note that globs are allowed to reference + the contents of non-package subdirectories, as long as you use '/' as + a path separator. (Globs are automatically converted to + platform-specific paths at runtime.) + + In addition to these new keywords, this class also has several new methods + for manipulating the distribution's contents. For example, the 'include()' + and 'exclude()' methods can be thought of as in-place add and subtract + commands that add or remove packages, modules, extensions, and so on from + the distribution. + """ + + _DISTUTILS_UNSUPPORTED_METADATA = { + 'long_description_content_type': lambda: None, + 'project_urls': dict, + 'provides_extras': ordered_set.OrderedSet, + 'license_file': lambda: None, + 'license_files': lambda: None, + } + + _patched_dist = None + + def patch_missing_pkg_info(self, attrs): + # Fake up a replacement for the data that would normally come from + # PKG-INFO, but which might not yet be built if this is a fresh + # checkout. + # + if not attrs or 'name' not in attrs or 'version' not in attrs: + return + key = pkg_resources.safe_name(str(attrs['name'])).lower() + dist = pkg_resources.working_set.by_key.get(key) + if dist is not None and not dist.has_metadata('PKG-INFO'): + dist._version = pkg_resources.safe_version(str(attrs['version'])) + self._patched_dist = dist + + def __init__(self, attrs=None): + have_package_data = hasattr(self, "package_data") + if not have_package_data: + self.package_data = {} + attrs = attrs or {} + self.dist_files = [] + # Filter-out setuptools' specific options. + self.src_root = attrs.pop("src_root", None) + self.patch_missing_pkg_info(attrs) + self.dependency_links = attrs.pop('dependency_links', []) + self.setup_requires = attrs.pop('setup_requires', []) + for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'): + vars(self).setdefault(ep.name, None) + _Distribution.__init__( + self, + { + k: v + for k, v in attrs.items() + if k not in self._DISTUTILS_UNSUPPORTED_METADATA + }, + ) + + self._set_metadata_defaults(attrs) + + self.metadata.version = self._normalize_version( + self._validate_version(self.metadata.version) + ) + self._finalize_requires() + + def _set_metadata_defaults(self, attrs): + """ + Fill-in missing metadata fields not supported by distutils. + Some fields may have been set by other tools (e.g. pbr). + Those fields (vars(self.metadata)) take precedence to + supplied attrs. + """ + for option, default in self._DISTUTILS_UNSUPPORTED_METADATA.items(): + vars(self.metadata).setdefault(option, attrs.get(option, default())) + + @staticmethod + def _normalize_version(version): + if isinstance(version, setuptools.sic) or version is None: + return version + + normalized = str(packaging.version.Version(version)) + if version != normalized: + tmpl = "Normalizing '{version}' to '{normalized}'" + warnings.warn(tmpl.format(**locals())) + return normalized + return version + + @staticmethod + def _validate_version(version): + if isinstance(version, numbers.Number): + # Some people apparently take "version number" too literally :) + version = str(version) + + if version is not None: + try: + packaging.version.Version(version) + except (packaging.version.InvalidVersion, TypeError): + warnings.warn( + "The version specified (%r) is an invalid version, this " + "may not work as expected with newer versions of " + "setuptools, pip, and PyPI. Please see PEP 440 for more " + "details." % version + ) + return setuptools.sic(version) + return version + + def _finalize_requires(self): + """ + Set `metadata.python_requires` and fix environment markers + in `install_requires` and `extras_require`. + """ + if getattr(self, 'python_requires', None): + self.metadata.python_requires = self.python_requires + + if getattr(self, 'extras_require', None): + for extra in self.extras_require.keys(): + # Since this gets called multiple times at points where the + # keys have become 'converted' extras, ensure that we are only + # truly adding extras we haven't seen before here. + extra = extra.split(':')[0] + if extra: + self.metadata.provides_extras.add(extra) + + self._convert_extras_requirements() + self._move_install_requirements_markers() + + def _convert_extras_requirements(self): + """ + Convert requirements in `extras_require` of the form + `"extra": ["barbazquux; {marker}"]` to + `"extra:{marker}": ["barbazquux"]`. + """ + spec_ext_reqs = getattr(self, 'extras_require', None) or {} + self._tmp_extras_require = defaultdict(list) + for section, v in spec_ext_reqs.items(): + # Do not strip empty sections. + self._tmp_extras_require[section] + for r in pkg_resources.parse_requirements(v): + suffix = self._suffix_for(r) + self._tmp_extras_require[section + suffix].append(r) + + @staticmethod + def _suffix_for(req): + """ + For a requirement, return the 'extras_require' suffix for + that requirement. + """ + return ':' + str(req.marker) if req.marker else '' + + def _move_install_requirements_markers(self): + """ + Move requirements in `install_requires` that are using environment + markers `extras_require`. + """ + + # divide the install_requires into two sets, simple ones still + # handled by install_requires and more complex ones handled + # by extras_require. + + def is_simple_req(req): + return not req.marker + + spec_inst_reqs = getattr(self, 'install_requires', None) or () + inst_reqs = list(pkg_resources.parse_requirements(spec_inst_reqs)) + simple_reqs = filter(is_simple_req, inst_reqs) + complex_reqs = itertools.filterfalse(is_simple_req, inst_reqs) + self.install_requires = list(map(str, simple_reqs)) + + for r in complex_reqs: + self._tmp_extras_require[':' + str(r.marker)].append(r) + self.extras_require = dict( + (k, [str(r) for r in map(self._clean_req, v)]) + for k, v in self._tmp_extras_require.items() + ) + + def _clean_req(self, req): + """ + Given a Requirement, remove environment markers and return it. + """ + req.marker = None + return req + + def _finalize_license_files(self): + """Compute names of all license files which should be included.""" + license_files: Optional[List[str]] = self.metadata.license_files + patterns: List[str] = license_files if license_files else [] + + license_file: Optional[str] = self.metadata.license_file + if license_file and license_file not in patterns: + patterns.append(license_file) + + if license_files is None and license_file is None: + # Default patterns match the ones wheel uses + # See https://wheel.readthedocs.io/en/stable/user_guide.html + # -> 'Including license files in the generated wheel file' + patterns = ('LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*') + + self.metadata.license_files = list( + unique_everseen(self._expand_patterns(patterns)) + ) + + @staticmethod + def _expand_patterns(patterns): + """ + >>> list(Distribution._expand_patterns(['LICENSE'])) + ['LICENSE'] + >>> list(Distribution._expand_patterns(['setup.cfg', 'LIC*'])) + ['setup.cfg', 'LICENSE'] + """ + return ( + path + for pattern in patterns + for path in sorted(iglob(pattern)) + if not path.endswith('~') and os.path.isfile(path) + ) + + # FIXME: 'Distribution._parse_config_files' is too complex (14) + def _parse_config_files(self, filenames=None): # noqa: C901 + """ + Adapted from distutils.dist.Distribution.parse_config_files, + this method provides the same functionality in subtly-improved + ways. + """ + from configparser import ConfigParser + + # Ignore install directory options if we have a venv + ignore_options = ( + [] + if sys.prefix == sys.base_prefix + else [ + 'install-base', + 'install-platbase', + 'install-lib', + 'install-platlib', + 'install-purelib', + 'install-headers', + 'install-scripts', + 'install-data', + 'prefix', + 'exec-prefix', + 'home', + 'user', + 'root', + ] + ) + + ignore_options = frozenset(ignore_options) + + if filenames is None: + filenames = self.find_config_files() + + if DEBUG: + self.announce("Distribution.parse_config_files():") + + parser = ConfigParser() + parser.optionxform = str + for filename in filenames: + with io.open(filename, encoding='utf-8') as reader: + if DEBUG: + self.announce(" reading {filename}".format(**locals())) + parser.read_file(reader) + for section in parser.sections(): + options = parser.options(section) + opt_dict = self.get_option_dict(section) + + for opt in options: + if opt == '__name__' or opt in ignore_options: + continue + + val = parser.get(section, opt) + opt = self.warn_dash_deprecation(opt, section) + opt = self.make_option_lowercase(opt, section) + opt_dict[opt] = (filename, val) + + # Make the ConfigParser forget everything (so we retain + # the original filenames that options come from) + parser.__init__() + + if 'global' not in self.command_options: + return + + # If there was a "global" section in the config file, use it + # to set Distribution options. + + for (opt, (src, val)) in self.command_options['global'].items(): + alias = self.negative_opt.get(opt) + if alias: + val = not strtobool(val) + elif opt in ('verbose', 'dry_run'): # ugh! + val = strtobool(val) + + try: + setattr(self, alias or opt, val) + except ValueError as e: + raise DistutilsOptionError(e) from e + + def warn_dash_deprecation(self, opt, section): + if section in ( + 'options.extras_require', + 'options.data_files', + ): + return opt + + underscore_opt = opt.replace('-', '_') + commands = distutils.command.__all__ + self._setuptools_commands() + if ( + not section.startswith('options') + and section != 'metadata' + and section not in commands + ): + return underscore_opt + + if '-' in opt: + warnings.warn( + "Usage of dash-separated '%s' will not be supported in future " + "versions. Please use the underscore name '%s' instead" + % (opt, underscore_opt) + ) + return underscore_opt + + def _setuptools_commands(self): + try: + dist = pkg_resources.get_distribution('setuptools') + return list(dist.get_entry_map('distutils.commands')) + except pkg_resources.DistributionNotFound: + # during bootstrapping, distribution doesn't exist + return [] + + def make_option_lowercase(self, opt, section): + if section != 'metadata' or opt.islower(): + return opt + + lowercase_opt = opt.lower() + warnings.warn( + "Usage of uppercase key '%s' in '%s' will be deprecated in future " + "versions. Please use lowercase '%s' instead" + % (opt, section, lowercase_opt) + ) + return lowercase_opt + + # FIXME: 'Distribution._set_command_options' is too complex (14) + def _set_command_options(self, command_obj, option_dict=None): # noqa: C901 + """ + Set the options for 'command_obj' from 'option_dict'. Basically + this means copying elements of a dictionary ('option_dict') to + attributes of an instance ('command'). + + 'command_obj' must be a Command instance. If 'option_dict' is not + supplied, uses the standard option dictionary for this command + (from 'self.command_options'). + + (Adopted from distutils.dist.Distribution._set_command_options) + """ + command_name = command_obj.get_command_name() + if option_dict is None: + option_dict = self.get_option_dict(command_name) + + if DEBUG: + self.announce(" setting options for '%s' command:" % command_name) + for (option, (source, value)) in option_dict.items(): + if DEBUG: + self.announce(" %s = %s (from %s)" % (option, value, source)) + try: + bool_opts = [translate_longopt(o) for o in command_obj.boolean_options] + except AttributeError: + bool_opts = [] + try: + neg_opt = command_obj.negative_opt + except AttributeError: + neg_opt = {} + + try: + is_string = isinstance(value, str) + if option in neg_opt and is_string: + setattr(command_obj, neg_opt[option], not strtobool(value)) + elif option in bool_opts and is_string: + setattr(command_obj, option, strtobool(value)) + elif hasattr(command_obj, option): + setattr(command_obj, option, value) + else: + raise DistutilsOptionError( + "error in %s: command '%s' has no such option '%s'" + % (source, command_name, option) + ) + except ValueError as e: + raise DistutilsOptionError(e) from e + + def parse_config_files(self, filenames=None, ignore_option_errors=False): + """Parses configuration files from various levels + and loads configuration. + + """ + self._parse_config_files(filenames=filenames) + + parse_configuration( + self, self.command_options, ignore_option_errors=ignore_option_errors + ) + self._finalize_requires() + self._finalize_license_files() + + def fetch_build_eggs(self, requires): + """Resolve pre-setup requirements""" + resolved_dists = pkg_resources.working_set.resolve( + pkg_resources.parse_requirements(requires), + installer=self.fetch_build_egg, + replace_conflicting=True, + ) + for dist in resolved_dists: + pkg_resources.working_set.add(dist, replace=True) + return resolved_dists + + def finalize_options(self): + """ + Allow plugins to apply arbitrary operations to the + distribution. Each hook may optionally define a 'order' + to influence the order of execution. Smaller numbers + go first and the default is 0. + """ + group = 'setuptools.finalize_distribution_options' + + def by_order(hook): + return getattr(hook, 'order', 0) + + defined = pkg_resources.iter_entry_points(group) + filtered = itertools.filterfalse(self._removed, defined) + loaded = map(lambda e: e.load(), filtered) + for ep in sorted(loaded, key=by_order): + ep(self) + + @staticmethod + def _removed(ep): + """ + When removing an entry point, if metadata is loaded + from an older version of Setuptools, that removed + entry point will attempt to be loaded and will fail. + See #2765 for more details. + """ + removed = { + # removed 2021-09-05 + '2to3_doctests', + } + return ep.name in removed + + def _finalize_setup_keywords(self): + for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'): + value = getattr(self, ep.name, None) + if value is not None: + ep.require(installer=self.fetch_build_egg) + ep.load()(self, ep.name, value) + + def get_egg_cache_dir(self): + egg_cache_dir = os.path.join(os.curdir, '.eggs') + if not os.path.exists(egg_cache_dir): + os.mkdir(egg_cache_dir) + windows_support.hide_file(egg_cache_dir) + readme_txt_filename = os.path.join(egg_cache_dir, 'README.txt') + with open(readme_txt_filename, 'w') as f: + f.write( + 'This directory contains eggs that were downloaded ' + 'by setuptools to build, test, and run plug-ins.\n\n' + ) + f.write( + 'This directory caches those eggs to prevent ' + 'repeated downloads.\n\n' + ) + f.write('However, it is safe to delete this directory.\n\n') + + return egg_cache_dir + + def fetch_build_egg(self, req): + """Fetch an egg needed for building""" + from setuptools.installer import fetch_build_egg + + return fetch_build_egg(self, req) + + def get_command_class(self, command): + """Pluggable version of get_command_class()""" + if command in self.cmdclass: + return self.cmdclass[command] + + eps = pkg_resources.iter_entry_points('distutils.commands', command) + for ep in eps: + ep.require(installer=self.fetch_build_egg) + self.cmdclass[command] = cmdclass = ep.load() + return cmdclass + else: + return _Distribution.get_command_class(self, command) + + def print_commands(self): + for ep in pkg_resources.iter_entry_points('distutils.commands'): + if ep.name not in self.cmdclass: + # don't require extras as the commands won't be invoked + cmdclass = ep.resolve() + self.cmdclass[ep.name] = cmdclass + return _Distribution.print_commands(self) + + def get_command_list(self): + for ep in pkg_resources.iter_entry_points('distutils.commands'): + if ep.name not in self.cmdclass: + # don't require extras as the commands won't be invoked + cmdclass = ep.resolve() + self.cmdclass[ep.name] = cmdclass + return _Distribution.get_command_list(self) + + def include(self, **attrs): + """Add items to distribution that are named in keyword arguments + + For example, 'dist.include(py_modules=["x"])' would add 'x' to + the distribution's 'py_modules' attribute, if it was not already + there. + + Currently, this method only supports inclusion for attributes that are + lists or tuples. If you need to add support for adding to other + attributes in this or a subclass, you can add an '_include_X' method, + where 'X' is the name of the attribute. The method will be called with + the value passed to 'include()'. So, 'dist.include(foo={"bar":"baz"})' + will try to call 'dist._include_foo({"bar":"baz"})', which can then + handle whatever special inclusion logic is needed. + """ + for k, v in attrs.items(): + include = getattr(self, '_include_' + k, None) + if include: + include(v) + else: + self._include_misc(k, v) + + def exclude_package(self, package): + """Remove packages, modules, and extensions in named package""" + + pfx = package + '.' + if self.packages: + self.packages = [ + p for p in self.packages if p != package and not p.startswith(pfx) + ] + + if self.py_modules: + self.py_modules = [ + p for p in self.py_modules if p != package and not p.startswith(pfx) + ] + + if self.ext_modules: + self.ext_modules = [ + p + for p in self.ext_modules + if p.name != package and not p.name.startswith(pfx) + ] + + def has_contents_for(self, package): + """Return true if 'exclude_package(package)' would do something""" + + pfx = package + '.' + + for p in self.iter_distribution_names(): + if p == package or p.startswith(pfx): + return True + + def _exclude_misc(self, name, value): + """Handle 'exclude()' for list/tuple attrs without a special handler""" + if not isinstance(value, sequence): + raise DistutilsSetupError( + "%s: setting must be a list or tuple (%r)" % (name, value) + ) + try: + old = getattr(self, name) + except AttributeError as e: + raise DistutilsSetupError("%s: No such distribution setting" % name) from e + if old is not None and not isinstance(old, sequence): + raise DistutilsSetupError( + name + ": this setting cannot be changed via include/exclude" + ) + elif old: + setattr(self, name, [item for item in old if item not in value]) + + def _include_misc(self, name, value): + """Handle 'include()' for list/tuple attrs without a special handler""" + + if not isinstance(value, sequence): + raise DistutilsSetupError("%s: setting must be a list (%r)" % (name, value)) + try: + old = getattr(self, name) + except AttributeError as e: + raise DistutilsSetupError("%s: No such distribution setting" % name) from e + if old is None: + setattr(self, name, value) + elif not isinstance(old, sequence): + raise DistutilsSetupError( + name + ": this setting cannot be changed via include/exclude" + ) + else: + new = [item for item in value if item not in old] + setattr(self, name, old + new) + + def exclude(self, **attrs): + """Remove items from distribution that are named in keyword arguments + + For example, 'dist.exclude(py_modules=["x"])' would remove 'x' from + the distribution's 'py_modules' attribute. Excluding packages uses + the 'exclude_package()' method, so all of the package's contained + packages, modules, and extensions are also excluded. + + Currently, this method only supports exclusion from attributes that are + lists or tuples. If you need to add support for excluding from other + attributes in this or a subclass, you can add an '_exclude_X' method, + where 'X' is the name of the attribute. The method will be called with + the value passed to 'exclude()'. So, 'dist.exclude(foo={"bar":"baz"})' + will try to call 'dist._exclude_foo({"bar":"baz"})', which can then + handle whatever special exclusion logic is needed. + """ + for k, v in attrs.items(): + exclude = getattr(self, '_exclude_' + k, None) + if exclude: + exclude(v) + else: + self._exclude_misc(k, v) + + def _exclude_packages(self, packages): + if not isinstance(packages, sequence): + raise DistutilsSetupError( + "packages: setting must be a list or tuple (%r)" % (packages,) + ) + list(map(self.exclude_package, packages)) + + def _parse_command_opts(self, parser, args): + # Remove --with-X/--without-X options when processing command args + self.global_options = self.__class__.global_options + self.negative_opt = self.__class__.negative_opt + + # First, expand any aliases + command = args[0] + aliases = self.get_option_dict('aliases') + while command in aliases: + src, alias = aliases[command] + del aliases[command] # ensure each alias can expand only once! + import shlex + + args[:1] = shlex.split(alias, True) + command = args[0] + + nargs = _Distribution._parse_command_opts(self, parser, args) + + # Handle commands that want to consume all remaining arguments + cmd_class = self.get_command_class(command) + if getattr(cmd_class, 'command_consumes_arguments', None): + self.get_option_dict(command)['args'] = ("command line", nargs) + if nargs is not None: + return [] + + return nargs + + def get_cmdline_options(self): + """Return a '{cmd: {opt:val}}' map of all command-line options + + Option names are all long, but do not include the leading '--', and + contain dashes rather than underscores. If the option doesn't take + an argument (e.g. '--quiet'), the 'val' is 'None'. + + Note that options provided by config files are intentionally excluded. + """ + + d = {} + + for cmd, opts in self.command_options.items(): + + for opt, (src, val) in opts.items(): + + if src != "command line": + continue + + opt = opt.replace('_', '-') + + if val == 0: + cmdobj = self.get_command_obj(cmd) + neg_opt = self.negative_opt.copy() + neg_opt.update(getattr(cmdobj, 'negative_opt', {})) + for neg, pos in neg_opt.items(): + if pos == opt: + opt = neg + val = None + break + else: + raise AssertionError("Shouldn't be able to get here") + + elif val == 1: + val = None + + d.setdefault(cmd, {})[opt] = val + + return d + + def iter_distribution_names(self): + """Yield all packages, modules, and extension names in distribution""" + + for pkg in self.packages or (): + yield pkg + + for module in self.py_modules or (): + yield module + + for ext in self.ext_modules or (): + if isinstance(ext, tuple): + name, buildinfo = ext + else: + name = ext.name + if name.endswith('module'): + name = name[:-6] + yield name + + def handle_display_options(self, option_order): + """If there were any non-global "display-only" options + (--help-commands or the metadata display options) on the command + line, display the requested info and return true; else return + false. + """ + import sys + + if self.help_commands: + return _Distribution.handle_display_options(self, option_order) + + # Stdout may be StringIO (e.g. in tests) + if not isinstance(sys.stdout, io.TextIOWrapper): + return _Distribution.handle_display_options(self, option_order) + + # Don't wrap stdout if utf-8 is already the encoding. Provides + # workaround for #334. + if sys.stdout.encoding.lower() in ('utf-8', 'utf8'): + return _Distribution.handle_display_options(self, option_order) + + # Print metadata in UTF-8 no matter the platform + encoding = sys.stdout.encoding + errors = sys.stdout.errors + newline = sys.platform != 'win32' and '\n' or None + line_buffering = sys.stdout.line_buffering + + sys.stdout = io.TextIOWrapper( + sys.stdout.detach(), 'utf-8', errors, newline, line_buffering + ) + try: + return _Distribution.handle_display_options(self, option_order) + finally: + sys.stdout = io.TextIOWrapper( + sys.stdout.detach(), encoding, errors, newline, line_buffering + ) + + +class DistDeprecationWarning(SetuptoolsDeprecationWarning): + """Class for warning about deprecations in dist in + setuptools. Not ignored by default, unlike DeprecationWarning.""" diff --git a/venv/lib/python3.10/site-packages/setuptools/errors.py b/venv/lib/python3.10/site-packages/setuptools/errors.py new file mode 100644 index 00000000..f4d35a63 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/errors.py @@ -0,0 +1,40 @@ +"""setuptools.errors + +Provides exceptions used by setuptools modules. +""" + +from distutils import errors as _distutils_errors +from distutils.errors import DistutilsError + + +class RemovedCommandError(DistutilsError, RuntimeError): + """Error used for commands that have been removed in setuptools. + + Since ``setuptools`` is built on ``distutils``, simply removing a command + from ``setuptools`` will make the behavior fall back to ``distutils``; this + error is raised if a command exists in ``distutils`` but has been actively + removed in ``setuptools``. + """ + + +# Re-export errors from distutils to facilitate the migration to PEP632 + +ByteCompileError = _distutils_errors.DistutilsByteCompileError +CCompilerError = _distutils_errors.CCompilerError +ClassError = _distutils_errors.DistutilsClassError +CompileError = _distutils_errors.CompileError +ExecError = _distutils_errors.DistutilsExecError +FileError = _distutils_errors.DistutilsFileError +InternalError = _distutils_errors.DistutilsInternalError +LibError = _distutils_errors.LibError +LinkError = _distutils_errors.LinkError +ModuleError = _distutils_errors.DistutilsModuleError +OptionError = _distutils_errors.DistutilsOptionError +PlatformError = _distutils_errors.DistutilsPlatformError +PreprocessError = _distutils_errors.PreprocessError +SetupError = _distutils_errors.DistutilsSetupError +TemplateError = _distutils_errors.DistutilsTemplateError +UnknownFileError = _distutils_errors.UnknownFileError + +# The root error class in the hierarchy +BaseError = _distutils_errors.DistutilsError diff --git a/venv/lib/python3.10/site-packages/setuptools/extension.py b/venv/lib/python3.10/site-packages/setuptools/extension.py new file mode 100644 index 00000000..1820722a --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/extension.py @@ -0,0 +1,55 @@ +import re +import functools +import distutils.core +import distutils.errors +import distutils.extension + +from .monkey import get_unpatched + + +def _have_cython(): + """ + Return True if Cython can be imported. + """ + cython_impl = 'Cython.Distutils.build_ext' + try: + # from (cython_impl) import build_ext + __import__(cython_impl, fromlist=['build_ext']).build_ext + return True + except Exception: + pass + return False + + +# for compatibility +have_pyrex = _have_cython + +_Extension = get_unpatched(distutils.core.Extension) + + +class Extension(_Extension): + """Extension that uses '.c' files in place of '.pyx' files""" + + def __init__(self, name, sources, *args, **kw): + # The *args is needed for compatibility as calls may use positional + # arguments. py_limited_api may be set only via keyword. + self.py_limited_api = kw.pop("py_limited_api", False) + _Extension.__init__(self, name, sources, *args, **kw) + + def _convert_pyx_sources_to_lang(self): + """ + Replace sources with .pyx extensions to sources with the target + language extension. This mechanism allows language authors to supply + pre-converted sources but to prefer the .pyx sources. + """ + if _have_cython(): + # the build has Cython, so allow it to compile the .pyx files + return + lang = self.language or '' + target_ext = '.cpp' if lang.lower() == 'c++' else '.c' + sub = functools.partial(re.sub, '.pyx$', target_ext) + self.sources = list(map(sub, self.sources)) + + +class Library(Extension): + """Just like a regular Extension, but built as a library instead""" diff --git a/venv/lib/python3.10/site-packages/setuptools/extern/__init__.py b/venv/lib/python3.10/site-packages/setuptools/extern/__init__.py new file mode 100644 index 00000000..baca1afa --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/extern/__init__.py @@ -0,0 +1,73 @@ +import importlib.util +import sys + + +class VendorImporter: + """ + A PEP 302 meta path importer for finding optionally-vendored + or otherwise naturally-installed packages from root_name. + """ + + def __init__(self, root_name, vendored_names=(), vendor_pkg=None): + self.root_name = root_name + self.vendored_names = set(vendored_names) + self.vendor_pkg = vendor_pkg or root_name.replace('extern', '_vendor') + + @property + def search_path(self): + """ + Search first the vendor package then as a natural package. + """ + yield self.vendor_pkg + '.' + yield '' + + def _module_matches_namespace(self, fullname): + """Figure out if the target module is vendored.""" + root, base, target = fullname.partition(self.root_name + '.') + return not root and any(map(target.startswith, self.vendored_names)) + + def load_module(self, fullname): + """ + Iterate over the search path to locate and load fullname. + """ + root, base, target = fullname.partition(self.root_name + '.') + for prefix in self.search_path: + try: + extant = prefix + target + __import__(extant) + mod = sys.modules[extant] + sys.modules[fullname] = mod + return mod + except ImportError: + pass + else: + raise ImportError( + "The '{target}' package is required; " + "normally this is bundled with this package so if you get " + "this warning, consult the packager of your " + "distribution.".format(**locals()) + ) + + def create_module(self, spec): + return self.load_module(spec.name) + + def exec_module(self, module): + pass + + def find_spec(self, fullname, path=None, target=None): + """Return a module spec for vendored names.""" + return ( + importlib.util.spec_from_loader(fullname, self) + if self._module_matches_namespace(fullname) else None + ) + + def install(self): + """ + Install this importer into sys.meta_path if not already present. + """ + if self not in sys.meta_path: + sys.meta_path.append(self) + + +names = 'packaging', 'pyparsing', 'ordered_set', 'more_itertools', +VendorImporter(__name__, names, 'setuptools._vendor').install() diff --git a/venv/lib/python3.10/site-packages/setuptools/glob.py b/venv/lib/python3.10/site-packages/setuptools/glob.py new file mode 100644 index 00000000..87062b81 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/glob.py @@ -0,0 +1,167 @@ +""" +Filename globbing utility. Mostly a copy of `glob` from Python 3.5. + +Changes include: + * `yield from` and PEP3102 `*` removed. + * Hidden files are not ignored. +""" + +import os +import re +import fnmatch + +__all__ = ["glob", "iglob", "escape"] + + +def glob(pathname, recursive=False): + """Return a list of paths matching a pathname pattern. + + The pattern may contain simple shell-style wildcards a la + fnmatch. However, unlike fnmatch, filenames starting with a + dot are special cases that are not matched by '*' and '?' + patterns. + + If recursive is true, the pattern '**' will match any files and + zero or more directories and subdirectories. + """ + return list(iglob(pathname, recursive=recursive)) + + +def iglob(pathname, recursive=False): + """Return an iterator which yields the paths matching a pathname pattern. + + The pattern may contain simple shell-style wildcards a la + fnmatch. However, unlike fnmatch, filenames starting with a + dot are special cases that are not matched by '*' and '?' + patterns. + + If recursive is true, the pattern '**' will match any files and + zero or more directories and subdirectories. + """ + it = _iglob(pathname, recursive) + if recursive and _isrecursive(pathname): + s = next(it) # skip empty string + assert not s + return it + + +def _iglob(pathname, recursive): + dirname, basename = os.path.split(pathname) + glob_in_dir = glob2 if recursive and _isrecursive(basename) else glob1 + + if not has_magic(pathname): + if basename: + if os.path.lexists(pathname): + yield pathname + else: + # Patterns ending with a slash should match only directories + if os.path.isdir(dirname): + yield pathname + return + + if not dirname: + yield from glob_in_dir(dirname, basename) + return + # `os.path.split()` returns the argument itself as a dirname if it is a + # drive or UNC path. Prevent an infinite recursion if a drive or UNC path + # contains magic characters (i.e. r'\\?\C:'). + if dirname != pathname and has_magic(dirname): + dirs = _iglob(dirname, recursive) + else: + dirs = [dirname] + if not has_magic(basename): + glob_in_dir = glob0 + for dirname in dirs: + for name in glob_in_dir(dirname, basename): + yield os.path.join(dirname, name) + + +# These 2 helper functions non-recursively glob inside a literal directory. +# They return a list of basenames. `glob1` accepts a pattern while `glob0` +# takes a literal basename (so it only has to check for its existence). + + +def glob1(dirname, pattern): + if not dirname: + if isinstance(pattern, bytes): + dirname = os.curdir.encode('ASCII') + else: + dirname = os.curdir + try: + names = os.listdir(dirname) + except OSError: + return [] + return fnmatch.filter(names, pattern) + + +def glob0(dirname, basename): + if not basename: + # `os.path.split()` returns an empty basename for paths ending with a + # directory separator. 'q*x/' should match only directories. + if os.path.isdir(dirname): + return [basename] + else: + if os.path.lexists(os.path.join(dirname, basename)): + return [basename] + return [] + + +# This helper function recursively yields relative pathnames inside a literal +# directory. + + +def glob2(dirname, pattern): + assert _isrecursive(pattern) + yield pattern[:0] + for x in _rlistdir(dirname): + yield x + + +# Recursively yields relative pathnames inside a literal directory. +def _rlistdir(dirname): + if not dirname: + if isinstance(dirname, bytes): + dirname = os.curdir.encode('ASCII') + else: + dirname = os.curdir + try: + names = os.listdir(dirname) + except os.error: + return + for x in names: + yield x + path = os.path.join(dirname, x) if dirname else x + for y in _rlistdir(path): + yield os.path.join(x, y) + + +magic_check = re.compile('([*?[])') +magic_check_bytes = re.compile(b'([*?[])') + + +def has_magic(s): + if isinstance(s, bytes): + match = magic_check_bytes.search(s) + else: + match = magic_check.search(s) + return match is not None + + +def _isrecursive(pattern): + if isinstance(pattern, bytes): + return pattern == b'**' + else: + return pattern == '**' + + +def escape(pathname): + """Escape all special characters. + """ + # Escaping is done by wrapping any of "*?[" between square brackets. + # Metacharacters do not work in the drive part and shouldn't be escaped. + drive, pathname = os.path.splitdrive(pathname) + if isinstance(pathname, bytes): + pathname = magic_check_bytes.sub(br'[\1]', pathname) + else: + pathname = magic_check.sub(r'[\1]', pathname) + return drive + pathname diff --git a/venv/lib/python3.10/site-packages/setuptools/gui-32.exe b/venv/lib/python3.10/site-packages/setuptools/gui-32.exe new file mode 100644 index 0000000000000000000000000000000000000000..f8d3509653ba8f80ca7f3aa7f95616142ba83a94 GIT binary patch literal 65536 zcmeFae|%KMxj%k3yGc&SCTD>S1PQP}R5YmQ5=~qJi^+zl1UE)DtPsG8blp-*!#RLg z0>QIub24npZS_`f-)#|`^OhvIcH|hGc(UT^E}VYJoC(K^_@EDjE;rth;Yer@_4k$X3I);E0Tn+-Zb>&yT9Ew!oxAMfl)C z#Z+d`C?Ev=lGJ)}%Ksnx|0)G)SVf_n2-;d?f9!~MzIJJ-=wKb=iHfW2QCpC29wSNm zA=ztsPZ<@3t`2ENV!bW?>DIbrM&c*bCbqaRzr~R~Z-r)Gl=RG-p}ugUHp=<&@N<(0nQZ)pc;t^f@UfdU)Xs*a2q9hEj|W&QGS`}Q+V zaO>`-aSJ8yAtP2OBNk%M7Utt!$6gfgmQ40WtW_PKSW_r1oOg}p=vZj3XtBjwwJ#E} zLMNCsnAlP1f|%AM?kIHMo~S5v2kZEcbEs|ZrY(iCq{N>@V-R$%P-2fEhzyjmCh@Sy zXyr*PE_By~_)26%86IRFp9Ya zkBHB1hGv2=t60ZM@2flwcy2#L^lN{0=%0Q@MjzL)ErkWFb2Ro*N07ImOt!9YmgwvP zqh2yflmnST)@Q6JEa3kv=;e&Js^gRcx7ile@Me+Xh_`B=wJ3|47Z(=9j;P;M4jj9k ze|zYYnyGIobV=&smWsjxVw3XZ39!ke-gcWd&f8i_T!k-^@^CA0*s%-oQ>v?$_-7%o z(GNN8XT7J;F$I$PlNQv_oLiavAq4>E7I2dQhlE)vSn!y;BSSI+5(`L`#@q*i(+$dj ziMR82oKzstr3NgrEei6^p%m@2rUhVv>rK-H3%XZ<_rUh;c(a2dG)%uOg$_v@w_EZo zlu%GsR0^7TQkP%ahpqsf^)t)7t)|hz?tCY-06G}<$V~#?~heoED!!4L2akG@t z3k(cUbnpdgqwk%>`n0WAC7vv#rU2V~=4eiAwpse1#pRD3*UlGpF7&;UP%~^>-Uq9> zqqY#gDuX1JM-HRLrTl?xL1RW6Nzt8%&-UwXtnfuqbCmh#A4k1U7-%L3c7Zx(d zuhG+B-K2d4zoLVczO#ufnYJw*t5&k#)-NC8`0Z!%(?;tLH)1SS=)o%@p*m1Hza}bC zH<@{EP=$nZv|K=--J~^q2RFJ=UsK7|s*{A7>2riBOI3;B9VN6@g>xk)TvhhOKNMSeI?sb zNT@@qXG7GtAEH*Z*I7+?xX^=^+#cd{e*xu~c+oK%QC`k~8T1Fj`XSd4etuu)23Ly= znHbY_evF#lbUsH*M$@PjpbB6kZlDn4%Pfry7Wc9o2a;HxjOT7A9>$Ks0zkIpxF}-P z4%J+UwB{X!v+x4JvU3b1r4SD4dNJCLBe`P~a!!^eLzUU1z9JMV04G)5v%Ur4xPh4u|g#Tc-(r0PB00 z<2OM*Q-Cajywm3kTRsx?bLZ%s;?w6_FF__SF*1GDPvs6}`fAHZ`iq5gfrnJz3GS7o z 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.10/site-packages/setuptools/gui-arm64.exe b/venv/lib/python3.10/site-packages/setuptools/gui-arm64.exe new file mode 100644 index 0000000000000000000000000000000000000000..5730f11d5c09df7758cfa1b1dfb5ced4bf2ce55c GIT binary patch literal 137728 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%0Ax*H)YN%7KBxJojF;|- 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 z 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 14 and version > best_version: + best_version, best_dir = version, vc_dir + return best_version, best_dir + + +def _msvc14_find_vc2017(): + """Python 3.8 "distutils/_msvccompiler.py" backport + + Returns "15, path" based on the result of invoking vswhere.exe + If no install is found, returns "None, None" + + The version is returned to avoid unnecessarily changing the function + result. It may be ignored when the path is not None. + + If vswhere.exe is not available, by definition, VS 2017 is not + installed. + """ + root = environ.get("ProgramFiles(x86)") or environ.get("ProgramFiles") + if not root: + return None, None + + try: + path = subprocess.check_output([ + join(root, "Microsoft Visual Studio", "Installer", "vswhere.exe"), + "-latest", + "-prerelease", + "-requiresAny", + "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", + "-requires", "Microsoft.VisualStudio.Workload.WDExpress", + "-property", "installationPath", + "-products", "*", + ]).decode(encoding="mbcs", errors="strict").strip() + except (subprocess.CalledProcessError, OSError, UnicodeDecodeError): + return None, None + + path = join(path, "VC", "Auxiliary", "Build") + if isdir(path): + return 15, path + + return None, None + + +PLAT_SPEC_TO_RUNTIME = { + 'x86': 'x86', + 'x86_amd64': 'x64', + 'x86_arm': 'arm', + 'x86_arm64': 'arm64' +} + + +def _msvc14_find_vcvarsall(plat_spec): + """Python 3.8 "distutils/_msvccompiler.py" backport""" + _, best_dir = _msvc14_find_vc2017() + vcruntime = None + + if plat_spec in PLAT_SPEC_TO_RUNTIME: + vcruntime_plat = PLAT_SPEC_TO_RUNTIME[plat_spec] + else: + vcruntime_plat = 'x64' if 'amd64' in plat_spec else 'x86' + + if best_dir: + vcredist = join(best_dir, "..", "..", "redist", "MSVC", "**", + vcruntime_plat, "Microsoft.VC14*.CRT", + "vcruntime140.dll") + try: + import glob + vcruntime = glob.glob(vcredist, recursive=True)[-1] + except (ImportError, OSError, LookupError): + vcruntime = None + + if not best_dir: + best_version, best_dir = _msvc14_find_vc2015() + if best_version: + vcruntime = join(best_dir, 'redist', vcruntime_plat, + "Microsoft.VC140.CRT", "vcruntime140.dll") + + if not best_dir: + return None, None + + vcvarsall = join(best_dir, "vcvarsall.bat") + if not isfile(vcvarsall): + return None, None + + if not vcruntime or not isfile(vcruntime): + vcruntime = None + + return vcvarsall, vcruntime + + +def _msvc14_get_vc_env(plat_spec): + """Python 3.8 "distutils/_msvccompiler.py" backport""" + if "DISTUTILS_USE_SDK" in environ: + return { + key.lower(): value + for key, value in environ.items() + } + + vcvarsall, vcruntime = _msvc14_find_vcvarsall(plat_spec) + if not vcvarsall: + raise distutils.errors.DistutilsPlatformError( + "Unable to find vcvarsall.bat" + ) + + try: + out = subprocess.check_output( + 'cmd /u /c "{}" {} && set'.format(vcvarsall, plat_spec), + stderr=subprocess.STDOUT, + ).decode('utf-16le', errors='replace') + except subprocess.CalledProcessError as exc: + raise distutils.errors.DistutilsPlatformError( + "Error executing {}".format(exc.cmd) + ) from exc + + env = { + key.lower(): value + for key, _, value in + (line.partition('=') for line in out.splitlines()) + if key and value + } + + if vcruntime: + env['py_vcruntime_redist'] = vcruntime + return env + + +def msvc14_get_vc_env(plat_spec): + """ + Patched "distutils._msvccompiler._get_vc_env" for support extra + Microsoft Visual C++ 14.X compilers. + + Set environment without use of "vcvarsall.bat". + + Parameters + ---------- + plat_spec: str + Target architecture. + + Return + ------ + dict + environment + """ + + # Always use backport from CPython 3.8 + try: + return _msvc14_get_vc_env(plat_spec) + except distutils.errors.DistutilsPlatformError as exc: + _augment_exception(exc, 14.0) + raise + + +def msvc14_gen_lib_options(*args, **kwargs): + """ + Patched "distutils._msvccompiler.gen_lib_options" for fix + compatibility between "numpy.distutils" and "distutils._msvccompiler" + (for Numpy < 1.11.2) + """ + if "numpy.distutils" in sys.modules: + import numpy as np + if LegacyVersion(np.__version__) < LegacyVersion('1.11.2'): + return np.distutils.ccompiler.gen_lib_options(*args, **kwargs) + return get_unpatched(msvc14_gen_lib_options)(*args, **kwargs) + + +def _augment_exception(exc, version, arch=''): + """ + Add details to the exception message to help guide the user + as to what action will resolve it. + """ + # Error if MSVC++ directory not found or environment not set + message = exc.args[0] + + if "vcvarsall" in message.lower() or "visual c" in message.lower(): + # Special error message if MSVC++ not installed + tmpl = 'Microsoft Visual C++ {version:0.1f} or greater is required.' + message = tmpl.format(**locals()) + msdownload = 'www.microsoft.com/download/details.aspx?id=%d' + if version == 9.0: + if arch.lower().find('ia64') > -1: + # For VC++ 9.0, if IA64 support is needed, redirect user + # to Windows SDK 7.0. + # Note: No download link available from Microsoft. + message += ' Get it with "Microsoft Windows SDK 7.0"' + else: + # For VC++ 9.0 redirect user to Vc++ for Python 2.7 : + # This redirection link is maintained by Microsoft. + # Contact vspython@microsoft.com if it needs updating. + message += ' Get it from http://aka.ms/vcpython27' + elif version == 10.0: + # For VC++ 10.0 Redirect user to Windows SDK 7.1 + message += ' Get it with "Microsoft Windows SDK 7.1": ' + message += msdownload % 8279 + elif version >= 14.0: + # For VC++ 14.X Redirect user to latest Visual C++ Build Tools + message += (' Get it with "Microsoft C++ Build Tools": ' + r'https://visualstudio.microsoft.com' + r'/visual-cpp-build-tools/') + + exc.args = (message, ) + + +class PlatformInfo: + """ + Current and Target Architectures information. + + Parameters + ---------- + arch: str + Target architecture. + """ + current_cpu = environ.get('processor_architecture', '').lower() + + def __init__(self, arch): + self.arch = arch.lower().replace('x64', 'amd64') + + @property + def target_cpu(self): + """ + Return Target CPU architecture. + + Return + ------ + str + Target CPU + """ + return self.arch[self.arch.find('_') + 1:] + + def target_is_x86(self): + """ + Return True if target CPU is x86 32 bits.. + + Return + ------ + bool + CPU is x86 32 bits + """ + return self.target_cpu == 'x86' + + def current_is_x86(self): + """ + Return True if current CPU is x86 32 bits.. + + Return + ------ + bool + CPU is x86 32 bits + """ + return self.current_cpu == 'x86' + + def current_dir(self, hidex86=False, x64=False): + """ + Current platform specific subfolder. + + Parameters + ---------- + hidex86: bool + return '' and not '\x86' if architecture is x86. + x64: bool + return '\x64' and not '\amd64' if architecture is amd64. + + Return + ------ + str + subfolder: '\target', or '' (see hidex86 parameter) + """ + return ( + '' if (self.current_cpu == 'x86' and hidex86) else + r'\x64' if (self.current_cpu == 'amd64' and x64) else + r'\%s' % self.current_cpu + ) + + def target_dir(self, hidex86=False, x64=False): + r""" + Target platform specific subfolder. + + Parameters + ---------- + hidex86: bool + return '' and not '\x86' if architecture is x86. + x64: bool + return '\x64' and not '\amd64' if architecture is amd64. + + Return + ------ + str + subfolder: '\current', or '' (see hidex86 parameter) + """ + return ( + '' if (self.target_cpu == 'x86' and hidex86) else + r'\x64' if (self.target_cpu == 'amd64' and x64) else + r'\%s' % self.target_cpu + ) + + def cross_dir(self, forcex86=False): + r""" + Cross platform specific subfolder. + + Parameters + ---------- + forcex86: bool + Use 'x86' as current architecture even if current architecture is + not x86. + + Return + ------ + str + subfolder: '' if target architecture is current architecture, + '\current_target' if not. + """ + current = 'x86' if forcex86 else self.current_cpu + return ( + '' if self.target_cpu == current else + self.target_dir().replace('\\', '\\%s_' % current) + ) + + +class RegistryInfo: + """ + Microsoft Visual Studio related registry information. + + Parameters + ---------- + platform_info: PlatformInfo + "PlatformInfo" instance. + """ + HKEYS = (winreg.HKEY_USERS, + winreg.HKEY_CURRENT_USER, + winreg.HKEY_LOCAL_MACHINE, + winreg.HKEY_CLASSES_ROOT) + + def __init__(self, platform_info): + self.pi = platform_info + + @property + def visualstudio(self): + """ + Microsoft Visual Studio root registry key. + + Return + ------ + str + Registry key + """ + return 'VisualStudio' + + @property + def sxs(self): + """ + Microsoft Visual Studio SxS registry key. + + Return + ------ + str + Registry key + """ + return join(self.visualstudio, 'SxS') + + @property + def vc(self): + """ + Microsoft Visual C++ VC7 registry key. + + Return + ------ + str + Registry key + """ + return join(self.sxs, 'VC7') + + @property + def vs(self): + """ + Microsoft Visual Studio VS7 registry key. + + Return + ------ + str + Registry key + """ + return join(self.sxs, 'VS7') + + @property + def vc_for_python(self): + """ + Microsoft Visual C++ for Python registry key. + + Return + ------ + str + Registry key + """ + return r'DevDiv\VCForPython' + + @property + def microsoft_sdk(self): + """ + Microsoft SDK registry key. + + Return + ------ + str + Registry key + """ + return 'Microsoft SDKs' + + @property + def windows_sdk(self): + """ + Microsoft Windows/Platform SDK registry key. + + Return + ------ + str + Registry key + """ + return join(self.microsoft_sdk, 'Windows') + + @property + def netfx_sdk(self): + """ + Microsoft .NET Framework SDK registry key. + + Return + ------ + str + Registry key + """ + return join(self.microsoft_sdk, 'NETFXSDK') + + @property + def windows_kits_roots(self): + """ + Microsoft Windows Kits Roots registry key. + + Return + ------ + str + Registry key + """ + return r'Windows Kits\Installed Roots' + + def microsoft(self, key, x86=False): + """ + Return key in Microsoft software registry. + + Parameters + ---------- + key: str + Registry key path where look. + x86: str + Force x86 software registry. + + Return + ------ + str + Registry key + """ + node64 = '' if self.pi.current_is_x86() or x86 else 'Wow6432Node' + return join('Software', node64, 'Microsoft', key) + + def lookup(self, key, name): + """ + Look for values in registry in Microsoft software registry. + + Parameters + ---------- + key: str + Registry key path where look. + name: str + Value name to find. + + Return + ------ + str + value + """ + key_read = winreg.KEY_READ + openkey = winreg.OpenKey + closekey = winreg.CloseKey + ms = self.microsoft + for hkey in self.HKEYS: + bkey = None + try: + bkey = openkey(hkey, ms(key), 0, key_read) + except (OSError, IOError): + if not self.pi.current_is_x86(): + try: + bkey = openkey(hkey, ms(key, True), 0, key_read) + except (OSError, IOError): + continue + else: + continue + try: + return winreg.QueryValueEx(bkey, name)[0] + except (OSError, IOError): + pass + finally: + if bkey: + closekey(bkey) + + +class SystemInfo: + """ + Microsoft Windows and Visual Studio related system information. + + Parameters + ---------- + registry_info: RegistryInfo + "RegistryInfo" instance. + vc_ver: float + Required Microsoft Visual C++ version. + """ + + # Variables and properties in this class use originals CamelCase variables + # names from Microsoft source files for more easy comparison. + WinDir = environ.get('WinDir', '') + ProgramFiles = environ.get('ProgramFiles', '') + ProgramFilesx86 = environ.get('ProgramFiles(x86)', ProgramFiles) + + def __init__(self, registry_info, vc_ver=None): + self.ri = registry_info + self.pi = self.ri.pi + + self.known_vs_paths = self.find_programdata_vs_vers() + + # Except for VS15+, VC version is aligned with VS version + self.vs_ver = self.vc_ver = ( + vc_ver or self._find_latest_available_vs_ver()) + + def _find_latest_available_vs_ver(self): + """ + Find the latest VC version + + Return + ------ + float + version + """ + reg_vc_vers = self.find_reg_vs_vers() + + if not (reg_vc_vers or self.known_vs_paths): + raise distutils.errors.DistutilsPlatformError( + 'No Microsoft Visual C++ version found') + + vc_vers = set(reg_vc_vers) + vc_vers.update(self.known_vs_paths) + return sorted(vc_vers)[-1] + + def find_reg_vs_vers(self): + """ + Find Microsoft Visual Studio versions available in registry. + + Return + ------ + list of float + Versions + """ + ms = self.ri.microsoft + vckeys = (self.ri.vc, self.ri.vc_for_python, self.ri.vs) + vs_vers = [] + for hkey, key in itertools.product(self.ri.HKEYS, vckeys): + try: + bkey = winreg.OpenKey(hkey, ms(key), 0, winreg.KEY_READ) + except (OSError, IOError): + continue + with bkey: + subkeys, values, _ = winreg.QueryInfoKey(bkey) + for i in range(values): + with contextlib.suppress(ValueError): + ver = float(winreg.EnumValue(bkey, i)[0]) + if ver not in vs_vers: + vs_vers.append(ver) + for i in range(subkeys): + with contextlib.suppress(ValueError): + ver = float(winreg.EnumKey(bkey, i)) + if ver not in vs_vers: + vs_vers.append(ver) + return sorted(vs_vers) + + def find_programdata_vs_vers(self): + r""" + Find Visual studio 2017+ versions from information in + "C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances". + + Return + ------ + dict + float version as key, path as value. + """ + vs_versions = {} + instances_dir = \ + r'C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances' + + try: + hashed_names = listdir(instances_dir) + + except (OSError, IOError): + # Directory not exists with all Visual Studio versions + return vs_versions + + for name in hashed_names: + try: + # Get VS installation path from "state.json" file + state_path = join(instances_dir, name, 'state.json') + with open(state_path, 'rt', encoding='utf-8') as state_file: + state = json.load(state_file) + vs_path = state['installationPath'] + + # Raises OSError if this VS installation does not contain VC + listdir(join(vs_path, r'VC\Tools\MSVC')) + + # Store version and path + vs_versions[self._as_float_version( + state['installationVersion'])] = vs_path + + except (OSError, IOError, KeyError): + # Skip if "state.json" file is missing or bad format + continue + + return vs_versions + + @staticmethod + def _as_float_version(version): + """ + Return a string version as a simplified float version (major.minor) + + Parameters + ---------- + version: str + Version. + + Return + ------ + float + version + """ + return float('.'.join(version.split('.')[:2])) + + @property + def VSInstallDir(self): + """ + Microsoft Visual Studio directory. + + Return + ------ + str + path + """ + # Default path + default = join(self.ProgramFilesx86, + 'Microsoft Visual Studio %0.1f' % self.vs_ver) + + # Try to get path from registry, if fail use default path + return self.ri.lookup(self.ri.vs, '%0.1f' % self.vs_ver) or default + + @property + def VCInstallDir(self): + """ + Microsoft Visual C++ directory. + + Return + ------ + str + path + """ + path = self._guess_vc() or self._guess_vc_legacy() + + if not isdir(path): + msg = 'Microsoft Visual C++ directory not found' + raise distutils.errors.DistutilsPlatformError(msg) + + return path + + def _guess_vc(self): + """ + Locate Visual C++ for VS2017+. + + Return + ------ + str + path + """ + if self.vs_ver <= 14.0: + return '' + + try: + # First search in known VS paths + vs_dir = self.known_vs_paths[self.vs_ver] + except KeyError: + # Else, search with path from registry + vs_dir = self.VSInstallDir + + guess_vc = join(vs_dir, r'VC\Tools\MSVC') + + # Subdir with VC exact version as name + try: + # Update the VC version with real one instead of VS version + vc_ver = listdir(guess_vc)[-1] + self.vc_ver = self._as_float_version(vc_ver) + return join(guess_vc, vc_ver) + except (OSError, IOError, IndexError): + return '' + + def _guess_vc_legacy(self): + """ + Locate Visual C++ for versions prior to 2017. + + Return + ------ + str + path + """ + default = join(self.ProgramFilesx86, + r'Microsoft Visual Studio %0.1f\VC' % self.vs_ver) + + # Try to get "VC++ for Python" path from registry as default path + reg_path = join(self.ri.vc_for_python, '%0.1f' % self.vs_ver) + python_vc = self.ri.lookup(reg_path, 'installdir') + default_vc = join(python_vc, 'VC') if python_vc else default + + # Try to get path from registry, if fail use default path + return self.ri.lookup(self.ri.vc, '%0.1f' % self.vs_ver) or default_vc + + @property + def WindowsSdkVersion(self): + """ + Microsoft Windows SDK versions for specified MSVC++ version. + + Return + ------ + tuple of str + versions + """ + if self.vs_ver <= 9.0: + return '7.0', '6.1', '6.0a' + elif self.vs_ver == 10.0: + return '7.1', '7.0a' + elif self.vs_ver == 11.0: + return '8.0', '8.0a' + elif self.vs_ver == 12.0: + return '8.1', '8.1a' + elif self.vs_ver >= 14.0: + return '10.0', '8.1' + + @property + def WindowsSdkLastVersion(self): + """ + Microsoft Windows SDK last version. + + Return + ------ + str + version + """ + return self._use_last_dir_name(join(self.WindowsSdkDir, 'lib')) + + @property # noqa: C901 + def WindowsSdkDir(self): # noqa: C901 # is too complex (12) # FIXME + """ + Microsoft Windows SDK directory. + + Return + ------ + str + path + """ + sdkdir = '' + for ver in self.WindowsSdkVersion: + # Try to get it from registry + loc = join(self.ri.windows_sdk, 'v%s' % ver) + sdkdir = self.ri.lookup(loc, 'installationfolder') + if sdkdir: + break + if not sdkdir or not isdir(sdkdir): + # Try to get "VC++ for Python" version from registry + path = join(self.ri.vc_for_python, '%0.1f' % self.vc_ver) + install_base = self.ri.lookup(path, 'installdir') + if install_base: + sdkdir = join(install_base, 'WinSDK') + if not sdkdir or not isdir(sdkdir): + # If fail, use default new path + for ver in self.WindowsSdkVersion: + intver = ver[:ver.rfind('.')] + path = r'Microsoft SDKs\Windows Kits\%s' % intver + d = join(self.ProgramFiles, path) + if isdir(d): + sdkdir = d + if not sdkdir or not isdir(sdkdir): + # If fail, use default old path + for ver in self.WindowsSdkVersion: + path = r'Microsoft SDKs\Windows\v%s' % ver + d = join(self.ProgramFiles, path) + if isdir(d): + sdkdir = d + if not sdkdir: + # If fail, use Platform SDK + sdkdir = join(self.VCInstallDir, 'PlatformSDK') + return sdkdir + + @property + def WindowsSDKExecutablePath(self): + """ + Microsoft Windows SDK executable directory. + + Return + ------ + str + path + """ + # Find WinSDK NetFx Tools registry dir name + if self.vs_ver <= 11.0: + netfxver = 35 + arch = '' + else: + netfxver = 40 + hidex86 = True if self.vs_ver <= 12.0 else False + arch = self.pi.current_dir(x64=True, hidex86=hidex86) + fx = 'WinSDK-NetFx%dTools%s' % (netfxver, arch.replace('\\', '-')) + + # list all possibles registry paths + regpaths = [] + if self.vs_ver >= 14.0: + for ver in self.NetFxSdkVersion: + regpaths += [join(self.ri.netfx_sdk, ver, fx)] + + for ver in self.WindowsSdkVersion: + regpaths += [join(self.ri.windows_sdk, 'v%sA' % ver, fx)] + + # Return installation folder from the more recent path + for path in regpaths: + execpath = self.ri.lookup(path, 'installationfolder') + if execpath: + return execpath + + @property + def FSharpInstallDir(self): + """ + Microsoft Visual F# directory. + + Return + ------ + str + path + """ + path = join(self.ri.visualstudio, r'%0.1f\Setup\F#' % self.vs_ver) + return self.ri.lookup(path, 'productdir') or '' + + @property + def UniversalCRTSdkDir(self): + """ + Microsoft Universal CRT SDK directory. + + Return + ------ + str + path + """ + # Set Kit Roots versions for specified MSVC++ version + vers = ('10', '81') if self.vs_ver >= 14.0 else () + + # Find path of the more recent Kit + for ver in vers: + sdkdir = self.ri.lookup(self.ri.windows_kits_roots, + 'kitsroot%s' % ver) + if sdkdir: + return sdkdir or '' + + @property + def UniversalCRTSdkLastVersion(self): + """ + Microsoft Universal C Runtime SDK last version. + + Return + ------ + str + version + """ + return self._use_last_dir_name(join(self.UniversalCRTSdkDir, 'lib')) + + @property + def NetFxSdkVersion(self): + """ + Microsoft .NET Framework SDK versions. + + Return + ------ + tuple of str + versions + """ + # Set FxSdk versions for specified VS version + return (('4.7.2', '4.7.1', '4.7', + '4.6.2', '4.6.1', '4.6', + '4.5.2', '4.5.1', '4.5') + if self.vs_ver >= 14.0 else ()) + + @property + def NetFxSdkDir(self): + """ + Microsoft .NET Framework SDK directory. + + Return + ------ + str + path + """ + sdkdir = '' + for ver in self.NetFxSdkVersion: + loc = join(self.ri.netfx_sdk, ver) + sdkdir = self.ri.lookup(loc, 'kitsinstallationfolder') + if sdkdir: + break + return sdkdir + + @property + def FrameworkDir32(self): + """ + Microsoft .NET Framework 32bit directory. + + Return + ------ + str + path + """ + # Default path + guess_fw = join(self.WinDir, r'Microsoft.NET\Framework') + + # Try to get path from registry, if fail use default path + return self.ri.lookup(self.ri.vc, 'frameworkdir32') or guess_fw + + @property + def FrameworkDir64(self): + """ + Microsoft .NET Framework 64bit directory. + + Return + ------ + str + path + """ + # Default path + guess_fw = join(self.WinDir, r'Microsoft.NET\Framework64') + + # Try to get path from registry, if fail use default path + return self.ri.lookup(self.ri.vc, 'frameworkdir64') or guess_fw + + @property + def FrameworkVersion32(self): + """ + Microsoft .NET Framework 32bit versions. + + Return + ------ + tuple of str + versions + """ + return self._find_dot_net_versions(32) + + @property + def FrameworkVersion64(self): + """ + Microsoft .NET Framework 64bit versions. + + Return + ------ + tuple of str + versions + """ + return self._find_dot_net_versions(64) + + def _find_dot_net_versions(self, bits): + """ + Find Microsoft .NET Framework versions. + + Parameters + ---------- + bits: int + Platform number of bits: 32 or 64. + + Return + ------ + tuple of str + versions + """ + # Find actual .NET version in registry + reg_ver = self.ri.lookup(self.ri.vc, 'frameworkver%d' % bits) + dot_net_dir = getattr(self, 'FrameworkDir%d' % bits) + ver = reg_ver or self._use_last_dir_name(dot_net_dir, 'v') or '' + + # Set .NET versions for specified MSVC++ version + if self.vs_ver >= 12.0: + return ver, 'v4.0' + elif self.vs_ver >= 10.0: + return 'v4.0.30319' if ver.lower()[:2] != 'v4' else ver, 'v3.5' + elif self.vs_ver == 9.0: + return 'v3.5', 'v2.0.50727' + elif self.vs_ver == 8.0: + return 'v3.0', 'v2.0.50727' + + @staticmethod + def _use_last_dir_name(path, prefix=''): + """ + Return name of the last dir in path or '' if no dir found. + + Parameters + ---------- + path: str + Use dirs in this path + prefix: str + Use only dirs starting by this prefix + + Return + ------ + str + name + """ + matching_dirs = ( + dir_name + for dir_name in reversed(listdir(path)) + if isdir(join(path, dir_name)) and + dir_name.startswith(prefix) + ) + return next(matching_dirs, None) or '' + + +class EnvironmentInfo: + """ + Return environment variables for specified Microsoft Visual C++ version + and platform : Lib, Include, Path and libpath. + + This function is compatible with Microsoft Visual C++ 9.0 to 14.X. + + Script created by analysing Microsoft environment configuration files like + "vcvars[...].bat", "SetEnv.Cmd", "vcbuildtools.bat", ... + + Parameters + ---------- + arch: str + Target architecture. + vc_ver: float + Required Microsoft Visual C++ version. If not set, autodetect the last + version. + vc_min_ver: float + Minimum Microsoft Visual C++ version. + """ + + # Variables and properties in this class use originals CamelCase variables + # names from Microsoft source files for more easy comparison. + + def __init__(self, arch, vc_ver=None, vc_min_ver=0): + self.pi = PlatformInfo(arch) + self.ri = RegistryInfo(self.pi) + self.si = SystemInfo(self.ri, vc_ver) + + if self.vc_ver < vc_min_ver: + err = 'No suitable Microsoft Visual C++ version found' + raise distutils.errors.DistutilsPlatformError(err) + + @property + def vs_ver(self): + """ + Microsoft Visual Studio. + + Return + ------ + float + version + """ + return self.si.vs_ver + + @property + def vc_ver(self): + """ + Microsoft Visual C++ version. + + Return + ------ + float + version + """ + return self.si.vc_ver + + @property + def VSTools(self): + """ + Microsoft Visual Studio Tools. + + Return + ------ + list of str + paths + """ + paths = [r'Common7\IDE', r'Common7\Tools'] + + if self.vs_ver >= 14.0: + arch_subdir = self.pi.current_dir(hidex86=True, x64=True) + paths += [r'Common7\IDE\CommonExtensions\Microsoft\TestWindow'] + paths += [r'Team Tools\Performance Tools'] + paths += [r'Team Tools\Performance Tools%s' % arch_subdir] + + return [join(self.si.VSInstallDir, path) for path in paths] + + @property + def VCIncludes(self): + """ + Microsoft Visual C++ & Microsoft Foundation Class Includes. + + Return + ------ + list of str + paths + """ + return [join(self.si.VCInstallDir, 'Include'), + join(self.si.VCInstallDir, r'ATLMFC\Include')] + + @property + def VCLibraries(self): + """ + Microsoft Visual C++ & Microsoft Foundation Class Libraries. + + Return + ------ + list of str + paths + """ + if self.vs_ver >= 15.0: + arch_subdir = self.pi.target_dir(x64=True) + else: + arch_subdir = self.pi.target_dir(hidex86=True) + paths = ['Lib%s' % arch_subdir, r'ATLMFC\Lib%s' % arch_subdir] + + if self.vs_ver >= 14.0: + paths += [r'Lib\store%s' % arch_subdir] + + return [join(self.si.VCInstallDir, path) for path in paths] + + @property + def VCStoreRefs(self): + """ + Microsoft Visual C++ store references Libraries. + + Return + ------ + list of str + paths + """ + if self.vs_ver < 14.0: + return [] + return [join(self.si.VCInstallDir, r'Lib\store\references')] + + @property + def VCTools(self): + """ + Microsoft Visual C++ Tools. + + Return + ------ + list of str + paths + """ + si = self.si + tools = [join(si.VCInstallDir, 'VCPackages')] + + forcex86 = True if self.vs_ver <= 10.0 else False + arch_subdir = self.pi.cross_dir(forcex86) + if arch_subdir: + tools += [join(si.VCInstallDir, 'Bin%s' % arch_subdir)] + + if self.vs_ver == 14.0: + path = 'Bin%s' % self.pi.current_dir(hidex86=True) + tools += [join(si.VCInstallDir, path)] + + elif self.vs_ver >= 15.0: + host_dir = (r'bin\HostX86%s' if self.pi.current_is_x86() else + r'bin\HostX64%s') + tools += [join( + si.VCInstallDir, host_dir % self.pi.target_dir(x64=True))] + + if self.pi.current_cpu != self.pi.target_cpu: + tools += [join( + si.VCInstallDir, host_dir % self.pi.current_dir(x64=True))] + + else: + tools += [join(si.VCInstallDir, 'Bin')] + + return tools + + @property + def OSLibraries(self): + """ + Microsoft Windows SDK Libraries. + + Return + ------ + list of str + paths + """ + if self.vs_ver <= 10.0: + arch_subdir = self.pi.target_dir(hidex86=True, x64=True) + return [join(self.si.WindowsSdkDir, 'Lib%s' % arch_subdir)] + + else: + arch_subdir = self.pi.target_dir(x64=True) + lib = join(self.si.WindowsSdkDir, 'lib') + libver = self._sdk_subdir + return [join(lib, '%sum%s' % (libver, arch_subdir))] + + @property + def OSIncludes(self): + """ + Microsoft Windows SDK Include. + + Return + ------ + list of str + paths + """ + include = join(self.si.WindowsSdkDir, 'include') + + if self.vs_ver <= 10.0: + return [include, join(include, 'gl')] + + else: + if self.vs_ver >= 14.0: + sdkver = self._sdk_subdir + else: + sdkver = '' + return [join(include, '%sshared' % sdkver), + join(include, '%sum' % sdkver), + join(include, '%swinrt' % sdkver)] + + @property + def OSLibpath(self): + """ + Microsoft Windows SDK Libraries Paths. + + Return + ------ + list of str + paths + """ + ref = join(self.si.WindowsSdkDir, 'References') + libpath = [] + + if self.vs_ver <= 9.0: + libpath += self.OSLibraries + + if self.vs_ver >= 11.0: + libpath += [join(ref, r'CommonConfiguration\Neutral')] + + if self.vs_ver >= 14.0: + libpath += [ + ref, + join(self.si.WindowsSdkDir, 'UnionMetadata'), + join( + ref, 'Windows.Foundation.UniversalApiContract', '1.0.0.0'), + join(ref, 'Windows.Foundation.FoundationContract', '1.0.0.0'), + join( + ref, 'Windows.Networking.Connectivity.WwanContract', + '1.0.0.0'), + join( + self.si.WindowsSdkDir, 'ExtensionSDKs', 'Microsoft.VCLibs', + '%0.1f' % self.vs_ver, 'References', 'CommonConfiguration', + 'neutral'), + ] + return libpath + + @property + def SdkTools(self): + """ + Microsoft Windows SDK Tools. + + Return + ------ + list of str + paths + """ + return list(self._sdk_tools()) + + def _sdk_tools(self): + """ + Microsoft Windows SDK Tools paths generator. + + Return + ------ + generator of str + paths + """ + if self.vs_ver < 15.0: + bin_dir = 'Bin' if self.vs_ver <= 11.0 else r'Bin\x86' + yield join(self.si.WindowsSdkDir, bin_dir) + + if not self.pi.current_is_x86(): + arch_subdir = self.pi.current_dir(x64=True) + path = 'Bin%s' % arch_subdir + yield join(self.si.WindowsSdkDir, path) + + if self.vs_ver in (10.0, 11.0): + if self.pi.target_is_x86(): + arch_subdir = '' + else: + arch_subdir = self.pi.current_dir(hidex86=True, x64=True) + path = r'Bin\NETFX 4.0 Tools%s' % arch_subdir + yield join(self.si.WindowsSdkDir, path) + + elif self.vs_ver >= 15.0: + path = join(self.si.WindowsSdkDir, 'Bin') + arch_subdir = self.pi.current_dir(x64=True) + sdkver = self.si.WindowsSdkLastVersion + yield join(path, '%s%s' % (sdkver, arch_subdir)) + + if self.si.WindowsSDKExecutablePath: + yield self.si.WindowsSDKExecutablePath + + @property + def _sdk_subdir(self): + """ + Microsoft Windows SDK version subdir. + + Return + ------ + str + subdir + """ + ucrtver = self.si.WindowsSdkLastVersion + return ('%s\\' % ucrtver) if ucrtver else '' + + @property + def SdkSetup(self): + """ + Microsoft Windows SDK Setup. + + Return + ------ + list of str + paths + """ + if self.vs_ver > 9.0: + return [] + + return [join(self.si.WindowsSdkDir, 'Setup')] + + @property + def FxTools(self): + """ + Microsoft .NET Framework Tools. + + Return + ------ + list of str + paths + """ + pi = self.pi + si = self.si + + if self.vs_ver <= 10.0: + include32 = True + include64 = not pi.target_is_x86() and not pi.current_is_x86() + else: + include32 = pi.target_is_x86() or pi.current_is_x86() + include64 = pi.current_cpu == 'amd64' or pi.target_cpu == 'amd64' + + tools = [] + if include32: + tools += [join(si.FrameworkDir32, ver) + for ver in si.FrameworkVersion32] + if include64: + tools += [join(si.FrameworkDir64, ver) + for ver in si.FrameworkVersion64] + return tools + + @property + def NetFxSDKLibraries(self): + """ + Microsoft .Net Framework SDK Libraries. + + Return + ------ + list of str + paths + """ + if self.vs_ver < 14.0 or not self.si.NetFxSdkDir: + return [] + + arch_subdir = self.pi.target_dir(x64=True) + return [join(self.si.NetFxSdkDir, r'lib\um%s' % arch_subdir)] + + @property + def NetFxSDKIncludes(self): + """ + Microsoft .Net Framework SDK Includes. + + Return + ------ + list of str + paths + """ + if self.vs_ver < 14.0 or not self.si.NetFxSdkDir: + return [] + + return [join(self.si.NetFxSdkDir, r'include\um')] + + @property + def VsTDb(self): + """ + Microsoft Visual Studio Team System Database. + + Return + ------ + list of str + paths + """ + return [join(self.si.VSInstallDir, r'VSTSDB\Deploy')] + + @property + def MSBuild(self): + """ + Microsoft Build Engine. + + Return + ------ + list of str + paths + """ + if self.vs_ver < 12.0: + return [] + elif self.vs_ver < 15.0: + base_path = self.si.ProgramFilesx86 + arch_subdir = self.pi.current_dir(hidex86=True) + else: + base_path = self.si.VSInstallDir + arch_subdir = '' + + path = r'MSBuild\%0.1f\bin%s' % (self.vs_ver, arch_subdir) + build = [join(base_path, path)] + + if self.vs_ver >= 15.0: + # Add Roslyn C# & Visual Basic Compiler + build += [join(base_path, path, 'Roslyn')] + + return build + + @property + def HTMLHelpWorkshop(self): + """ + Microsoft HTML Help Workshop. + + Return + ------ + list of str + paths + """ + if self.vs_ver < 11.0: + return [] + + return [join(self.si.ProgramFilesx86, 'HTML Help Workshop')] + + @property + def UCRTLibraries(self): + """ + Microsoft Universal C Runtime SDK Libraries. + + Return + ------ + list of str + paths + """ + if self.vs_ver < 14.0: + return [] + + arch_subdir = self.pi.target_dir(x64=True) + lib = join(self.si.UniversalCRTSdkDir, 'lib') + ucrtver = self._ucrt_subdir + return [join(lib, '%sucrt%s' % (ucrtver, arch_subdir))] + + @property + def UCRTIncludes(self): + """ + Microsoft Universal C Runtime SDK Include. + + Return + ------ + list of str + paths + """ + if self.vs_ver < 14.0: + return [] + + include = join(self.si.UniversalCRTSdkDir, 'include') + return [join(include, '%sucrt' % self._ucrt_subdir)] + + @property + def _ucrt_subdir(self): + """ + Microsoft Universal C Runtime SDK version subdir. + + Return + ------ + str + subdir + """ + ucrtver = self.si.UniversalCRTSdkLastVersion + return ('%s\\' % ucrtver) if ucrtver else '' + + @property + def FSharp(self): + """ + Microsoft Visual F#. + + Return + ------ + list of str + paths + """ + if 11.0 > self.vs_ver > 12.0: + return [] + + return [self.si.FSharpInstallDir] + + @property + def VCRuntimeRedist(self): + """ + Microsoft Visual C++ runtime redistributable dll. + + Return + ------ + str + path + """ + vcruntime = 'vcruntime%d0.dll' % self.vc_ver + arch_subdir = self.pi.target_dir(x64=True).strip('\\') + + # Installation prefixes candidates + prefixes = [] + tools_path = self.si.VCInstallDir + redist_path = dirname(tools_path.replace(r'\Tools', r'\Redist')) + if isdir(redist_path): + # Redist version may not be exactly the same as tools + redist_path = join(redist_path, listdir(redist_path)[-1]) + prefixes += [redist_path, join(redist_path, 'onecore')] + + prefixes += [join(tools_path, 'redist')] # VS14 legacy path + + # CRT directory + crt_dirs = ('Microsoft.VC%d.CRT' % (self.vc_ver * 10), + # Sometime store in directory with VS version instead of VC + 'Microsoft.VC%d.CRT' % (int(self.vs_ver) * 10)) + + # vcruntime path + for prefix, crt_dir in itertools.product(prefixes, crt_dirs): + path = join(prefix, arch_subdir, crt_dir, vcruntime) + if isfile(path): + return path + + def return_env(self, exists=True): + """ + Return environment dict. + + Parameters + ---------- + exists: bool + It True, only return existing paths. + + Return + ------ + dict + environment + """ + env = dict( + include=self._build_paths('include', + [self.VCIncludes, + self.OSIncludes, + self.UCRTIncludes, + self.NetFxSDKIncludes], + exists), + lib=self._build_paths('lib', + [self.VCLibraries, + self.OSLibraries, + self.FxTools, + self.UCRTLibraries, + self.NetFxSDKLibraries], + exists), + libpath=self._build_paths('libpath', + [self.VCLibraries, + self.FxTools, + self.VCStoreRefs, + self.OSLibpath], + exists), + path=self._build_paths('path', + [self.VCTools, + self.VSTools, + self.VsTDb, + self.SdkTools, + self.SdkSetup, + self.FxTools, + self.MSBuild, + self.HTMLHelpWorkshop, + self.FSharp], + exists), + ) + if self.vs_ver >= 14 and isfile(self.VCRuntimeRedist): + env['py_vcruntime_redist'] = self.VCRuntimeRedist + return env + + def _build_paths(self, name, spec_path_lists, exists): + """ + Given an environment variable name and specified paths, + return a pathsep-separated string of paths containing + unique, extant, directories from those paths and from + the environment variable. Raise an error if no paths + are resolved. + + Parameters + ---------- + name: str + Environment variable name + spec_path_lists: list of str + Paths + exists: bool + It True, only return existing paths. + + Return + ------ + str + Pathsep-separated paths + """ + # flatten spec_path_lists + spec_paths = itertools.chain.from_iterable(spec_path_lists) + env_paths = environ.get(name, '').split(pathsep) + paths = itertools.chain(spec_paths, env_paths) + extant_paths = list(filter(isdir, paths)) if exists else paths + if not extant_paths: + msg = "%s environment variable is empty" % name.upper() + raise distutils.errors.DistutilsPlatformError(msg) + unique_paths = unique_everseen(extant_paths) + return pathsep.join(unique_paths) diff --git a/venv/lib/python3.10/site-packages/setuptools/namespaces.py b/venv/lib/python3.10/site-packages/setuptools/namespaces.py new file mode 100644 index 00000000..44939e1c --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/namespaces.py @@ -0,0 +1,107 @@ +import os +from distutils import log +import itertools + + +flatten = itertools.chain.from_iterable + + +class Installer: + + nspkg_ext = '-nspkg.pth' + + def install_namespaces(self): + nsp = self._get_all_ns_packages() + if not nsp: + return + filename, ext = os.path.splitext(self._get_target()) + filename += self.nspkg_ext + self.outputs.append(filename) + log.info("Installing %s", filename) + lines = map(self._gen_nspkg_line, nsp) + + if self.dry_run: + # always generate the lines, even in dry run + list(lines) + return + + with open(filename, 'wt') as f: + f.writelines(lines) + + def uninstall_namespaces(self): + filename, ext = os.path.splitext(self._get_target()) + filename += self.nspkg_ext + if not os.path.exists(filename): + return + log.info("Removing %s", filename) + os.remove(filename) + + def _get_target(self): + return self.target + + _nspkg_tmpl = ( + "import sys, types, os", + "has_mfs = sys.version_info > (3, 5)", + "p = os.path.join(%(root)s, *%(pth)r)", + "importlib = has_mfs and __import__('importlib.util')", + "has_mfs and __import__('importlib.machinery')", + ( + "m = has_mfs and " + "sys.modules.setdefault(%(pkg)r, " + "importlib.util.module_from_spec(" + "importlib.machinery.PathFinder.find_spec(%(pkg)r, " + "[os.path.dirname(p)])))" + ), + ( + "m = m or " + "sys.modules.setdefault(%(pkg)r, types.ModuleType(%(pkg)r))" + ), + "mp = (m or []) and m.__dict__.setdefault('__path__',[])", + "(p not in mp) and mp.append(p)", + ) + "lines for the namespace installer" + + _nspkg_tmpl_multi = ( + 'm and setattr(sys.modules[%(parent)r], %(child)r, m)', + ) + "additional line(s) when a parent package is indicated" + + def _get_root(self): + return "sys._getframe(1).f_locals['sitedir']" + + def _gen_nspkg_line(self, pkg): + pth = tuple(pkg.split('.')) + root = self._get_root() + tmpl_lines = self._nspkg_tmpl + parent, sep, child = pkg.rpartition('.') + if parent: + tmpl_lines += self._nspkg_tmpl_multi + return ';'.join(tmpl_lines) % locals() + '\n' + + def _get_all_ns_packages(self): + """Return sorted list of all package namespaces""" + pkgs = self.distribution.namespace_packages or [] + return sorted(flatten(map(self._pkg_names, pkgs))) + + @staticmethod + def _pkg_names(pkg): + """ + Given a namespace package, yield the components of that + package. + + >>> names = Installer._pkg_names('a.b.c') + >>> set(names) == set(['a', 'a.b', 'a.b.c']) + True + """ + parts = pkg.split('.') + while parts: + yield '.'.join(parts) + parts.pop() + + +class DevelopInstaller(Installer): + def _get_root(self): + return repr(str(self.egg_path)) + + def _get_target(self): + return self.egg_link diff --git a/venv/lib/python3.10/site-packages/setuptools/package_index.py b/venv/lib/python3.10/site-packages/setuptools/package_index.py new file mode 100644 index 00000000..86bf851f --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/package_index.py @@ -0,0 +1,1176 @@ +"""PyPI and direct package downloading""" +import sys +import subprocess +import os +import re +import io +import shutil +import socket +import base64 +import hashlib +import itertools +import warnings +import configparser +import html +import http.client +import urllib.parse +import urllib.request +import urllib.error +from functools import wraps + +import setuptools +from pkg_resources import ( + CHECKOUT_DIST, Distribution, BINARY_DIST, normalize_path, SOURCE_DIST, + Environment, find_distributions, safe_name, safe_version, + to_filename, Requirement, DEVELOP_DIST, EGG_DIST, parse_version, +) +from distutils import log +from distutils.errors import DistutilsError +from fnmatch import translate +from setuptools.wheel import Wheel +from setuptools.extern.more_itertools import unique_everseen + + +EGG_FRAGMENT = re.compile(r'^egg=([-A-Za-z0-9_.+!]+)$') +HREF = re.compile(r"""href\s*=\s*['"]?([^'"> ]+)""", re.I) +PYPI_MD5 = re.compile( + r'([^<]+)\n\s+\(md5\)' +) +URL_SCHEME = re.compile('([-+.a-z0-9]{2,}):', re.I).match +EXTENSIONS = ".tar.gz .tar.bz2 .tar .zip .tgz".split() + +__all__ = [ + 'PackageIndex', 'distros_for_url', 'parse_bdist_wininst', + 'interpret_distro_name', +] + +_SOCKET_TIMEOUT = 15 + +_tmpl = "setuptools/{setuptools.__version__} Python-urllib/{py_major}" +user_agent = _tmpl.format( + py_major='{}.{}'.format(*sys.version_info), setuptools=setuptools) + + +def parse_requirement_arg(spec): + try: + return Requirement.parse(spec) + except ValueError as e: + raise DistutilsError( + "Not a URL, existing file, or requirement spec: %r" % (spec,) + ) from e + + +def parse_bdist_wininst(name): + """Return (base,pyversion) or (None,None) for possible .exe name""" + + lower = name.lower() + base, py_ver, plat = None, None, None + + if lower.endswith('.exe'): + if lower.endswith('.win32.exe'): + base = name[:-10] + plat = 'win32' + elif lower.startswith('.win32-py', -16): + py_ver = name[-7:-4] + base = name[:-16] + plat = 'win32' + elif lower.endswith('.win-amd64.exe'): + base = name[:-14] + plat = 'win-amd64' + elif lower.startswith('.win-amd64-py', -20): + py_ver = name[-7:-4] + base = name[:-20] + plat = 'win-amd64' + return base, py_ver, plat + + +def egg_info_for_url(url): + parts = urllib.parse.urlparse(url) + scheme, server, path, parameters, query, fragment = parts + base = urllib.parse.unquote(path.split('/')[-1]) + if server == 'sourceforge.net' and base == 'download': # XXX Yuck + base = urllib.parse.unquote(path.split('/')[-2]) + if '#' in base: + base, fragment = base.split('#', 1) + return base, fragment + + +def distros_for_url(url, metadata=None): + """Yield egg or source distribution objects that might be found at a URL""" + base, fragment = egg_info_for_url(url) + for dist in distros_for_location(url, base, metadata): + yield dist + if fragment: + match = EGG_FRAGMENT.match(fragment) + if match: + for dist in interpret_distro_name( + url, match.group(1), metadata, precedence=CHECKOUT_DIST + ): + yield dist + + +def distros_for_location(location, basename, metadata=None): + """Yield egg or source distribution objects based on basename""" + if basename.endswith('.egg.zip'): + basename = basename[:-4] # strip the .zip + if basename.endswith('.egg') and '-' in basename: + # only one, unambiguous interpretation + return [Distribution.from_location(location, basename, metadata)] + if basename.endswith('.whl') and '-' in basename: + wheel = Wheel(basename) + if not wheel.is_compatible(): + return [] + return [Distribution( + location=location, + project_name=wheel.project_name, + version=wheel.version, + # Increase priority over eggs. + precedence=EGG_DIST + 1, + )] + if basename.endswith('.exe'): + win_base, py_ver, platform = parse_bdist_wininst(basename) + if win_base is not None: + return interpret_distro_name( + location, win_base, metadata, py_ver, BINARY_DIST, platform + ) + # Try source distro extensions (.zip, .tgz, etc.) + # + for ext in EXTENSIONS: + if basename.endswith(ext): + basename = basename[:-len(ext)] + return interpret_distro_name(location, basename, metadata) + return [] # no extension matched + + +def distros_for_filename(filename, metadata=None): + """Yield possible egg or source distribution objects based on a filename""" + return distros_for_location( + normalize_path(filename), os.path.basename(filename), metadata + ) + + +def interpret_distro_name( + location, basename, metadata, py_version=None, precedence=SOURCE_DIST, + platform=None +): + """Generate alternative interpretations of a source distro name + + Note: if `location` is a filesystem filename, you should call + ``pkg_resources.normalize_path()`` on it before passing it to this + routine! + """ + # Generate alternative interpretations of a source distro name + # Because some packages are ambiguous as to name/versions split + # e.g. "adns-python-1.1.0", "egenix-mx-commercial", etc. + # So, we generate each possible interpretation (e.g. "adns, python-1.1.0" + # "adns-python, 1.1.0", and "adns-python-1.1.0, no version"). In practice, + # the spurious interpretations should be ignored, because in the event + # there's also an "adns" package, the spurious "python-1.1.0" version will + # compare lower than any numeric version number, and is therefore unlikely + # to match a request for it. It's still a potential problem, though, and + # in the long run PyPI and the distutils should go for "safe" names and + # versions in distribution archive names (sdist and bdist). + + parts = basename.split('-') + if not py_version and any(re.match(r'py\d\.\d$', p) for p in parts[2:]): + # it is a bdist_dumb, not an sdist -- bail out + return + + for p in range(1, len(parts) + 1): + yield Distribution( + location, metadata, '-'.join(parts[:p]), '-'.join(parts[p:]), + py_version=py_version, precedence=precedence, + platform=platform + ) + + +def unique_values(func): + """ + Wrap a function returning an iterable such that the resulting iterable + only ever yields unique items. + """ + + @wraps(func) + def wrapper(*args, **kwargs): + return unique_everseen(func(*args, **kwargs)) + + return wrapper + + +REL = re.compile(r"""<([^>]*\srel\s{0,10}=\s{0,10}['"]?([^'" >]+)[^>]*)>""", re.I) +# this line is here to fix emacs' cruddy broken syntax highlighting + + +@unique_values +def find_external_links(url, page): + """Find rel="homepage" and rel="download" links in `page`, yielding URLs""" + + for match in REL.finditer(page): + tag, rel = match.groups() + rels = set(map(str.strip, rel.lower().split(','))) + if 'homepage' in rels or 'download' in rels: + for match in HREF.finditer(tag): + yield urllib.parse.urljoin(url, htmldecode(match.group(1))) + + for tag in ("Home Page", "Download URL"): + pos = page.find(tag) + if pos != -1: + match = HREF.search(page, pos) + if match: + yield urllib.parse.urljoin(url, htmldecode(match.group(1))) + + +class ContentChecker: + """ + A null content checker that defines the interface for checking content + """ + + def feed(self, block): + """ + Feed a block of data to the hash. + """ + return + + def is_valid(self): + """ + Check the hash. Return False if validation fails. + """ + return True + + def report(self, reporter, template): + """ + Call reporter with information about the checker (hash name) + substituted into the template. + """ + return + + +class HashChecker(ContentChecker): + pattern = re.compile( + r'(?Psha1|sha224|sha384|sha256|sha512|md5)=' + r'(?P[a-f0-9]+)' + ) + + def __init__(self, hash_name, expected): + self.hash_name = hash_name + self.hash = hashlib.new(hash_name) + self.expected = expected + + @classmethod + def from_url(cls, url): + "Construct a (possibly null) ContentChecker from a URL" + fragment = urllib.parse.urlparse(url)[-1] + if not fragment: + return ContentChecker() + match = cls.pattern.search(fragment) + if not match: + return ContentChecker() + return cls(**match.groupdict()) + + def feed(self, block): + self.hash.update(block) + + def is_valid(self): + return self.hash.hexdigest() == self.expected + + def report(self, reporter, template): + msg = template % self.hash_name + return reporter(msg) + + +class PackageIndex(Environment): + """A distribution index that scans web pages for download URLs""" + + def __init__( + self, index_url="https://pypi.org/simple/", hosts=('*',), + ca_bundle=None, verify_ssl=True, *args, **kw + ): + Environment.__init__(self, *args, **kw) + self.index_url = index_url + "/" [:not index_url.endswith('/')] + self.scanned_urls = {} + self.fetched_urls = {} + self.package_pages = {} + self.allows = re.compile('|'.join(map(translate, hosts))).match + self.to_scan = [] + self.opener = urllib.request.urlopen + + def add(self, dist): + # ignore invalid versions + try: + parse_version(dist.version) + except Exception: + return + return super().add(dist) + + # FIXME: 'PackageIndex.process_url' is too complex (14) + def process_url(self, url, retrieve=False): # noqa: C901 + """Evaluate a URL as a possible download, and maybe retrieve it""" + if url in self.scanned_urls and not retrieve: + return + self.scanned_urls[url] = True + if not URL_SCHEME(url): + self.process_filename(url) + return + else: + dists = list(distros_for_url(url)) + if dists: + if not self.url_ok(url): + return + self.debug("Found link: %s", url) + + if dists or not retrieve or url in self.fetched_urls: + list(map(self.add, dists)) + return # don't need the actual page + + if not self.url_ok(url): + self.fetched_urls[url] = True + return + + self.info("Reading %s", url) + self.fetched_urls[url] = True # prevent multiple fetch attempts + tmpl = "Download error on %s: %%s -- Some packages may not be found!" + f = self.open_url(url, tmpl % url) + if f is None: + return + if isinstance(f, urllib.error.HTTPError) and f.code == 401: + self.info("Authentication error: %s" % f.msg) + self.fetched_urls[f.url] = True + if 'html' not in f.headers.get('content-type', '').lower(): + f.close() # not html, we can't process it + return + + base = f.url # handle redirects + page = f.read() + if not isinstance(page, str): + # In Python 3 and got bytes but want str. + if isinstance(f, urllib.error.HTTPError): + # Errors have no charset, assume latin1: + charset = 'latin-1' + else: + charset = f.headers.get_param('charset') or 'latin-1' + page = page.decode(charset, "ignore") + f.close() + for match in HREF.finditer(page): + link = urllib.parse.urljoin(base, htmldecode(match.group(1))) + self.process_url(link) + if url.startswith(self.index_url) and getattr(f, 'code', None) != 404: + page = self.process_index(url, page) + + def process_filename(self, fn, nested=False): + # process filenames or directories + if not os.path.exists(fn): + self.warn("Not found: %s", fn) + return + + if os.path.isdir(fn) and not nested: + path = os.path.realpath(fn) + for item in os.listdir(path): + self.process_filename(os.path.join(path, item), True) + + dists = distros_for_filename(fn) + if dists: + self.debug("Found: %s", fn) + list(map(self.add, dists)) + + def url_ok(self, url, fatal=False): + s = URL_SCHEME(url) + is_file = s and s.group(1).lower() == 'file' + if is_file or self.allows(urllib.parse.urlparse(url)[1]): + return True + msg = ( + "\nNote: Bypassing %s (disallowed host; see " + "http://bit.ly/2hrImnY for details).\n") + if fatal: + raise DistutilsError(msg % url) + else: + self.warn(msg, url) + + def scan_egg_links(self, search_path): + dirs = filter(os.path.isdir, search_path) + egg_links = ( + (path, entry) + for path in dirs + for entry in os.listdir(path) + if entry.endswith('.egg-link') + ) + list(itertools.starmap(self.scan_egg_link, egg_links)) + + def scan_egg_link(self, path, entry): + with open(os.path.join(path, entry)) as raw_lines: + # filter non-empty lines + lines = list(filter(None, map(str.strip, raw_lines))) + + if len(lines) != 2: + # format is not recognized; punt + return + + egg_path, setup_path = lines + + for dist in find_distributions(os.path.join(path, egg_path)): + dist.location = os.path.join(path, *lines) + dist.precedence = SOURCE_DIST + self.add(dist) + + def _scan(self, link): + # Process a URL to see if it's for a package page + NO_MATCH_SENTINEL = None, None + if not link.startswith(self.index_url): + return NO_MATCH_SENTINEL + + parts = list(map( + urllib.parse.unquote, link[len(self.index_url):].split('/') + )) + if len(parts) != 2 or '#' in parts[1]: + return NO_MATCH_SENTINEL + + # it's a package page, sanitize and index it + pkg = safe_name(parts[0]) + ver = safe_version(parts[1]) + self.package_pages.setdefault(pkg.lower(), {})[link] = True + return to_filename(pkg), to_filename(ver) + + def process_index(self, url, page): + """Process the contents of a PyPI page""" + + # process an index page into the package-page index + for match in HREF.finditer(page): + try: + self._scan(urllib.parse.urljoin(url, htmldecode(match.group(1)))) + except ValueError: + pass + + pkg, ver = self._scan(url) # ensure this page is in the page index + if not pkg: + return "" # no sense double-scanning non-package pages + + # process individual package page + for new_url in find_external_links(url, page): + # Process the found URL + base, frag = egg_info_for_url(new_url) + if base.endswith('.py') and not frag: + if ver: + new_url += '#egg=%s-%s' % (pkg, ver) + else: + self.need_version_info(url) + self.scan_url(new_url) + + return PYPI_MD5.sub( + lambda m: '%s' % m.group(1, 3, 2), page + ) + + def need_version_info(self, url): + self.scan_all( + "Page at %s links to .py file(s) without version info; an index " + "scan is required.", url + ) + + def scan_all(self, msg=None, *args): + if self.index_url not in self.fetched_urls: + if msg: + self.warn(msg, *args) + self.info( + "Scanning index of all packages (this may take a while)" + ) + self.scan_url(self.index_url) + + def find_packages(self, requirement): + self.scan_url(self.index_url + requirement.unsafe_name + '/') + + if not self.package_pages.get(requirement.key): + # Fall back to safe version of the name + self.scan_url(self.index_url + requirement.project_name + '/') + + if not self.package_pages.get(requirement.key): + # We couldn't find the target package, so search the index page too + self.not_found_in_index(requirement) + + for url in list(self.package_pages.get(requirement.key, ())): + # scan each page that might be related to the desired package + self.scan_url(url) + + def obtain(self, requirement, installer=None): + self.prescan() + self.find_packages(requirement) + for dist in self[requirement.key]: + if dist in requirement: + return dist + self.debug("%s does not match %s", requirement, dist) + return super(PackageIndex, self).obtain(requirement, installer) + + def check_hash(self, checker, filename, tfp): + """ + checker is a ContentChecker + """ + checker.report( + self.debug, + "Validating %%s checksum for %s" % filename) + if not checker.is_valid(): + tfp.close() + os.unlink(filename) + raise DistutilsError( + "%s validation failed for %s; " + "possible download problem?" + % (checker.hash.name, os.path.basename(filename)) + ) + + def add_find_links(self, urls): + """Add `urls` to the list that will be prescanned for searches""" + for url in urls: + if ( + self.to_scan is None # if we have already "gone online" + or not URL_SCHEME(url) # or it's a local file/directory + or url.startswith('file:') + or list(distros_for_url(url)) # or a direct package link + ): + # then go ahead and process it now + self.scan_url(url) + else: + # otherwise, defer retrieval till later + self.to_scan.append(url) + + def prescan(self): + """Scan urls scheduled for prescanning (e.g. --find-links)""" + if self.to_scan: + list(map(self.scan_url, self.to_scan)) + self.to_scan = None # from now on, go ahead and process immediately + + def not_found_in_index(self, requirement): + if self[requirement.key]: # we've seen at least one distro + meth, msg = self.info, "Couldn't retrieve index page for %r" + else: # no distros seen for this name, might be misspelled + meth, msg = ( + self.warn, + "Couldn't find index page for %r (maybe misspelled?)") + meth(msg, requirement.unsafe_name) + self.scan_all() + + def download(self, spec, tmpdir): + """Locate and/or download `spec` to `tmpdir`, returning a local path + + `spec` may be a ``Requirement`` object, or a string containing a URL, + an existing local filename, or a project/version requirement spec + (i.e. the string form of a ``Requirement`` object). If it is the URL + of a .py file with an unambiguous ``#egg=name-version`` tag (i.e., one + that escapes ``-`` as ``_`` throughout), a trivial ``setup.py`` is + automatically created alongside the downloaded file. + + If `spec` is a ``Requirement`` object or a string containing a + project/version requirement spec, this method returns the location of + a matching distribution (possibly after downloading it to `tmpdir`). + If `spec` is a locally existing file or directory name, it is simply + returned unchanged. If `spec` is a URL, it is downloaded to a subpath + of `tmpdir`, and the local filename is returned. Various errors may be + raised if a problem occurs during downloading. + """ + if not isinstance(spec, Requirement): + scheme = URL_SCHEME(spec) + if scheme: + # It's a url, download it to tmpdir + found = self._download_url(spec, tmpdir) + base, fragment = egg_info_for_url(spec) + if base.endswith('.py'): + found = self.gen_setup(found, fragment, tmpdir) + return found + elif os.path.exists(spec): + # Existing file or directory, just return it + return spec + else: + spec = parse_requirement_arg(spec) + return getattr(self.fetch_distribution(spec, tmpdir), 'location', None) + + def fetch_distribution( # noqa: C901 # is too complex (14) # FIXME + self, requirement, tmpdir, force_scan=False, source=False, + develop_ok=False, local_index=None): + """Obtain a distribution suitable for fulfilling `requirement` + + `requirement` must be a ``pkg_resources.Requirement`` instance. + If necessary, or if the `force_scan` flag is set, the requirement is + searched for in the (online) package index as well as the locally + installed packages. If a distribution matching `requirement` is found, + the returned distribution's ``location`` is the value you would have + gotten from calling the ``download()`` method with the matching + distribution's URL or filename. If no matching distribution is found, + ``None`` is returned. + + If the `source` flag is set, only source distributions and source + checkout links will be considered. Unless the `develop_ok` flag is + set, development and system eggs (i.e., those using the ``.egg-info`` + format) will be ignored. + """ + # process a Requirement + self.info("Searching for %s", requirement) + skipped = {} + dist = None + + def find(req, env=None): + if env is None: + env = self + # Find a matching distribution; may be called more than once + + for dist in env[req.key]: + + if dist.precedence == DEVELOP_DIST and not develop_ok: + if dist not in skipped: + self.warn( + "Skipping development or system egg: %s", dist, + ) + skipped[dist] = 1 + continue + + test = ( + dist in req + and (dist.precedence <= SOURCE_DIST or not source) + ) + if test: + loc = self.download(dist.location, tmpdir) + dist.download_location = loc + if os.path.exists(dist.download_location): + return dist + + if force_scan: + self.prescan() + self.find_packages(requirement) + dist = find(requirement) + + if not dist and local_index is not None: + dist = find(requirement, local_index) + + if dist is None: + if self.to_scan is not None: + self.prescan() + dist = find(requirement) + + if dist is None and not force_scan: + self.find_packages(requirement) + dist = find(requirement) + + if dist is None: + self.warn( + "No local packages or working download links found for %s%s", + (source and "a source distribution of " or ""), + requirement, + ) + else: + self.info("Best match: %s", dist) + return dist.clone(location=dist.download_location) + + def fetch(self, requirement, tmpdir, force_scan=False, source=False): + """Obtain a file suitable for fulfilling `requirement` + + DEPRECATED; use the ``fetch_distribution()`` method now instead. For + backward compatibility, this routine is identical but returns the + ``location`` of the downloaded distribution instead of a distribution + object. + """ + dist = self.fetch_distribution(requirement, tmpdir, force_scan, source) + if dist is not None: + return dist.location + return None + + def gen_setup(self, filename, fragment, tmpdir): + match = EGG_FRAGMENT.match(fragment) + dists = match and [ + d for d in + interpret_distro_name(filename, match.group(1), None) if d.version + ] or [] + + if len(dists) == 1: # unambiguous ``#egg`` fragment + basename = os.path.basename(filename) + + # Make sure the file has been downloaded to the temp dir. + if os.path.dirname(filename) != tmpdir: + dst = os.path.join(tmpdir, basename) + from setuptools.command.easy_install import samefile + if not samefile(filename, dst): + shutil.copy2(filename, dst) + filename = dst + + with open(os.path.join(tmpdir, 'setup.py'), 'w') as file: + file.write( + "from setuptools import setup\n" + "setup(name=%r, version=%r, py_modules=[%r])\n" + % ( + dists[0].project_name, dists[0].version, + os.path.splitext(basename)[0] + ) + ) + return filename + + elif match: + raise DistutilsError( + "Can't unambiguously interpret project/version identifier %r; " + "any dashes in the name or version should be escaped using " + "underscores. %r" % (fragment, dists) + ) + else: + raise DistutilsError( + "Can't process plain .py files without an '#egg=name-version'" + " suffix to enable automatic setup script generation." + ) + + dl_blocksize = 8192 + + def _download_to(self, url, filename): + self.info("Downloading %s", url) + # Download the file + fp = None + try: + checker = HashChecker.from_url(url) + fp = self.open_url(url) + if isinstance(fp, urllib.error.HTTPError): + raise DistutilsError( + "Can't download %s: %s %s" % (url, fp.code, fp.msg) + ) + headers = fp.info() + blocknum = 0 + bs = self.dl_blocksize + size = -1 + if "content-length" in headers: + # Some servers return multiple Content-Length headers :( + sizes = headers.get_all('Content-Length') + size = max(map(int, sizes)) + self.reporthook(url, filename, blocknum, bs, size) + with open(filename, 'wb') as tfp: + while True: + block = fp.read(bs) + if block: + checker.feed(block) + tfp.write(block) + blocknum += 1 + self.reporthook(url, filename, blocknum, bs, size) + else: + break + self.check_hash(checker, filename, tfp) + return headers + finally: + if fp: + fp.close() + + def reporthook(self, url, filename, blocknum, blksize, size): + pass # no-op + + # FIXME: + def open_url(self, url, warning=None): # noqa: C901 # is too complex (12) + if url.startswith('file:'): + return local_open(url) + try: + return open_with_auth(url, self.opener) + except (ValueError, http.client.InvalidURL) as v: + msg = ' '.join([str(arg) for arg in v.args]) + if warning: + self.warn(warning, msg) + else: + raise DistutilsError('%s %s' % (url, msg)) from v + except urllib.error.HTTPError as v: + return v + except urllib.error.URLError as v: + if warning: + self.warn(warning, v.reason) + else: + raise DistutilsError("Download error for %s: %s" + % (url, v.reason)) from v + except http.client.BadStatusLine as v: + if warning: + self.warn(warning, v.line) + else: + raise DistutilsError( + '%s returned a bad status line. The server might be ' + 'down, %s' % + (url, v.line) + ) from v + except (http.client.HTTPException, socket.error) as v: + if warning: + self.warn(warning, v) + else: + raise DistutilsError("Download error for %s: %s" + % (url, v)) from v + + @staticmethod + def _resolve_download_filename(url, tmpdir): + """ + >>> import pathlib + >>> du = PackageIndex._resolve_download_filename + >>> root = getfixture('tmp_path') + >>> url = 'https://files.pythonhosted.org/packages/a9/5a/0db.../setuptools-78.1.0.tar.gz' + >>> str(pathlib.Path(du(url, root)).relative_to(root)) + 'setuptools-78.1.0.tar.gz' + + Ensures the target is always in tmpdir. + + >>> url = 'https://anyhost/%2fhome%2fuser%2f.ssh%2fauthorized_keys' + >>> du(url, root) + Traceback (most recent call last): + ... + ValueError: Invalid filename... + """ + name, fragment = egg_info_for_url(url) + if name: + while '..' in name: + name = name.replace('..', '.').replace('\\', '_') + else: + name = "__downloaded__" # default if URL has no path contents + + if name.endswith('.egg.zip'): + name = name[:-4] # strip the extra .zip before download + + filename = os.path.join(tmpdir, name) + + # ensure path resolves within the tmpdir + if not filename.startswith(str(tmpdir)): + raise ValueError(f"Invalid filename {filename}") + + return filename + + def _download_url(self, url, tmpdir): + """ + Determine the download filename. + """ + filename = self._resolve_download_filename(url, tmpdir) + return self._download_vcs(url, filename) or self._download_other(url, filename) + + @staticmethod + def _resolve_vcs(url): + """ + >>> rvcs = PackageIndex._resolve_vcs + >>> rvcs('git+http://foo/bar') + 'git' + >>> rvcs('hg+https://foo/bar') + 'hg' + >>> rvcs('git:myhost') + 'git' + >>> rvcs('hg:myhost') + >>> rvcs('http://foo/bar') + """ + scheme = urllib.parse.urlsplit(url).scheme + pre, sep, post = scheme.partition('+') + # svn and git have their own protocol; hg does not + allowed = set(['svn', 'git'] + ['hg'] * bool(sep)) + return next(iter({pre} & allowed), None) + + def _download_vcs(self, url, spec_filename): + vcs = self._resolve_vcs(url) + if not vcs: + return + if vcs == 'svn': + return self._download_svn(url, spec_filename) + + filename, _, _ = spec_filename.partition('#') + url, rev = self._vcs_split_rev_from_url(url) + + self.info(f"Doing {vcs} clone from {url} to {filename}") + subprocess.check_call([vcs, 'clone', '--quiet', url, filename]) + + co_commands = dict( + git=[vcs, '-C', filename, 'checkout', '--quiet', rev], + hg=[vcs, '--cwd', filename, 'up', '-C', '-r', rev, '-q'], + ) + if rev is not None: + self.info(f"Checking out {rev}") + subprocess.check_call(co_commands[vcs]) + + return filename + + def _download_other(self, url, filename): + scheme = urllib.parse.urlsplit(url).scheme + if scheme == 'file': # pragma: no cover + return urllib.request.url2pathname(urllib.parse.urlparse(url).path) + # raise error if not allowed + self.url_ok(url, True) + return self._attempt_download(url, filename) + + def scan_url(self, url): + self.process_url(url, True) + + def _attempt_download(self, url, filename): + headers = self._download_to(url, filename) + if 'html' in headers.get('content-type', '').lower(): + return self._download_html(url, headers, filename) + else: + return filename + + def _download_html(self, url, headers, filename): + file = open(filename) + for line in file: + if line.strip(): + # Check for a subversion index page + if re.search(r'([^- ]+ - )?Revision \d+:', line): + # it's a subversion index page: + file.close() + os.unlink(filename) + return self._download_svn(url, filename) + break # not an index page + file.close() + os.unlink(filename) + raise DistutilsError("Unexpected HTML page found at " + url) + + def _download_svn(self, url, filename): + warnings.warn("SVN download support is deprecated", UserWarning) + url = url.split('#', 1)[0] # remove any fragment for svn's sake + creds = [] + if url.lower().startswith('svn:') and '@' in url: + scheme, netloc, path, p, q, f = urllib.parse.urlparse(url) + if not netloc and path.startswith('//') and '/' in path[2:]: + netloc, path = path[2:].split('/', 1) + auth, host = _splituser(netloc) + if auth: + if ':' in auth: + user, pw = auth.split(':', 1) + creds.extend(["--username", user, "--password", pw]) + else: + creds.extend(["--username", auth]) + netloc = host + parts = scheme, netloc, url, p, q, f + url = urllib.parse.urlunparse(parts) + self.info("Doing subversion checkout from %s to %s", url, filename) + cmd = ["svn", "checkout", "-q"] + creds + [url, filename] + subprocess.check_call(cmd) + + return filename + + @staticmethod + def _vcs_split_rev_from_url(url): + """ + Given a possible VCS URL, return a clean URL and resolved revision if any. + + >>> vsrfu = PackageIndex._vcs_split_rev_from_url + >>> vsrfu('git+https://github.com/pypa/setuptools@v69.0.0#egg-info=setuptools') + ('https://github.com/pypa/setuptools', 'v69.0.0') + >>> vsrfu('git+https://github.com/pypa/setuptools#egg-info=setuptools') + ('https://github.com/pypa/setuptools', None) + >>> vsrfu('http://foo/bar') + ('http://foo/bar', None) + """ + parts = urllib.parse.urlsplit(url) + + clean_scheme = parts.scheme.split('+', 1)[-1] + + # Some fragment identification fails + no_fragment_path, _, _ = parts.path.partition('#') + + pre, sep, post = no_fragment_path.rpartition('@') + clean_path, rev = (pre, post) if sep else (post, None) + + resolved = parts._replace( + scheme=clean_scheme, + path=clean_path, + # discard the fragment + fragment='', + ).geturl() + + return resolved, rev + + def debug(self, msg, *args): + log.debug(msg, *args) + + def info(self, msg, *args): + log.info(msg, *args) + + def warn(self, msg, *args): + log.warn(msg, *args) + + +# This pattern matches a character entity reference (a decimal numeric +# references, a hexadecimal numeric reference, or a named reference). +entity_sub = re.compile(r'&(#(\d+|x[\da-fA-F]+)|[\w.:-]+);?').sub + + +def decode_entity(match): + what = match.group(0) + return html.unescape(what) + + +def htmldecode(text): + """ + Decode HTML entities in the given text. + + >>> htmldecode( + ... 'https://../package_name-0.1.2.tar.gz' + ... '?tokena=A&tokenb=B">package_name-0.1.2.tar.gz') + 'https://../package_name-0.1.2.tar.gz?tokena=A&tokenb=B">package_name-0.1.2.tar.gz' + """ + return entity_sub(decode_entity, text) + + +def socket_timeout(timeout=15): + def _socket_timeout(func): + def _socket_timeout(*args, **kwargs): + old_timeout = socket.getdefaulttimeout() + socket.setdefaulttimeout(timeout) + try: + return func(*args, **kwargs) + finally: + socket.setdefaulttimeout(old_timeout) + + return _socket_timeout + + return _socket_timeout + + +def _encode_auth(auth): + """ + Encode auth from a URL suitable for an HTTP header. + >>> str(_encode_auth('username%3Apassword')) + 'dXNlcm5hbWU6cGFzc3dvcmQ=' + + Long auth strings should not cause a newline to be inserted. + >>> long_auth = 'username:' + 'password'*10 + >>> chr(10) in str(_encode_auth(long_auth)) + False + """ + auth_s = urllib.parse.unquote(auth) + # convert to bytes + auth_bytes = auth_s.encode() + encoded_bytes = base64.b64encode(auth_bytes) + # convert back to a string + encoded = encoded_bytes.decode() + # strip the trailing carriage return + return encoded.replace('\n', '') + + +class Credential: + """ + A username/password pair. Use like a namedtuple. + """ + + def __init__(self, username, password): + self.username = username + self.password = password + + def __iter__(self): + yield self.username + yield self.password + + def __str__(self): + return '%(username)s:%(password)s' % vars(self) + + +class PyPIConfig(configparser.RawConfigParser): + def __init__(self): + """ + Load from ~/.pypirc + """ + defaults = dict.fromkeys(['username', 'password', 'repository'], '') + configparser.RawConfigParser.__init__(self, defaults) + + rc = os.path.join(os.path.expanduser('~'), '.pypirc') + if os.path.exists(rc): + self.read(rc) + + @property + def creds_by_repository(self): + sections_with_repositories = [ + section for section in self.sections() + if self.get(section, 'repository').strip() + ] + + return dict(map(self._get_repo_cred, sections_with_repositories)) + + def _get_repo_cred(self, section): + repo = self.get(section, 'repository').strip() + return repo, Credential( + self.get(section, 'username').strip(), + self.get(section, 'password').strip(), + ) + + def find_credential(self, url): + """ + If the URL indicated appears to be a repository defined in this + config, return the credential for that repository. + """ + for repository, cred in self.creds_by_repository.items(): + if url.startswith(repository): + return cred + + +def open_with_auth(url, opener=urllib.request.urlopen): + """Open a urllib2 request, handling HTTP authentication""" + + parsed = urllib.parse.urlparse(url) + scheme, netloc, path, params, query, frag = parsed + + # Double scheme does not raise on macOS as revealed by a + # failing test. We would expect "nonnumeric port". Refs #20. + if netloc.endswith(':'): + raise http.client.InvalidURL("nonnumeric port: ''") + + if scheme in ('http', 'https'): + auth, address = _splituser(netloc) + else: + auth = None + + if not auth: + cred = PyPIConfig().find_credential(url) + if cred: + auth = str(cred) + info = cred.username, url + log.info('Authenticating as %s for %s (from .pypirc)', *info) + + if auth: + auth = "Basic " + _encode_auth(auth) + parts = scheme, address, path, params, query, frag + new_url = urllib.parse.urlunparse(parts) + request = urllib.request.Request(new_url) + request.add_header("Authorization", auth) + else: + request = urllib.request.Request(url) + + request.add_header('User-Agent', user_agent) + fp = opener(request) + + if auth: + # Put authentication info back into request URL if same host, + # so that links found on the page will work + s2, h2, path2, param2, query2, frag2 = urllib.parse.urlparse(fp.url) + if s2 == scheme and h2 == address: + parts = s2, netloc, path2, param2, query2, frag2 + fp.url = urllib.parse.urlunparse(parts) + + return fp + + +# copy of urllib.parse._splituser from Python 3.8 +def _splituser(host): + """splituser('user[:passwd]@host[:port]') + --> 'user[:passwd]', 'host[:port]'.""" + user, delim, host = host.rpartition('@') + return (user if delim else None), host + + +# adding a timeout to avoid freezing package_index +open_with_auth = socket_timeout(_SOCKET_TIMEOUT)(open_with_auth) + + +def fix_sf_url(url): + return url # backward compatibility + + +def local_open(url): + """Read a local path, with special support for directories""" + scheme, server, path, param, query, frag = urllib.parse.urlparse(url) + filename = urllib.request.url2pathname(path) + if os.path.isfile(filename): + return urllib.request.urlopen(url) + elif path.endswith('/') and os.path.isdir(filename): + files = [] + for f in os.listdir(filename): + filepath = os.path.join(filename, f) + if f == 'index.html': + with open(filepath, 'r') as fp: + body = fp.read() + break + elif os.path.isdir(filepath): + f += '/' + files.append('<a href="{name}">{name}</a>'.format(name=f)) + else: + tmpl = ( + "<html><head><title>{url}" + "{files}") + body = tmpl.format(url=url, files='\n'.join(files)) + status, message = 200, "OK" + else: + status, message, body = 404, "Path not found", "Not found" + + headers = {'content-type': 'text/html'} + body_stream = io.StringIO(body) + return urllib.error.HTTPError(url, status, message, headers, body_stream) diff --git a/venv/lib/python3.10/site-packages/setuptools/py34compat.py b/venv/lib/python3.10/site-packages/setuptools/py34compat.py new file mode 100644 index 00000000..3ad91722 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/py34compat.py @@ -0,0 +1,13 @@ +import importlib + +try: + import importlib.util +except ImportError: + pass + + +try: + module_from_spec = importlib.util.module_from_spec +except AttributeError: + def module_from_spec(spec): + return spec.loader.load_module(spec.name) diff --git a/venv/lib/python3.10/site-packages/setuptools/sandbox.py b/venv/lib/python3.10/site-packages/setuptools/sandbox.py new file mode 100644 index 00000000..034fc80d --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/sandbox.py @@ -0,0 +1,530 @@ +import os +import sys +import tempfile +import operator +import functools +import itertools +import re +import contextlib +import pickle +import textwrap +import builtins + +import pkg_resources +from distutils.errors import DistutilsError +from pkg_resources import working_set + +if sys.platform.startswith('java'): + import org.python.modules.posix.PosixModule as _os +else: + _os = sys.modules[os.name] +try: + _file = file +except NameError: + _file = None +_open = open + + +__all__ = [ + "AbstractSandbox", + "DirectorySandbox", + "SandboxViolation", + "run_setup", +] + + +def _execfile(filename, globals, locals=None): + """ + Python 3 implementation of execfile. + """ + mode = 'rb' + with open(filename, mode) as stream: + script = stream.read() + if locals is None: + locals = globals + code = compile(script, filename, 'exec') + exec(code, globals, locals) + + +@contextlib.contextmanager +def save_argv(repl=None): + saved = sys.argv[:] + if repl is not None: + sys.argv[:] = repl + try: + yield saved + finally: + sys.argv[:] = saved + + +@contextlib.contextmanager +def save_path(): + saved = sys.path[:] + try: + yield saved + finally: + sys.path[:] = saved + + +@contextlib.contextmanager +def override_temp(replacement): + """ + Monkey-patch tempfile.tempdir with replacement, ensuring it exists + """ + os.makedirs(replacement, exist_ok=True) + + saved = tempfile.tempdir + + tempfile.tempdir = replacement + + try: + yield + finally: + tempfile.tempdir = saved + + +@contextlib.contextmanager +def pushd(target): + saved = os.getcwd() + os.chdir(target) + try: + yield saved + finally: + os.chdir(saved) + + +class UnpickleableException(Exception): + """ + An exception representing another Exception that could not be pickled. + """ + + @staticmethod + def dump(type, exc): + """ + Always return a dumped (pickled) type and exc. If exc can't be pickled, + wrap it in UnpickleableException first. + """ + try: + return pickle.dumps(type), pickle.dumps(exc) + except Exception: + # get UnpickleableException inside the sandbox + from setuptools.sandbox import UnpickleableException as cls + + return cls.dump(cls, cls(repr(exc))) + + +class ExceptionSaver: + """ + A Context Manager that will save an exception, serialized, and restore it + later. + """ + + def __enter__(self): + return self + + def __exit__(self, type, exc, tb): + if not exc: + return + + # dump the exception + self._saved = UnpickleableException.dump(type, exc) + self._tb = tb + + # suppress the exception + return True + + def resume(self): + "restore and re-raise any exception" + + if '_saved' not in vars(self): + return + + type, exc = map(pickle.loads, self._saved) + raise exc.with_traceback(self._tb) + + +@contextlib.contextmanager +def save_modules(): + """ + Context in which imported modules are saved. + + Translates exceptions internal to the context into the equivalent exception + outside the context. + """ + saved = sys.modules.copy() + with ExceptionSaver() as saved_exc: + yield saved + + sys.modules.update(saved) + # remove any modules imported since + del_modules = ( + mod_name + for mod_name in sys.modules + if mod_name not in saved + # exclude any encodings modules. See #285 + and not mod_name.startswith('encodings.') + ) + _clear_modules(del_modules) + + saved_exc.resume() + + +def _clear_modules(module_names): + for mod_name in list(module_names): + del sys.modules[mod_name] + + +@contextlib.contextmanager +def save_pkg_resources_state(): + saved = pkg_resources.__getstate__() + try: + yield saved + finally: + pkg_resources.__setstate__(saved) + + +@contextlib.contextmanager +def setup_context(setup_dir): + temp_dir = os.path.join(setup_dir, 'temp') + with save_pkg_resources_state(): + with save_modules(): + with save_path(): + hide_setuptools() + with save_argv(): + with override_temp(temp_dir): + with pushd(setup_dir): + # ensure setuptools commands are available + __import__('setuptools') + yield + + +_MODULES_TO_HIDE = { + 'setuptools', + 'distutils', + 'pkg_resources', + 'Cython', + '_distutils_hack', +} + + +def _needs_hiding(mod_name): + """ + >>> _needs_hiding('setuptools') + True + >>> _needs_hiding('pkg_resources') + True + >>> _needs_hiding('setuptools_plugin') + False + >>> _needs_hiding('setuptools.__init__') + True + >>> _needs_hiding('distutils') + True + >>> _needs_hiding('os') + False + >>> _needs_hiding('Cython') + True + """ + base_module = mod_name.split('.', 1)[0] + return base_module in _MODULES_TO_HIDE + + +def hide_setuptools(): + """ + Remove references to setuptools' modules from sys.modules to allow the + invocation to import the most appropriate setuptools. This technique is + necessary to avoid issues such as #315 where setuptools upgrading itself + would fail to find a function declared in the metadata. + """ + _distutils_hack = sys.modules.get('_distutils_hack', None) + if _distutils_hack is not None: + _distutils_hack.remove_shim() + + modules = filter(_needs_hiding, sys.modules) + _clear_modules(modules) + + +def run_setup(setup_script, args): + """Run a distutils setup script, sandboxed in its directory""" + setup_dir = os.path.abspath(os.path.dirname(setup_script)) + with setup_context(setup_dir): + try: + sys.argv[:] = [setup_script] + list(args) + sys.path.insert(0, setup_dir) + # reset to include setup dir, w/clean callback list + working_set.__init__() + working_set.callbacks.append(lambda dist: dist.activate()) + + with DirectorySandbox(setup_dir): + ns = dict(__file__=setup_script, __name__='__main__') + _execfile(setup_script, ns) + except SystemExit as v: + if v.args and v.args[0]: + raise + # Normal exit, just return + + +class AbstractSandbox: + """Wrap 'os' module and 'open()' builtin for virtualizing setup scripts""" + + _active = False + + def __init__(self): + self._attrs = [ + name + for name in dir(_os) + if not name.startswith('_') and hasattr(self, name) + ] + + def _copy(self, source): + for name in self._attrs: + setattr(os, name, getattr(source, name)) + + def __enter__(self): + self._copy(self) + if _file: + builtins.file = self._file + builtins.open = self._open + self._active = True + + def __exit__(self, exc_type, exc_value, traceback): + self._active = False + if _file: + builtins.file = _file + builtins.open = _open + self._copy(_os) + + def run(self, func): + """Run 'func' under os sandboxing""" + with self: + return func() + + def _mk_dual_path_wrapper(name): + original = getattr(_os, name) + + def wrap(self, src, dst, *args, **kw): + if self._active: + src, dst = self._remap_pair(name, src, dst, *args, **kw) + return original(src, dst, *args, **kw) + + return wrap + + for name in ["rename", "link", "symlink"]: + if hasattr(_os, name): + locals()[name] = _mk_dual_path_wrapper(name) + + def _mk_single_path_wrapper(name, original=None): + original = original or getattr(_os, name) + + def wrap(self, path, *args, **kw): + if self._active: + path = self._remap_input(name, path, *args, **kw) + return original(path, *args, **kw) + + return wrap + + if _file: + _file = _mk_single_path_wrapper('file', _file) + _open = _mk_single_path_wrapper('open', _open) + for name in [ + "stat", + "listdir", + "chdir", + "open", + "chmod", + "chown", + "mkdir", + "remove", + "unlink", + "rmdir", + "utime", + "lchown", + "chroot", + "lstat", + "startfile", + "mkfifo", + "mknod", + "pathconf", + "access", + ]: + if hasattr(_os, name): + locals()[name] = _mk_single_path_wrapper(name) + + def _mk_single_with_return(name): + original = getattr(_os, name) + + def wrap(self, path, *args, **kw): + if self._active: + path = self._remap_input(name, path, *args, **kw) + return self._remap_output(name, original(path, *args, **kw)) + return original(path, *args, **kw) + + return wrap + + for name in ['readlink', 'tempnam']: + if hasattr(_os, name): + locals()[name] = _mk_single_with_return(name) + + def _mk_query(name): + original = getattr(_os, name) + + def wrap(self, *args, **kw): + retval = original(*args, **kw) + if self._active: + return self._remap_output(name, retval) + return retval + + return wrap + + for name in ['getcwd', 'tmpnam']: + if hasattr(_os, name): + locals()[name] = _mk_query(name) + + def _validate_path(self, path): + """Called to remap or validate any path, whether input or output""" + return path + + def _remap_input(self, operation, path, *args, **kw): + """Called for path inputs""" + return self._validate_path(path) + + def _remap_output(self, operation, path): + """Called for path outputs""" + return self._validate_path(path) + + def _remap_pair(self, operation, src, dst, *args, **kw): + """Called for path pairs like rename, link, and symlink operations""" + return ( + self._remap_input(operation + '-from', src, *args, **kw), + self._remap_input(operation + '-to', dst, *args, **kw), + ) + + +if hasattr(os, 'devnull'): + _EXCEPTIONS = [os.devnull] +else: + _EXCEPTIONS = [] + + +class DirectorySandbox(AbstractSandbox): + """Restrict operations to a single subdirectory - pseudo-chroot""" + + write_ops = dict.fromkeys( + [ + "open", + "chmod", + "chown", + "mkdir", + "remove", + "unlink", + "rmdir", + "utime", + "lchown", + "chroot", + "mkfifo", + "mknod", + "tempnam", + ] + ) + + _exception_patterns = [] + "exempt writing to paths that match the pattern" + + def __init__(self, sandbox, exceptions=_EXCEPTIONS): + self._sandbox = os.path.normcase(os.path.realpath(sandbox)) + self._prefix = os.path.join(self._sandbox, '') + self._exceptions = [ + os.path.normcase(os.path.realpath(path)) for path in exceptions + ] + AbstractSandbox.__init__(self) + + def _violation(self, operation, *args, **kw): + from setuptools.sandbox import SandboxViolation + + raise SandboxViolation(operation, args, kw) + + if _file: + + def _file(self, path, mode='r', *args, **kw): + if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path): + self._violation("file", path, mode, *args, **kw) + return _file(path, mode, *args, **kw) + + def _open(self, path, mode='r', *args, **kw): + if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path): + self._violation("open", path, mode, *args, **kw) + return _open(path, mode, *args, **kw) + + def tmpnam(self): + self._violation("tmpnam") + + def _ok(self, path): + active = self._active + try: + self._active = False + realpath = os.path.normcase(os.path.realpath(path)) + return ( + self._exempted(realpath) + or realpath == self._sandbox + or realpath.startswith(self._prefix) + ) + finally: + self._active = active + + def _exempted(self, filepath): + start_matches = ( + filepath.startswith(exception) for exception in self._exceptions + ) + pattern_matches = ( + re.match(pattern, filepath) for pattern in self._exception_patterns + ) + candidates = itertools.chain(start_matches, pattern_matches) + return any(candidates) + + def _remap_input(self, operation, path, *args, **kw): + """Called for path inputs""" + if operation in self.write_ops and not self._ok(path): + self._violation(operation, os.path.realpath(path), *args, **kw) + return path + + def _remap_pair(self, operation, src, dst, *args, **kw): + """Called for path pairs like rename, link, and symlink operations""" + if not self._ok(src) or not self._ok(dst): + self._violation(operation, src, dst, *args, **kw) + return (src, dst) + + def open(self, file, flags, mode=0o777, *args, **kw): + """Called for low-level os.open()""" + if flags & WRITE_FLAGS and not self._ok(file): + self._violation("os.open", file, flags, mode, *args, **kw) + return _os.open(file, flags, mode, *args, **kw) + + +WRITE_FLAGS = functools.reduce( + operator.or_, + [ + getattr(_os, a, 0) + for a in "O_WRONLY O_RDWR O_APPEND O_CREAT O_TRUNC O_TEMPORARY".split() + ], +) + + +class SandboxViolation(DistutilsError): + """A setup script attempted to modify the filesystem outside the sandbox""" + + tmpl = textwrap.dedent( + """ + SandboxViolation: {cmd}{args!r} {kwargs} + + The package setup script has attempted to modify files on your system + that are not within the EasyInstall build area, and has been aborted. + + This package cannot be safely installed by EasyInstall, and may not + support alternate installation locations even if you run its setup + script by hand. Please inform the package's author and the EasyInstall + maintainers to find out if a fix or workaround is available. + """ + ).lstrip() + + def __str__(self): + cmd, args, kwargs = self.args + return self.tmpl.format(**locals()) diff --git a/venv/lib/python3.10/site-packages/setuptools/script (dev).tmpl b/venv/lib/python3.10/site-packages/setuptools/script (dev).tmpl new file mode 100644 index 00000000..39a24b04 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/script (dev).tmpl @@ -0,0 +1,6 @@ +# EASY-INSTALL-DEV-SCRIPT: %(spec)r,%(script_name)r +__requires__ = %(spec)r +__import__('pkg_resources').require(%(spec)r) +__file__ = %(dev_path)r +with open(__file__) as f: + exec(compile(f.read(), __file__, 'exec')) diff --git a/venv/lib/python3.10/site-packages/setuptools/script.tmpl b/venv/lib/python3.10/site-packages/setuptools/script.tmpl new file mode 100644 index 00000000..ff5efbca --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/script.tmpl @@ -0,0 +1,3 @@ +# EASY-INSTALL-SCRIPT: %(spec)r,%(script_name)r +__requires__ = %(spec)r +__import__('pkg_resources').run_script(%(spec)r, %(script_name)r) diff --git a/venv/lib/python3.10/site-packages/setuptools/unicode_utils.py b/venv/lib/python3.10/site-packages/setuptools/unicode_utils.py new file mode 100644 index 00000000..e84e65e3 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/unicode_utils.py @@ -0,0 +1,42 @@ +import unicodedata +import sys + + +# HFS Plus uses decomposed UTF-8 +def decompose(path): + if isinstance(path, str): + return unicodedata.normalize('NFD', path) + try: + path = path.decode('utf-8') + path = unicodedata.normalize('NFD', path) + path = path.encode('utf-8') + except UnicodeError: + pass # Not UTF-8 + return path + + +def filesys_decode(path): + """ + Ensure that the given path is decoded, + NONE when no expected encoding works + """ + + if isinstance(path, str): + return path + + fs_enc = sys.getfilesystemencoding() or 'utf-8' + candidates = fs_enc, 'utf-8' + + for enc in candidates: + try: + return path.decode(enc) + except UnicodeDecodeError: + continue + + +def try_encode(string, enc): + "turn unicode encoding into a functional routine" + try: + return string.encode(enc) + except UnicodeEncodeError: + return None diff --git a/venv/lib/python3.10/site-packages/setuptools/version.py b/venv/lib/python3.10/site-packages/setuptools/version.py new file mode 100644 index 00000000..95e18696 --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/version.py @@ -0,0 +1,6 @@ +import pkg_resources + +try: + __version__ = pkg_resources.get_distribution('setuptools').version +except Exception: + __version__ = 'unknown' diff --git a/venv/lib/python3.10/site-packages/setuptools/wheel.py b/venv/lib/python3.10/site-packages/setuptools/wheel.py new file mode 100644 index 00000000..0be811af --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/wheel.py @@ -0,0 +1,213 @@ +"""Wheels support.""" + +from distutils.util import get_platform +from distutils import log +import email +import itertools +import os +import posixpath +import re +import zipfile + +import pkg_resources +import setuptools +from pkg_resources import parse_version +from setuptools.extern.packaging.tags import sys_tags +from setuptools.extern.packaging.utils import canonicalize_name +from setuptools.command.egg_info import write_requirements + + +WHEEL_NAME = re.compile( + r"""^(?P.+?)-(?P\d.*?) + ((-(?P\d.*?))?-(?P.+?)-(?P.+?)-(?P.+?) + )\.whl$""", + re.VERBOSE).match + +NAMESPACE_PACKAGE_INIT = \ + "__import__('pkg_resources').declare_namespace(__name__)\n" + + +def unpack(src_dir, dst_dir): + '''Move everything under `src_dir` to `dst_dir`, and delete the former.''' + for dirpath, dirnames, filenames in os.walk(src_dir): + subdir = os.path.relpath(dirpath, src_dir) + for f in filenames: + src = os.path.join(dirpath, f) + dst = os.path.join(dst_dir, subdir, f) + os.renames(src, dst) + for n, d in reversed(list(enumerate(dirnames))): + src = os.path.join(dirpath, d) + dst = os.path.join(dst_dir, subdir, d) + if not os.path.exists(dst): + # Directory does not exist in destination, + # rename it and prune it from os.walk list. + os.renames(src, dst) + del dirnames[n] + # Cleanup. + for dirpath, dirnames, filenames in os.walk(src_dir, topdown=True): + assert not filenames + os.rmdir(dirpath) + + +class Wheel: + + def __init__(self, filename): + match = WHEEL_NAME(os.path.basename(filename)) + if match is None: + raise ValueError('invalid wheel name: %r' % filename) + self.filename = filename + for k, v in match.groupdict().items(): + setattr(self, k, v) + + def tags(self): + '''List tags (py_version, abi, platform) supported by this wheel.''' + return itertools.product( + self.py_version.split('.'), + self.abi.split('.'), + self.platform.split('.'), + ) + + def is_compatible(self): + '''Is the wheel is compatible with the current platform?''' + supported_tags = set( + (t.interpreter, t.abi, t.platform) for t in sys_tags()) + return next((True for t in self.tags() if t in supported_tags), False) + + def egg_name(self): + return pkg_resources.Distribution( + project_name=self.project_name, version=self.version, + platform=(None if self.platform == 'any' else get_platform()), + ).egg_name() + '.egg' + + def get_dist_info(self, zf): + # find the correct name of the .dist-info dir in the wheel file + for member in zf.namelist(): + dirname = posixpath.dirname(member) + if (dirname.endswith('.dist-info') and + canonicalize_name(dirname).startswith( + canonicalize_name(self.project_name))): + return dirname + raise ValueError("unsupported wheel format. .dist-info not found") + + def install_as_egg(self, destination_eggdir): + '''Install wheel as an egg directory.''' + with zipfile.ZipFile(self.filename) as zf: + self._install_as_egg(destination_eggdir, zf) + + def _install_as_egg(self, destination_eggdir, zf): + dist_basename = '%s-%s' % (self.project_name, self.version) + dist_info = self.get_dist_info(zf) + dist_data = '%s.data' % dist_basename + egg_info = os.path.join(destination_eggdir, 'EGG-INFO') + + self._convert_metadata(zf, destination_eggdir, dist_info, egg_info) + self._move_data_entries(destination_eggdir, dist_data) + self._fix_namespace_packages(egg_info, destination_eggdir) + + @staticmethod + def _convert_metadata(zf, destination_eggdir, dist_info, egg_info): + def get_metadata(name): + with zf.open(posixpath.join(dist_info, name)) as fp: + value = fp.read().decode('utf-8') + return email.parser.Parser().parsestr(value) + + wheel_metadata = get_metadata('WHEEL') + # Check wheel format version is supported. + wheel_version = parse_version(wheel_metadata.get('Wheel-Version')) + wheel_v1 = ( + parse_version('1.0') <= wheel_version < parse_version('2.0dev0') + ) + if not wheel_v1: + raise ValueError( + 'unsupported wheel format version: %s' % wheel_version) + # Extract to target directory. + os.mkdir(destination_eggdir) + zf.extractall(destination_eggdir) + # Convert metadata. + dist_info = os.path.join(destination_eggdir, dist_info) + dist = pkg_resources.Distribution.from_location( + destination_eggdir, dist_info, + metadata=pkg_resources.PathMetadata(destination_eggdir, dist_info), + ) + + # Note: Evaluate and strip markers now, + # as it's difficult to convert back from the syntax: + # foobar; "linux" in sys_platform and extra == 'test' + def raw_req(req): + req.marker = None + return str(req) + install_requires = list(sorted(map(raw_req, dist.requires()))) + extras_require = { + extra: sorted( + req + for req in map(raw_req, dist.requires((extra,))) + if req not in install_requires + ) + for extra in dist.extras + } + os.rename(dist_info, egg_info) + os.rename( + os.path.join(egg_info, 'METADATA'), + os.path.join(egg_info, 'PKG-INFO'), + ) + setup_dist = setuptools.Distribution( + attrs=dict( + install_requires=install_requires, + extras_require=extras_require, + ), + ) + # Temporarily disable info traces. + log_threshold = log._global_log.threshold + log.set_threshold(log.WARN) + try: + write_requirements( + setup_dist.get_command_obj('egg_info'), + None, + os.path.join(egg_info, 'requires.txt'), + ) + finally: + log.set_threshold(log_threshold) + + @staticmethod + def _move_data_entries(destination_eggdir, dist_data): + """Move data entries to their correct location.""" + dist_data = os.path.join(destination_eggdir, dist_data) + dist_data_scripts = os.path.join(dist_data, 'scripts') + if os.path.exists(dist_data_scripts): + egg_info_scripts = os.path.join( + destination_eggdir, 'EGG-INFO', 'scripts') + os.mkdir(egg_info_scripts) + for entry in os.listdir(dist_data_scripts): + # Remove bytecode, as it's not properly handled + # during easy_install scripts install phase. + if entry.endswith('.pyc'): + os.unlink(os.path.join(dist_data_scripts, entry)) + else: + os.rename( + os.path.join(dist_data_scripts, entry), + os.path.join(egg_info_scripts, entry), + ) + os.rmdir(dist_data_scripts) + for subdir in filter(os.path.exists, ( + os.path.join(dist_data, d) + for d in ('data', 'headers', 'purelib', 'platlib') + )): + unpack(subdir, destination_eggdir) + if os.path.exists(dist_data): + os.rmdir(dist_data) + + @staticmethod + def _fix_namespace_packages(egg_info, destination_eggdir): + namespace_packages = os.path.join( + egg_info, 'namespace_packages.txt') + if os.path.exists(namespace_packages): + with open(namespace_packages) as fp: + namespace_packages = fp.read().split() + for mod in namespace_packages: + mod_dir = os.path.join(destination_eggdir, *mod.split('.')) + mod_init = os.path.join(mod_dir, '__init__.py') + if not os.path.exists(mod_dir): + os.mkdir(mod_dir) + if not os.path.exists(mod_init): + with open(mod_init, 'w') as fp: + fp.write(NAMESPACE_PACKAGE_INIT) diff --git a/venv/lib/python3.10/site-packages/setuptools/windows_support.py b/venv/lib/python3.10/site-packages/setuptools/windows_support.py new file mode 100644 index 00000000..cb977cff --- /dev/null +++ b/venv/lib/python3.10/site-packages/setuptools/windows_support.py @@ -0,0 +1,29 @@ +import platform +import ctypes + + +def windows_only(func): + if platform.system() != 'Windows': + return lambda *args, **kwargs: None + return func + + +@windows_only +def hide_file(path): + """ + Set the hidden attribute on a file or directory. + + From http://stackoverflow.com/questions/19622133/ + + `path` must be text. + """ + __import__('ctypes.wintypes') + SetFileAttributes = ctypes.windll.kernel32.SetFileAttributesW + SetFileAttributes.argtypes = ctypes.wintypes.LPWSTR, ctypes.wintypes.DWORD + SetFileAttributes.restype = ctypes.wintypes.BOOL + + FILE_ATTRIBUTE_HIDDEN = 0x02 + + ret = SetFileAttributes(path, FILE_ATTRIBUTE_HIDDEN) + if not ret: + raise ctypes.WinError() diff --git a/venv/lib64 b/venv/lib64 new file mode 120000 index 00000000..7951405f --- /dev/null +++ b/venv/lib64 @@ -0,0 +1 @@ +lib \ No newline at end of file diff --git a/venv/pyvenv.cfg b/venv/pyvenv.cfg new file mode 100644 index 00000000..0537ffc0 --- /dev/null +++ b/venv/pyvenv.cfg @@ -0,0 +1,3 @@ +home = /usr/bin +include-system-site-packages = false +version = 3.10.12 diff --git a/zensical.toml b/zensical.toml index 3d43a5bf..a2659051 100644 --- a/zensical.toml +++ b/zensical.toml @@ -31,6 +31,11 @@ nav = [ "tlm/db/sqlite.md", ] }, ] }, + { "Executing" = [ + "exec/procs_flow.md", + "exec/author.md", + "exec/execution.md", + ] }, { "Deploying" = [ "prod/dev.md", "prod/testbed.md", From 3c448aa077d72273638d1bc9741c32502c7ef9c6 Mon Sep 17 00:00:00 2001 From: djgreen Date: Fri, 5 Jun 2026 15:16:48 -0700 Subject: [PATCH 2/5] adding the other sections for the authoring section --- docs/assets/retro_jpl_logo.jpg | Bin 0 -> 66045 bytes docs/exec/author.md | 75 +++++++++++++++--- docs/exec/procs_flow.md | 26 ++++++ .../procedure_pictures/retro_jpl_logo.jpg | Bin 0 -> 66045 bytes .../test_procedure.hermes.md | 68 +++++++++++----- 5 files changed, 139 insertions(+), 30 deletions(-) create mode 100644 docs/assets/retro_jpl_logo.jpg create mode 100644 docs/procedures/procedure_templates/procedure_pictures/retro_jpl_logo.jpg rename docs/{assets => procedures/procedure_templates}/test_procedure.hermes.md (76%) diff --git a/docs/assets/retro_jpl_logo.jpg b/docs/assets/retro_jpl_logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bb2afc27e9447fbc30a793a9b3cefda00f9cd7e0 GIT binary patch literal 66045 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 z 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 **MANUAL INSTRUCT:** +--- ### :fontawesome-solid-text-slash: Redlines * Red Lines are used to document a deviation from the approved procedural steps. These are critical to repeating tests effectively and understanding if an anomaly is due to test response, procedural error, or operator error. @@ -58,16 +54,77 @@ When using a FPrime code block, there are additional features that allow for int **REDLINE_STOP:** -### :fontawesome-solid-note-sticky: Notes - +--- ### :fontawesome-solid-compact-disc: Record +``` py +--- + **RECORD:** denotes a manual recording of a value in the procedure to be written in by the operator +- (record in the next markdown Code Block) +- **RECORD BELOW:** +``` + +``` py +``` +``` py +--- +``` +:fontawesome-solid-arrow-down: Looks like :fontawesome-solid-arrow-down: + +--- + **RECORD:** denotes a manual recording of a value in the procedure to be written in by the operator +- (record in the next markdown Code Block) +- **RECORD BELOW:** + +``` py +``` + +--- +### :fontawesome-solid-table-columns: Tables +``` py +|Test Number |Condition |Notes | +|:- |:-: |:-: | +|Test 1 | Hot | | +|Test 2 | Cold | | +``` +:fontawesome-solid-arrow-down: Looks like :fontawesome-solid-arrow-down: + +|Test Number |Condition |Notes | +|:- |:-: |:-: | +|Test 1 | Hot | | +|Test 2 | Cold | | + +--- +### :fontawesome-solid-camera: Pictures +``` py +

+drawing +Label 1: Retro JPL Logo +
+``` + +

+drawing +

+:fontawesome-solid-arrow-down: Looks like :fontawesome-solid-arrow-down: + +
+Label 1: Retro JPL Logo +
+ +--- +### :fontawesome-solid-link: URL Links + +--- ## :fontawesome-solid-terminal: Common Code Blocks +--- ### :fontawesome-solid-satellite-dish: Flight Commands +--- ### :fontawesome-brands-python: Python Scripts +--- ### :fontawesome-solid-code: Bash Scripts diff --git a/docs/exec/procs_flow.md b/docs/exec/procs_flow.md index edb1192f..31ac7fba 100644 --- a/docs/exec/procs_flow.md +++ b/docs/exec/procs_flow.md @@ -14,6 +14,32 @@ Terms * As-Run: The procedure used to record a specific test instance. * Test-ID: The unique identification of a test or a condition + + +## Directory Structure + +* user_folder + * procedures + * as_runs + * campaign_1 + * procedure_pictures + * image1.jpeg + * image2.jpeg + * test_procedure_TEST_1.hermes.md + * test_procedure_TEST_2.hermes.md + * test_procedure_TEST_3.hermes.md + + * procedure_templates + * procedure_pictures + * image1.jpeg + * image2.jpeg + * asrun_generator.py + * test_procedure.hermes.md + + +## Procedure Generation Flow + + ``` mermaid classDiagram AuthorProcedure --|> Template Procedure diff --git a/docs/procedures/procedure_templates/procedure_pictures/retro_jpl_logo.jpg b/docs/procedures/procedure_templates/procedure_pictures/retro_jpl_logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bb2afc27e9447fbc30a793a9b3cefda00f9cd7e0 GIT binary patch literal 66045 zcmagF1wa(<*EYO>fYM4zDxHh8v~+h#E8QU7C?E((cXxMpNOvv0gw)bqF8M9~>R-?M zjk3ZpbKj@Wb*>p7<{o|mu%yMM!~t+{0012958wd|@CP6wBBCIpzz%dgEG%>^EMi!tY-~boLPBhOLVSEuLOeV|QhY)p8lqRk6r@B{uy1Insc9LRXb2hk;80KyAMZv% z!J{I?BBg$XP6VW8BBiFKrY2^kW}>3Tr=cdLpdclEMf8fAl$MqTiMr~ytN%9t56s+e{%Qi(?*uOoQr_o-z)vEZ*b^6P3ItK zR)QiQ(vYASQ2`f(`13+)0F-gq)oSlb5zWYSBJouBpw(x}`TK%kCBFXkhG!s< zM*%tRjj;ZV^T%uzsuR=jsI4|F0KOJK*Y>#!vW&MFL}9l0mu2zD{N^@nwf}YCbiDkP z9P2imn5j*xCZtAB-|u$F&phDne0q5{)fQR*13a@E-n=@nR`u3cjk%AR9hc9lb76i= zLKHlpE0-qdWwW)&x-AI&xYMq2F5lUP+Fuo%zCnLx%#R{ z`6%z@r_1uC%KiS9yybJZ$P!%z&s`$BKRbHY2kyH84*6CF)(*F1&ahC9i>@FU z(D;NI`FOEPwTmCPyh})1m+Vw*9{cDPxbQW?=UF1 zS_@R8XSY>z3NvHb>G=qh8UN`4q$;oUwCfr(EtHwq3;le{9W!utWAK6alcoBto~>GA z^kL5>!Twn+DQ=+on4Wfaqe^khkk+ixP>pq%k$8c%hENm6ftOmTy?}vyP?sKvO?mD1 z`tkt~5uHU1r3PPqe9{D;xQ=)&d-*ZOIDJk4-9z9YiM6w5WzvHT*Httcth%{_!U^?T z|5e?@C5)|mX&hGjQtAz3=!^7;7f#4&*J8=vaR$AI&Y0ybhSq$qk=i`ENWC;hF751O zO2I{+ubU-;_%Ubq)6t!!(%r8A!%H9uNZ*fw*c#;Vt3k^pqXgELBW1KPdD%SzVe!x- z2MAL;qftSU&&2V3^KH7J_)5sZ#7DVncXFygE)N2&z7cklQcHt_YQrWgeKOO4CA|?- z{Bo1i&?tv@v)+jlK3wP<@gUMp#J6s_kji?vC9?`w%oyi-(3RWS(4VlOLfeBk1^N z?aXk9n3=m{Jo}<|{k~w@-Kf=3`u4x6M-Yt*&5jNUrl(Ukt1t3S4X*NHC^}%da?qF9 zjr30?fzuT+3T;EZF;QstF4Fz_nPt@QORW^^Le<+SuGkT^da!0>QT7TW$XvuE^T!2F58l()3oYYL=LYlmB-gNx}(*XEG!^B0LE2tY-Y6pZksD) zGafIuN$}5p#Al7(LbH^?&NHNBDfA`$70`tAZo}~hfVj2^m-4rp*o+Xj`o?w532yhX z>Wy@aSjBQh`-`nT?;kz7_ErkaivOY+4%6*sB=?i14z)xNA1$bT@0z3TonQ_RXK=)J^xhsYGK@-7kIWt?Nkq9jkDh&GOKZ@ZMs zyiP4>GtJ@wFruXh{tVlwt*@~b#?~T9%z>MB@*X;pdp&msM*j6m3jaD~AZk;D^ z(6x3`Od-l0l;(u8b1HDsT?Mm>6TfnNN4(iFd<~&Kz4NUPCo_FpI4Xg1)T(oa zw{r~>?ASocbz_+8?vomHC0@Rer+**CNos#G$x`Q+@)$Z5u$-++22HND!r@_y<*7EP6?nDt~#XKfSd9#z_NW+BQEBV`C*1cpT zpKp6^e%2`;!4~^j!02m2<|NW&I*8%}K*1rbe014aL~o96KNv*zPS@-^G`Z>3z{x8$ z>vW7F)g>RX2LR*slRM;_S~4~V(QEEW3>^}2NLaAIH2t6I=DOux8%rEHuzC+~!z^{;Kc)lE1vwscU-5I=3-x|Z8n*M6iQ1@hM)T(JzCZ{OW zZ8n}KJf5l-#KWA*!Dq;pormMP_~Ji2$u( zgef1sh7)mVU=+`&!|sHO`@qDQt^}cI%Ehc@&C+PtvLB|%w58DSgsr_jZVI|Vyf!^A zXxR83GcDKLt}?2j_uTfaRsL?C7Wd-tTI_HA5$WGN0HW`R20x$&B2F(jAGABw6jW_M z>owksguADw=d`d!Fy;l}iL<4dbLoSwH$twp3WkWX=9L+XDt8I>xB4A3bcej8h8!r& zQ*lRIaY5s5Ax{NZhvgX0G%l5EGV9^*#rDd?5{b4_UO~HxwhCxOo0wv1&A|2MNN6d) z+Dh21EN>7U`o0?X=R0oiP$QaqD98!gfzJ06&gO`(P-n*tBV51Kpxw1v&$u4|8>J}4(H=C8dV)kMA9cc+y@+a^ z_*~k&l#GNy`1Ro>f)ploH^GaL9&2}LM6?t5Uz5_`cT1XV;mASN)PP--}USy8S(mnQqEqCNyD&JF+2B%0j<-g7 zUESU5Z8)J3ef_PIGw8}yT59v>SlP5Y|An!J-)_$p^?`nzSe$b!VwkWiHxh}~kKU*tqj(NdZNNcAy>S5@SCMRFSp`3EV-pg zAaq-EuLy0`Qr(or@IdfU;s-mmQlYcM;vH0FIQGE-7Vkn~LTgBV`LsN1^_i!xi{UDx zGwA}c0V79>5!dofrex=)Vj07~?gv{gq=V@J%55~|gllf>bqlIDH!t4qh=8r@ZGc06 zWcXCwC<{0C8ghPG&sKF#sckHOIxBRGo#8FNdBk*GQR5Nwd;Jx1DGH&arZ(pZQ$5np zn0#%NaqINWq^VVV+@x_C&2iV!ahw07_kU)0$gEBXE7#atp|e1>F-7@rv(8K|>fUVK zn0%l3--|Do(%a`D^M7eCNIPg+j`B_NUd`VDvussxAL5s1^pTfiF}~&zt;empB(>Q? zCD8Gi_Rux3&6UUi01zo0EPgVf3ggZA))dlTAwz7prP!>JW(NO&%ZTmKO9#+0Eo9-2 zyKFp(Ir^>I*2wsjor%0*0aCVvnr2#70omQg@u3a?a$2NOHi4R^;~j(T>q$5lrVART z;m|xseICch^33=OD#OBFJ)%D^JsGfw3dJvL#Iv$Zv-8^`%}=ZOe`=IUh1lf1o^Sq_ zM!7aqBgLW@yWA6QSB2G5kJpgufE^}mQ^QmD)~-wDdZs6-1rIX^6gPMSmwFg zfJ--c-T^n;ayAL0L;bTIuIthe%^4`6;713Y7OwhB^)RU0eYFZY_RgXR29L?Sw z`1Jqli#Lwz(+)F16NHC7WxMn1eD6SG*waPgD`JuNOLj|3nxJ^xh@9>6wnw4h6tGv` zef~8UZ9u39!&`95!NIKW$5ES_W2i-1>)-CVvu41qg2%p{KExmeEb!td^N82^goQME zom_VCaA{xL&aRj5v&fhH#P}LSwcMUmp%4JO0h2kzn6{~CsDPQCKqtYkt}7iJ!(dN}CKh<&## z+=L2VPzz{Yh-XD^rJmG2nd7GQ%ySDE4jb=?@-BjgEC@O}oCFGqjl;{1yw(iw007+FcQirZRCm&{AHu+6AOx7~XKeOm$EhxM zcQW68%(E1fcXM#!l>xi8hb;Q7VD&nkS6ZJjltzlQ#`C}EdDVMQsP;G@rRb9hD1Csi}p0F9&|EmpaP&Ph+0|YJ(>YiqoH<}fG+D?!n55OEL> ztOPOSXPhbT5eU2(76UrqUryF!)x#f(@1=<+63XA<-R9nDRr$h{pjDIQP7HJNi5;*Y zF3nZT&~ICh^Se41jVAIT{<-y@!8>WCP10eto9Oq(wQu24Qye2n$o? zj7HMli+Z_Qvnai$)w5;b`0mJaHJz+{qq#^u_G%>XocYY*+%B4vN#B^i0|0pS>>gi` ztz*&g;2-@iu=^&_EWMQI%(c^Ez2vgJuxmnx&iMlNdRrRNXZ<3UJVZjN&@gf>^|^s{ z*U6JC(d2{0!0fs`H;hOv)!&&v1Z#yFyb4SFm>}OD=aqvbxZ}qzf=)u(?5K@IWsNV4 ztW_>TPW9`pGA;&Tz*$SzN~Q~0<&%7)xf7joaHy~DV*MbrL1*)uy`>1*2Qy(?KN|*j z8miDiC*q52KAZ8YUyvt*P0!`b$85e5U!*7eI#dtyEAP5o2K{KI7+InjJlD3uU;jMj5bu;({^pe# zr~PkR(1i>-@m&;E_ue6U&ss3%oeVe{1g5{Z%*TE&vPkVJ{As6v`>LpstDJkUW%}CR z4PIy6h0XG&_9Fd9xfRRRi9~#P?DYFa$kEcG+#>Zw_1?{E7<`8-wq_S3@~kHKa^)#d z?vc2DAYmyKo?)RgJ45Vdtv}ub842HQT+~?~xg+bo_(EjoI%T)O$YhA>Im4o9e!@c- zuRW=xi#_x_RxMn?Z3zaA7^Gu=1By?zwzJ`V`m@b%1zkA=e6}%s$`gf<-s9VI>726n zFBmh(&~n*m>B`T{xsvCt=kH^4mPVjukNa3jX_*{Bf+%9ym--N0)Lz-#$DOk~PI2Ce z5a%&oBJ+1%Oc895!p!2}2ZV*lZ?hcdxw@{mIGl5_nn7&eb)4kuo#MtYooa_(mBj_X zACfX2=;>#4I|!<|nynyOROeZGr$;4IhZ4Ea%zMS-22ZZ3AC$@NIe5>#(Oi2hEZ!<+ zY>PijQ;M&w)|2 zhSKen$EoIQDl?8ybmYl-JJSRrQ$``-*^?s#u*PsAnU_OmPS1s)fFmZ&q&bY8>EhX+ z#U75`TH)XM6{q{fao-o#hNcH6^JOn^UJ4MkIpF>tZr95A@33x<=(IFug@;p?Ce=9M zh+N&9Q{Bm#SMr5dK0^H}A>@hIRAZCc!xobP^IE74Nl#)ZYF+#R;PONJtza>@Z!>Tw^7!llAmni1Irx;dqdk)6 zDAJ@Cr1{cdt%@ds(tYm7uM-cX)^?3Cz77zKK6qjcYnk*P9Oj6wgATzZJG6w}s|7Fs zZTja(@ahCXs5*w|llBC+teyg_Njo(d!cDNq=67GjKTV!hKGJC?+H7T}H+2m*+mdl# z_KT_DhT*T-O!+Y5EtUcMSk|6l5|JK{H=UC;mA*}jg^S9)(XWyNmrn2A!NP-3e-nA% zXM@F`-ty%7PD&{*RAzaUk#kA|nLF4mf z6YxT(a+R~ITvzQtzcfAU0Z>T_)gQH;2p_#=O+27uGDB^&E#$T_GMm#BYImFj@(z)8 zlf+}FTd9vC$DcC8GG}guZxwFrPJ$mo(`^aHsLeNyejJl@--|DnGIPryq;=@4D#YoV z%Dj6_8Tt7T{x4&8tzJmn$lnOjQ+^HwF9!{*vwBJJOD;%Eew0bG$E^?DMd;F>d%ZO0 zFdRxOuS-IzjK3_f!oOY0{HZ>v)$b~$ERIw?%= z1ly|hfdI52b1jv*FM8HHO!DGo65DCc!;b9L(~c>drOx)`qvDFj=Z>i-cPaUzgw^hD zD|_L09J+m(xe2lPVv?XMbV2cf@PBMVik6ZA{=+7z68t&R6=SmYOSoBC-Kx#eO%kD| z-PhMV6j40Vw6(xyH=8H?!!o7ty&ful&H!mV~K3 z)DoU;D+8>{>*ssD6Oe4TYU>rP)>v{!u~`r%)&=}pRU{*gsbT*JOdA#`*HInyrNp+( zO%Vn)gL2bMt_Rg))PEwE62k3;YZAI@j5V;>v)k)S_Onkk!muXc{SOrDSvaZ4nAJ&T zYiVE+EM5L}f__XF(03A8x)6>!z#1^x#lkoh#_Z>7N^spiUcV|%c|1NzalmaY zD15>dGokmO!1J6w1AsBqahegqF32lWDD5|1&BX2Ud_?z)l04NxtI>KQLX3M^QRn7N zA8hJ3_e@?q=G%_23L@#Rrf_1apO-R2T!c}9^{kwU6P`O0Ui`@4D*QGXyC=sue2f{S ziR=KSn9`GN?uy5V8rLW5QhMmsei!FD=G5n^A9*MgV5p9Wn(q>sxAS84H$SrVRDP}A zahG4vC+M{>P6k#@bFYJ;A;hfLYulXNQ$2ag>CA-@!-h4lrdMdW89fKBU1XQH$8+dc zb2hiVe8`jj+G}s~54(v(+AHYMbZ1_#h-La)pO1ckB?JJ#%vk19iD7hT9Wkc*twK*Ug}0k+s_M+#|KBtTY(+Tz_}N+f9mV}$u{XCg z+QGx&6BdU#{3rQ`gx;aiq%Mf5tM12rFn4-Lf!P}_>S}^sA!>QM_C$<;IhJ(3hAly_ zC)?e2`3(&>-ANdj*2lnac3m&oQdzE)qe`baOvkPft?SD&bt>~eIB>bXyn6t=Uq%ak zhw&6bHhVP6N~o%&1rtI^I_H#QhcrJ<7Q;K1P7#2}5Wsqy9UY-CCSMa$Ggrk!A3v|b zhf;7(iks`j?N9)<*Rf{Hf`YQH-7#O(VR;(=L+rxWTwa9VG^F=B$}iQ2RQ8$fwA9S| z_u(0yo;n&hB;P|#5pd({>YOez<3B&rTYuBJLSfPqYeleGw$<(Aaua91<8qsym(EKm z*Onz+Us>yppgmf)G&RF4HMu});vg{Wnn+LmjB>S~$SxNr9o>mFP{h%>wT%ck9XtYn zv%%#5*v}V!-JVSr@3>v&CEzsk8Q+7H7g^{sa`aB-k>$keQ5r}+O?23-EfKa{Np-X0 z9%s`)bL@T=WwHA%R!qSR(Oh3%XIDVyA2x5~-v^k_SI3uEqtv<1%K_;mnpttR6Y~1r zW$wqCAnzmw*%Rapz<(B}&nTej<2K90EwPiu3Q*6I#CP<>Xpa2|E}q+zgDLAx+Oy$M zezMZ8rk!b=5*H#*Hd0;V>GvL+ceqGb=IP6|cl!f(>@ea&fULn%@k(ete7K&&+ys;!OHWcW=}xQxh)b-5^8G{;uN z;iMw8A~-D#(0?%X|IV=>%#ML(u@92c<|ihGa7vkX{KjD$pg+>lR{ugr<3C;RN}>@f zpFfkY=kU>SyRsyzHE9YViGbKvC_Z@rjGb~gVtB-Fl)4>h>XS7I9L0xi^s9HG)5?UE zy1cO3PLba=8zTk1%os2R^W|sGk^1Y zk3$WN-_uYK+W2=m1GzrDzKj~o!C#%79Y?374}y6Q1`A+Sb$k9FUb3C#yo)DAyyoui|D4};9L;?<)||I*b?a~Z$9xcB+c0CQsP9^=pX z>aCnTts~k80PblzrrO;+hW+ls4>n+FBahZ}iU+A9Qx%5?4E{>+{hD6C23zWCy>!zp z*4cse%wx^$pem_1n%V#o#LW9*I2<&iYwft8E7`^O=JrOLskngiL(xYrflz2& z*Wuzl---0+KXk+#3m^9xi%u2-lYHrJtpQMgkfH6eEL!k4NTwxkS7`EWExVcnwsg6M zBnG<^R|!Y3(mxDLN9NUIcSQ%7rmHp zPwvP}XM&puO}+ru1us5^Y}*MZxX`+l1gBhj@;-+DkwHQZY{nNji0b9R8N97rhOwc= z91Mdo8U%c6u^`K_B{*X1>64!1UJLa^VYcg>Z_O92VcXGT_ATuSL~r5hb#cM6>Bj5z z4m(TUUNIM!f2aijd3FK5=6Xdg9t!^h8 zn?Fos2U^9<8sKqmY}%#=LOTzow)>hXKhiIi1Jx&QJYE6?nGQT=zJ5fuI1TiER+-BP zY)`o&R_Agbd@g=gd%fSq-bo`mQlUrcQN`Zk{ZcYsw1I?|#)^?5C7i;1nv#FF&6GpZ zi|jz3M{wn^OKc_Xw5vmNmFXU)uhVhl93j1&mU zb5AT3oqM+0b=H%E+rC9nkG!-bola-Enf^;p005?`C^AH> zkBL6Ha%#$#M%2I6_M+Wssg=cJN%s>sjIMjk2XNj5%rUj5c|x zrCDwg(j=r(cBgcA3j#_`2#B7tb7b3OPQUp+5+5PXl>;s2p z_(w%V`d8m1>fV?V3@gXxwb$Qq!x;F|$6!b9x zCz=q_GZJ^j(-c9Q=)p$TAj7U#GT)s;S?yQK8AeHnR>@rEPj5i#?&>?<`PJxE5mx%m z3bJBYrBvGkhNZR>PP7+l^bD!nRp`6uHv)!i^P&EIR)P{0rGLGY@LMC5u_;f58aMaP zzkkY=_z^gN@ESwy4!N5w+M6sVWx+w4Er_uIZl_|;@oYZi6J&6Ae$Im#)x!+DgtK;6 ztT&ohol^D7m^Z_-O<>r$yPQ9s$=_SHC9TxgjuZ;w*eG@2<7L*`khY$k;NjC+DrXB+ zajpvV&BaI2uu2_uPTNX3`8>zQ`~1sXRU^g5jm2*{{_6v7eV?Vlx@Z#U+q6RHZ3lbf zqUicHv}KRLGbCMO6o$Bqe2Y*D7_`M6@lHc+d}M#f{_ec$rg@g`U=l>bWSDT-gX=Qk zHn1Z1-G43q=Ly7m%<#40P!;U9+iocwC$0{=;ZN5b$7Bh=RzPyHw-?T3dG^Iw7n8WM z?uj{U_a@J)_Wi9LyDqSDdGz1d2i990N}wF_XI>HyPz@y_$PFAo5fldxxUqExAj|^H zRT=-+zQ4~aXG4zr(YX4mLIpRMtk?yUgw7~dxZx)Sjw5dSubVG=1CLsl&C-uGr*E+Ua0u{E5Mf_P2(Z^rVXu?I zAv^&fVm*KH3i~w+D;pB|n|DGuxD>*a>>`RvAMlhvs)*Wsg1vhB6m~7#Q~3Lx)KB7R z4Xi6JTb0FUzXl|B<6v`4Q~Tx=X|Y1HK#tLM?j;A}F6;OJ{5EySecQn#uddDoFMLbN zh35UNml);FQ1qb-b3dqig$1=cGOtzsmy_YN{*~#}^F?`U8SCY-3ynO^K9BnXBZmo! zsj+m+vt>624xRf%bd_xUM09&`e&zP5P_K`>$XGWH4*+>*N%q8;a#eQXrN8q zFQ&c~%Im&+CZ-t!UTWA9pKDw^$Uv>#)6Nv7=l6bk8v0cX)u%x86kqWyNB@YvQEh0- zVEMz)QSwm16%~ukrv;O#ITvzckfV=LO01ph`Il1DF`nQn+)5qbUi37*xg@CQZAKk! z6sn9#t(mP-B-0LmMy(a&y!6g!cHgbaAV>b^dlCQn0TLy;_~$=_Wp&JSWP`x0%!U<< zmgPU-_cye@INC^iD7H5br!9&OA8OQ7oh}EZO4PnQqyAVoE34V`EG@Pr$U{9Vt?q}j z9LWo|lH*3wIQ6epz&KM6Kh*M6wuXkzjN{Vng8nT!4t8_(Liouj+y!CM2!haKFixRZ zZnhTkQ+$?sQ4Q2@#qv#|C{ZnI4bn0Qodo>$cEnmH`-qE_{)xFhn}XKXgORqM(waM_ zaB-R1GR9te5{t=#1P|S&7w*wa(Az1ObtUTQLoK+ti&=%`@D>*&RJJ5c)c!UNs&IlTihU%S4!(}|O(pLdakPv&FW%*kAfZ*A)s-Uy+7fg_j%+ku~Pi?LPwIL}#AkT99>m{PBfEjg5z0wXZ?pLTz zKB$K6xzSlhSp$?eBCTl?vD2YniZY6F+YAyTNc6P#d3>*)S86pPKRb}5DtvBXsUsdl zKZOJ@DP{%F?}4kt69W+sJAF2OBy%IZzYB^^<+(MiDNoeE+WY1hr9+w?;+)*jgbVGe z!7sl-T*}&IBV^Qg0GJP-$Ei0IJ;`xm89^tUPE0FPTbv*Qspp;7`Zvcv8CME|KQ&>u z4$2dwpUfVqmFtjZNYW}d+p3(P7rUQl{UC;JYTuaBd1W2)WMzl+&6~6ZZx3tx_os;G z!%7xwlNE=R=2MPX6PHR8ib`CslV(hr28BRTABv@8HXy<&ut9bFP(Iaxo*++^^DMI- z_H^EN2~B;n0Zy`?%A9bV;|I!5E`5J%%VRhhn+E_YU_0UUE|Sm)VN(;%^oB-jBU2+d zjxAY(P*pN|kkec^kbBhZ8|}BK^q;<^?*f0tQt|rKo{RSCHjqNX`=~X@!KqrznnvI( z$B?@s$ICU0>~uSBiGsS$I*Y5fK~Y8toC`dsnKj=l`z^GIKd2U~TtCy+dV_t4)Xi(3 zpUXMxFdKUpHu!1u=ZEkNaQxvLFrkp>&e@jeT)c`e>iEG%>eHumg@^SM$sOe$@*$}; z`*zP9spbSt<#EPnz}b% ze*l;yzM+6mTm&LBUf}g=YG}L;LNmrMCt#nP)fxH10pwFMxJ$o`iu>1lCwzc zo@^AJZKuQ=$+{Cm`W{;cICsEdyFMaFtgh@$e57 zrWwn&?@4wTkv>(D5sbl!x#Xjb)BEKLme5Cq!O~uYzCp_=P1bQdew3lM)-|%%o&is{9E%MC8}q2|!q;94 zA37u7ar;hLw$FD^vJYFVpsJM0^i`S(A(oXx_$wgwEDK%Gd6{w-pJANE=9<9DNYTR5I((MdBPGP z9u_5?cmtrCY2yh<>X|b0C7Ggb$&i5M;WVv6v$%J+=aUk9?(pn; zUS5}bzdnr9H||^4eo}hDs2avi#qDUNL60CJ+x-h z&ep)@h+*egBDwJZ-BwQ#m)u1`UCi$G>5kJG6U$;u>nHtM=J7>?WBJVf@kXGw>91Xy zpr%MG>rsF3ghn#!*I`*KD9*Q{C6N?aCf4Jo#PA<>em%oIDoho|B@V)pr?8GH#9WsA z#t=SHI-)%Br9n{YyTA_OnX=I&>KH#Xee7bH;4t&92f#T6C*Ne=;`1CO+~JvQ(RUEl zmd4_apedX(O<$aYxblE#iR^DuGAJLZ#fVsVxXc6gt&um;n1?JI*oXy&(l4J4dAq5NB5NR6N3C`O31N>fjCf&> z^Ip=8slMTFKdsxlS6|}T4G2=gAr^+U)m*i~@_NA@?kKC;uAvK51RVBB<;)asb5ppf zqK2^sySvZic{SaR<}6-oj#UND?TcIYR!z{jghOc^jRTol`k&BGDAhSeGwmpjS=mZk z$sB}=Z7Fq&1gku1&qt_}VYXxrYK_W5^M?p;2t&aATe4K5vxj(#)bZ?JStx0`K7H-f z6{!z%gu$vh?Mn87`Pa(KlkNfQofugAbzN(5xrHiX!4`+8kLVyci{7ZbgEO4dkDsEb zOr^#fR%VkE4x^5WCov$a!Bl=t{lZ`m;+wN@`jvx{rsG3EFcm#(g_g!o>ns7`b;=cr z0`GTugI~PEWMhn;&dBTV1w0X)R98Xd4IAg}$keGn1+@5G%12d+I%P=-fj0%*NPGe8ue;gJ~{Q=c@7y z0k$^5JnR-B0SLR`CZhGhTtw&t*lQ9 zJSaBN+cpl$6w;2OIYq^xWz@AZs^@w0hvpLw382dkha|+r=WfB|MnUA#Z+K$+a3@C$ zSvut|k097iv%@ug_DTlr)@Qf5inl>^`k@vB(<6l{ej<6X==SXMf_+M!a)+%>XaxHt z)rMO~1opAMqyFO{#mUp%i=I5FqJJa*cXx-w8>}S5YM%n2*&Kt29}|PWZ=@jwxo>DF zycd2!{-@OOgqjts2&KA-Od%=dx36-Q5^uFrhNX!b6&w7&HAKG3vt(6nbDN@Oy#n62 zoIPvWgJ>Bx(pKu%LGP6dyhXvxy|qTW9%X3`1+D8c-yZ-ZVkcZp7gKKxD_ms5GaY0+ z?=fp8EyGVg79*59cvDp-K+S*I)K7e5uQ7pHM6RX`CM#%Np`A*C5TLG>$v+Y6bSwuV*ld9cEaiC z!ow!F(X6XbAlt1Ie)sa;tm2QbA9Ka=DoJz;BXzQaAvCE>-`pYpGdq1I@!f`V8{hxRb@VT zGMWB_edOfN6ny_@3JQ{2$FFNuIq}a5a!=`<{Ou=Vf_-AN2%eaIC>LSJZMegPRplHM zttP8z*|tRiWJ-iZ(`u&tnO0&uf8dsvdh@M?QBK1`_BM&7vOb->Q2?`$fK_Anb4Jw& zB`M>6jZo>#$}ZX2RewzZ-m*61Jl?2y+#B2UP5Q4Ja#gpM>hfhajZl~k!v08mr@ev? z7~cSpDD=(AVFme@H|e2p1v}S1ltF+_3*Or8T%btR1VzB1^b(iUxn5yHV+h@M6W2UH zP2+*5PUKnbmYl5}HxP)V-FLN0q0S>3=1EDbJ;vF!dY?DR|7_pj_B+cTT~M4{=KUBF zet7}jVvp?DR*Gx&O z&XU7fs@M@p^l#_Snq~px*55Bi9z_i+8na?19r=iReQ(-rXbSQ26_=I+$%vc_WJdo8 zld3>g&Qg)H^B3`2lVG=g#ZgcVW!k$Hw)wm8x2`VEE6P^e-jzzm1unh8N9d8P0ESff&?qzXm_#y~=H ziP{qpH3sG$Jvlx6?9W<}sLyOph^Y!nw=v_Ew&Z5@SwSQNV!?OyW2dnqr_}S_9K|#m zT)KQ)N*;X}sq$Lo)IYCXm#+j#8LX&<&c+(Kp0hE8&JS3QLB2r(;mor#^wX!6*a+I*$_jS8P;1# z2|{Gk&M%1YDtWusP$YJitF1dT&cs&HXNz6oc}NOiOz_c+9lH})d=y7)spNsBB_ zY1?Y%do6uguiv|;(<38HYE|!9(hB-Y&$XHd6qTNT%P9@3DYW@SOrD1zN$xrG0QjpT z{N=5`HrrPlU{gzy2Yx%@=S@1HL@;|6b3LgVvyL229Zc53 zjGvOoVln>yX67d~3T;a)*id3(h?~ZAm3Zo#rn#tef;%BcUZ5t;n63UA@;@!;3X@Lt zue#=dzxB^r`pQ8tt=pK*tv@zhyt>d zD<9gezWlsj9bjV`h~qFA$=1ckjIjaFVM{zDm`J~@Dfwqm$iRYPk?-$zH?;$OH|C(U zp|FE8Ic$^uZZB)Wf+4(6qt=&$YQ$UIAi1?bg=R5Gol{bCt7)H`w{Fs{3TJZsHol%?(8*a_ zM24L3!r}YwyB^$U+C2jL| zmp8m1^Z{UL(Rh1l*x&E699`)=7tT3NS40*i4O!g}=Fxhxl$J8n9M|4A_Qb){<=-W5 zr86H$H(8z2jXrdz#9OBpI!E~(2IA-*n$eUix)Ntx1mW2cHSk4XRGF@}!fxQv;n+{oPcFlD#B!^(KC-doLJ$6{9wj&gNY)ZAk^AuYnV2vuWTrY>&^Yy; z?zic`@huns`QqYf2Ep6JAQh^N33C^n7R0(6E=16 zsz;u?NR!CDO|~XA%G(y;Z9g5e`m%|lJL@!1Q*7^X?^x_^`ugA9x}$t&uaKcOSf-bW z#9u0vu<>>Ho&^yY`cCceX$Mhn`XPOAW6knbGjZ;`g-)8ZP4ch1ca38F5qv*nn$G5M zZ4UJ_e2c=vv~B*i)1FTl(`=lzN&%-{|I?>4w{Ap~rQQf!K0aG;Ik z4XyfL;<5I5CUr-$l@9=RFO3h(?0IcdD}B4RoAE}NZ4O&P&t9XGC}65~Yu^X?IP(}<##RYHyd zTyr9CX%J@w#vwqF^L4+*k;Js~8=mjV$$4$Oa~9tZ|MsE_5c}pu$vycm;Jubb&Vee=KZBmx_r&?B-@@E*6BdjsZH%Ju*2=q?onOM z%v|)E0??c>x{ED*p4T+tlBpHQ)03*kQh9AE;h!h>(S!V!!j$*xk?O@CMW{bLvibr+ zzxp`RRA1iyY+%2bS>!JXT6P|xde8=XJlS&?E@ZU2vi%YN_7v`9hJfc7_RQh|pyHMA zY4Vc;BZ2?=QWb%^l#m_LR1*inJ?JHtZOos84==KTT4DPX+rrugbxrYTCWc8&%qc`h zDNyD^EcY^zbfMYQDt+mo@QH>@@R=svJD=cXiz~yJW+nlnyq)&24W2niB^5?(Ci)P~ ztud#rPBnBNMY~6M9Pe)9?O;_FbvvKb(2pOkSiPA18pHOnaa%A)aO$*(wfV%_Wu5W{C_(#X$IVn%y=o|yZx%;YRjOY?jEB3-7^o>tDf(^f>2rF6je&z|3|ke) zmEJ;yt28>11>SU7Fpsf4<&W+u%2-pz)FG!0ug(UTh)jQ0^QuY7THIxZ53Y;91+A|n zSUxZr4O#{Zuh?)Y{2T0l9M7UOUeVVe$5i)W+DeD`X>iYp&)}W~v-LDSwXf;|V9-`hYVyM)DqfB{lJrT^(LM2OHQ%#&QXrx`zq|>Bd!!q@H!jAJWH0T?{Em;2X}W1 z?(PuWHNo9AK*&EN`|NY?d;eQ&)+~mZue!RlyQ;ccptq03XjOI49Zofoe^*0iP;t{9 zvj}{Kj0UH=!eRH?sL?|}pVw+9fmU|fE4$p96>`OxZ+h5!uOk|(YHuU~KI}Q(0{qyB zV=o8b#fD4C0o^JN$vh|5ha{aSQ1}7U-|T=Y}pqonyxh3 z)eWTY{i7c(dYkRMht&<|@0SIOzDiky?%uJ&+TWelXUiFboNZI20zCQn``*+8HCIP8 z#HA3YbS&KIm?9Ce;Z-i`n?aDW>R30Ug?IqzbK~&(_CSRoFGaGPRSvlUzBRQgS>N!5$yp+ zJXI#03*SYkWXhok=ows5T`jW$TEW0piuL4saw%#qQ3;^1LY= z-b}c!B`+@QoR%@)1@&Q?>1}HtXC^Dp_bE0{`f>BQqk*5daR@MdV|1D}yvEx&!#kHu zoVc^d_GFW5MP*F+%bbexIx5Kl(F?YnkQM_QBi(8M#R5zwT2vQc!_@uvJ` z8?K60sAt$bs+G|1E#s0(njg_M+aN`!mzC4wDzx+)tfd*GB$4(Kha_Ql3Pjom;yWQK zWl1bQIj5;3?j#wiK60~+K&dQISa;!solPd^y8aJ6xkq#=op_}^_cZ41vzCTM+}E5N zF2ppC+s30LITDd-G3}+VqJgulT8?hrU||_#8)IEp8~7S`fHKm=#uKKLhDbvzONl(r zH=qeBy{ypEBHO&1<<&g)T@cEOeF6$+#{Z;x+TX`oAy9kJ-La4(11&-fTVH8ADW|Rm z|11G&=SvgU6>Kq|z0l4;buw%okkQVgcGiK8g*Y_BtuEvIh(KG`*!U*{fW(OnH5JJZ=d<;i2egT#Z7a^5ABTcGgf| zwF+lVD?m-nHftj9kryOy*;VH5X)Ny|pFk|<3*c(!Pj1h-<9(lW44)~!tru8@b^+j!&X;evF$1o{r+)kr#DG9#AaZSLynwxCc zTxhOJS;jDEHI@V8$-U^Nr#GR1ZXBQKFO?x>P0MkR@_C4ZHof!M1hTMOna78^TE49{ z9xp|A@%cp2Fn2yyrA5d8xiGXnG0Ef180hR2-9D#jBp+z@K5nV}%dW(=wvoLs<(dE| z0dwQe(0_Nrw)v{#?3&2-vLFFvR0%9X<@h&@iYWCIXf`J?eJI-;j%&24g4GaHp1m&- zs5bJVJOyJE$bX(FZk8g*7{XXgIFVf|RC#;zX4N9~L1wb_EEhJPBM~1qo^{f-EK;d} zah=GqW`@jE?Yn%&>jBv(8x|v+i3lJ*iJDA4?*um61jojgy{AC68m9Aq130(plC7_C z^U&Q6|B7uerS>*CSwy?8YY@QVBvVL94S!K0vD`25CR1oKclbwh(1}M^+syLNcPDn! zj|;GSFXF7ZQr0vs9_gN8Uc`mLmM%)q*7qC*MiuSzm)r$A<|#Qx z)eAB|>6asVRa;ew5Z?6uZ?5AB_W=oy2BL~>HGS_BkK%u+BvfVBUFumq+xP(yfUYIZ zo_(%2)+D~&syRQ}9Xgu`$~ZQwSFA5cUUnq4B2#ARW_Hb2i3}b z`-P5xK3>SMnJ)Ms0Ol7)-gz%NLrNPjhu?-pk%7VJuO62{PyW%eO*N>_nAjxuhNhvf2Rv>nB;2!eUjlkKhe>s^jUq-wMlSd=VrQ zEz3g5O(b~N-oh~L2Fycjcu>V5!5mLnfjgE@2=x`NB?O-9r)Ji}grrvg;U#per3naV#vfDTq*h28wUU{g0sNt9{tz?1yoR0_L2H6)zb|;;EZS!%VSRIH16(~0kXnf z<>o>_kQAm7fG;VWLtXDSlEK?LOM6zPo0rvbA(o#RSCf(yFqny8Secd z5t@2y7`_D%z@Rz7M)RYt7g1Y>k)>sL+&G!@P6aIkPvjBSLWIekg?kYV+0laAd6hN? z(EMbzK9UuU+HzFX9btV`%wHNuG5H`7je#Re8oYYSG)&=r?Upzh?@;0O`r+#<{(q2L z8v^0h?D}E)k5u_${CxSK4>*tkh5uE^{J2%h2vjR{J{)6O65P%}paJN^>5j^E+JH(+*-amW8D*G48%n_GkW zQn%vLR>)kEO)V+$)sJ?raM-*8X$TRmt;M)vxhe^tV9v_g;am zzI%vjC-RCUal>GxJ)fCy%+K^?#iE6?m~RVFw8-gjhi3L6*A1SXJ_fRt6I6P}y1NgE z9D)%w>6Bo*Y2=2Va#*5KR1S4qM(q754cn-qT4LbPthG|Sp_hbZwRDo(_8NK`(uIvz zkC}#Y+_EWI`uM+Qy#ws8sJIhoQHbu=8QL9T0|FhEP96WX_trb|4^`UFDTjYFq4UPZ z-2|};jFwT7=p!OPCre=FOMqLQ#3?y>p@r9=TSK88Gq^CRkW8|1y?AT8mjJB=phfxz zGUalTb$Blvc=1rMfWKJHB^Q#V3_mlk2Ay=B^FnFoD&@MzfZx2$M6+~iKU@My4kc?J z6eG7|*`?c2>W#GpUyw6RsDLyTOH+$r>)}0DWV%TP@9W)ye==rgET3dW zIrgic#s2acwPBQEd_1mhluwhwY@}y5$w64*{$1s0r!(Ak-l<~^TcJ!F&tU`4=tI)1 zl5TRC)>4>I;_`IKjBeg5B@mB9QB}0G4l@uPM_EXL6j1_*P;8A5Wd7p6 z%-J)0>HbPB={N6t;YA*)nf=MVwGMdk#N~nn=>sqsX;}0ot^zL*tm#6%hm| z^P#ft#^t{fm@kPoe9P|ma2jqW{*yr_3<#ZNmaY@Q*72)I>C5ZZSh`z=&Jg8@-mV5{ zx%k(iZL<9a^H^wa^$GZ6s(V0Tby{`X)zxZm3qJavIiyk`-6Rc1$)S)k&NF8VmTF_H z%rLqOK-)>-I|3YVsvm{)5=sNLw=N<4%xm-H3+)>yRJ1GQ`}u@YJ;a~BL;c)BVaHf0 zp}EWE7}2E02&?;+I8xj?T8hY#rtC7ocs#fdemk<)M}<`|uJ)4#51Y-0jC;CQ7&tGT z#y+a)MRfW<)&8!+^&hq09k-~n$tUptN%i2&Hr6_m8nD6l#JO;=F!|Mb{j+^oE~u^~ zimo@Ve|jyaTwP~?Vao|#&qmSVpqm8T+U(x9Ih(S@{p@8qvx|gUxz#mc2Yai9bD8H-8CXyVB7 zl8~8=nG9+TgH9BYbOkpB@*TB)t3oh}*dJB9@-3K|NZKD*KoUVBEur2bz0Z&${3n$U zGUR~fhY!;z$1;sZj{U!ohpq+ldQ}^=6jjA!qMN z$L&393orSVg7)%f%CU@>TwMbqjpnSaZyb$5Z-a7n?b@ zn_8o|%Sf5hYQ&Ff&8bmUS}ZF>*_IGc04j+_juqM_Ce;QKA!!&H@*CZbmNHY%#EtVM zx!!}t;qPhfo#$=o2Ne>SS3z>=uG-z|6HGfS!ke90G@@;^p{H0S^qqCZF?tYD(kkm0 zd3$b!S))1qYCj;$VI~!#+S6bVq+2n4Y&10BpV-3hh)QmDj@W;RbI4_)3TsYfJ4{G) zfw8}vAeG1dRUtirRdod)QE1Vr`k5v9Tw+dQIk!kMo!%&dA|y;wUL2-VZ^FUA!6>=pxW!+6AT5Ia>A$wEPCV^d%^h+ zIL;9Zk!iyUb(lB zPmL!{-|!)$o(MQo0rxR>vcB(@i|OkwV=L6Pj{{>z45h8AT|3W&mB*(?ut(A=A!}2r zZ{Dm9p80eE{l&*@6)zwn&r!;Qi+BB3Wb)!&i3;cucrER|m7uM-U^uLbBr!9z;`FSjWz=pUQ}~5gNz7Gk+FZ z_?kj7VWOkDDcQtl;O>AEkLo`PW(E=DQ?XFCJv{sU+ZLmS7$!@rPRy767Hx*MI=cZg z?P^3iT6JD^Om_c>h~0Klouq7$m4fh6RaAGn`$Q#Ca6+1}6iuEaJRIgnh9bH6UTX`2 zcfcT3+prxQ$AMt@){{@fmW#vMl_RvRLy6en&yhb=YI{nncBE}S*Y&d1^kvy0J4 z)N<6qm?L&N_#Ch}GM$esL+t)m5GW+TU0_tZ>sf9{ zV6p&MAQ!3-RW7&+$X`utZCNu|HDU)GGF-#xs$;XP!GQ}jj6O3<$rXSn=7AGMv>3~AMdt%MK^;X8(3FR^ z83bmz0PNPD0mlKGIEUNX+1qTXIvPLHx#)QZ!0A7G;a;RTP`gtdVl}$1TQ>xxD+qxK zF;2X-S2s_bJN0x$$Ws6yUW%+2C>ccd<*U;PitT4;0IlwaS_3m9@%=1}$~U+)MbUv* z(vEMTkzC5HZxhftiL+m$ql>CYiquV=ASks6m4z^0dfish0pkAZ6dT#6U%4k>)IUHz zYcfbse)<^|6~6x%AgolCj|hm)I_(vPui#@9N7)ao#B#AI3CRDTML9QNtXsD`(^A}8 zJ%dR(N}}cToGF+Khf+vf4dQ~Q^)%N>x*y5)ClFuc{gt8{^qT3gYCOf@;DKk+d@H37 zV4m0vB+}{PVF*qOCFxB|+v_~dL)h|?pc26kj3z?tR_=K0s!deQiDl9hsugA@zG~Rq zfa%=m);;HrC7gto-2yR>cT)K}@!PZY#fi=$f;!V`)sC3m`p5*{^O5PwO+Z;uwU(ai z>aQXgBGT<1vPkKU8t>IKuQh!8M`jy#%(l%b>#Cjcn7*b_`ksAsLD@wfTE&85H3EmWN@+;Ym`Jb|NR{GU5gDcR94^Z{>YFVVhyZR+{36tYI1dc~g z<>qp?R26xGD*HLQQNtP|c8fJd>A6OVXoY-gMLC0TR7@H~DouI*n)GR zRIT@_jeYuu@zNqZ@k)7pF9M!~nRDKH9Vw^w3-*o-SNizB$ksAU;>@)3Y~p-t%N5_z zsrG^X^zkYUDC~zxAkX?jvcH0qE!~O@F!^z!i`N17&`jCPYwsNI%$8scB7VY`S8`c# z+W=Ttlnt21?M|#J{nDc^h(MUx>bUt7jh=VV@mN+iTtRt{18?1WL zysLWC_waO>;N+5-VB@ouG*>7q*my38Vx$r|D-Koc#dS}VcqCA;bJKt z@socz*<72iWBcj-NS~Pne30qVDa}MZ-n|dV?1*q_b#ZFL-}m;xD#+-pt!#C)!xKF8 z7utY#3Y5tCj!7#P22gEnZZCTB7SFH|=Xma-pp*-z9ms3zNW@M-uQ^ctxjMZme(`fe z{eV=|1H`JVDuy$kaW|LDRvxE1M30v!A^Xkv(|rE3hJZ8@5T*>Xh{mt}Ni;K@@?zM$ zg&LpQjWjncl2~4Qz=}q3>$!Ai&SW`-FrsHUB{oQusGq3P6^m?=+I%2w9zGuJ>J6WL zU>5;o`4r-^o|$~xx-jOSg_w+q@%RoVC}fk76>gVUL}x20IxsDsxJBjpC9b_Sxy+Bz#MI`1=L%0K+!3wcncH$|kT(gp(rGV2zjb6tuXD|ft*IhPOh#8{9FJEEi+l=0y-BNNCz&&TV zEPMV2W7bw!s+wT9m0ydFfPS#1!Y=b6q;9f?jAk#K&Wr+nS6a`FZs9v4cQBb7CwoUx z;LT+5Wf=Pf_z9Qw7+ScOh)M~b<^2v)@kSyvwE~2|d(6nuK(*EPHp{mf(-1#E%_ysc zbu3NbD3?m&=5!@CU!qM~@nnTmh8TPR`a+VJt5U5pYA!}dewJuw@=3+4mB z^rTC3KbLPX~A~htc$H;mwY9G-u?GIHn6gLhm zM4dj1Nvp8D76^UOS(pH={00ra3T19;c;gBrEnsg1$+CrFrIYeQsoD25I@SOnuy(ZF z-|v`6#wC>8H?1Bde@u9E9kP6MhXHl#7Pn;h^n)wLo(u-l@rMU%n}pQri^6!YTMf{7 zY^u1hpXx-6B;ANcs>$X4zG?nrZ8zHf17tu|RG|Z^9-nNWgCOZ5uStAMRdQsGk-s6h zx?&cPNlm&qZm)d2V=-rW4K$yJda%}su0$$5kr#nNr}i1H=Qy!N;AKLzhWrL@MGpLY z)_$<|@f18<{es#S46Svt>h_Wc2M>05d_+?}L{$yH=(aDMWjvZmr;v8MscWn_;;Qj!1kc|yajDQ958b)VPg&iVptNdUX#f_KdgSa7Oa+=R{u zxRKVEcwg9hANx1lJlMt*sWL3d$SCa%G1=1&!*OqbF+5uK7;>+>-|)J) zrgJ^qnd4r8j5X($Ui~ucX?tZ2VvNsCXLC4!`IbS_lfbaD?Sv?77K_`;2iGnac;lFo z@c|l)K0F);Hup}4LBo9OljF5THfd*yd)3F&;gezde4=gh#HSg83PN>`@EDDBX%g53LjxeevJ>m>nQ?COsZH^^KXAv zQp~`y$FnU)fM#NBj#ji7@WdNn6fWra>$JFiC0!ewNF_-Ht zrG8(Zm@VbAey*j)))o(mOeb>Xs7-T2O9i!q_F<~rP3#YwhyGUPF4&=qzly09bptFQ8Qu8Lu&eW;mIbWh(Us3@XKAV)4e4!2BgKsV<@`kA zz08L*)OMeE5JB~$jimF_;Ssx@O4+&oXZJa^U9xnWJgpR;mJ@W62#S1}r9XdDTDrk# zG%f8?P5%pfAu6J_*1YkXu|ZcWqk8h9_raNbX&bOyH=`ZXc3Ud8)}^b<4L#kLTl({) z(n9k<-N;Ymq2X0!G>cIgeis9_~DaA5}j4V?XvlOAc@%EtRhoTqf#|RVZlH?-?5al zlOc~8m)0p?sL?}|V%uj17drJz%#%}=s6B)0MN7?$Blu3>Hmh1eaFfNaI#FRa z6g%5_GQH*Xj@+DPz_u9v@@Obo{7(@?3zVcdTXRN(L9JDxE0_-s@zq+-3h1QpjVbP% zHyf_?7rd279+3sV44VLd^!d%M{iWE#Ik)7nr^AS-RSN?G9^IbO`G#1L&GjPW*o!m-J7 zO8l)_r9${rot6v^UgNw7KzLJ^VY-J#kYrX$u^+6*PgHWp`m}xWAv_7@k8W|S`l2r^ zrCwOSGE{qgkl%D|zM>TKEv8fKKowD8#WHXSV00+0;Nt#9r`&!#5B&8hTdn4TFFtI6 zygnB7;2Z;iNrZ-um~x%(bKL6U4$zLBI=UHTvjY{6fTL$+s~HYqF3mYXfgH_!SjWvX zOd}oh^EJhF;}fGR9$zsna&#;kr-F~3C+}Y=3Q5G?FR&vk$&p)fb?$PQAN9>N=)LC=B=-wq%t-D)W4;5Lud@*bx>P84pSe)M?E4$IOp z{g((*J|@e=(&f3e8}}E@3hAk27Ul09&fFLdC3Wc#P645#aOXmC`2)rJ}e3O!kqOeBK!f>g2^Y zKlH~y-Gq%3z_N9GDQy1A%0xi}2Cgmu19EVU_oS%Rqo^)$By}HU%gE5)P%KhuX|$$#3n0WN?QV&Im3EQIl2n&0I*eXkb$9d{gn~0Km0sb?IZo> zQ7qp)%6E2aSQio7>FuIf5)2a2v!9G!$nu*$+SVUyI2&v4kV_v{bAM&6iqRXy8-?|$ z%WUI}#HHv*)CMUOTp5JU$1&&}38e0)U_qIH_B);bHTAmbwB{svR6 zv$=y3xh=>(9NQp)GXxOJ_Xi7IoGvzXU}yNyNuXevHToWVtF7jbbx^d;ju{u^$UeA@ z40md}rq4a&TRV;ukDBktp8j#br{$78fzt4I9e9p1j_PS;t-Lz zMxW9xu7%FL_@CW7I=Z;p0UTL?8bI@W#239ab>tx6fyA*BixHZ{gy zs8-vJ1cmKOEX+ze9TWa9e6>uDqtrro5fPB&`rjhnOX(F-F3`z46wLcbJt3weXM(KebdQma{-aXQm{{Wb*9Z^11|6v!Mp56p3&Yq7 z=^8(Q@Vi&>3VDj5f+nI;NgIYqZitUt&XO4V!zMSa?1XJY9qQNFl=gv)*;m;TOR^+z zH!^uxG&}Ng^y=8Hz@pmQr8w;}^zz}Vx;wKjYPPmr+wUiuA6pyiZI(}S#gMuOhsCZ* z#Z~;2Eo+fY9}|dXUu4J0ax@)RjogvoG_Lm%u=BX`vIr?C!gtb{j9d^7f_T2zRM(bA zeb_fBnq7D*3*Po(OA%d*55`$*eh(f4jvBF{#{RA$u1#4PK+30P!1ah*llma#LrlM{G9CLy%mx68t-rTi@6xq93>hDv30f@6gIn zhPqnSTK1+JTx}_A0Gn1)tN;&(I7UKU#R1n?oW3yQw0gxO0Fx0MhQ4q91jtx_mWpl* zo!wQA4=0CG>@X3;eWmaqfKoTd&}G!r3@m?uYtc7`4xUEB02Vu@mBh~89^>cXvgcHg zgj3q6q>Dtar&~!1e!2&>C{iGH`5HE7HsT!<0cwCUyFWq5qEGkueyU}TieF;ZSYSuY z_8br3b7F`#4d&^4o&$;=F#nAnK!HJnL;byz@W0UmP_r2Hx8Q*wCI-2X0yNgY!2?iV znYZA<79S$KD;s?w`-@Z3i&fH#Q=%0GhIOVo`s-I({^Zfs6dB@LQ@q(A0sb-@_j{mk zNJlwuwJ9dW38APHaS_{C*Joj8=t+*^+2O~=%*fYFk%<+y{*54-z#~ooMq4OC?jiso z#!_xs$l^U*x3>SS3LynqA^FY+3huNB9SLq4CQ`8ZB}y=GNx*yT@Jw_l2ZS`XQ)gvd zz29JmLRg37bKN%Ns)ses)MRA@eWGk5W2f)huVvJHoffNg)9{F-2Qr*Vl*Ou<#v!Op zgLS+W`zmJi=2;yI#YFlgZB7xQLJzex7Bva#$H=u}F6-@G!jS|MUD%bbA1fJ_R2%Kn$LS-mfD>D|xF9)gZN{q*0Q0)M~_+2`-}lCDrhb2Mru`!x>f& znw}7uz}a$nT3@y@_=2i6;kK)Q?%`=QxliqsO*??c_np1CSgNa~B>Ffn4BZMlw^G59 zxJjrwbn-SW4}rcujz-Jw*B}B_o6B!BZQx!-35d`9fsC&^W2*54e zZ%o@Y2IVXL_ZZ7Bc1E%)8-lakac;xn6q*Is!YbAAEG3qmK`D+{T7slAJr-f><^90A z%&^8#<-7h$)4EBZ{(MxgK9LLvmy#S5M;;70U7dc637@_2gf)sIx1M1;#8hP2fc=gBU!THlQ5)s&aDdpF*Qy%oEmHfyI7IZ8a)#u z;9%HYmSPRD+861MhdjhF#i4o&Q40&Hqe@qo4k5V@lwO!dVC%la3!#+Bqgz~NwyOg7 zPq`#tj1cF+HCH|5uRjk2ti7x~l@1G2wvDuEfP>|^2QbPmma1hNk~>(!LUyb8D>zl; zz;)heu@`!L_XN5{a*4Vjc$!8bT7OC%AkvCJ)b3ZB4(0b573c9vuvW(;6zD$A;cJ0TV-_>jjL(4((%5TFpsn=8UR+goctp{)G}4nx08~Bw@pa$%6o<=YE5w#9h~e z07MOchVYqG^Bv+juVe$!Zs8UF#F>RJYlFHN)kxDkxT0(+;1cRuJu~Wx zMZ;$n-*!W1^W8fO&jPxabobY-&4a!guf=F{qUOzm9@T?-Gw%wu*SyZ2BTBfy^~7v) z_g__VKJ}8O6j=%4iVKTO1(25u4)%(}gG@MeKuvs_Mv8nks+te)`CuHc&3B$dJ^xGX z9B}pQ-UfmB3SWgypIL=B5^r;6EYG(&K#uwi3qCeo?zO6!4dI}Sg2^qp`WekhXhsqz z{9klj<_KL)vOUJ1T9v|tX{F^8Ws4}FyhHP#HIS3v8OS8VjoHbbmBD;pv6lH&@cO80 z;@k`HFk!YBf8u@a^-j+ddiq(Z>X)$lvt(n-w+eAZxE8gv)ZJGhDjhhtR#MCtkC>T? zYGK`pn8a&rgWJJ()2XnvvAJeZ?UI)0E><+vl!9FLC2Q!G0`y9wCuL_~DMy@z)`FQiL z{2xQ=uM%6RpuG!2rz-R$br`0;ct>5Q!Rx9`q16_KfO@~JBlZp-HM9IZ=3_=lpFxR_ zQti*%|4Tdq0+P8j*qWetQ@9{6^?PI4pH{aViPs2^yu==2{me3f|L4GAWw$qMe)BHo z#IfHgkS@xGl3p63wyU%OV%Jdx-i^3lGnw)!e-pTsB1Lhk>wqKMh zJ&Y1N&Ulat@u$r<@L3JLX>tz?+kjJWn!)T(#V!581wP>HQ^4zj?`M$nN}16Y1xJd_ zxL8$d%+ssVE0`l?IA0B9%{M*QA8jpHnnCE2X|>|%xBk>hO=F#lFeZ@0RHMd2zBUvQ5M@~c1^FNCC2Ra85^MPmRw?vqMwhFV? zs2NjtTr-I)F14&H;&uNUoK5F%Ce;OG%34-G6*yZX0f^EDbze<1lAy6ZGIvge+;?Vp z)_^N*Qt0k3Y;+hVrK?hXpXbbJNCY4|D7i)N-#30tE7X(8)fVj|;g|$y^33$6z40TKpO`X)QfpV*U})`t@-Dv$4t2@zXA1mY@(#^?Q8reGnfu zS8B&XmbkY8x4!l+{~Cl}^Zyz?stSBL2k2ZdoK;P(Az5A7A#ydWjdz%Q5;$2Fl#Zk( zt)PC1KMarrlVZJIlCMK#2&>8Z9ajHLW6GDn`}Vn(r5&+2GSf(PFm`|tZ%DyjE!~sO z(Mb*fJ@5lBt1BF7`j7eMY%II^=3lbn#dU27+G{yd8)3^&szzz{4yeq1B#AIe{y@@G zs(&#W{z7B?W4pc~9!5u*R!K0*+Yn?LJF*gkp%&whedAA6C(VwmpJIBIq0Z{|j`F~xh7g3BTT%O~QQq*nqB;!voU zM99X$=AS_~`0J0V7wI!q1+@|RI~1`85ygTu^B0BmfqI%HH1VFrazjK>F!|cwy^WS75Q&4 z-nYsBCP#tKnz5XPYNajGl7fyX;lo5bc;VqrEeYtr6xAs3MM>$pYI`9Vu#Y5^7 zmgfxGLRBEImwLpySf`1}K2Fs#K(mj8pxiS?JnTg^sJ)Eat=G z*WRxIU0In@#xhhv`eS%;8G9o!#+)8ox@^z!6ojw2b{PT&w=#itzwXtW3GXFI^f7 zn>&Y_$`-FhZ%Cg|(S!{VU3xHToU>KMnhc7b+ggtwvr3LG@^F;R5zm)$v1q{@Gdg)? zhFwtjT_*E(WdfzU%me?fFS{fS4-3_%WRNwsv5kU3;oG-aw338 zH&d0k`?sqP5KR37i6sr`Xz4t>)S-RtpHc0dx|H;!jzPE7%2L8h znwihx;jxvpnXwavyrW7k=DpztLD^D>*06a%FoF{RgoTtZ{_)yj3x z0~OG=OCczZ7w?zmUQChNDalFAr8yFr0p9?4|Kw{j%W>{>c=9+#OCG&(l7D%0Q3eoLxu@TzF>IY(wSKRZ-7vy*Tn&N1lc) z)2A=~@nkFtXhKT*_Ib6vkOsi3fSIjJbaKCc+M~Yz2yv&B0nU*w=u5oei@m{Am?0Z9 z`|(2)#vJU??dK6!xyYYz)5WMWY{+91@7O86BPp9_k5-{B?cU@QEo~7(qb|jm<(2a| ze56dpKV%N(c8Oi)cZ&76me6vsNPoJY*C zgoKw`=3@jD>CVko)Q6ONVosH~sC)%A#@i5(Z?D0ZOmwodx05MW5M)Uc9PE`O^eLsv zL#pdUO=Ph&>@IfE%`e6za!Lbb{PWvGO3k(dXvm5~AuzHPj$^7}&5mKz046*h>Yus? zl$kcCRbR1B{tcpQ!{!gT`a#6Xz3W4o}K@KdEAVUgaTfF7!>B7S*8nOhaW? z$aDp}fIMq1%la&Y5Ac?pvN=IvlkHFM8V}svEEJU8(>==qqV4 zMQLctqHEY(Z{hp*Kbk7CGnML%cZ?m^oR=l(bSH8zTTvxQmL3zDIeb03)-9;!@E8=j z*o7>&mOo9EAuVC|As+k2Pm(9*7SQVadBr6(kvpn|WM@Vl_45Ok8cFAI?^K1V2SrJY$}BQyb6S6cIgJ3p%t~qd(^xgUCImOME^g?`&mBVf z})QbY0u)mLM>~ zDQh{(1l@Z@L>47%O2{sXz2>)v8042 zn{}gP5QY(}R}a~JgV1%F=UFo{k$ZEmIVtSxcABh{%w5-!k^vrj-(A4**s;aP^vCkt zAMV0g=i2TX(OHUzX>%T?@Xgq6JfEnOHWpR+uxT7S$8G6QV$BL(6WP9}jqzlE_@2mt zkM*PV??Yab*ug>HZoF@Dl}w`_NFH>))OExB#kh}$^tAr!1G?r9szubJR>fYORQ^4q z#kkG#b>cU$_G#+{X9U`Q`LAaumjbuBt!jdV1Y_-WHNAf*7gDYhH|JN45yE-XvkEUX zUelfWXH0Y@gq_n<#hI16eThP<)hh+C@0WghVFu7eUiHqiS=vVe$m&g$I(LD802C5KJ21@6Rc*y zA(S`jjZ{CRE@L2e9M)Et4Cw5*K|EMZQ-XE=(IHNA(uL{5fXuK4Q4dWvj66ngI-1DN z{UfGiOUV@fTJSzLkrTfgQZE?vg}fZ>Ax3N2d35ApV6PXoBLOFkW9=pJ5+q00$Vodx zrRZfgJ}=G>RuGeFqi;a?i=kAq3j06GaBqwR^0_jspEu^meKQngW7BZ^fs7ar?Q%!M zz5?A&vSfsJ8mJp*S%>JN6|C4e_P$uYj+@@f(8ieX3v97RZ&)8JNq`AmakrFtHhLoN z>R9b^Pbjm4C(X0C#r#T!hPYt#x)n>BOm1+*H?4wlV@V5Dbo*dRL+ItBjdn3o8?WbZwvGV~k~PEIx>m^nz}+?W3BWJ$9r-VOAjB5qCtU zVbo)nDb|fgW;netq2nrO9Nj!^w@=fE#7|+CX%V>m?uoymj=FI}*Ehz-?BLoxWDI)D zIs@kDS-2*5-P+_m@FIdyE*`EKzX;}!nnlcL-mAO1T9ls%M_)L_-Un>+3d}rDwe{Cy zQ_t?$*j>NrV#KpX%nQ*+6cau=tLK`>m6vX22ftfA+u+Zx$(w3q18VEWgs5@q8e2?V z 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 **MANUAL INSTRUCT:** denotes a manual action to be taken by the operator - + **MANUAL INSTRUCT:** denotes a manual action to be taken by the operator + + +--- **RECORD:** denotes a manual recording of a value in the procedure to be written in by the operator -- (record at the end of the next line) -- **RECORD HERE>>:** - - **CAUTION:** denotes a particularly important note for the operator to read or take action on, likely related to hardware or personnel safety. +- (record in the next markdown Code Block) +- **RECORD BELOW:** + + + + +--- + + + **CAUTION:** denotes a particularly important note for the operator to read or take action on, likely related to hardware or personnel safety. Redline template below can be copy and pasted into the appropriate procedure location. The section title of the start is intentional so that the outline feature of VS Code shows new redlines in the outline. The end redline is not a section title so that the start redline captures @@ -39,6 +47,16 @@ Redline template below can be copy and pasted into the appropriate procedure loc --- +REFERENCE TEST MATRIX + + + +|Test Number |Condition |Notes | +|:- |:-: |:-: | +|Test 1 | Hot | | +|Test 2 | Cold | | + + # Startup @@ -47,23 +65,15 @@ Redline template below can be copy and pasted into the appropriate procedure loc * Complete the steps for start up - - -```fprime -R00:00:00 CdhCore.cmdDisp.CMD_NO_OP -``` - - -```fprime -R00:00:00 CdhCore.cmdDisp.CMD_NO_OP_STRING "Example Text to Print to the Terminal" - -``` -ADD A TABLE - - -ADD A PICTURE +
+ +drawing + +Label 1: Retro JPL Logo +
+ # Test Run @@ -74,6 +84,22 @@ ADD A PICTURE * Complete the steps for runnng the test + + +```fprime + +``` + + +```fprime +R00:00:00 CdhCore.cmdDisp.CMD_NO_OP +``` + + +```fprime +R00:00:00 CdhCore.cmdDisp.CMD_NO_OP_STRING "Example Text to Print to the Terminal" + +``` # Shutdown From 0e940830c17a30c80677386d45f5f6cb9434655d Mon Sep 17 00:00:00 2001 From: djgreen Date: Thu, 11 Jun 2026 11:32:59 -0700 Subject: [PATCH 3/5] updating files --- docs/exec/author.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/exec/author.md b/docs/exec/author.md index 9268fb51..02500e36 100644 --- a/docs/exec/author.md +++ b/docs/exec/author.md @@ -5,7 +5,9 @@ icon: fontawesome/solid/pen-fancy # Authoring Authoring the .hermes.md procedure templates is recommended in VS Code. + Terms: + * Markdown Block: Manual Instructions or notes for the test operators * Code Block: Instructions that can be scripts, logic, calculations, or commands to the software like FPrime. * Section: Uses # to denote different levels of section headers. @@ -102,11 +104,11 @@ When using a FPrime code block, there are additional features that allow for int Label 1: Retro JPL Logo
``` - +:fontawesome-solid-arrow-down: Looks like :fontawesome-solid-arrow-down:

drawing

-:fontawesome-solid-arrow-down: Looks like :fontawesome-solid-arrow-down: +
Label 1: Retro JPL Logo From 53fe91723bccfb7519e20c7c26f1fc266019a6d9 Mon Sep 17 00:00:00 2001 From: djgreen Date: Wed, 12 Aug 2026 09:20:49 -0700 Subject: [PATCH 4/5] adding pictures and content to the execution tab --- docs/assets/communication.png | Bin 0 -> 4321 bytes docs/assets/create_fprime_profile.png | Bin 0 -> 65394 bytes docs/assets/offline_to_remote.png | Bin 0 -> 95030 bytes docs/assets/offline_to_remote_2.png | Bin 0 -> 174802 bytes docs/exec/execution.md | 117 +++++++++++++++++++++++++- docs/getting-started/quick-start.md | 3 + 6 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 docs/assets/communication.png create mode 100644 docs/assets/create_fprime_profile.png create mode 100644 docs/assets/offline_to_remote.png create mode 100644 docs/assets/offline_to_remote_2.png diff --git a/docs/assets/communication.png b/docs/assets/communication.png new file mode 100644 index 0000000000000000000000000000000000000000..5f9033ca512d5a9abdb9a0358b9329ef9f1b47da GIT binary patch literal 4321 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°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

hbaj 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ó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 @m$;=XH3I;1GOFy<|8BK%_+wP$ zR$ay{OkI%bw1^3>kO_LUv?A`4IJc>!67o(D5{mrGwE7kf6$N73DrN*1E>3&AUC#Hw zpJ~|I%ybC*b`YV0AFqj7f&8f?CL1QmaSD>^@`6+9c!~H_LDWg`?&WA3C}pyWV`94U zxayk*j)G9mgwLEzVbH}VcemI^rl*ieLIfFC&dtA_C>)X1nK<~z5{`jCQJ$+Q}q=LJNxHMAb+9CL6ac3cS0(lIp za9RMQp0bYIyovv&zC_3K;oWeQQ!kjDjuyRrZV9NN`g68-8<}u)bnE-4Z^D~oD3Q6H z_+-gPYI9ruZ2fx56t-&DF+OJwKPY~(K@Y|}%_3|a*ZYJ`0AV7)*yk6L7B~dl?ZKGU!qbCVKbb3XAPjMD=&R;K@#@fNtK*^ zKtsYt)tn=OG#$-H9?g26pQEgUvkT_&S082N;z7+^$e%m#@+YGrmDGGcvT)!gvPQIJ zuPuf8B>?SAY9bZSNs-jQzblV+7BJayiT{e1vPOvdNeX~_@0*$&dHsj^H?xiON(1cm zbxwaxgF-bUFD%Jvqlv$@Z=f+n{4eVvfuQ*E3~<6w0X zftyu$orG2z0Ca@>y(N3x$13MsmC?P3u5SFyuqX=ye*YKP9AUrU4e*3xof8Y@#2ai6 zAPe9JJNN0{7mINek?Vx$giy^ z_gFJ_9v%GzzARI5d={_p$GW-~AUZ7?b&p zIj?LQq1(8zSHKUR@YGJs>=QiWoVm&DAN==~tTJjlAM0n5^Jn0#{40?34@Ds8;KN|J znpja_K;;5gL~cL_?>sd-JDlK(`fmn-_>>&7=Bd9?<=NT$$L-PMl}G!cS|KhwXozEecBUu^J{bGm}{=BuJz=j~m$zNlb%d$&5(m7^I@Q0yNcOCeo#EPlA` zSkws4i2E3cT+_=JsHV1R@x)ms#${?qlS0)MR=FzbN~sfH+v2`m@i+d5jPl0yPWgWG zVwg>K>ZI2$!Xd8;c#4}VmaZ^zMuQq}ZI`}#S3jm8!#(S;g=PdwMyd5Ml%6=?BxlMA zGA>bSKe%I!0lYhXKn(9D;UiPHh3ZERM)&s%;;hytTjy4yxWKo@YvyF?%s??BiM+qD zAw&@nj}vm`%9x(khl4Z??GSh+BB!#ejOv4%_$z*`CZeZ3X$4gev=vUBE`qY?~ZS|M`Waz#lQAM01~UsM~NCb=1~3T|)kskyVo zs?-+>+3gk3CfmU(8KwsC$fWKmNCm`&DtDomyXT6PH+2~nb$#Aa{Ob$bOgzgam~dyh zoi%AIJ$QMtpzlIjky^Rwyr-$(G4jk>u%*v(cw5=O?1|K?xTq4i$@b$?ePG@aQ1B_* z>D(7-yy;A8_7Q)9!a8!~Z{&%arQ@Y3Ql(*A@%>N>qsFOrq;Ls}c&zO0P=Z-#*MthP z^2p%tmAF!2uXkF4ZJ`1BC(Xf3mZX#Ju3U4<3KI3KFo+F=SFxfA=WKt7lFej6+GV8;fcDlDJs|VX?_#bG3nPi)MOrm-WG-I{e4e| zc{Cz2Rij2svZ>#ZO^DHv=GmX&yS~{jl%(djpD>Q+EW|~B$k%S@K*)^u*l-4Gx_Zp4 z-@n3b>u_@TfoP(=4EB_+^-Bj1B+?qKzz0y6&P1 zR@K@GIi6g8%o1*9QI1S~N!aJU1^mQBcf3 zO-_4|QOu4GB4qdq3`O~D29IfLxnY*DI@+H0YVNu`2(vbjgKpxb(@3o&K5^aF+I`TS z#Zqf=sVS|8<*^*!k|^OEtYb|TYIRKHJ?V-d+xs;M_4hxK!6+%^AseT$Hy}^n<>KCS zuFgi4YqdGPe8Fn<@pdeKLbTVnSaM}p#jnB|rs9_)l6{5Vt)t0`JBDgG= z|6%$i<#qKT>+W_Qjb2r&74hg3b7TWTZz>yq$LKAYtkl)x6OEt)C1EOW!R;RleQdiv zLHE2XbuQ&uBD0klg!ei;fM26NS@h^XyTXf=2eU-RO5LBz?=g>>W0^|G9507J_=?J6 zKT}>uB2@<}MW>_L3$K$)o2qTJh$=nq*5_nSyiq7-nQF{T>LanA7}_5dIevXt1;t!+N;XHyB6ohx1i>jVl7xp)7lQE z(AJp`8r7P}Gug7W%^;4m&cwP>3`}=G61e&v3A1Y)yM@OppAGifP=I@EE^S&bW;wgmJEzGJ%MDL7Q; zYZYY8Z*voh)>6XPljEq>W>E;NN;q%(eY3ORrGJApKu1BOy!Gq^%2t-nP{Lv=hWKgff=I!H<|MbYCPvoa<%@?nwRkb3eW%H3VdMXjy zI>I3j5wVcOzq3Vld^I9Eo>l+rQZ((mVRk*PME*kLHZo{H5((UzxqsLiZ9~N9RPRXX z{2P%U-Vet(4rILP{+rbfJkXg5Em`FEuKTTo>U_Rb->OkxY^z(Sol-asEeugH{sA`N zfmCdIf~XS_$=9ry)^d{2R@*3WCy7NC`R0KxF{2as64(vFZ~n|zaJ)=?D&iHnv0Dr@23#K0SEb1FC^SZ((Yvt%>eJc!ZqeB69z_z#!T1jQf zB6?pwu`m3Z{yuQm=q-4^Y_$4jO7FVUKf*_gQI$t227gKAo+(v4Ht`gTi4XGZa!qP( zeweUyqc}E6?yDRL~N-W6%LFT09a)K4asvVq%p&%pg%~|_hGJh6v zGlB|WjHVOn7IEa1C03{SkKx}%gHi&~0$QRvK+(Smk!CGt33?dxdsaP(NGYn`AHA8YjegWdp09yQR+JIF-$QKh+UFzAchOOR?g zE3@e0i{0b1acZ<>l?XXDP0#SqLDwg&XppE%fEK9a(bn{aqi0?1j|-w%?$|RyV-xXo zMq05DHG>&1=w|)SyHT=ey5?(f%8+t8hg)R;WQ|=iFVc`b~$8Pkv zYuIOp$i<6k$H~`!_qNO9MMgxbvID2*w>8iE!R@^J1ZB_%4)7a#bxK{&TkYT2)Eo?X zxrzov(x?T@8d0rVPsq8>v&iVSA4vw>eAE6?JPUYl4WO%H>Uj-!;rA(|5|Q+f_AjV7 z!`vJ<=CjYKl9rrwRa8JBz}{{;in;cJ_od$kh`zua`h-LL3QdRva* z_bJ*#+U}CwJB2*)=9MX=cQS0v!GZ(~eGGVUpyMS1pdC)iS%vsYm|pX$hs=HJCiHb! zt`k01<_sPjW;5uBz9XT*T;QSXPE}LOazrWu2>TFxKK(7WGaS>rypj-$d)w&WXQ6Pcw@HQwYOUk5Sl6i<8-apx`l=_ozW6=!58_cP%tKfyBY@iN+_ST<{`G z(>S3dS*d_lE{Y8dQw-wj!n~nqnw<8-v}9^3$baEhc5~5hiHY#2T3>wdKGxHMmJ?fY z>^!5iU+YMa@u(_1yEMTciS(Ik@>Rmg$Ve#Mc{D&r$JpBFu-R{$ z3zQ5)Qf)m#PDZMO(^6bUD_vIeRD>S92Yy$F4^p2b{QlY3 zp7aqO?QD`T{_M>*d3E*Kzc#quP5to{)lcg1Mh@W4x2zTs}r40fk>yHRGybjTs z|M|Nq1TmF70Ux&A&jv&{{;mPq%V+KZY>&v;kBID!d-40=uU@AZ!2ay~rRM2I6pQjr zDRYtBhOn+fTAb7l{89O@YCeyofu+x5?38}Vvj}?dbB@-Tn zE5&%HQ;_`Xc3w!bd3r5wb^Mc6)nd?Qk(Vr1W8dx`TJ}Ix?w7N^_Cqq_&XPs9^SWcf znMNF>&x5E7Lmb0x_C}0$fxtiGs_*SY;yY4vjqk@{?8$Rt3(7=k#XboKSn;HNt3g-$ zkm}fSCYrNW3J&v2`bN6dtpa@LF=nUvBK?uKer@oiZKZiqJuLjiM{Q*_sE!Yj!&ByA zwb>)py(ONkgh^GCo5*ET$x-LNSNVW%b?1p^>X(?ynE-j0)4Tbl+jtly0#g5Ppi!sp zz}!n-+AA@7Z^ZC(4ZrcjGfAqDbZ-F=mLG)I<7`DMM{E47=c3{xvXgEy5%?4;sOH@L zy&V4C%+l#m5Da_gQR+iZeB2Vv+KIi`0H@%Bi=98;w;eBfSBck^ioy-AkteVamyh;g zp-w&uf=&*m5A9kE`NnTU@;P;G>%NF`d7o<3Xle3u3&m3EHh^acQ7a{b@p1BGn%~QcXSbge z$JE)Ou5g-$nT3dp@1(D14paY_vUmO4(u8$VkN_#os! z0o%|QmdGA+^P%wRw5FK~-cNq%oWxW1<+UB`&lS6Yl)$m96NmOx`;Gi0P$dwK+t06< z#%@H78)HTCtii0=(G~5polxlaMVDq*8|0=l%&pfDBx1_Wh^Om=Tgg&w9vl)i-g~PP zWOOKVK?DqOKTAi*FfxjTJi`K|%xN9{guXfm_Z6Ogskh^#rhjCTwt9Sy%>6x?#{L4+ z*tFVrvTD=Z%!F|D=`EVd4{k$f@ba{ZaV__}@FOYE;&gBqk8kqLl^6?APqodSw0NJs zo4DIg3{+xfUcv!*NAe`3>T-fZ3G|Qj!J=G0eTis(T8E_+rUGf1p~%xe&@LX+2IhD` zkM`;S?ziC5_JIY*N0dqt@956D9e!?|81o|)IMjetF)rs1Q%zZt;Eo95)8z=W%uN%X zgL5L1{0F6L8Q2VAkpwK*x9?EhcE`Y~vu+I=R=!$Z0CTWDRK=b#>|}EOL=4$B)VWpU zr#lsM(&W-rKJJ}#i;BdW?3qXpO#89JPSt4weg3kRu=rhBMd@xDKm)bLk~_pN*gKL#>fjzBIxG{8TLWA5Tm2>nQbWI z-*q86$c#81F+ztwC*g^`MBhoo{?QrO_>K5hb#!_uX34_`;#G?tQS$ej$ph0hLdH_2 zn)@lD{hfg{64hLrJrmaGi#(|`{65w30OTdc>aQSGc?B%8=^OF1B@g6lZ=M_c*p=iw z2R9|eRc3Ub=QXXyi!FYYe6XU2RezxkLHYs~ndb;6&&6o8L*NF5J=6|7m%-HRUV_)O z6kToZ&FOzWE4KIJj4cT~3LHAqUNMqCmD`bU;!?PAfp%@%}>1b;_lfc7(?=rHe>heN!x_nA@ zVsq>PFWC%qMd4eNujUp-$9aW?0t>eQqdQa`2qZ@tj*S5@O%V}}Z4*nZe#FG2Vn%Bg z^>*Wf8T>|RaUg`~53%kDhtTq#kErBFK7o4hO&n6xn|AZ7u;}QkgOC@!X4O`5A$2$$ zQ`BxOaWBX5Us5~iHv5NXJuM0p&9f_UJ+Kz~2S;ZTcNI+h9v6tMt=^7$iu+Fx;7me} z-ri&@9g4yyT9YAxz=B8U<7`vecG`NxD0-`7^_!oZ_8}o+HR}5juLrkqef8D^?d6GK z=`kj$R!bH92eC%v2g^d(b4ZD(={#(c0o&%|aJDw?o&*^=6r5Y40h5ogESr0k2fL)CiM=F1XOJ zT%vTL2jU2$w=k8Q#>hJ8NboV;JQX19nD2)8E%R%I?5?3(A)@9Ta1|odVHpwL7E*Ma z9kDDHHkFVyk zK(5|37&IysGolKmQ|kW?ot(UnM8lCZsW)l-9d2*7oc3sDPSP-A{-OsYoGHiUQXSLa zqF$PpCj}@h6tUo@uopYx7KxqgFz`3?}n> zd`{&~EVs-tYQ|gq=Z6YEX(6tm04FmZ_nelH*PP5|XKJL(D_`SH?JZbtziC2R=OvP? z4tKIXiXT>MJUAaG-4)aF$fbxoT>0j>SG&jbviCc^&?pSxn^)EnmZ)YDCgA;uy&cI6 zPIee_zlehzsh=N~`9Kf;al%2nQJEBlfxD$o)<2xQB*wkXj|CDcGew$xo~9>s{9b%N zlZZL!as1+=qv#5_kEhL)-oo1Dj_5A}+J7LvQu@`g0pXFpP??QCSdsM8^a6Zq( z-^SZdI&>`ce%cf&4S;!RI?H?OHK|WE2YiG^UazmxgOl4iXVwlS_jb7UZSAD=-t1gg zTVjNokfc@~S4fGy1ARpHI2Kw_A6h2YUw3_@c+Ko^Zrw%t-;lH-ui$>Au>`?tm35r literal 0 HcmV?d00001 diff --git a/docs/exec/execution.md b/docs/exec/execution.md index ddb32fd6..96280f12 100644 --- a/docs/exec/execution.md +++ b/docs/exec/execution.md @@ -6,9 +6,89 @@ icon: fontawesome/solid/play Add a flow diagram of the process expected for the procedure usage +--- +## Quickstart Execution +If any of these steps seems confusing and needs more detail, check out the sections further on + +

+
+

Remote Host Connection

+
    +
  • Click the button at the bottom left corner of VS Code for Hermes
  • +
  • Switch from Hermes:Offline to Hermes: Remote
  • +
  • Select the remote host to connect to (should default to the correct location)
  • +
  • Click the left panel icon for Hermes
  • +
  • Create a profile if one doesn't exist yet
  • +
  • Update/verify the Connection Name, IP Address, and Websocket
  • +
  • Click the Play button to connect
  • +
  • Open the procedure of choice and start commanding!
  • +
+
+
+

+

+ Hermes Offline to Remote +

+

+ Hermes Remote Connection +

+
+
+--- + +## Create/Update a Profile + +* Assumption: FPrime Backend is running + + * If a local FPrime needs to be started, follow the Quick Start instructions -!!! abstract Authoring -!!! example "Test Execution" +
+
+

Remote Host Connection

+ + + + + + + + + + + + + + + + + + + + + + + +
DropdownFPrime Client
Namefprime
Address0.0.0.0/8000
DictionaryBased on FPrime Install
Protocolccsds
+ +
+
+

+

+ Hermes Offline to Remote +

+
+
+ +--- !!! question "FAQs" @@ -19,4 +99,35 @@ Add a flow diagram of the process expected for the procedure usage -This is new Content \ No newline at end of file + + + \ No newline at end of file diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md index 7994b00c..5972a3ce 100644 --- a/docs/getting-started/quick-start.md +++ b/docs/getting-started/quick-start.md @@ -29,6 +29,9 @@ This guide will walk you through: By the end of this guide, you'll have Hermes connected to your F Prime deployment and ready to send commands and receive telemetry. +

+drawing +

## Starting the Hermes Backend The Hermes VSCode extension starts in **offline mode** by default. This From 6b2449a1202d41922e8e6e0d888c2a88072b0ea2 Mon Sep 17 00:00:00 2001 From: djgreen Date: Thu, 13 Aug 2026 17:11:49 -0700 Subject: [PATCH 5/5] adding finishing touches for final merge --- docs/assets/communication.png | Bin 4321 -> 5625 bytes docs/assets/connect_hermes_fprime.png | Bin 0 -> 72834 bytes docs/assets/create_fprime_profile.png | Bin 65394 -> 45072 bytes docs/assets/fprime_procedure_dryrun.png | Bin 0 -> 67083 bytes docs/exec/execution.md | 74 ++++++++++++++++-- docs/getting-started/quick-start.md | 40 +++++++++- .../test_procedure.hermes.md | 18 +++-- 7 files changed, 118 insertions(+), 14 deletions(-) create mode 100644 docs/assets/connect_hermes_fprime.png create mode 100644 docs/assets/fprime_procedure_dryrun.png diff --git a/docs/assets/communication.png b/docs/assets/communication.png index 5f9033ca512d5a9abdb9a0358b9329ef9f1b47da..642e80dad5620a9c0987b6507684d3b2e8895e0e 100644 GIT binary patch literal 5625 zcmeHLc~nwszoz=M6PeX9^f)(aNy;fpZBS2Tb7@wJnsYhNnvx=z13BeTIE4cam6~&= zgrcaWXgQJdfQky5;(#bh0$z0W-Mg~;|6O;j?+-S6?`N;y`0V%nJv;o4?$upDDIfp< z*meDy&RqatD@t(Q`NuZFky4SHD)`&tb@%E;Kw+!QcfrNK>@M8C005N4?BrSr39e`E z+`4}m;9djG=vrG_(|p<1KgG`aIw>F6Skl2j*NwXt4lovfEOB^ab1P)pVi10W&^JpO zVZJWC<^Fg2LN1`AW^{_m47{E{H@Q@o-BTFe#93aY_0N9&Kv|q$8TmGa$Fyg_YF=97 z`kLr~*f2m?0^sRuK)N#^#cN@aw#r?ZV-EKYmyS&~0^$Mz#diStP(Y43;I$2p)xXl} z%I7e+4ALsCWO}xjGk~tBhkfmQ#cqJ|*<*a}(qgInkCCh*g7pe#VQG1KsalgaN9J?| zee2Ku)Dk{4UOh404tO2|c#H>xq>kkZ0h0V6BV`qW#CNz~Gxh=iB=)ZVwtP`wX#)WJ z;;-vmxc|g_hT;U1x>_FU@d1TOCl}y%9DZ~i-Lz$3?2&&sseJs!K1H9GXC_tyQc4}6 zr|+*-2HwSw7Z^LOnLpoBmXu1f^S(4ZXn*ug+}#_;em&$~b&a%#GhbiVlZt9+MJwz* zjb+4%J2O74&?pOeTA6CqukVM6CX&kWjRm5{eF?-t*DIaQK4BGa5)mdD5sM)eUqj(h zSF&zWcvrGYhTk+lke>asthr-G#oO`-J{|P#nvYOp#Rn%fvP!kAo3cZKXYy#OXqh-9 z(E+8@EhpQ%{XyJmsVKG6l-;6)LDBEUPd*ON5^=j#Gc^+_u8Hpr^{KAtoKIlgH(O`! zlC(5NWN=3xAkB>TpM2oZhP?8M6W8d}cq!90QPdelzmCqylY9qnY(x%E_G%}`$&N_G zs_CD3H}f1CT`4o974V|(%Sw|N@_i`@dm>6ZNqTR) zMdI`w4^U-Z-8=kvwk3-EYSh5s$&m zdr>oQWU@yel;sxFe0;?mP*YmvEh-pIT-1!X6TTrj(Y%}+O`x0^G_Fz^G-FKaPnomr z143cqo~Xz+ub~6BZUI(b^A9>{OJE=hGoy&O3ar`LL1nhtfTl8=Y#qsvJ=fRT{8qgB z5UT;#B7ayFXIZ)p{#U#SWSRk*u1XcPv4yl4MsnTo$}kyxDpNG|8^2D?W|e(BtlaqT z($zv=`PqQ}<74;a@KTES4WY0Az3A)&C=~PmC z+GqJ>X6u0zOfjPO$QO15t=VHdUn80@e3}&3f)j(uoH^Ht=-y1iV@tyhimKT*WtICp z{iY0SPS!7NF{}DR57n<~XF2nz(;-1OBR4b=+gS=K=ZPJTGs$YP%PFrCGwH$Wp`zZV zS>@FA-cP^@8PA!6`fK9*U$o zWqsrDz~il@uH-<3bvOK+Ej}7=wTs9JM4r5V;N(CgS;Qr)Y zUgq^jNXlY|EF??@^J^)|Z;d2W3RMgoqZDUPj%8j%b}OmC-}guid@&(3(FlVrFBm*g z%G^ANSny^nV;j%t7xZW6mvx@RPL8Jf(snkcdk?hIN7t*wN$ zXR;&xiAFk-W49Zyi2*^tg1>j9K>ppzoOU3(=D+E&8O$-%V8n!7AF zr+yoEPSTcT$~Q2d+V|sPIMBhdvdsj^$+@o~vv=1O9XnI%)3;d;TIs47(J)~I|KXzq ztwU0;ND5V3ZVT5>PgDJWWyu}=-1(4CXXVMcxv>wMx9gC6k-F@d9jOzXW4Bvb{!cC{ z`tsufXx5I%iY=RI3PLO_vbaGC1S{E&IPkb)@WBQ#7Abtk;~NiIPc$+j32y4N?2e9a z4|-}hQ9gYu_~Qx+k-l1#s_5$dVUZWO>0P@ZWY~)v{wKc+6z%ZdwE2u;U82%2^@lFugPyS)g%Uu z_#3P};fV3Qt%N{ER$A#~p}9)A{096`)L!o ziri2up)zx2Z9?_zQbefRP&GS&gF-UXE`#+F_ZZeh0K+F``N!KY8Ls?!?lVwH3A9jx z-a;O?c0sXwn?f4%gnrtPVNB z2V3&RIjI~hM}l?Cqcv4?;;okZ74^Ah@l{C*MMf5Z7fk=LzPxBhrEwS@PlIs~x^!b9 zzPEGTdS2CK7e1}`#zdc*?SvL5$8#P}Dvc}Jdava6z;9yVf}AE@ih5ROFj{_dC4V-c zMb^7dJnzNhTBY%+`QOf8fVnZk%%0# zXt34h%}*+E*}_o){HFqc$g65?5x{`bVO@rln?AKjlmF@P@{2-vRR9@m{zD?#*~sy$j_knpSZ{o%XhMy<*}5vN$5e5vFpNM%gZPn``qW|*}{y2 z=UFvzliUk9RkiHhY8vKU2HRnzG>-3dfayVzWJpgj2Gv}7hvK}#hh4a|K}j2Q|Q}T zw^dW4^E7QkgGrvv3b72hQBgw!BcjS<`Mr@?7bA(G3L(Q9&O3tU>GE2Vu@(N)w}4vU z*Ls)W;fy+U+8g!SaC9xu2!SfY?zDqhtPFhdG_Kr}Z49M8*LExvFjkpuBC$)W8^{xt z=qS=ZZhC4(R>|L+s5$Vr`&pwcc}2yu=N8}?dBlPDtA!J=xtQvHlqmt!$1go*F2}8PgJvEwsQv|P!!96*(ZVwc2bhj{c$1NT1aJlUjKV! z(^OtMx6e7056(*x;t3Nkp|FMDXB3BEvX9Gy)lEz`qV;5qyQ)p_y8IcBuK3H)CO-|z z-J{}?m>1$J&lO#SGd{~OS!)n+_v&{6mI4RoPbBsW35kD*eb^Q?IdE^^bX>NoY=TAZ zvg&#rK9F<^!wR_;Cb_3fZy0TPpnpLAYT7F4g`34Z-%%@?A;&75t(;&V5-bT1Fv-DR z%u#F&PmfMXs=q>Tl2-f3~IDD3B_1%dzr8O&~3k7cP+U2fR z1J6BOP6h7^S)ZPO+p4EDjuxk*WMFAV!)ZGVw{6q`hjisU+iSkZbgXg*d8Yz6Q=9zE@3Aol~M_WBq%CxO@aJ6rlqKAT|TQQ)tB6o zq?kw#aq#N?;Olwol|3R=--{#+&&K$F2KSVXTDRYX ztA;9Vjwyfi7P9&^g@UF(@oOq#QvMk>P zA6j~?^}7k%Q?s_${Pi$Fm91W!t>mP^dtOe5$2xTyj#acpA>Qx6Rsbb%B^NeN!$z|| z-Vi>1pqNqOVzBJ(1mq%7LJSX{S;)YjZ5uH#XWq0mhM9F*AE{i3JiU4I?iR=pk?p6; zaXHl8;WXg)&xi}=X?AyW+imhe3eSEH)9VjKgxfo_zsbbRI2M>vJz8P(FOVB9-z(aK z|7YgzeqT50MZJuCze1-XRAipFp2;bxa=XUlv@3tiFJ3YI_%ZyYVbaEgFBsyDxHEMH z^pB~zzRmi(d?OjvA{I%zqh&gGOn8r0cP+N$?m?m^W<`s)L9IMiTY$fntE4hZgejNg zRm~!YeI@+G#Gy|F$V)~yD@B6|BQ_0$$aY4hTq5|Cy0qLvv>W9fdV@s*#~Nal$LJ3g zXOVopaE0K#w{@S4`8m&Bd^n47RWC%C4=_v6$CwA-07LI#lFoHanoocJsWNJeWoo6< zZVwV&tog^pFy^9^LM<L>2Ncu* literal 4321 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�<$ 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 Wb-{ifcdbSYd>rjuFbrG&`2PT-_u(=C literal 0 HcmV?d00001 diff --git a/docs/assets/create_fprime_profile.png b/docs/assets/create_fprime_profile.png index 4864bca46bef5fedd6ad718d809f641fb99fd82e..06ecb703a0748cc64f95f21d0a76e814ccdd935e 100644 GIT binary patch literal 45072 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°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%0Dk3t@xw(!L-s(zDs%7ajW8M1nZ8E zKMX-SXguuvp9UZJ`T0(%b4r~K2@9VhSPUVdh>DJp_8n{pMRZIoLc_wL4+@?n^oYnP zfkM=Zc}QrOfNk4WV5v}XqhTsZduuVfVi->8wV0FJgYV6M6b@bc5%e11Q!xmQv*Wae zOM#q_n1tNCe1u0t!jzGL?3^5ggoetuLZY_rXvRicTv8(TD9n?LMidnlAuK!s`S}G% zOE*n(r?{mM$j;6|LSmv2XGCPAnC0Z;LLV52$jB%mLi$T#k#5RBR(1{|qoSeL2a0eA zHiQVl@PA-X(6n%r^wBQeQ5dD9rXoHe5x#zYh>DIzP*AXh&wuob;mwp@3Z6`Jmc)g` zM}fq&GM={f6{MR?$S){BMrP*Z90kLo+sLRWA!2*s5%jyJrdHC;yk>eCZd6pXXf^yF z8fFoQ6qHFxM)*zS7vseIB$ABDh>4A{8xGA(8JTc(b4N^UoVX3p2TE8O8JSXs0eZdU zpVuYQsp6}KCtN*sFlA&REin|%E^d>ZGg~MfJa-Q*qN5@ap!1s?4YEhlZ1a={g+;|E zDlUdGIT;0oMMz9cLPA2K)TgwxbQBerAW!5*gw#LQo!GcIkq1OdO6p`soOQ}pc&Siv zBVYVjqNj$T%vPF=TmX+Dn-Wfi%d;TX0@tK^G zD(+I#(#5a1_;{3;S0F7tT}a$k*wGJ?D+LlsFEP<5L`flLHfKw|*=p2^*2zu_<6Tf# zh?v+|vCA~p)YeX>&(^I1S2qoci%VckPLa+_a&ii)Yifk>&1N$Si;7TKREUz&Qb}7; zQ899Ia>U=P>}*6vMoK4&-^}ls%A%&WMu@thp+U;qoSh?KvTRKz6Dq5!B+krllDVxP zD!yuX!p%*K(%KfZx6~pv%_MCvBf|)9pQ#-wYS@1MmQF8;GcqOtrnD3U23Ry|dnC>8 z>Y7?qR8~pZXJ(pFTvCeg$SBm*)=3@7FDMkb!Ft2aIY~G^Apwa-BNCI0FqtyY-#;Mr zo9%?eZY#W0sJIa@ZMb_@?f`z&aST6L`T}lh-472_H3D7$yk{V33TYCeMPmv?Yip~J zReMJVs;a9|P*5o3XDi_O9TO8POK0b(zix=Wy<8N(kLqtTRH2k`{dL*Z$psc(?v`pJ@ zlr)koDOgBmB<8NJE{P+_V^TIYfOT0Z%Qc{Y%{CqSvHcI#`7j_WnABmKm z3g!#r)Y#Z0oeyfgZS8GRCcVAA(us+PjJ8`Aj5h@_M zyQH)XAt9lnNwafXSye5aXbykZH#8zIzhH98izJ;yt-GL5H1W3f4r$L_-QChwrp$S( zP;rA`DoJ~3;S!M!4{u+%M4IsZ1yAFq%|D0kNx&<=3mz`^Nt(mIBv_I$2_!KoNyw1o zVl*b(749@6QIcFaWKAL|D=VMuHIp=QbMx#D1v5NyNdg5l5^H9l&#Mq`Q$?@~Ch z6F}{aBubK|8HtVr#i2G5EQyN$NSgLEX(g@@aPNAN}P}IOEA-JZcM<8txYD zzpPB+N_Q0%l_HSXNtubHDcCq1O<|UrmL?+%)Q(A<6l^k#dCc4m;}y+goW_x!7a9V%=xkVjbmwwkZ-k>wQ&KwX|iDIsLK~UMf`F2(YuddqRk6 z+~E>x#BE)-!@K7YJPfh$&`j45CmZlY19zKPoJ3))yxZEhcBd@xk~yylSby8Pv36_i z#-7{BcuWW&+yA!qY{Re(&vrqTJ%4T8+4^A}ruD)zTe}Ku<7~a?OYzgzjXn3)?)YsF z*8h~nURN4NB`(%sF@HGBUrOnZ8+Rhv}{9Y z*c8FSQ8msd+dh?d+EEsau~VLtngNutz>Z3WDgfF#U1q7tzR71MD^k`}0knW*?1 zp=!|IevqjBbO8vU=Bq-5iZ2Y8C+RDMDu{0^XgCD!=BDC{gfi9i4TReMRjByl!I7)) z*q#%5txyiUP;nzb3tea=0@Lyln3|{Ji$Hole1pT_t)73aLWPP6kXOIM)yiK4l*2r1 z%~gE+z;X_XjDBw)w3>g^C*!T&wYg4R>?vk-hCwD!zS?`A2_$ z%$YqKj&tUy_yXWCXAa!l)ji}YRH(QJ?wn9bTE$g^<;;;2M<-`EIlHL%f^apftW~H` z@$JWDN!oU=tnGhms3d*k;L;@R42L-maCCHnqobpp|0-s}<=U7E6)L`ZxVyWn=R$;%a&FNam}#?QkZ{NYY%XNkPm*1}rB>nGq5ie#sS_S3=T?dpX#`_V5uo z?1JC4Yq>s8?ARQ%VeQxC;TzV6bXXs(A!f=U8&ePRkoQS<9?*DBwPSf+JfK7QwoTKG zh?z;+8ICSq2+GKTCcpsS$P~CbI>2$_KkX}S9Gxz@Q_QW+=$F$p|1EYezE6EB|K~V3 zO?5Zrzw&f~!<0^hby!LqZNr})w!BZVqx^S<1Jh(}K4B*R*@?07UnOZ3Dy| zp7*?D=@L0U)84t%#FdbAR7@&-G+IPO$08%I1Xb0w$j{0^ZEF{@lcN!zn2neaFL>%h zk(HhdQ(-k~Doc@?l8N%FMx-W$!OPnl?j9a6M8%`Au>t0cRAd#Dp|znJhR{%?XXQZa z9RQ;-7RgzK2oKOAIyx3fX~k%$t%Wf*1}Wx3w6r#$q_PR^9c?JgE{4HJkEFChv^6&% zCNc!3+(Kw|0SJo>M`}t2Dy!>Zj0w6#Z|WNko72!PMHrVNX$L1SWNyA2rAMDbYReE@ zZ@EQ?YL0^=LSm8-5)}&v2M6g8xi~w)#f>M<`5-(j1~EZyaB#Hv@4v?Bry*MatDx!f8NnT?d30>mU4F=vj0O42G+TxVe04~|Jf zS$Q?;s>_g^l!n@dR+QwV!N=SBSX?jY0}@b@YZ4BusjWarNC@Im(qKwXgSWf;v;%-_ zl||dItznm>DS&x#MR!k+9D>ga5qO~l56kDd(Y#8|TBB8PIg+NElFELh$MYhGM$BKb z3^57G$TFv)u6rIj8YEYeNBA7jH&GeKYDRO3>K80$Gu9$SbKt zWn~GxG_D8?k41V(1LBj*ke614r31C_3N@moz6|TO??6v?Ju=KDtXn;V%)C5!dwe?= z_DFBgPX|L-1cF1u5ugi1SX2zW?UXYyBS||s!BrQB+`Uht_Ms1uy>JseH6Eh1&YC?3 zmF@kQH@pBL@kTV%Rv{t9jH3J;l$RD^Xm}<1%HrT~n*&@typhq+kIKdt7-J()(A1Bd zoI+%qveDAogs3nb5==R0tFJ(k$%L|sa%5+xBPl%t&223>x}hDq@J#63f{+=eN6*4O zm@;!=Of{pfsR`bWPKeDeN9USdXzXl9Kwv1!c)3Vw9FoeLkzZ1e+{j|Iws#?;vKhsd z^$7M0MNUxzYKv1~h&7?LG!H=m-f-1;VyLAa1%>q}Dr!PvbTC5WQ_xu7i6zBx2=a8W z5VvU8t|)2h#oVqYv^G@1S94LbR!Lfgit7g)zK&1LggHG0k%=bcnllj@9gDK!T=@B3 zyfDf_(uv4VkB5`9D~d|;;N#;1Lv%dSjcM?)A!$!9U%6H}FfbTFK?a0JM#5)?yBCOQ zB+Y>3wx;|7%pDpMvZnUIn`lVTyeVpUcv!BMv=u0qCut9q)h$GQMFZyc^lUE3VG!MQome)|g4RXrV9rQ`Av7G>m96NgDnq(48h+tv=x@%D zbB9ao+EH0#g2pQb8`lk?c5nzqb-h@=pdKY1i;$L8iA_uAp|Ncxsxk`D*wBj7qHK7$ zYY`S6gVf>%#2L#_oYRN}tyyr;Mxdjm7HifmhPiGK!}D8^ogR<8npRX47~$q_pSZrs zV7cpp!;oUiLULLLqQaw*Sx^LD_W11BpNXV72kjMKf}FiCqUOGLQLyzS{KCWF=-_}^ zx7`MFbt_^rO3}7YI8F3vf_}Dl#BAjS-54kGd%nPQNC~kytKZUUl)(X zI}ai{Difx%7C5h{)(fkW&QO z(_*mW_-1IMb5PMT4>{#s$nbDQL{$A4TTNeD9X!6c2*9GtDDhSRDg`Mc(`h`$SJ5mb!8>8bId5MszW_* z%*jYZirI{?L^B%eijbC90&jg7>TByzSlEKGqOW$}Z%kcBHxz_MeH zn2Dr?s2v>On_7vCb;qHPj*xGf&z^M~;tLv4-QI`B1r3BBuf!!0ldRh@lkX>3G)b1u3TEka9qA$pf@!0?hK zD6MHjQAHyA6Sizy+v5DWj8uoDiN4kfUPU~uxRNp zf?|!>*qMOp+;CK7hhf{?Of)W7il(|gEUh#l&|}WTaJM7e!_rY%Q4W6}Pn0&aATB-@ zv(@2l6)LVfP!`6dX2?x!1vzFUCMF@Ts0#Jv`S7JCJz?)6Nn12&lC(vWwnx&0r*J1P zpRz14v;996S47gvJf(Q-u7Nqra#8!WkhGV#FFZZHEvG8^>f}KF03F?V!Q0m#-dg_m zgol@pT)(LG*TXPzhpZta1U_Eg(E761;QudlDgevn?`%e!(ty|nOT{CspU1P4#NTduA2^z@PQQw;_KyuQK5 zHhz(~6iHh&YA3jQ`%MzGz{we|9zO8(*Foc@g+I&DljZCSoz7qCSd1|Z6?IK$YHLIB z{LRn@20-KP1#dq+9GqOG?1F=X;pXN7Pal66g7omx2Ed2+fO~ksSF4pfhXeez@bvIT zNQeRcK3>oT28esDj}QF){NSe1AS74^cP}lRHClLTG|&YF!^^`Bo_+yva?!xU-5Vb6 zUhvUq;O^!l^H5&?f$;J1lC=5zX`v4ZgS)2>{Iwc*d0ODF<>-SJ!Mr%a*9$JnVlyXa zxOn3V zDr#$yo12TS?hd&|xT2yAsflrLceB5S>q>FOB(40XaOT~?*GkgLMIFkdxdLl5i(S53 z%%-mHw)9*Szr@XiTV+9)J^z$S<;61EAL#uNOE}i^&4MYb+b_N=P)S;ait7mqPk4*Wo5~wg zVNv)j_7vFKxfWcKq=)9sllxtrTwG=E7eQjCCe1Bb*F@4Pz7@C5;`PsT5Y)RT$?N{;bfOXlbB>lyM>oC|T*_KqOP@#fy zb{a_&?9r2q%~{!Us7X$4t~|pdB4_OF(?dm&bJ$!T6a+mlt=0#s_% z*4v7{5b&tatMGI79D8Sm+c~ClMmWR4!O`ZJ(rd?*Rqj^^y9>{*5suDISD0?gRxw+% zOJVlxscF01S_0M?o<3)5PPlmaW6rEuaB_8lv%ALX-}FUx*8Xv^o~`+{;L7S4>(Q0x z=e2;x+A8T)7CTPaWB5GQmZ$Go7C~D6EBPySZ0oPhVnw>=PJ@~9)*5#Gn*iH8 z4=b|g&&BvCi#Hu7%Zuidb$r5}#Si6O@#A8eS-++p`#aTt#b3*U(y3{ce6)?l2c+!P(Vyvf^G6wkxS^h}6;1S++SPWSONwD0U_V zj!olmcJqKaD+4apf|RSRU3?JX=LfCZ#ns;z!@hV@t6 zpya`1ny3ADmJViGQWX4rz2)5y9*L;P za7!LLI>I#|0x2m;(73xHBq|O`(Sb;e;Z{@+AprltV5FvJAT=cwz8>yVb~nC8 zj<$7jahGFyGt!gc>*F;^8dKvmQ9drNFs5h1$HQ$3Q7fdwQPzseO6Y840`tq$R}W)q zIuhf;F~`vzp-~Bl3J#D9DB_b-5UlqXa&q(VL~=?pbiUqjaQB1JXoSC~M(V%D&w!+) zBxqcn5fBoA9%V}vm-1|IG_ zdiY}fw1xO<$J`oR^&K6Th=8DQ+36H%2$1cZOh1ndwzV&9dX5W%LZgvxN`b$(FG4~M zLh{beUI+~hhSuMJw2Vw7M1_i6Qp(~hC27vJ@d9h^V^+R7jLJxGrMQpQ7pZBc$qnAh zzHIuXX!D9+bVK`m67+}jc{J1O1I2H~h2e2a747i9;+|&K!}iLEfn}SQr=*2`@m>53 z43c=$-)}2XGryQeN*-9}7jdN|?ckt6>--+%<>w+iM2CRzIHZ|O(E9o#F)JToG08}X z&p>KM1`FC-XKkPz-~X(*0P&_%=}uc!otjdk$Rhaxk#0HI!9 z@bM2uPJSK&4B<#mO+}dA(;|?ZT@VrxgZ#WKXee-}SvDTo5X6Ofga2_F#@&Ir``Af*$zm z{E?MchyYzI+UGAuOhf=8qGBbkK7M-SnNtwp=Vj?|xVR!I)r2%-yoD^R2rIDnBq~q6 zhl;&N5EKWlyE<{%(41bMp}L`dKwGh=}N8A72clV z$V|^fY*+}~eWPKFi$hFIIMU2{h}QW@U0^xs0|JqhmWG7*Xn6Vs%m0eLeuVhCL9f*y zKFWZ=giOT7#v?jB5*gW9@X@$pwub@6#8MRHjfhNPrq6craBE-o5b#bpTc*P)~+3$anrD6FnU zVthOjGK-NIro*f`8kjQk5E~T&Z~q_!8A6brnTgiM8W>`7(NI~A*r+f>r{^IfAq;wb z0KBzYB&C~?k`xbpV4$3`Xb9AayD$UWpCdfH0+4CWKtf_Xl2S5YN{L5OS~@}keW5c% zp|CI)`MIS?j4~oB!hqQ19Aujk5fmJX*tjI57!#likClryqCzcgIyBOV%nYNnho~qc znp&Hn^KwF{F$*nK<%kSRgfSrkVF_lKjS-WbtFMHl`OX4ev}hQut!;>miWUN1G(0Q? z+}qoWh=@qU#KsBY;uBy@PLUh9_)dR$u}4T~nE1nIV^b5-($g_8IEbE}9`yD1%bxCz z&Q5f7b;%y@bW;Wn9y}!G^^FZkO5%~87SiW@J6EW)=T9;U3y+Yn3?ZQs|COs&A-|wN z!sAh>t5&T-OIxdiNBiG4qGozXy4g9o($LuHVGq$domWHBjeRWVIHXthq97>|#`pv@ zRhW>LR|)@cGjd~NkepQwb4mu{!qU*+R0v~o63SW{5geWfe=m}T6CB<3s5FJc5UfXK zdpFV|;t=BJiQ4W?gr$^1>*)r+L`p>`^l|w}2?~IR z#@(_%-N6yA9)4&n&qAQTw?zYXbcBmvG~(AhhU!!AqWZ++h$+dJ`sC#300-YVB5S)7jBX3fEDM-8G3naGItMNNGHB2%i66A^**m_$Sd zdZD!~P}1Ik>B8yRz8HLo8IAmv}p{=nTv0(;u^|d3( zl#AG`3M58Fpg21L9v&JvySt;RtqED#88Bz1qrR;M;R(5j()ppXC=0=Xev&sFO7`(L zps6|!ZcdKyj7&v%d@>@UA`ug+Lve00>dJEv8y+Z{{hZmekyucN#JDij)|MeND-A`p z?T85vlS_#bvkIYcnFBX3E%NHy(NJB6$Z!M7DsmAR5{~%PDl`1r`N2KSXqP!esC51>!2th(@2%_@p zQCwROe-C$Lr^X@Ol!Tn}T4>z0NQq5BZ$lnD{DYBNREbbs7%H+7q1EfqJa-||Vj|(@ z9fQK8NZjfjj7d>0=!tk^Izj_Mz}i6!L7H=LS#lE z62pQK5EzN3wgx08CLkrZ9GPipNKQ|KhnoY;#pTd>xFR;A5Sb}S$ggUIIXw|+`L!rE zB_dF7Kw(J*62cRp(|W?)(;w|!bx1T7BR4q-!6D&D&MrVqSRhJ@a^T|WgNUR|#2U1? z>6TgW_6|mCa}8n>Q&CiqiK4P{M1~|FI!uSzPTr^}&7Rcuze1As#Ju_QQCw0g1VF8L z{=9i|YSh*6FRn^$NXAjn{T_-(J_D-s6 zY7iI{g#Lj6x%h)bnwD-tZ(pCxjqt4T@`_5VUb99n^Pv9;35k>1a&&YIO3TVnRb4Hl zo1Bs=mxv@8jdI->!(~T-nmC7X!XqNEc<~ZcR8~niwtK|C4PZI3qf%8}BirqmN7qKu z9%$@ugl}jhBC=aiQB;Y>+Dc?+rXn%F0$veWF#3idBDMgrL5WB-7}49=in{7@#KcA; ztEe1}<~X^jYL2rHinG&1qpj-dhA}!7nW;vU)YKs^w+gAr@$e6eLv?jE3gZoyp)D78 zn9Ny7Oo$Z?)XBvSnN>|lN;0D$ApyO$`7owsATHL7l8j_zWu_x4Sc}5OP9&sT4p4D& z^*~f|Hj?8b;O63t*^bW0Fh-)fFdb#pbqJ2FM3kcsGLkZok(P|Snihm57s8aB4Bz-l zbhbAlD=i7tCF#h_$wqqZTx29iqM|qlLqoL)jw(gOY%i1-*Pykb7OByp$g1l`a$F*u z=gh$zCpUydCLzz105>-`+VO-N0M#I3Vt z%ZNcj)jXu<<)F5!50&Mm2oKVt)*Oto^gzTKJTbpD2d3J&NJ=pwFEJR6!$XLVNk@0b zT$F^yAgg&UGIGqQ?dU~ic?tYOW6;x51!H8ObOb!SeUOu1fRLa-nb(`tq#fL0s921O zd*4Oj=0or`gj(juog6XSGZeWAnTRvjpr^eVnJI|~i%CLxM=#QoV$oQe3+FkraLX(g zgaw*WmX(IG${Ym6l_J?whs2mPWT&T~pr#SA<`QJ28xfpZhK{yog47C;`uDlDLegXvd z+y8nROAxI06N2^&2trPNK6F|i%k6Q&aR`oxfs?}=1o(NQD9?oYx@ytj*&a!Z#^NR< z#D=4xvj+{;rHGD>Ls4-B3JbE3R#b^VUk|u@`y#!#T-te-DHX+awa70jKxS?uDw1Ph z2-KpyAX$doWhx-Wgw&KIxH?8loVGY&6tI3vkCD=6Y>kpNKH4(jg3vs4M@)_LSAkLigPnyh)hCaQWEm= zvSsH~gdqaC`8midtA!~p40(mch>gxbabXtXQp_+$1xs6D-hZ_uJ#U`OT~N3SNzb1z zg_4qzin_Xbd6FF4+uMZ+|(?DzjWC$ z(F7YB8c|eSA{sU|?*01@2mvWW=zMQ(X_4dGctk0Ob{Hn(L$_PEZk2fCZEux~Mr>)|@GLq)dm>Yt_L*VA=Ef-}31_#Pfo;+65Cm;x} zUOH&qJ>luChqs3o-Wnh1LLy}7)W_Eset{u~Gz7zB%0x<145DKq5f%}Nppanr>h%bZ zj)q?ACBw?GF_G}mhsm+AdLN4x?Bwc>u&_`#SSSN0KGpn zzJV}=MIlt@0Yg|gJbnEv^F(eQ@Y4sw-PLlTMV>l{ulMw9d1C4heJRKbo5E3G{ z*aUcaz|T*I=$JTY+z2c<90q8XkdQy%yd9 zfikQbpwS>KItDslPiVY!FoZ@RG$086{(&$=Mk72X3R*9B1o*haGcZhcVT6YS!IWu6 zdP)qmK@rde2FV;*Adk-Va7U28C-i0(5>_xOoOZ>*@k+KmfucqY>oK zV`#OAiH(J)w?D#Jr#NL6phs9l6vCq-q1E~zA}R*4ksT5Uw-*f2 zF>>9m#??jE4EP3x!w{r{ySpC(yglHn^@g9X4?==;@bdIPP>>#e{yc^_815P`goK4b zs}Dzz))&E{2Kf5<3Ru6rJs5T*Vk5(#(QrQ530iK+^VM3kR42=t3mI{7=GCcgaO3r@ z9v2tcaRkao&ph&iZ*Vw50(IgC^W0yjgMUzn%wO{=Siis^I5=w%sMkvyjEagxfIbv@ zt&fa+=mRbeUk8PR2|+8v-;@vB?ZeSe9&W{x0;kIYCwTb>z{|@ME*h;I%d9tqBP>t{ zS2r(2#m2}n&xX(tczF3DCOR6vS}(Z!1|c#m1irdJ;g!IUFoYUH;mKon0}Y6Zihv8J zvN$yDA1F!`ZQC37ngvzneS}zay`}xQ*#F3Fv2r&e~)7uB35s?TC4wn32J9YQa zA~Y-%-o60{qM2_nq9Q}(>SM|+$^srK?5ouwC^%Ts9UK@0KV1+aA|enR90U(9FNrUw ztb)TMk&<1EERzvBUtjp^LnK|np^*p+3xl7JR*n}92-Hj7`0MoW(dy-PtdP(Uc>4xQ z=i3+`4Sj%}ebU#8nMhjs&dfB6ptsCgS*RlOHI7b}m8r67(`uFIMEFyL z#e_SCVOiyA@#~`b#A?!MD=sbWNz@^FZ<$xVO%|_N7Ns$+N_=R4HWx#2sCptklkuI5 zyQO!mxVO$5+cYS{U6%AXAu>J!dP9H^Hsi&6wBXgT}=1HJO9x=h3!QA zIn2$@E%Tdx((dGHSUEyfIR@2o{&+xanykXMXl|A?D`6{Xz3BepidKu@wqDt43uidY zc9MA6np;*dPn5ZGM5~goPOdVa>%}wGJq-x*cEPNPupO)xI8FL7Q3guhUCf({Wueq9 zr7UdANy=}czAEXI^iQldS5|2Askm3_x~y=Xe77uQwEk~N(eiG|KjlbZi>whpto&2#TjDn1=VU%v z%2e!$AJ*;pqF9XaaHkGq2fA+Lf&8MaJN1v82$kq>d}4SV8j`ot@))u zH@0S11BUmv`teN$M9XtOPBe@eW#ZWV=`_t#Lpg-wAf3RJu?+5t_73z_A9JU3~NS`7E<=|Msab8 zoZqbsb<*5+L6KdO)@a}pZ-QSz3;YV2RD9D>*bHrY2|OmsekPLUTwi#+5rMv1nHQQ` z={S7j0*wdn6Zf{v=}yjnS>|#qb6^+e=PdK17w7S~$5!T2C+2r-!?y+JOB0Kl;O;eb z|0cnqBv*IN{rFhEC+3R89k&U&xyj-kOW2nSXAXiAG7%J?A@8>TUEM9)!{jiEi9=qL z_{)5pi>qAEshG`naE7Z37X?k6$}}-Q&VQ0$6Nh@pKRfZU4<|Uf`XN2l1b-iIxVTuZ z>$JAx;--4%#Sq7%}Qk(~!qtN|L{ z&&}y1cXv2X+G8HL$@CZ(LAh!s)*?*QA(?xhs2j@s9?hMdo#5`y12&vxk(S&K?qZ3< z>^aVp(`^#>iLm5A7y6}KCu%ikD#wz#Ow9G#%zdi3F~NG_>fr@beg(=33sGNR4V@?V zk6X(GSGY{nJLRdg7iAuMa;=6UXRKs^QV;EgZI`53sg=c6T+LinStSdxxIloynG2`5 zTHDs$l;Lhy7wB7-BXq+f2w8KVif=mBJ&2&WyX2IknMm5v$rXX2@u(~-LStz*!h`)L z1gqsg2|73^9%W_KC@U?4vxBRwqSX0l;G*%C6_8w6d+Tfugcw4gadCu)uK{{L9|Utn zq^}?1v&xX3oh2)v!^1-0t(jRfWn;MY)?1NM)h#D2aZ7_D+}R-fbiODqEk<=!Exa@y z@DGWC&eIEyvt~nU2uE*o9vbswp!L@y$j@U&VNC+L_10TpFy_LLlq2s7xv(L6`S_uy zwFM40-wId%DD-w!%DuCZ5uvhj+DGe!%(4b#wJnCXrw80zoe>;jL_=ac5`Axl-Vlnw zK!3P8JHXA^8CnlFxI4K(=jD#j&`@X`F5Q8cgp&(gg9{Ml7mDosB3Wdq^YfV`N*?c7 z(bbB^mL?RI7Qo%r1@3Nc(E4iOtnrtF8n~*M`8|8qETrWZqBJoKQ3{qYX{-x z;f0`(aLjVx>g*6%Veahg0snwN_-Q?)4Bh>sQQ6##AOH9kY+h7@idBc;r)6hn4#J`m zQB_lq%F0TFh6KUQ$xRMV@ijys&s>I3ogSWkLD1{ghcx``8||J_cl^ zMdC*{c_TZ?2#v-K!J%y5p78L|AtYE2U0^W${dCX=8Q`UHgOi&EL2SRSuJH5;gdsRk zR?pjpZI`6EFe)oMNA|#RWi#)U=60omfs7z{4J|59lcPH zUWVe7P~3c*22sKBXzOT1Q$-OX6LU~go{!<)X3U+}j?AnAlr*>DC_9KgAZSy{K&KL)nVs zC@aiCQgIDR3aZgxVn%I6mh5j$%d0?lelU{F)fgD8gT|19q0%I@HCAG7Y5J6*XS;B6 zgripkf+Fe>=I()6j?M^*h(S(yJ-j9kaN&U|%a5){RYL;`8~RY26@#3VTVD--FLy+xWTUcU9kRSa5$x@R_U3%_wJt_# zW+Pgw8c<$TfRffO1VtypJ){tA)je3WdKrpax{;TY37s29L8j-g2xq?#7-ORmA036j zz;I+HN7`suD!%EkkhBjn@=6gA5P+=Gau_rd+dU^b{DFESRxa&@F|`Vdo9dBd%0YcY z4KmV;(bwIIvXUIM_RU3mYa8ln>d;hMj>z~ll-4$(++;>aR|jIF*tU2sz|^qqk~D{+ zxL20jj#}H=5TB4JdxzUQIxsvuEPIc+Map)Y&h<{xVQcQgl1JY~`i{r!618E~y<*O% zfzJsuLivC3-SYpUTPt^pTPr^;{#wGE+0O|#R{n{(owR)AxEx6foH1*TGmM#8FvZ6s zr>GW%g*mdPmqWmskVK^AH6hT&6^Y61n3q?8ZJU>)cc2FuZJSXRU_fDR0-id#2<`1{ z=vcl11?e@2(&*6BU5&2#R;*go2}4RT=I%U*rq)hmoSa1)3k(cIT46QP3aTW`0BUCw!^J*6Ubu5pHA)9|p(NRi+Yir2 zXmT+I7i~aDW+nC>UIBevI<`LXHuADk(XssioLs|@AC-naJ*ykgd+d;g4Fabq8(SUAs8XSLGbm}BPKlPs^->>oG@_6>wMdICLbyjX)-IWc1$|v;s;)qJa|a5Vda$Iw7b%JH({5o>z%Xx{H4DiV9dPyZ znb^Z@i7$sIqvA5q)jc0|wJk`EO2*<9%h20YiRkze3=g(o)x2H|4t1cnYXJ3?<#4hk zpUH;ggjut0MQ~C!f)lgkU724JZOhjOOPfDYpSwnh}U^`XD54-4xHQCnGn?)Dy3HP*u~I0y6RH6yjC3k$pRP@io;c47e5 zZ|b=+lJ-kPcuW%#0wYmcU5~2TCPV}WPwow$HERxrwhh7G5QFr*7OYyEI3?Cd!hD$~eZc~OQD4JC;(ly~CbLe$Nli@wqb#7FqsC241$Ksnr@ytE7@C8a1S ztw5nE7M>jLmb#%wYu6xpds@*xzXRdML4* zP+=#s5)+`0Nk@879d<7)LBWEpa#;jV9n2|cL3?LE+MD~47!!@&rfSSx)P>HlZFXTYU=>&D4B)Ff_0Nh2{d(4$ebiaS1v*79b~)>nrRNuk3AQ{3rKt+pi-k zzPVrua=b z8YVj%*7hdqhY}BKcg&xQWi&M}Eb&q5no=f~MSD(;bKo3Rg}N}y@U+rV;rzT3o^`xb z+^Asrd!n?mOq5dvS%syD57kW>VNvS;lsZ0HZ!elpw{y{*ZTK|1BuQJ7HBaKo&d$N& z#f#C_-Y%zceG`VeIm{fl=|QyI{xSyce*@3`;?F{gJ@>qZj2(|pYSWynzW*n`N7n8q zQy}Y(I2q#iT5#dzlDT1J@+AwyzvWcd*NdoeC_90 z@%a0A^y5EB2Y|$U=*`->lA(Zg?x=5L#}uM}5B(ibb3b+7R2!o^d>zFb&0!s`=G zaMkMNI9(r&<+QBnc5W0n!^u+*y@%(t&YKDqw&0>^u1T51#EE{k_P%H&on=@XUAu)# z1uyPeAh;Lz;_fZ(PH}fqyf_W+F2!AnyIY{RTcEgma`JxXf3C@%nLS&cwbs22IOTE2 zj`RW6Q?jejZX@}Oyk^Cnk9t0+5gQLmKbM@23^fmm1;{phAs0Rp2(T{&BHB002Mh{&-sllIhiSI$OX126p6Za182PcHAJ^h;y{pZH**^rDZ2p>@y6sZe#&gzH!X7Xa|g4*NLxgM_p0K5hAYotz-B2n1<)zmFU2nt?+%T@2N8q7mBKsK z*j1-x!?~TuWOGDN^H(|y#z28qhvs)AB$>|l>!r0Oz0?9ND^Fs64QPaH!MVU6_H}D6ZfqY@B2g=(wQ?wFl5H?`{HeM&*7a8-GK zkfIeDCY=Y*;fizyxOxh)8`g!M0-zW57%Roy9Sq^AO(#orxbUpI>$Ibq^Us0I?aL{{ z>g%nKb{z^HXKPfA%}r>BguPsTZ8{C`AUb$Tf6Ln^$TDyh^Y&w`tB8q*2Wz1J+@!0% zflRpWj_PJF$-QcGuL@JF9pQ#TZVa&YWZMsuvBcqe}?Q6F^jrtfm zEeQR!+6p-3TlbGP@JujP zW5`a_ckKN5e0Pcq)T%b1jTT#`GR*-c`1$(6E%5PsaKi*cDDBP|e|Lt4pE5qu%Ds zr@hH>_g0Q%D}bjb!NYX3|F^Q%UoOfSH^nB3U<76O?*9=*U}i59x&JBRL-T?02Z;r- zn4Tug(Yd?L#bD)l8XKGGMG0`I>%WvLfnF*_5MB+(e)iVPt`g^;x0{x zL)-a@j5i2|g%*x5ucNd_Jl#D}j<`-aIqO{Y$+f?8InKWb;19WTolRh7aIsDJ9qZ2JQ+eH@R(xZf3~ymzr+p1SiQ<4uFGI z5}>i$0DmwAjL}LVA_RJAh%!C7ZWx}r948kZ6|kb&r`e~L69s(Vo&0xpVN;>Y`7nK( zX%Us_K*7JVhOCIV$tn)O&CNZ+A<`5;E8-8X!r=>Ku2td7FZwHpS#_!hNp)x|hsUCK z$7yXP3WLHSPvs9+y<9QbWYs8Sw`m8t^gjg07uqAIckoi<4W`uI)vlcI4GGR&r~CdA>@yBVSNM+( zUmF9K>+JWD>DQ44Kk+GuX(daVnV-5p0^m_>_#@})atAecg{(Xoeb-l7X?kq-a5|1Z zZ4u$}3tV2Qvvw+hHN#HQS2LEMt-Xd$=~GaJ zq5i5+LUJNA^x^0&pyO_e?d!KirK$BfrWe!p~d zJj1DR5=@Ibw(QdV@2!Zj$%6rJDJfYcB>F->Rfm>U$!ADJ>a%`7KHgXRK#}wX3?TlM z=fff8Vw+C3(G!9r*SfZ<3%57DH_*@uI@=Jnu(IrjlSd*1oii1_DYK3-F{x>4)BBm( z{rjhGs2|-CEWypq8=YOfVU(n;HQI}^+}6fWQ!XQ^* z^ikK1tDw%UgBQX_4vAbe4fW~$T+^Y!ebn;{JUv(>;VI3YN!rfOYHbhczief!z2b}7 z+G|wra#v_9;Cc|Nwz~<9gp=bw-t1@;e__czfPsstZpV{Lz5^+ zS9q)7XaP0TXkz&cz?V}lv0Q)#{WBqBk~NuYc2E&3iJRCHB4Vh&VEdn0F@C~>6MOW@ zOm-!1Hpho}u10m=d7hq-2D-!gqt%BcDSZ9H+-X z)Z=#sypymZ&t8`ZZfvnQ`aI9Wjg0MvN&jl}$3HBA?sX?0n_0W&*VI79?eQRmyxT0O zn4zW;kGj+tJz8JZ`wF*7VaRX$f+HhCe`ao#edXl>Oluau*I_bS>yEroz4?heX`5ED zm_}TKE`O1aw|9ITDYL~fQ3dh3ms^-5q}`%hJ)*b@kQ64)Q(!_BxffDCUA; z>!+HH&CItNfsu_hhX>7CCj83XHKk)q1%M-e4n-@j)Y$UjjIK z>Ue)OhizdYB1TwPm|TJ^bsD`iH^G zp!af#ai3+#gW@(}pAjjZ-&7bs5WB{AMx8}$4sUw_=24*L?gp zQ%>rE5YqakiHstFd6la8VT0zz=FuSBiT1;#*8i0ExGm?X0Rq(R<&5c2#pV6(`FU;u zyig=NEV9{rL?EMA>f80O@G$x8Z3HTNiv<`cQS&QE3;q%;;R*W0nAew={ZML(Un9Fu z3>@R*%zH}a6!Ao4_Gu;G^b@(MNTT8ij6_8X#MR_Dzc^KCK^cP;8wriO<+(%$bK*TU zK3z)zERD45hBfnE=nty!5#+#o9CZaB8Lwv%f>JI7l%^81lUfbYr$6&4^P$T|hI|HA z4&)vc2=pKDy{f%v9Hb_SL8ySQ}gW&cfGQOvzDfGT7=RJ`v_ zsOi7*+S1^@Hzfjfb^R`Qz!c5Hvdn<90h)udXcpgJn7_T9h_->pKw|d&kL{QlzkW9x zl0IMaX6ok_Yr2ExO)VeUK4f%WCB^9?Lr^0d(QJjIEch2+g7?J@9$?9z*^1y=`yXNUM>{?;E8)eKgpF>!G4h9=l_^Yf>Ycf8XxDd#f$f z=&domJeR#^tM?J2>?iw3(5Fj~y=|)0#1Uc`LlsK$9G}3lfa?^`E4??ASEzIWFzGe^ zI4>hGo@uR|*-&5Nf%7owR>L)6n)0y!UT%W|AwV4DMbBXEH~K;Kx;Gls}~<)6B#IIDM;y zCBJUaZU6gRJ|gS;c+Yn55~P=Q;VrGRZG+Z_)KqqI)IoMpO|TM3v_$j2 z%q+i#>oz`~m3CpyucF^aYVWqlYss|s6tqz#{Up~GYH4jPc^aaekXj|yQ;M?Jxsl6r zk(AZmlO6E)%+1+>@&ufOa36B-nG*M!>TEA=F2jb_$w@Y{WLn!LOB>M1^%EO-i0Jhj z((@|&T6pM_lJ6I4o7|de@8(vr3f1lPT_v;Nz#m8F)_%ofB-o7vzo2rrrK1c;tH;!l zVyjo02fpMCk{E%HtK8K)s~?K(t*LDn5HNUDY4`YbPwf@Ku4NGsn8N-qbmzaD5mDm$ zXXevrsHwXzf5yassGLj2|F?Q*<=j3xeFtj{uAsNI+(;+sRdSnYhTqz%gD_LqquSV= zD*#ST3mKSK`r3$@pWlT@OozL(YlcnkKy}OrsM_Cfe)fpjmO~0sWuAGl`>^X7JLByv zlr6F}{SKe$tH0m&=(+%WZ@BnYAEbrxULBq@jz8azXs4GK6hvf9ZyWjjUB+L7G^a`>dc{$Z8sUk$pjzbxg;Aoa`}N~7}6oGdl}F_+%yiIKlmk~S8R*JR%M zs343z;b|HhSsi^^*(qohVDjSKjc??;Ma7XL zH^FhOs-90>NOJzk4w?k-#R;+Az@_{;N{&ZI2l^#5SwfD>>Re$eM{pd~`yfW$3)QJm zQ65rpTY;HUEAiAU&OsIi9#RCxbY!(I_OKg+_k#dxjv3ymyZqgh>NhEo7*GU3PBi3; z2&XSU8in+q@33uLY9NumzVF{7ikDjF91b`s8tFyI3r(<#+)OBn^f!-^j+#g3KJ|RL zR6J2a(}(vwsRSy#UR_IP_ZJemjM$vr(`LyMrml1oOZBm*DEU9-A0kKT+WduM1+hY&;&l2G9R7 z6MfN?#^?+2m^osVs+l`dH&~1cKhQebCP+#e_c<44`+9_a7#xh1{VmMr)$bSF9+VsU zwFv*d;P*LExZeUzdn-0H@;CCxcL~d9qj~&P$=s}KSc0 zVYM~AmjaGFF$I#FAi%WJJRz&NoA|q!qi=H2N>bk(g-FV^OdRK!EQ${lh9mA?-JuL4 z-@lk;p@a|U4ZH^we6mA8WNxS1DSNQ{;;^s4eK1eM%}uDB%si{XOBj?0gU=SGu{~P} zHK|3Nl?0_#-)o;lyxXu5r6x#WGJGXIs=OzdLK31Qq5nlO6$x9-Ih8}0G=$} zgr~6;GwZ%shHwZ;WmfcjpJQd37tSF5J7ji)_>F&^hYkjDQf>mif?j!r=FAcWW~?Mt zM)98{shO@)%>%D-2Bg^sg3E7pHap+hGFsY{>qUxm9y>1%Va{0P#CN3t0Mb zZhl3S{DELKr@>;c7xj<^xbdv)zlw8U7_APknKA}%?NzeW5D`BnIb%{3L%f3LM!)kv!k-gUZ za8x^C2rGn@Lzj}I95YFg7H*>M;`uh`--j+m>}qYE3w3n6h>#W+%}?tf3A!3#wY3~+ zbGW89#?f7Hjoai{rQ<1Dm9yg+kWyn5tJ$sx=iNpaQU3f{h9}%~|FLzLwnvB&*UKpY zyX{*vXX766iIFE?xgFuo(kU$NU&^yu8Wu{;oqq8$Mom;0B(uVScywhTD=woT9B{6oZ2AMCwzZ*tuywNIRK9q3` z?aZgB&Ln^%lYTl2cf4ZrOS4KiNr{+A7OLq6AUmWN|BO@_xLiLkWbzX&Cv}S0n8r2g za=f^z8D0TfGALjC5M?1=XND0HxKj+U3`-)>S{F5flwH zsQfxn9HiNu?jg>)31~3?G%9}Zu1E<8Eo|4yZYSI2n&yTUX z$dEbs_VsJbUWG@FVO$Y8Iho~c{bz{1@>p|10DLLR?`K3RZ`Xld#_lu-D(2Do4CMR= zIqUtE-Okb7LVoReHatZ>KEvgOaAh7c!|g?dp1;jdb>189@yyW?fE&fES9d$WSZUNo zqIl(r;F0ev3x*eK{&- zO@(ap1u+FvEL3{jsHdw~R$adZ*$?vtvC2@k&hbptotIVWwI@*r+=-hBITaU&tfpmJ zgwt*OT-nfUbvw1K%xEXP{IyMFZxG*l2(Tz!G(GMZRx{Lzf4E5W?kYsDS-*yvl=~7s zmm}4d0!dXIo{+~gRLp$tDJmWR$;$kdT+;T!8l=iEV@Z#;z7L4{(?>wIa*a*kc{=pv z^z-3sKwXD5W@DE+k&R&Z_T9&{%{Izwg1{IPcFe4`XHk{x2eHH7tDz38tG{rF8L!8U z^i)rD0^cU@3)KZ&wlAs8M8z*)7R{c|NHtxBAAC3}v-XvOZ6 zZTNZg;UT|N1QHDm4LSBU$_OiqxP$>b%1@J!=_I3}hxoP;frBu4#3i>kpHog@T+&ku zC%C`gbb?L7$`wZJ>{o(mTUs5tsA|q0Gts2psrU@I9lw8lCz|jwrcV_kf z)YQNstrMsKLJz8nN~FlgNxA77>BcrZ+d{)^2?Q0FtTrijgnjDM_V~aDgUdb!-=Ymo3PM@~Di4QqqJ&>K-@s zm-C2=B_(3fX`XMNI2kDe1&xb}3Hq~iBZM`HV>o5rzdwYh$*GT2crvibu_Ln@sRzPI zO_kVWP5;OffL)CA9I@wQ3pg4Of4k@+qo~TdO7XiRq*NHfCu~k~V+MIm6V1%*$P+ec zu#~n}OIGs$4X#i1=hLemJfkgfhqjs;N=r+8S0pE0pRlw<69M6BEJP4Yt=-vtvF(T!eU*78d`pa&YWPjq&I5ZRRV9n)m&S0m=+>SX@Dx*p)vP zRlpl5$%_abt-;J~tX@Im#McPE4^Kycz{(I|y{`mjvt45GkjAA;h{`AK*R&fwyxlBz|y8cD)19L1$S;-;33m z2XCZdWOL#M8K9`#%fC$h5M`nW3^cg0Oi#UAygYNMu3zX)wY|ER{_;a5Jok6HD`#rY zrs_`V+~a5#*JfM!O6mcE@BAW5aBdvhAl(_ueMpJpvvFP5k$gDGnANmC>BQTM2(6WkJbGM_{FRWt*9&q zDPr1)ji$wb>s`}@Vx!|zy{0^_EtrN9`*;26=;%gmZ2{gJLX{XE#sVza08h$I?Gk3glJ?07~!%VsiPmP~$gb@hg!Um^qVDMI^ z5sP*~kvZ^<$mKQV=;>(l%m3(w$NNO9YH2m2YhGtdPU3-^r9^P$neXsev()4?edsHN zJ}j6ZiPzOsqHCXA8@dd`3^=T&b8u3k?1tg`hB32FX*c>(cp-eN;lxw@G{A{!{vB0% za1c&ZCvZrq_zi(4;1;2E){}$A($dn@Uu}$C>=eQE?1@Yw5glLsuk*WaOzAecLCvS3 zwUX9`x&Jf&j}P(u;{ND%eT7AWL$_KO{E^DW!68wAS#Xaav-}?k)ipc=^A@6(X8a?{ z2}QU`yHs9?Ur?TQH}O`^$E!mIZr3mIM6JrVZ`VXlEo2xg8&g6?#gOrr2xgJld-|Xf zU5hdIs^oWRjcWYgu9Xch-ISRU&&L9e$zN0g4-O7;WHw+CP_XG|q_7ypOiih99-keF z>v->RQ}Kost*>vVe)|w`%mnwHS3HyydeFX%|Jjd{{;iWrWy^*>cl5T1hCagWJ?TL9 zNx!FC>X?@<=&n^R>dcV>ITBOcLN5tU9G>LPwCsF)%m(qPcXPE=72P#eT)0U@D{qa; zj>c%K4vPL55gxvm?FeUj*HarT+tn!jWhg{hm}BAMQG;(2*NCj9i2Of)^v=>8ZEdBM>`(R#YJwU2*Q?p6 zm-=}{;u1VC%O6V2;qG~om3Oi2nMvpSaw~r)mE+#*e?NvhvGE89M!(7{C@}d4yh;_q zOI-~VS8AA5Q-qGyB%ySn1@7V~eK>6%c1-eIY(9@4>PdeQX%|~Og&3V>hEWu0V*%?#d^#DH!J=gE9 zYMNIYUW1867{hgBUjO4!F@iRRIEI$7zrR1;cd(cAHJx{AgqsFV@0Ka$n$+z}r!dm? z`Bp3e#?vu~opjoOjI*lX$-Hl}MU~e7Xh|Z^$1ZvJ6oFm;v~i4+Kac@GQ31W;T>7Hi z&qT*#x)BJM!|RuttOFVEUnm>FSGlUIutod@(I0ue*>K;`P`U+SH4s|ghx zm%mhNO0!U79DjC3A>Z!s0+%i>Pe1IVOots(5`9s$%ZwRf)oDeZWM!?#Z!Ie$V%DmZ za#Q4bdPah4J`p^23nr&49wXE2KcX`aR^&6@CX*mGVIUTrxG1ag1?ffHY{bW8$J4-_k2n~w`Q(0=k1H-80h2j^Cdf=JGO?)D`c9YULJcF} zw7>1_h=d_haE&OgpjYBh;&zVP=EFS5$NOW;c5&wC&`PxEsG0e-VEEAsEXH*b;rVz4 z2Z@(v?5UySFgv)J(-3c7UC+Vvf@fWJiF!_-7EE5RD6d{$+P9S z(re8jDzoLwLRvg$c8PQ&&38GtmV(Ua>ZRuu#7c(;$ zVZXO!s@o~ADg4nYa=a@(i1KaG1`fVvY2H@m@;@q zGoDZ6N7m-1HgOV!wZ%)WYjpf@(qL`A@y zTQzzhI?jE36L8-Zm*XebCpNr({)+f)^h&%S_KKh-_K1rvcGZjp`%@~`(-*NZ6h|o5 z`=5o21^6Pu5_n?>cbnnb2cAjV124`0vveJH-+OdCtpa7{PT9>jk8(}t8Yl}Mdgcng zt(I)t^%u+iaY5h~|1~GK(mT2=F{FjB!flfob(ook;^3WSsZO%J?cyaux|`%9H_6A2j*hLPBSy8IFWm)&g;@B+DAv~2a3p!OVeF9M%U*kX zduHe$uu=8e*7U!fn?B~)yRjcjX+(!FuhIuy!1?c03jDXFyV{Nr@s&qOB}>a_ozH5u?YRp;uk-TTxF7Smq?lNd>R zuSgxZw=0(Sc$a#)r3%CNCYw9lyC{mQ{51dEJqS+)^e#Ze`o`!t`jLQ8<`hx56aM*B zY`3REO!hL!+CpcI7ivs;@~e^U3lm6HgV&%5>dCxpxxI(Z=LGbYE%{={@`IOg^Xf$* zfqnZ?sfX9TCe*;2cCW`PLkK?0m8GGMp^TyB4s_VUP9!W;C_4IZwo5M4*+}6C#KmBz zxAM4Igcv%33L}XY6JC}S>04@U6HiG0*+|VGin;LHrZiih6O+C>qYq766um`&H<0bj z_nfcwJY>`KR#ObS%6m1M5>0-Q%uoDfkHc$ty>z?fhV8`r2-o7o8~xNPXGoF~O1>a= zt;xN4S&#L!_3E@$rwU~_doXwz5t3Vbxwz%w6L}}!fYbK$5G~`N-&>8$#%DSePceLQ z9uT)IMx>tqUlA6yq3apO@%)_IJ8HSW{pj5f;ufbak(vCUe|G?chg5yO;}pNe2xCTT zNAA)Gx>4xq8pw#*cwS~lPqL7|AN;tPTw1@Sr2Ey%sp(-6)emr56OvdV6#OrI%h6ip zDQfG=eByC7I_;Jm-iP4QMdb1(YcUnve;jT=S{XoB&Mx&UwW=y>*b!B&)>v}y~zSv__S0_ zg!bPDEpIk0?Y7ThvTpiMJ&HOXd%b~B>O*HSGv=yWUxW4yPhcZv5eMk1KJW#jeq?m0 z2sWKKu&Up)4%KPup6$sJ^G0>&sWTdSoejN^7xFKqj3R$x4pc2Ntg9F>|7<@viOK3x zAmaBpB3wWkuI$(7HZHZ6+&UB2BtlEkSUMo*UU{y0u*+0c5Kj8ssEj+A^pyW# zeXNBt-b|MC$DCXis`+A(X+o~mr#-f^0$ef8x$O4PX+COTd*`RNQ@j@j)=Ii7C|duM zRkhpe#28Jf+9-X@%Yp4we8%8YJP>&h1yc~1iEC_#^!&`~M7P3!I#=!S{0(ezTH~ST zFfQ)X!rS0!tuJ%+va$FD{MLz8xCzSCMSF2^na#4w9#W*MyYBSC(}B1*l7?nPMIb)2 ziiDEcF9E_&5wL|W(MYJ40mP_CiP?!Wx1Lo8V#JHNA}T`S`?B0AknUr8lDsJxXCZaw z*T`d6(^ON=2;&4X1q{bzN|z9qO+#ywn64jP&Fc269Kq6VvYhi=Q9Oz=>KT3*M=lV~ zZYR8&j*B+#HPFfRQnSnZh`YE?Ye2GN?YM#pOLW0S7yC`_;HGg|Y^mi0QslE0fK*2LAeUmM$2%$?iYwo5q;!DO~GXzh`8`_8|`I zVCTx~*)eTQ;nmdIKrm2ASkih8Lb6MZH<_%*e&CPo zcc9K%4Sa)s8)JHlbI{As7`Hp~C#K(SsR{f z9n02vJAVb|d5}g7SGrKK9WWkeXq2Fiwr+lQjGU|51yMGtwgVRQyU~bfrp<3b>b7&F zTCpZ`|FF5gWlP#l2|TpvoPa-GYh69iDL&3*IXLWQVE^_~6>~vMM}fGk-UkV(W+e*- zdW(&3?0A_iefUjj|E^MTl`rno(sOWQ-HZEJNCP#q78^;>qP^ndb+66p=w}+CRSvEx5w<77vuBR9`M!YtI!CbIxiE8JF>O{V zqVYFp48vG+>Q6_3uTj|8_V38e`8}QAmn2;~xBHOs0SP6LrPCn^xq%q*qWsB4#9pWk zVo;!5+@DWxccFe$c@39j6o2l~Gb*O?D;~+9 zx5hAb>UV5;)$ZuS7}?V52R@a?0>Q8CG6|L)Kbpp6IA%oY9(!KeAaPvCcg@J(XxS#_ zO2*uD1nleF#=U~TS=TyCVo@zdFtU(rbN+guR8GWwHa^ubf4%?H>M%!*7al8*IfxzY znbnpkM~G>k12X(23HmmLok+U*G7DKLg_*1rI@qnM<_(QJEANS|M*%6 z9{r@Qsgq)n+rLBQX*Hy#>#Xv=1QdQAfW+@}!?Gj6w$;9gKyVX}dReO}tznikaY}(( zpJ#a4?S%&)+dfexkvDLjtcz0n%xJh_j1$K75xG!cZUSYlA|6}voY1fT3`-?&!H(E> zB@wa+{EWtYM`y}fJ9iIt_lmQJgS*t{Q?nDV@*ZC{IQKGHvPBk{OCo)|Ve8gdxbDA- zU>(+V7l4ysY`@Ou=af((uJ{`A15DDK2%AtOHVRoO%srT1$ zN0ynUYknEB#WXfCwez?`-7Ju-js}X_>m317iIHKPVk6~OkW&HG>I?-M=~$HbD>b41@2 zr%Y?nbn3VG(oQdU61XvIOx`eck#B8y{}0(vsnhW@*zedU51H@mIvZ(vRZBu2glEb+9>4|_$~gr7~$(_)Vv^0BcGj_M#`9E@v>&NzK+a;<>HtQ$A)mu)Va zO0q=6z>^-`qCp+xIH}Laept%k5`8H}M}gOWupioE#>G&q|kXFfF-M#iX42#9L{B4cw;2!Y6V6*cnmO1F%; zIS{9HcO?K}#E>H4+<<>>@bv;bR+!HsOPuPl-c@~I@y{C^`7-b+oz%q ztK~B33MfXc|7_VFy=)-ALVw&xq2S8#6aV@wVrcg8G_`_QT=R;X_vP~Rtd^IGAbrGL z7CtBnlpmqWjv$bRb?E$i8VUcGH7YA6D@oi> z9wu-pOu=tyn|LB6`@tYVaA1n+7|Z4Ul;G`t*^=bF{tBghhEID+^+wOgk2- zF5lm@xyz4%7aI*=AG39{h~&@ZO$*?*?J`;5aGDKloG@0tv&r2^_VPs2TFvCGD;S7` zR0=Qc{+2czHEf5Ud*+pcSiH0P?zlcXvIvFZTE5`!wgF3c(}6jURbDf}8Tn_wwk&4M zCq4%_UWMA3QK<+i+uewp92JE(BU@j_VkxaZ3r`SGSA5MPX@JM}O&TJVr{1H%mG3DV zB$2NYyoJRM&je)m_2hMak`nI)FN)F?fi?K(`nykf=BD4Z%SwIv^>tYhayq+WQZlCutyh%UOl2(Yfr1EOQDxyk%|>S~|%{ zZ$GX?|4op|nvI2FsCjITI2pIy=LIc}cxKRQ35}t2PF=Z)$u5y}#>zic8|6KP075g9{L(yXXW$ zAGIC4D4VzRbf@rpK=*WN+7sjPO(7P_c$xP7uqU>|b%HIeQq?U>Eva>UnQ1UR#V0YiKV8le&$A?P z+$zEh(Gxozp8Dx02R1Y52)-(_fHer>XS?dL21yiziIKvG2RX>X-8+|YFfBP_>c2?q zaukNrUuGgJQZZT1qQT}magd&B_VPRZi_$?zS$0LFmKZvbA@ym|M|%lfEHC-a=KgK1 zR@76VjY#S5Hz8IE&^d#vU(uIzS4mJ#Od`b73q#$GA*U|QpRS=H?4D&X%PnuUc6i3bt&AM;o;3p4SDZE&{Vu6Y6s)$2&r6`!A ze;p#_#?U})h!J6o(FqS(}!?L zK7~J3$5&OG5$;J#ms%?z4b`N2wXxw9U@`o3+HjhOV0X}xIJdWP!lQHXOBxcG!jU4? zHR*SJj=C;opTCPKxf!wQUb2T_;p(U$XI-A!33TSON6%D3WqO*U18HIu&**-&&C}9hK@^%oUd`vt+Y@+Qr+lxNcOPW`)8RF9RMjtV6`DI*eKk$60CUJ~_N*RYKc>ItWqb*) z>}YC&u zd=X7^;JMk^St_sVD_)u$Hhf}y+WUj%rsl{t_zQ!aRt;Xvq1A1d+SQ%C{$=X2sDzDv zw|_b+?xY7Nq1!*{D=H6oA;8Gcn=?b-EgJduB-o72W+L{d`9YS69kn|oe!|E zG5(@IpQkllT>MK7;Ks|Jbz!I~llY;6$vA|}{pFc&OY z*lQlv_^+!wE#O$?n4_7ECaNo&)C*WM<^)=zT3C`0`QCz?3;u0LFiKWM(NhU#>w zLQdq42^8PObUrGpLUS}?OGKaZq9Wp}cTz;r^a2rEQC)&Dg>STx(nHN_2G6@Gdh%h- zXsf^`Z2dqL`-M39EbW&1Y@OFsc?*5~II^%usvzry3;Z zt?AX;lYMEO+cOgT*JToPu@<6dn1@*0>!K;y!^SUP)lrLI~fn?=<8|CwKKmXG? z!CU&a^XFXx7GEoDPw1;wrN57W=W8I^9W-npK<6hES?CSSmg+nJxE4;MK)S(4S?fy&}BnlP7H1E9>Ok$akdHkaxZUhw63jA|t))g285V321r=SqU z>9he)S3Lbs01oe4YYVZlw+CGV?DuzR<^R;SA|F)Rw+ZP%Y9vj4c);+-aXHW~#}CE8 ziSI_+g{J0BS7)?p&bgt=Q@+MaF5Q@k2~y-lG&!*i_0WhMwl?6dW3miAwWOLHMZfAi zOvzMwn^T47W<(UTvk%p`EbR0c-_OCVq2cn%=tD=Un_1pF=K2EH0$_Xzm_zQq+&li03j!Puri)*oh z{mA&*-R#W*$R%d&*8a5fjx|tX+OAu2*bdltP`cT>#YMRlDF&qpH-4o3cb=;PriXP) zmK}1No*9ZP-5o10-L*R$8mC6}-V^N3bW_%c!agK&69T5Pg$M%z0^py9nv0~ViOHLf zA7iyuzy#jW1csXWXktL}o4~-4V`7=7$d+GTi+-)gyTG23MPPI*jT4pQI%Az!Y!U35 zV?k^oypPo*?O6>oJSRQA*ojqUe+Z-Tv~r*z^`KyPbf1BoxA1-ggZs2x{I%Mb1=xzu z>UC2H*`~68c{Q^4slxQYtnMR+ZK{*Kq)sxNvGiTw8rrl~5Jpo_ez{)IxFM^MeLk%^ z^tR_LPl1?@=X!0IeXfME4g%yUf%uH{n*#ye54d;GD>0Wv{qBJjZBeQD-c+@_p7y1? zSxU8Au#@`rNzLU-E|fIf=!yL7W$)bOB2f(9fH^rq{_LWP=Z4yYeaqHYm=|6PEO)i; zN(bQpSi8qebCi>lV~ejV?&MVS<2)oJWG5~;%j!jIZ7ybW;kEp{%=Tfm z)&Zu!N>kbYY-Y-cdEAldXl*r-G(09o8*uk8snfq1X{GHeMZhn$f1s*15eARhg_rN9 z*%1qt4i1j1aW*CMgNn&PQR5+S;S=B>PzgVqL`vUJDC4`GT9-X3NTnls-rUJ-*RFdo zg)cB-DWrzV_Wb%{3xRG@!cS(OST_*u;_4_64*fw{VA{G+_HbLnLn@#JYvaF1`_^bjhO z5$RVAvu`R2ndJ@sN_hcJ2&ZOGziFO3>xVe9nzq5j}Yk`F}Abcd(QiL z&;QT%bbPP-y043qBj}h5$5ms70Z&OT50`~}UVLdGR`fmW4JABtzddL~u62cFfuPEi z>a;1l5 zT5S(RttToTCN(YL3djXrkuZq74d>QOT+0hQtXEW+SY8%!EKqB&P!iPOyvPR;I83jmM^v1hbcWz> zm}?JDmFV=j@|w5$9CWv%R>b_9ZH7{Ynxwvz^W#SG>u*k8ATQ+k+h?)`RU zak8qASngRaW&FJQ@@XR#2u;EPxw85;{yZqXN#J_ok8O<5S(4+M5$ChGIUF0~%JvSf zUm~&$#p2V7E)wp^o?$V!N0|y^*m%Z++Qe1QaitWCbI+d0gcNAwDukFp%v|`|`T*g) zTwN&}`x!4=(7Rv3wPF~2@$b)Eojq)MY(|zHN4@zqA&XLtVkX!6w)=FWgV8WxRsW>7 z3PVUi6-e4OxY zqx!h=K7Q*+xTo`*3^}5j;K%Cw_yfdZwEB@t?*@NB%{~#!;R_JvF)2q76|*lQrhL&t z^9&49pG}2ZpVWcWG0U#`H|xI1fvc^9Ew_4UD`(0qoPK1sR2{H%+&<{yU(uZEnl{q9 z>BTC?=ZWwNm}P}69Q^sZY?^=`QxnGQK4!ayiNTBKNd*0%cYCY4KSrFE=;aW12d*_v z^O9TuQhVBAcHgClSqmsTmC`x2dC&!lXCNRB7NoSG!hz8v6NjMvG(vHO<2tGL!md% zx!<+<#dtAj1?0#$cMppp09Feh1cx)l2B!5v3H~Ujr`Gnge zC^f%+9(}0a&KF-E2z_*!K3qdnv$Z$G5xQ5=SQdWv!{YAk(nN0W$$Av0-nD)~V<3tH zh(({J9MNBrpLK?WUHz$o@P=mj*Lm;B(9FbhFV}!;KF;Daq($B}SR(uNRD4FldZMUw z?4!11T~~nDc5HJG=Gy}sNldaO^yeP}#<--vWyssTq)%fc5MocnOgRLuL6`=gT@WLM&Fsa{SWrYns2_}`DJ z*wy`e$-h4%qA1xjC<#UzTvz$n<$c&t`}1+@>*^{t=V9K_UwBH-et$E1DSD}3XN;FY zrHJp^ahmFZnG1;rLS0y6_$$ZYdZVlosP{Ki04-%6lTnpfX#DR0<#)*WzRm@*4qrd6qIeS z%3wx1=lHIAq4jtwJ_$8FF;^yWldJtf1B8Iy0T=S zZtDz>{fEUI(1-o1ReD^G8N(QmOaHG~2Jr51kICh!cm|>d?-uI_E3eEvMyREfR$Gr_ z_(9i}*?f8iWC2_0#^L){dZS?~F)CXHQ5^mFR6qfqKGzQpJhG&1J9&XGej3t>#ALe& zq~Y$4cY`T!cXy|&NDU2=CMXr}(^JO2(Uv{mJ7`>`Sy!X=+pif=Un~*lr7?&$Wa>EX zj#Dvj=vA@O3__m_&~lQBMHV?^@;H*#7+VDE5l9kFM{z;~zC;gj1nNqwlv*$+1|bt< z)$>iG&f+Z4FT-W9h9r>VF8#9P^AqGi#5qn%yLh_Wjfw*p4#%RUw2`>GC@j-Fz*IqJ zIJ1g+eNY?+6w4|y?=Jz5JMsMv>Ss@;;ShmvO)H&ITvq?~uo(1Oi00C@?Ibd-FPU4j z1i!l)#;(W*8G(cJ2^5#n+p>ssNy}V8v?<^3wYgpEvLOXAQyI-fwu~`aMf-SSO|HL( zb>qsZ$rLT~ZT@jyl}rp+5aue??|LhfSL8hG#dEql;y^KYS)1AWLTT^kmo&%+@cm?E_& zivZp3Gty#r;LvB78VDU6aw8RUhC4$ZJAc}>Vh2N#&zUT4s*wz}#@<$h!j8mKJc^X>?4*rE^fF6AI&Uh=cILqqV#>=kk!>|%`rpM*iAhKX< zp9wi$ZSmgY#WMMper8A7!)>$kGJW*0CibsVI_PmK8NY9yWg3R&Z}*Xu|BH~3yw8!X zath4yCvWk_!rdbA0K^#9Ew1S=bn1#P8gMxf=hI|I7BQnE}q=NyZO2AXOTC8)INd8|v* zx$pfPM*}`fa(;VK`yPW^HSPAxg>S|rE$3^I8ih7D+TV4FAH=f&ALR7@YO^)l0hPvh zhhIcyOKY-bRgH*k^WNEYNCFqYO1P^*5p!M!(kY7!A5!R=r1xoXu*aYYQ^d}r{Cj`m z(vg!)#nP@nGziVy-+rk0ww#W~Y+LLs=F9yX?Wt1cvvh8Oc*Q+BPhwixxsnxb|Z1w4Fs{kcpKEg(XK@c38ykMK_pFV?j{^F8)K zU)58EzskFrz)nh%3hes0+&|3F%Q38byo~P7W@2{j&&1#^NwwPTYFw!f4X=_u5KO*7%(l4`Ky49U^SAXWGPm$(!pWo(=V2^q(qB_!ALihCk zco6s|OKgN+2iO=hVP`Im`22>lW(pelqx`-lTB}>S$kO)4BvDtkr!e?NGh`IYV?gOH zc{lQIn+EY&d7En}rFYo3rH(^nP4N!=S@O=S(QO^TWsoS0J1<|y3u%&MWxBRsWO&6g z>6eB%@_l1d4$>ltbrDhX%XEOnOAV(%>d0|91aVsf0o~FQVu?Sid%C32B4i^b<0g6K z?}X`QcvHlY-+Lq6x5XtC<%K!7MUefqR&(!vbgl=a2p&v`;xuw{cGse5hK(kdn0>b? z_DH73(3YmBIT%59(2Q&b@mVBBf@8*r`e%<%r$R6Pa@~@!vsY@970|SiiEoi@IBIo7 z^``HfTyxC|qz%PK--gRH z=#0NJYu*N|g8gG_!}?onVg zZ=O|cxfI54IOA+W2hp{2r800r^-i81<$~O4M=RX4Lp$?f)NA;qaAHPmdy#&#`jmc0>E- zULx97NCEZ}jC;O!$6)*7R&mQ+uA(fF1AEmw2zOptF;BU8B(|sa*E@#CL;sF; z!;3clxt}5~3wFOLCRL*z7Q_80w1GXhSB^ZKiWEr0%46k(-lD2U2}13#Q{d6Gwb-2; z_6&Gzoa7O2ABsrq4uoU6wF55Mh;6c-PZ)s0v$G-p12PsE7%N71ck?w5biPr3ks{3) z5j4pYyom#@kVXlED@!3;WsH_<1<%T_{pe=eTJH6TiF7XL%Yt`x$i&f`_qLUQ(bxJS zy<@A+eYK2?mh-Ga)khYTU)AmiKk+Q1&)K!+P?C>1gBX8U+)!Uo1&0#q2gjD+ByC2i zps{*YXmVSswx~WaNPP1+Bz}WVj0?N2U;X!UqXeqNBL8d>mE?WuO}~Qr&G=&Kg=nMo z46Jc|=BOj$9Rk&YOM8E{Wa&rOcz0|r*NpQt;uO=F0*>@DWM;SKsZ3$rnR4=6pJ8Sw ze>#b1py)g&tE*V+$vh{py0Qv~oN6;0c;rjVp|!UD4ok+c=BVe%Q+bi+9Ztf2u8N7+ zfyUdXDOGr++5-o{%x4xDdCZ|0w7+E*8>*_0rKP&tg@XI&pxtK(4FAtIv{2g_6*gWL zdjYjxpFTqV#eKaUVKLmmJ25QrzzT*S!nZ9pR1Bw7`g&{E5%mKnX@H~ohvN8(13DJ3 z9bv`S+|t5qR_ipUo6IY_yL|Kv54$w?6ch5DwQr6QoZT3zsEKo&kJ-_W{$s`ANuPoD z(iIim+n~yxPH@HD~n|}*1H~5 zSISb;Yw1xPFr8}_k8!H-~UqySoC|>+UQfZ`KJWk^46qm zwuP-6>A~CdUP0_NL&NS*AJ6)etRb<$rZlraTaS{gp@fYxZ{@ik@4_qE#qgfr{j5Mq zZH3^Ktl%Pz9~&FI2QCpCQPy@!DIIn<|?eWu7Qh7jPAep}( z!BN_Ff=QcSSmM{$7AeKe4*OKBHUG+3Cu;;b)A?I!cI!mu&@0dPF#`v4=XD7tr#Bw^ z*F7@jSWChNbP&4_#zl$&G#8t9OI&>`_OI=n@&3mY7wBnldElij{W6L`8KvK{z-EO5y3zd&K zvjxE1mu$*Zrbl+8J|U^@wcD{3ERd17apIV@c(~~CS^(W~&(S?hC30gN)`5Se02@Yd zLWz%al@cw-$u^?8Zt>7&{Urs&{$`(UMUZY+9euz-Pd4R5Bnb-f7lhp2QJ@?*Wsn^M z;dKXz4Nk8qi9)1+HznRIjY9iZ@RQPD3<{@Q2RVkH!3Ne(!8&qv2;!zyye70C8%JHg zM4-x}=2g;>>pWg39Q}1fx-qfmV%yz)$7fjXy$egGoH+aWY+kj&2Is%P`w&rvMuMKg zz}XyOJ1@nZ+o$1&ct97{#=Y4;p%{g`LA-6|vn>4#s^R$EI&U!5&+(=@$_cSO^qX+8 zv(H=hLb(r0SGP*PxtJjSa_)`i9=-6DqBDV;pvp&0sGNJ!e`4J}G|gbM5am?3vHv$- z(s^1ls#x;gbSJc3bOt=*UAlTV6X)OalQ{6*zt#%SEa?r>r;ax_DW>6aqV}A32wYh`{+D_4Ku2S~hzm~?ZlDrB1kF7yO0r6y6 zkWuH1Q~O-!;lmG|$McYfptKg-s=J9wyUW$Cq3g#u|BZPdv;>(?nx7PgIBAmOZqVolvWf{z~cv)@?8sxe4UE7l-Vs5$QG1D1N zuimrLr@UA~N$T=jq=KL6@#}LRY&!lZQsGaeRq}bu5brL*JZhAPi2V(I|EeEr(ZM%L zy6;0~kQYcvcB4td=wTI`zY*wlOt`-iz<_Z<4oc@~+9R1}gjv;XyX4EwB||g*N$~W& z@9YjSU8CQUJvWa_25{)A#?r9~+!STM_M=mV=kFi3TTs_mFUoXaY70t92Y9of!3&$aw#JDs z_9vwVaHuY41gJtEpAJmSD#x}=N$dh8op=wP`#-)ePD0R9Wee@{r3F2bmo@&<)0QXk zEvGzHUT}wo&g&V;?}f_xRdY%tt?Z29OuE@S4Qke)`;EbzFB^qU$E}9i42OO+e*u%6 zG(XxcxotKBGySXCRM$I^FIKbYEDVwGnt>Ix`;ceLGrWY~;pYR6*oYM7-t_;+?5wC~ zn3t;ZbxN_RID6}FpPPPDoF%x`d6VzSIq-BRYg6Q40&WskBK2JH`Il{}PIeRVW7ZM( zCeoDc5*C`|NskXBi>?R}{*$zfv|kTofyn}j`~!ze=h8T{q$DEakBeN?4=t3_{w$va zT7>NKj9)_zvOE7Q6P9(nPq9-kmD#vbjZP{&uAeELuMS0vt@YW1G?R7+9q*~Qy#{|3 zQi{^#lnd+g)O6oCHgz&p42@nRGii394{U_1zn}B>l8Lt(zpN7{c>gr`S{_F}69(q? zyN`ddPx|a-&Vt49WxJ;Qi%3puNd!shWfpP}WWxKLbdoEGj|4cjtl#m9l*-Zz5E~eG3tg-q-vamO1sv+*e zKJfIGiGe)6uwWxM>Cr)U>KGWuQKz>z)rws>&Mx5=kbw`(Tof_=W$M8(O3N)ILP@Y> zO#AG8>%rVpRsm~yQYp}S-ksuo1{@>Sr?y1+L+qH`kXn3kBAg5HbR7ychH zcj=TnZq1G@g9`RsxrV#9*1pVj1}38R1V_#vW=QN_^Su9M6#P>EjTrkgy&?H>7LJG3 z$^n)HW3u-}8x!MJRh7VhU6@squXB5$1En+%${NnuSbmKU1%AZW4srbHWfvKxVQ3^1 ziKF5r=50%yK$j(Tno+*6jkT4!6w!#kX)XHBsKZoyw>>K1FU<$6n$>pKU%hg(Ks+*~ zsow1fuANbzKZ6Xg0 zr4Z7r#(5*+auy5?-Ck@!PXC%ZN@=e%7N0e{kCHG@nf~%WIHj@S$<2JhcNPw*Q%ahc z&zN&}Z}Vpx2i?ibRI!IhYyemM&pdav4i^32NPEnv(#mK)FcI@{=ID~r&wU2o!XnyH z$Sv_aOS4j$()839QiO%(YPX8_J~jp(9!Pt( z{%HMS-*P*CuB-Rw{P%CPgV4)>vXvuC)u^a^HI6Ub!`&$4x4NERfd?0rM~fei&X^l? zgCcWgm1R)y*PS6>;Q1Zs%B4l-8jHb;crwgOAj$X&nxHHZ0!W?2(LznsRz6;2rZDae zxkuh*p&0o6>=T1SgFWHGY`41qdTwfA?zbCsoJEyWK!}^9`)xB!qFZ~8gntKKP=Dx2 z?h_m-HE_7`xeW8*kOTH;DjblTmBj_!t0iU;X&i4+sHr8Cg#@D}LbD)tP6#AH>%mdM>aO=63 zo{Yq(paA%7l0lZM`L)T?BxEcpM~Z))Y(MwOJ5bvswhx4+s(Fq&*t9%nJa2@E0(>?m z_^tJTo_AoHmW_BZH%nC6SK&2XtPv1bTi4^H*WZ^PEE3C{LrYl`BBJ8jJ`pxErZB6#^tPJ3#}_Hi!SG0AwA6MlW|dPudK_%<13s~RB34vZ{WP9=)fWiuGJ!W! z;%W)4F@KS!Otbd|aB_M8Q`7x^dSs2-FT3hF!bFiliS($gR$t*f&RH*pnaBb)Ar8Iy?5N!jF3))iVi?8`!yvEu7Y3$!|)4nHL%*0W5xOKaSIPUeDniShk-q!zzskRaX*9bu(yG9c>rfFE+qqBj)i zJttf)Om!=;hg4MnZqY2941`YNX&#oCXPb;adqtb_f7 znKHN5F9NSMIdv8^QP5D|)tk7qFM!bjb|<=(I4{?_{c^I~nMGGzpCMw(sG4HOLw-A< z+{HfT?IB(z<8wBk_Z=P3f0yp_Z)3*gzJ&pZ zo>bd8dVn_3T;zq5AGZDYT(imN1Pr{}9Pqmv)0Cu1?l?!niYq|q1B*>_$BJuO**tXP z@o|t^@;2nvtDiyi!FE1Ekx`nP@7gj&Ei%+?=nqQNZq}l1-Pb{1x|*TTx7T@Y7*KA; z>|!|#B&orIU6;G(~k zSqMgC!WhfF7-4GzX>I)616REMX3`vV=)YS&Un``Gh-1wheZ#nTystfT;W?Cw|71^7 zC)cs2T@?OAKT$4b9ZU-y7b`iM+)(!aYgDr^RfOnmHiG}+34haA&pSFqN|;`#z8AMb z+F)VdKCiIj&9p1I13jJK2Ndqd{j}coZ^q#ek`ESI4~%#;pj?Mb;&3rrz~Wm^Mv5(8 z3E8u6=Wu?pY<^JNtlu)RB8V|A=b*2}NX;+TyRh^0WT54=bt-X^#v;ERBjqIv;oGvk z+RFrK1UJhA_m*Zc%_Q`koZ@CUQe=;rf7{fmopQgX`gv)!p8O;jvOmj8q?~@^_EPUh z#>zc&Mh;{VC#jXi@Q$44K6ux+&G+3D?E=Ok&}&;Jk#e*p+8`}VY?W9Fbo;dbg&yAvX_k6x$0J~htBfPN!$rW zDvW@~&gbmEirujZ(pXp{zxo#rLWTmrfxk2>$&%k^98)gTB900N@XP9B9z@0sT!|^5 z7p0Zqn4VRvQX-pDY|4~mcx1%tnUew#Nb9e6E$~lR!%#E%RJF97#86_==b>Y)2>i%H zeEnVu+@p$6YqQx4$Os7*#HX%M1JlqW4cr_*H_QZy)>-tM8ecY(cLx#8n7A8vPox~W zVBVLTyReRDT$e8dEuql@qE}>}3(owkZWygAJTJ#^* zzIO<&VPne@{2Q<7E6+GA;hZ5jxZL(BfsP=1`V9@mFsd03YCw$9lL3cQp!=|=pPt_N&H{MjvNUU+_IrY-QpV7JAj#1d` zj}zx!N9T>_nypz6x`Q4i5b*^td?R5&%2uJRY~fOleov6(V)$a>L+sJ7i}ag%sOW{= zJLYkTX1{3LLH#FU1jP>r8@4vO=BVpfKfBn)(l5rWu$v3DSR~v`9q2HEmZ$dl+sx^K z+aH6?OhBS+HW+$lbN#o+UaJE{Ik)o2T8p~zQ-x}a@Z0PvZFP*9DX>3=E+RLMa$bYH zM_Kzv(p~IVM0X8yBOy`&8mKaZ0|hF-9I^bZWPfBKZC5-ZxA#-IuPw{ z9}P|$2hYQHHDqq?tH-ElO>QaWayVDwM<`C66CUD_De?v=UvS<*|K4bAjjJ0MBftFn z-|CIhjOw)_^DsFa0a*p{Nvx)FQ}i+E;jbq$&B21>X6Q#)!QnR_$aV|F8k73 zY~v4cj+RD##tU6LV*HBkl(;62DeIGcG~ohpCV$mm$ky}Pi2`=k4>kc?F{79V=ImKC zJyDT!f&#OxcqQ4im~miR?Ch_X6=}Q3>}+So5Gy%b%S6@}@2SC@5MVMJ=hysNN zUf20!Ock+lv$V0&SUk}uB6rHkr4L;Wh>RhA#|oPBdc{!hRm&2A{x$nx^fM;>{0nzY zE0WX;_du)YjW9j|5Rbsy)zUl-3$E3)#I4P)o2AC}#d8oW9eX{gmTet)K4`eq=1q3M z&VG8Iu7guA``z13kW1BjvxSs5$>tn$`o&+?nlCArII++*%oWs_UftBkulyz|iqR3t zEM@Gqv$-AXU=%H)tuy}A%1}Z@rRR{j>2+}?)g_&yt6yM7*KiT5Imegd)4xrezUqc) zNB8z>1`L-{jTzg3d+W$O&(*7@Vl7stP$wn1;pWaqSHxL|ij{!&pz0hgvOLMS^GokA z=qE-r{>bh2=MqIcymlJ;jWU7Q4RPIP9mh}l*@@{aPb1+VtO0mEt7?E2xgi__`;k`EMhV)V0*ph0FOtxoUy1CSrq$%!WH*H;o zT8PU9_J@vJ$<>WoXYIFF;XFKL#Y2Ht`!MB>j;S&~VF^LpZ~Lr{VZ{QuY;olS{DE|I ztYXNwy258j6JID(t-zH1SgzSAk_Q$Wdw$xr0ysbpYg-F!Y|xc(n!cTC3rR^adZ=#5 zMl|sG=vPXq+(3WdFpIba;&z->7&pI(9^XJCey?8Ph7T2e-YKIP{j*usgPmYjk!e6+ zX1ikuQiAI_(@5yG<&T-X<4KNz<|*f8_z-@5GFh&J(+nE_Z z?pBWypj~a}CB3>~2fKyP6kGT;F-uU=n8o3lRKC1?Gb*^Z_bINvd_V=RMJ5~qQ$hJ= z@85^^;K+${C&<%x?;0uP<^8{wf6vgGagW9g#xwP|xJJ|f4T`T7-gxUJpr`-*`C%L} zRw;^8d3?kzrPDj7Kxvq4uKZO?D7H{8gE;o*N$}xv;N{w{^x-+^+Px1S_QWM6L+2~r zC1+YxZ`L{DS{WAmvrr%w?4Pei7b^~b#tHN@z&M2+C0$%zf{~?q(TOo;r|lp9ui*9D zHd4a-pW6Ye`YF zmT(;ZGr#T!tNVi()Pvgp>yfbFlo$A{{zq2<*ZVd`#e%p6b3YT=Mpfnnc$3 zObH{@YpX{*(i{I8-{NA@v|M7Rlsd^M1jCwdEu+YABdbsQ=#({GoIefw|6 z@J{7EQ~oPehieVUOD;o0Lw!|NCZ+13<2wV~#LO8LX9nz;8`9g#fa(4- zl(GjYxpWsaX24)LvhyiHRwy;KWeawRn}*!z69}(kz?r$pz<3XN*=-{!zu7^)34S#_ zYr&a^LQ$EJa7KT+2aU03Ka2{E{kcKiK?Pv9l*=4Fu zGAjDaXLTTB_%rAhIiH+Q*}m3h9-Ea za53~<5Dm2@$3ACv{-dn!jb!tc$o-$>1fXhHBAtbfm6a)~Bao}c&1VGGS`k8cYrPh{ ze$=%|1+uW%tqTcZ*difSdG(e{(sSxC4ooV|^=J*VH?BSKaxYqVz5W=5eDXl$y`2Q0 zCa1bz+7a;8a7i`mSj4G&*}<{9K0k^L|#~8H3qgO@YA-HP&tAj!l)(kl zdrs;FJL*{vHq!bvq>5cJntQSwwf|F&Aj^bEG@86?f5}Hzm+dLU{eHkLE*a(Ae=|~3 zBx9PMC8VXYN3P*o;lmGp>fG#z(3=FN#ZyU27o2{ncl)u0#Lh|bS{dXlupdULADsy{8AD&ypIAWMb1>RjDA(dB_3+2V{v zzsWLD_^na6>Kbwe2i$+nJP)W1dB&%vu~SrcP20cN$R#NHAoXs*&FNO?tL{+W`p|IJ-eN?^ zDNEY6EOKaBtl*m+DZY|^QFArRA~C9{*n#^}KUdS=!B;gDLF%&-dNo-o+|Zp6KA`(YBY(ASbq9(+-%s4 zCEsJk2FuwCMFCd_O@ky;f>8#}X|fc3A^x4sBJlK^EHqm9rs@Ho5n+JFM;Zo~_a8l* zr3O%sdOYJkfS$y18xNX(QPQbj3VWLvr(!$aMjm$IK9YC@azl?LFHojZVk=(fm}HJ z?lAb=c|L52J<}ahNs93jBO>IMF@hSRTKD^=bwVlTFkt$yEVpmFdoLZ z_vDfIJwd_xES8ppp-RDG{^ib`z?J^MOssL*Yp3pS-}dCB8d%5K?}*7WT~uHD2)eNQ zF%#-Fx_`}Qtm%?)_z?W?Rs+Q?BFLVpk0I3`ZTpmb=zle3O>rJp24%Qxa8mcxzQ1Ag zQNkCE?VB&cep+3^tRDYP$3{jE0&|%a5+j!YSJ}Ea8-?Cv=h@@oVJK{_0gim`Ol9{j zGRXi%D`RgD_!L$?L`AslS_o(+p$k4^n!ka&fA=QgreJjsutu2N-RFaDZc49rkHPal zLzn2-;=P~H$#tO2!U_D=w~Ab?3c*5&@$8?5=)>V(ZkRV-z7wCoRMph^ksRgT-Xr!6 zj$Zox-iDu-TYB?HjIMzVz`77jO~ag+Jul?27(o9(TYIqLqw4JlMf{OGY<~kt&e;_6 zt%BbWfqm#CkM+#$I8$C2L&P2Ouk^32&lV0PHkwZeaN8_eZ~_jC+tVL^WP6`9;W3G%1?wRT{)3k+)~%s zyA-xnGCsjyl<-;8WXhdymJ{40sCL=7_oDr_s;07*QgvXZYsIt5+6k*pf39nFx64?4 zhgMf3A{O;43ti{TjlZn<$h6Wz$R+4K=W#*Ve`%rdW|3gVB8ao-Ods6Sfj@VBgXpeI zMf4v#h<#{y-`Tk&p{A~Bx$#ditNEPtpJJeaR1-TZReN|M2I3dc1YT_Br(s}Di2iEQ z%VW3<4HCk=ao4W%k&mZ_qYw>R!~}hXbLbeBdVIy&7LJ?pkCDW1T@pe6*iz#LC#^_^w5KY^_F4#SK{ge3o=n)*EfRVThz;P(k>TY&`E1$Ov{%RZq7Z-x|eWGZ4YX+ zDq5&EIE^g=^d;; zSJltOh75DHgcvx%hen;2D+61txFwNuj0`t@q3YXvyNUlq7$tmeG`~6?sTlYXEuh+9 z6m)gbaTU+7mE3ZE2ZZq}7}Q{uLc$N%k%GN+Z0zZj%O?F8czFA9ldWDsT^lE}zxv!A z5}LUEx$f&X7uD#9dRAJF5N@%R5U7?&q`i69+`f7I!F*J2uIPTy8KSW8JNakct+}8} z?JNJ5cff!GotFEGutY#7h=`qAb8_i*On;Fpx8gV&76zCS*>T_ewlmQNVTm~il#rJR z?JXv-*|F;^vt%XuzL@-WB`k3{GfCDTk8m&2;N_E$kc8LAr>ADZ_MhNX&l^`%{Pjda zrS>LUkhCw6DdMP+AJH3r9hR^8?Y6)-Q_fe4RVuT|@WR4o=K94-tIOZKx@_&Sm}^$8 z=Nn3v9mID*+=i=n1TFo<$sHJ48P4{|jv-TeXHaZN!Xgdb=g5v_#p?eQ7N(^Db z?X~Tp61B{M3K-3QUxF6zit)q1hGuW0BDaCMl$1qHK5UEpGDT(e_g{w4hmoj^njmVxx+jufV$zd@&=x-{C-^=qe^nv0$J=Sqjrvala<1W) zGP`w0qVF;r78klYGoYuwv3IM!KbLcpaV`m6C{`wZ9Bo=yxa3?6WF&rqn)h z)YAFZ8<}_J@ZCY(`ZvdW2hpxS+z8Ep6aRf@j{cR7|;y}gr87F1JaO}=-ga|cq(`# z98OH>DwlYgE9$hLr}`p5&}HAB=Ji8X!_P8_k%h}mO?ywIWe@sIrh%9B4FKMNu4QMO z<8^Sb!GvN|!)I=RuTE+K*VNzpVU`Zoj2>k668?Yh^`qD{&(>#?466w}%ZLoTF-H^i zyV43WU(~dpDNF?>T03n#B9$aFY+QfvN(S|aPvV**)62n+K!#lr-^dd1A<(Cwz*lL? z^UQFi^}dK=WkHE6p5T1MUwJot^=3j6v+Cl}@j9@0+wG&+Jq5$xZ5-@5RF;0TT)i5a zInz|+YHC|DUcL`I2d44wH-jBSKWez;KX_&sGsp^zxnT7vXsV@q-LsgyB9SnXqEO8E z+sY3`!&71(eDRk@{N9Nz-QTwe{N~pcU)h%887F)i}UX# zOz$KanPaJ*6ZTMC;t|jqJ_*P@S=LaFu*kKxTh3M>l zWdh9WLm)zMS;BNy_6_D|)ZL0bNOF3!WwY3t*pZfXnpz%~_*wf=umOR3vc)0~1;|;~glRO|(4R}lAL6=Iy zm>PP1?y*^H+ao4N%Is)6I4r99+>gAr?n6>$c3gC|_)^P_zJS)K$}1AMSQJ1>UO&(F zCcGCQQo-gw^es9;?}->wtd6RO$NgBzI2sO?`2Js9@xJ@regkh_G5+C_mZpF3V3&kE zUMM0yWeke%ND@OXOLwU zf&MK_5M{7mNKFT3=nS9Z36Id^0z4*k)NUSpA@xWumU)8*HA6r`kXg+6!9RJlS-pl}q2$9j?nnVG^*+vVf1SA$X zqp3Y=rKqV{5{h%JZRj(&`GiReth5J3D^y>VU;Mi@=;bSUBp`mTKq5vmm`_U6hn$7OUp&4SiBdq{;J0L18M_ z-j5ByU8wFCRZ+o250aom8yq}V2Y6u zH4AgpdH3`1)3{^?D+fbO9$Rh^ZeocOL6W#=H9sc+UUy+qmQiec=th;A+4&>-_2iT2 z=){nrRjL!ylEFbiH8n3F008EJwv-p8PSJ3D4$lfF;IT`8RFqFH@gOETR$s+LO}M)V zK$Du15RV3^Y4CC@S5Q0pmuP8^SGzl1EpFtfWw!*twLC*qw6z5<4;G^eH)!W~03?`F zdmC#A1ddMNB{muM`=6&?hZeMcO@m>7uueHu7;i)V(iluTMEH1HZoAL;KA9Ds-V)|R zL58hgf9631;ZxKmJ4@okc@RO#7J#SdUcs(^Eqit{Ea^Nb+>ZVckCOgn*t7@Dh&VH$ zMTEUv<(2s(xi40yG;DA{s2zDOUuFLG5Am*N8!ssHAJ!l14F7)5NQc*yulvuAay zE+g4cJ33A~!_BZ@y_RJ$H_!isWl;tQBt5ASOnKq;s?EReQYCM@0sFmuboNg!3;`^% zhdkT%F>wo)#LTt>cu(;07|eMk8Dc*pJwj4{M(umnynKY$Q%J+YQge)Rh(dX3=UAw2 z07NS{&A-E0f^`@)YOvZZ+X`9ZC#FibTX(j$m=t)p86dt7P!-t7_^Z(^AwI&?!+h-X zs(F(vqUuyU(|h-3VpMe2x1fT7aaJ3G5?gvZ;%ntz>MxPiw;^r%qk3YDjI4*pHLlN3 z?T;%|=%j7tZ1r=bB>FF~1BFkM_Qx`1|iL z1Do66r1jrZ0`;soJ*sC)A@EiFvsWe?=$=TLnpzTH>@`Aib=E@=^wvil%+pwVx+hY;4>*=kcHY=l_d5pB~g`7oVMyP0arxY| ztEi}MfvKLyWUe&>WVFR2q8V>VT!hzxapK#X237C-kID>f+$ZjO!4!g8AfZXMdi%njvjc(<2F znywZgoV~2LOb3TZg)<2iEJ-|wJqfXW^paSI4KkIX;u3@tuC>1wamB?*aB&ejAtcoR z*Y%6*ZoeMbCsUYae^#%>e^msfY8eH^#p(2tUY=eiyX|vsZ9N3k0x!J*qm*rx5cWt- z4PH@2`M$=s!k1H!Gd31gSC>XXN%>4lib=p`Dxj>)zP}$N zZIyWY`}^guYU;FphGk9QMzH{fWTMV8@#3bc{`HM9+ldci?l&;+Drsv6qs<>o-jk#V zyvoci!bhkegx>$+{{H=Y*PdF8+U8(K$a^*CsP_gEIV&X!2tHb^oPAuJGGiNp4MBMW zD^F_e8fyt-Ve?K}U0v{KGmSjk*wvMyqk~b%={G-) zg&|9Rk82J?(L|apu|+$<=Z~Lje{GwY!v_a7D~s`CsuhqQ(psu*Xn?YTx#FipFhMA8 zC~TegosC6|ZNJJ#wkgth)xCIVm^~mQYCrGo?RU9vZ?8*97%zEcl&g|RoeJ1N7c_dr z^Rm`#&yQ%p(61PtkZo(N$%BjBoV*tlQ7;aThDTN@x=`{`T4f@Hj3aVs6=K~|CYu2| zcJvn7Sga!A(JCLY&lBlOHz)eV1-X3eGbwn;Of`&CD~f}`TBr#$#)a|LmsoIXT=T>q zPMFjU1)5SL3t=yf8{fpJSuGk$o*D8y`F@Ygdq_W9IftW6Ovbkwn#UTpH}1ET#bHb= zg>=_>2#A>hKzm@%%%*xhLK7|!{TEfz;F>5s!qLo=0~h)y{${Mmz~VOh6FsQI!zzvV z{w8%mm?)z}haVDH7Pz4ie^X3K;1a;wpb0VX%K~7tqFFTX%PHW0NTlo`|08d^#2RDf zt~V>@XF4?vrY1FWHD;d_@2ikVmHqP-_!0KUyOJN+?qwP-_+|LCX1$h%bu!TNtT($x zdAexITxit}dgtK+IW$3@Ya z#z{ekj;>Nl3E(jKvmj0{PS?b*g$d}yZjq}C61_<0ll=TTr$ir^V=j}(+s;M+9@N0@ zElAZfaq;mxrl%F+)Zc_rP@%N7klfrtAw6GNt&q($CCz8wmz@qKlnxG#!r0SHSs{E zuAoqCFzlXF;T{;-lS><;G>qO=LhF=C6Xm9NfrLZa=lTOQ)@LFVY!7iO^inHJl%=8; zb|7iFIxLZzFfv-4|GnxR9VlGmNIvVnVJ_rx1c{;(kBVhcTG6mY#*>fIP8qM`BU;Nt^r`s%ye4Le~!oSmKICT5$(Ja^kl?2o23`-gV! zhhKJ)>vw|oe)e@`1FDqd-7_lN`KcUwUfWHt6#&z@Z` z(OrjtIW#>Cl~seBpnPgOZ}FrnvqvpDU#~0rgt4m^Fa9bs7P4P_=zWg!59Fi$&cpQ^xp1q4f`hxhFAHmxk*v=FD*}+1bZfDg6QMx#TsRce zOH`fM^Z@q)Q=`Rjkv1mJ)N`ws#Weot`>189%MR50Ae%1Kso%~Xh3$HII1c=6QswV{ zc9t~Z*YiF#1>P8r4BL6Fe>EPCX$#q3+-_|0Dm5#*uQM}U>INsfII3xhzu@DoKn1s4 z`-cc4zS_R+Q(stJi*Y zZF%vL>FIvPH6*J99n1b~Y%&X@>f{03Z$MJgsoQ9e$n#Kh*{9CDS6$^25)Ks+fk8&- zrrAgEz~%X3hVgfxP6dh#$9liwcGc(St4k(()#v7tx@=F!SWEP1Drhx|*L!LAJ%f9j znMqHNqp(A3_m}7x*+taT(-5NiZ*(Brs0+X6Nz@S$QHx_m`DEnX!VUF?;K=hiLu8=i zGSQz!pD)XYkMosYemdXd4A^vpHqveV-lk8eTw&K3W1t~^z>M1t#+pg~WXtnpIqOg2 zt99S{Ey@7g5VoY`l8t)#vy7Lv%W{(D_J_o|cHXS5kk8M!%t?Sbbs;<*+(pyVP&ezs z#>QKgsi~$56TlEd4v&Cf>05sKCkMNm5ALLgPXl)}LXfL|W7+rm{Vjhb9Ubva+5Xv( zd}4@q)|U6}vrdSLQ}Y5v9zW(7axI#RttMGdP1Vsf^;+LEoo;kfxt(&WcdZ@jv(SEr z$O32c>hgT$l=n_5L)xcgW}CP5T}gMNSJueqFOTP7j<~omAw#fc1?^V)NHwJtaHq$I z2r&t1iErk?r@Ce^*x0_+UM^a@br1C3*f*w{soOU$tF5a~by_faijJwa&;J~FVf-Qs zmM+^p@7BomPKdyEK%yOWBy}Q3NA+1oD&MlDRyl3#!u2m->Qw-<3uYjhfatSjyE>xC zrD98TFMq4kg}_rvRFwEn!^kq^5KUgdI;X6Xt#!zLK|>xg4TUP{C_ma!zdnJ9caK_* zn;1@lO#{X^qW4G~ZkMpefjG@G)=zA9N(Nbn z;-AG)gVx2c9{+!yMf~@Z6J4n(Ti@IxFIu}OSfm$x&UUJLx`x1*CZVODrIk?Z3Kv#u zeQIe%WtjM~1Bta}sQ{CO&$+Dc0f`F;<8yZ#LMv*>3P2f=djRH;wW zBwHIXycVW^-AdF_KXE;iqsZ4YcMUvmWpBqEe8ssNvG3>TkJ~8Wc_-1G+%?5>fpR0V z>QZ4>{>ZPSMtr&gx{$C&kNi}VNCzE(G8L;=3XTy7wK0y$b&h?Tq@)eivtxfBtkS@z z^G=7UP$ige%vq$ZZuU;GsZIINJ|tNEE1^!obdJt!Tk|JQ_&n)~H34GvR?RJ!+Cs;! zDLob;C*d}KQ%8N)Xpkx1`6n-7>m#X!MMI;iFf66w)NQpQNio%W+`@i;w=O7kSi?DS zKkcX*!H-UJ#k0IS-p<#C%f6=8E{eopuPO3V&xyub{64A5Fjkyv~(A z!P9bUJBgN|huyg(z%ogMR{TUr=eus1nN?(nNMQ1c9s5D+@7_`H&b}VSn9%!8%Pw8M zFsXUAMG}v);>5Rf|6bABIqKHhp{gncQn$yCN=Rc42zCQi@+&mb3#towagBwgoS4*J zl@ME|JpYxx;u~L4AW6~0&{X!${V{Z@vCQ4`ajLd%sr$Rf;5p6ftl{L7v4gBto^j?r zdrk4DQpix>7Ky%KA*CMs`tYqIr~P1=Pc_S232srCYMc7Lqp_Bype0BC9_t=0wpomx zXp>gqO1y|#`#4NTola4_Y)^e#i_OCQ{5(vx=@&0rz6fGk8D&G*`wHN=TS)ZG)6@Ho zPQi@i{987a0JgW$bUO4#(hE9OZlu$EP}Ip4sYaPgpr%1h5i$EHV=vrO3~@i2#zVgI z2qh66j?|K@pK(J?qOzEpMyb6=`yYWjY`D_@651`~M!Q(;%;7{HKE*eyt+Pg=+wC&6 z-`c0k{%{~3Q7~`4*O82R{3$`6xcc>OTGv#5yrMlBxpyg-+O{Tk4Y3P~;D?j#3@6y~ z|Kyw&q9RTrZK88D1!n5)j2$Y|-6Uh9RO;b@FMV_3UnU+Vqed6W)+#KChHi7Y*>I3; zojee#5$ebtdo?{xVzlP2F0A02&pvZOjnp2jBW*AqU4G}9;})u|C0W;_E15AAcS=3^ zZGf_XWvo@C2L{FdtYuBMZlzdO-joGWnac?+L$K)AVHj_taF4sy{N=h*bPqB6dl2h^V zYSFoT{BjFah2=7To_#3)0{hDVBdZH{O=`h*x6?U~h+93zxFyED)n&G%3M1NBwv@2F z)C;Y(6(?KT%tL^YSRlOkI|eRph) zHX(PB)5+@F{v3%7n^mYpW1|ik}Zc3GY63_2F zAmgN&5^sG7ix3JpzY867g$`O1Dsp|k<^M4yCAf4qq>KAv#h$T{Xs#y_ChzOJyHEJk z``OJ~{(ejx@JbH26}9mLQ12)!z>UWYBkCzv9v>k^p1f95<8;#;?<)74Hv9bX2ltyd zZ`ucDmS`6qS8Ea^z{(5pQqp7C?B?p_6V@r#3Xw7(S-{-k0YxHnvs_Hx4~j)guc%X4 z7mIPJ8 z)#dmA)OQV1e_33{OwL=7~td$JmtMZ{I8cYV~r)HNuduqFicIq@muVz0F}< zkUy*1X2wW6E>V5{=Q`qRQGtZ5KHY-{R7}@di$?uBNS6EegDT|1$94m8$+@|?L&EwV zy@)-2r{ahUYR)pZeKVqW39)|iB~N|y#g8^;!i%?QEjp@?oFhu~xz@|0+)htFr>9Gs zydiPko@RqRNJ-}A<714yv9a+LfOjCxnCN$58sRLq5pw-??`l2c3R}=-I?ih;_1MpD zxID+eA%)>p8|$M-WM!a2!&qHgQ_@uB7+d^#P>D6U-2IAzf|?}LNVizGf!SzsbTaTh zi(=U4p@9Lz{Z$SI2T{=`Ah}Vj_+v6WHjj-dNv2&k`-xse80Iu&4Yhnnpaz2_OGt zU2#s*$44AgC#5dlB|5R7{PkqHe6Ey$feuEV>I?%1NkaezAW4jy-uFy+eerfUUj2=1 z5-VpV9vEHP`B)SRWk?X187Hv_6PT$W5!admIV*!vzCs!q{r0a-5>(#q-s(iHeJmCw zhmlraXfK;Xs#^twuY!-2jf1oemjL@dk&T6QSFT)Q=cU|n^#WNQ_Xu&m)Jxptu{}_N zDAm}c#c|Q|D0L=hDHO!r$Ejb6rWIj!%6u&?C1dEkq4s9{r;)QW`^Cj8BTd1YN`vz# z8io-ozG}er;V5K?aaf~mH9^ekda`Pg`K1&KARgo#8vx9Fk-^}#c!F);i-7+aACJZ* z=jwZG-t>ze+~FAK>3ZNi<#nW4$60s`3VNMp2PvI{sBQAFa%yyBUTpvvCbZc`i8v!# zxwFB;okvJ0q_XmT*#|+ZFQtiL_n00&3@fMwh!#;y%;I3)jjh=V8HjSd{d$xDL>`A^ zW}!06UHy1vIzrZSTsBaKn zw{vn#bl|z{jillcYu00?Pc;C#=uTdn$BW4mVqwn3`p-L% zgO9iQ`_2BxUUK1IXCz-^Ti0ECoe#YOv{MH70OyVvhOOVL1m@)9=TSa$hS~waGJlED zD~UxZ+DC7F*|l!_WEU9n z;GAzw7xe(AT^*R?szf5?b`N}|8M^>k4_9}3rpNfUji=04U|K+SPUQjZIO>=N;F$o< zrE0mp+#v!7ay6xX%T4WSD>O>6LiOh(86}f#wo#}<*Zq9)E|Hx^e@2<;uwBg{fF%bs z5lN+72Q zUwmQ7&&5TjIVtSWSoRIu+IowXlhZdkx+E^p5X3trEj8v99S~W!wrE(nNPk2+@+)7LYTxk0LkmD;hBI9PbKsYf zQwSs`IK4m=ynieRSJhFj|9m$(J|Uh_gyp#T=rxJyY34uITGS7Y%8R|k7D*}7+#CQ( zUfjLJz$g!iIDI{xF64Mu&~~Q%6qg*ee7NCyb-IQcMD7?M^1eyQ9iAOhxn5r$1BBOb zy4D_JNbH>2`>=qp)CuWqFQF2WbQKs*j_NBYS?nH2IqBUHZ_AVjCZeQdS_2_A2#sN% zAMd~psRVa_Tb3+b>R%B9G^T&7F*skILeF*KPUBu5`=@Sj?AWah(p-QCp6Cg%Njrca zC0U)EopNYRVq=HeIXFL4RmJzd+|bXiD%uib*9Y4%7;HyDfccYr1A$5EJ;;$(Cc47O z?H89{5Sgd1?JO|L?4A>fqqC28Mn)o9+R*iZ6cjL+y>)sgaFMp<{W|g0_RbXnBE>vM z(ZT64c{WNz#WB1#ukQ0kl6kt+b0cDT-)F2G9I^ZqgOj89`{zds9fUs7hg1T80k<`P zx7NC@Go*W;QBImC4~bvj0stC|MX4wb4vsxIv6FNt7`)C7EWX``(>8{kqfS^y<7Sbi;?J()|%nV}!3x^sGyVTi4*^=va z-I}Dvx(zb~-mudgT$k2>#&9|H>-+>~T_r|%(y{sQIF<%dYC=9Jf2{iwzWp1({W-bj z^Svw?aibFvjsTvA9v&cwqfr*81b75tH&o)*v#vVmu0DVa!Xe6j?1`}P&}@@j7{Bge z<=)2c4b*Citr6m9CDs$fh&kv2x`&Q3;5ymtJc}>##V@g4yf2K)zF6QR;xD4FPojVA z?2yxE%Vr4K69d~^A|k=PRaDLFSyF9HoohG)8ja-)qwDD1-^L|hb`zX4&J($a6r3el z@ZPEt**lLN*C($LMr2oc083IfswKxyPh;{tp78Ks2L?*H9?!URxeJm74TlNpOv!~- z60NR+rIvWTr*C9rl#SKku*XPMA5mS&E|64 zPpK36Wcy!gEp`iwS?p8FITn}7schXkCsMdc_9vU3$LW~bBP5<$Td7Z>`jt0$IzO#GvenuLty|#?C9HjW=p%~ih}Z(> z=g$KY@BI{gMp4Ek+Ja9d-1bBO*=!7}FdynG18)9{z}=aQXPTGK9|MyxtaS)*CHAK7 z?-Td^8HS+ecxLA2z|WtP$Ue;B>4z8X_BSU>#7=G`mgtafB;(eeMAR1Bq+#)r7wR^- zZ_&ok(o)8({kWLNK>*%I`gW43(Sx* zat)@7uq*3IL`oPeFO7>08hM^U^14Si5Mav!TF+{o>6t?Nc3v+N^Vu z#eih6P@ORy@tk6#T>0%lv6JJIZaynUnr{6^jWJO?Z1X&fVlCcgr)z&nKf$x~oa`5Z zJrQcCUH@$(rF8qB%}955RU!t=MTpaANhoOG$i&jCb5|AnD~l7fl3pc%9CNcdY`Ylc?}k@_RT#2yRp83H08uH6tK>Hgrxr$ zauIZv^uL|hOsM97cEpVZLOR?1=92stH=l*ZO%KjZ53Ybi)cxh!uAN1J4hJNI?&)Fq zS-4!Dd5)pxU1KC%hYY);bQ|{^7e)pYN$>TABdi%yV`%MbSX00vr2TW>U*&jR%qPv? z87Fpe?RE`+TW9XtUerS)=IyE2OW;^))2lZM)02I&D z{9>_QR%>E=F?Z`VY;qjLm0En`nBfnFY8Q1U=n?;ymj&Q9l714+my6+9J;dEQ~2rF zv%5UZqyX@;1gK1dAs1*JUH`cl0aFkSs?r@~_)?IkzJ$t-4wd!DaX0EJCe;wGQHChu zAdSuG{2gP%Lsr9P*d_7Q`-=k9;|LqGn#vr`Usik|ub0!v7cf6LJL&k=cE=K#M#f>S zRZ$#oLp;tna6ekt{SP`U(n-kfQWW6W#I9zoc_*LKoX9TPgsX7Nv3)6+E?BOrAZ`JL z5#8|(q3JynHdE$qgb)4HXkt|Q?)+uajlwO5_T;iEI}5u&k916(ce<#PVOW`&CkK?c z@uBX9GidYtC#wmobj%J9*+rwla7S43@Z)mD7-t)2UzM6ztP=Co8jVtR=VYmrTMZ5y zFU#Jl4;K5W2SjVM7YLM)l{x=*Y)?M4EPV&?U4PF7owY~z{X8V=r(Z4B=erqo4aPI{ zksQZstGAh^;i?T3w@=xamwl&9PuS_RW@o}rqTbY`H#IqhDmRw8dq!e$!OPv<0bZOo za6iO=uA(4y+u29FaJo+UcFKxYll@~)`Qw^Muz+y?EFjgK6dlPg>b4pjw`A>gM60w4 zm1qSek#${nikWOQ9yh(5ULuhVW7O@xAeDR4M^#`(Ch2m$q#;sE#_pWLVM??vZGIw* z2@yl;k*wn>md#t>mrT{;upbqD@F;H3+|<_Xe@?Z^_`JVS$oET6IbH#an(@}I*2&qK z;o;p1rdP+Ozc~sPey%+9ljjjgcl5wK56j(FTjb0dcwdABu;D+672AkH(ct3QO2rI} zP>gfPpRLmKNZv&tV{Xrlvq)!+%)BsJ7klTme&LpWI7~r|nG^N+QbB{SoXqm>n9)|N zme6=gkwG+`Vj-n2`~4eX2j2$@=CxLs7umP>A5}D@3^fW9eW`UA*_{}Mb+(%~1L&XE zZT?{^6L@G#G+S(bv&*0tR!kQocX^&B{PQ1(^0SEI=rUuUfDrG{9xUYSoI zH2(-CIPO36qX;SePmLxB`h_bY7H-P&7Qzj122TCcT+2yOWJlu<_EvQ=i{Zr;;jo9g zKS=C$?$Np3QYgEP@x-F~uSQ$FMV9Y(JD z)*VYKKuM6Is~cb)aKAahq;b*Y^ksb65O=qwytD>wg*T1m!XYc;tmpznKeOEPu=j}~ zTs<#hJeLCYoqFt4PD9LAV3C)QD;!$5_qeaF)_R5+>}xdI+F+=TmgOjcm**hUXALvb zNB`^p;Y*Yku?3fHM#(NJUVn)iZeDl&mAlzDKk?XWZL3Y$`otPOP@Ax>RZVmp`9$;` zE#W&1#x2aW8bRsPqG;_k+oo%4w+ZiE-I_Z-Eu1a6Q@@o;w-uvt;76~lk6GOyPM6kO z2szZ(b%-k2mDtO)2%w`4(>)OjS(i{L<+msC9B8~aBAD+Z^42OnZ$$)8rqcvx_0>mS2)o^&X@VP}QR z$ysJX_NA8=Vfg!*ilEg-`<*FX5TVZ1jrOxraa4MZ*fBS^7&7Ac-Z>TAm?mgEWNo6E zkS~wIYl2q{>3bLv5pgy)exU>tT(zEThNNV)h%*4>c9t+D3SFzvR)K~w);9q0wL9Kz zToc+~9nb(AFn~Sr@Cbm?lO&c(u=myC@}?i)dE?^ZUOp(c*$(G(>o#>SwxM;1LfUN8 z?(QCZa>Ro}f%*<4M{NG`GBPYWIy3XD3-pGR(JgpzeR#FO6KM(?J};x30mte56>%sO z0@D=Ox92ihTh40CBDPFRCEw@n>-S=$>n?+K+mWEh8PW6cX)ikZbadVZnm=@fD(Gcy zPHwcch=$s&fk5#sOP;x|Of}3G1Jh*0_-FhU?Be_k6QI6+31ey}n73yU%+jZ+x-$<& zt22k0rA6%KOf&ITH;kIqq(%0|zw#Xe#iG(19R_RDxcG$s}~L4u3-;N{$69eXbC+_kpv zq*rie50gXoD;x7&n+R#dB7P@pZO%4fZsS^;X)0XV%r#?BQYM9~)x?PHFYy@Vq@;cd zI}7UWs$C$8`Sok_xdO19KR+i&&U}q_5{-Nxai%#Iyt3kjF8?1}rjhMeSurx|GtK}t zXdRjKB*3nSGCqD-h#`9OMy*f7o4G&)dJ1ISgrB~?m!uE0b0OFr zRh8Af2E1ZoQ86(@U=SrempC05$Q2i2ZlfpW?(PwjlE|{c$qI>c^_S?NprMJcjeIpg zK|vAcz8^HyJ0Rg9gIVw4E-9q3mbqX^4RyWO+xrdZkP|L1&yTaSvWNiy@@t2#wJSXF zwVskMpjJA&yNx|B-R0#KUZznr=hrAQi;^&n#jkKc9(4mLnk4LVi3qZ(s7bfabNQFj z&!U3F&^uC%H6{9eonqz1%{OOeL?`MT^OtMDyd%n5p;9L&m+WufG^f1W-L;st2RMo* zN)4Y+`CG%B3kwRgsc-c5_u~N>KiB&4;W7N7uZ4cIC#AoHg!0bG$!#zJic(Sr*7IR7 z6t@I(gXf6=b1YZ(HSbn?JHt0i8FQ8qFfmk|n-z>4V-(_l^SjgnK>{X~(T{%~uP?8E z0OYC;G!tfc9oS>2C6-b{2jV16O^p_BjNcnnIl9b(Hj>W_iTe8ba{8^r!{)kQrEv4} zLk?H|8ra*1!jZ`F;Nh6zK+9qO%uFIS5lr_Z*Z+^Yp$aBM5c9abfxmCBq|?2lP~db<@JX0 zl&tjhe2oi^)jEmVyckAWl<+Ca)<(W4&&l`PA z9S|cpv23r;?v~)6RQeH}Aw+)}XWfcI zwYK>KM&y&nJLJ(;d0I*e%ND$zmqW#1-fJlz9VG!lwOV*I-zIV5a=ksb@Vm;g+?FQj z;P8-99ISupptYy%DX|zTGxbgk(BkC@{==i146_qvpT0R^@ z3NSz~;eAphLLXGs$NS{n+}$gvv+W$7F1IRLesoKH;)DZ~(u>t*Q;jI0)|wiZ9^ut0 zi+3+OPk)tVFOso8del8;jWj7;ywUs2YJ7X<>(|0ho;6lUMB$%jZ%PzW*g805SA;kuf*V$9qIgjhvb?{jg|IWj;g;*o7Vgi4A6U5h_zt(obR=8F8r) z>+VLjP3iy*afO7etZZ`RN29)*Zu(*asncvY&5Km33hXijD47EF#nVb1WHq$6+D ztd7fVs5H8K8bWtYz(Qb^ed={?;sfuGhzq{e+e@|cOyVaY^+fc+0bXx(d#`0{Xz%8j zX5E<8P-CO#I#pwJfB1OD_{YO^Fl3PVVd`yVjmg!e4M79VU#_QSNGE6`6|PiyO@}{b6Qk1=p^u@STY%d&$brmN5dXl)P5~oh}3@l zn$=$%)5G~aflcbA(yOG0s=7E zFhO9sb+FQ9XOhukbEZN4?k?~S0tQb~u^6Nq>e@955LJS1h&UXE{nS)7UgKVk%?vSa zItICv<4}6jqS5kGAT(2z%*$@V07=V4wRMUs>DvK^kydW%_Zs=Yl}L7SQqL+H48?=r z_8a3V!VRlTX~>rkc>AIZLhUJ}lr&R6V3j#K_QDGg#wI`XtK1`NL)|zA^=m(T2;?2H zv^(h`qU;f-U(7Ln=U#zlQg|OmqLoycdU5$G{4h^Sau^8xfr_N)FD)B#E?r{w8XvpY zcb&$H>cANg7mJnGE0CA(yVbkVdbRX>+ZQeWugNTEkm(GF<*J9UeR)5=M|~%<-3aQF zbJ+i4xU(}r-oX9LeZ}4GQztfNk%+>D>lWJ$5}6~VqXq!-%FIoN3wWG&jWV#KFbgV&< zkpm9agtSi{Znb;Yc)g8c}-P6^APv<|~PdA+~hDFO!IgUIfBtFMS zPC8Jlpm8NYh=`3BtL#Yb3NZ~0yFUr0 zP(t{E5NicKPac7<{*UQxPC&6zN-K_J}z70M{nUsN--wIy$<#oSZrZu!pidAC5WYXA91i+w$l z3;|#)0k2+a>gLJMuaP;sSwh2Nn(DbvYkqB(R0LB*Ln@w$442%mQ(xh-L)`rL%obj? ziuLY|#P?A8ytdoTYPI_?|ElN752JZU%fVLR6Z;SOT^7*$YhT*Vlp8;jEePo5$iqn} zAZ|PyT(fB$t34ZF@c4*+`)-@2Dr^@S(orY9qfCF)h1eUAajJQWCK?_dC4P_9B;J!IU-ar*o#89B`rjHffs&4;qde@FV1Mj*`!9(E zG;_Ul&OFkH9a&Bx$l(*Y)Yyx!PB(-~JP&HbC<|m!l}l%%eOsBr?SFqqHbQV zr9>DOCLe)kca|ulD7PH@u<;wfCn{cVKy5G_Ph zLkd>h;LhBA=5eDT=Ul&*a#?yj*mXve6 zuNXNtE+Tm~@~q=vygrQ98!4@ZKi`s}!78ZJlI9u?`nPT1z5 zT?#Yf{ID2iNv?j}Rf$g?zaYjpQo;GB0}gmql~K)+$E-4N71OSY#Of@-M_YAP>MX}O zHP?IN%XF~X)tMql6Lf~zcY>m#;p{v*mF=n-7oWR`j@}y&YnR=Q?t+_8${At97jPd!_}H$fBHZ zx)Xpm@N*SCG@Bn8Skr) zbntnZXD4E;;?B>@*6h3UnFgiIJjtidM#(^*AMi>92lw%doKs5irR;l^?@kLpmnhrW zlh?2fb~EsJYkbg;G16I5`FwTr=I-86LQ31o*V`LiXOwinL5|?#dPTd0=A>=e0+U&{)^mdJSsSHT`ThO@?-RVZhO1`3ZF2jbJku!>fa& zQqbjf`=1+#ZhodiD^_Q~rGf50bSJ|9m6!UVm6~6GDE99z#xn8llXp*bRb$j>|LK5i zwU6KR!ER?{P|b-)tANn`k9MBm2dqGg$$z^UGL7FHdE>^NofneN-u(a4Z~5Qvl=$E7 z4owIqwooy+2b4bHKr6P5Yy1UR=l*%h_K-Q&k*k}&vhpvbHQj{!;OqfF^afzw9b7rI z^($BdXwOk9$tM(~r18JDwj>p>$0Tmxxupz;Is}a_pJ&? z693C{02t%4#(0zb=V|{RHX}zr9Jv59Sa<-%`R_-RDV2O{Kzww5XFT-3c-(qs%Tm#C z_4W-ulXJwy!3ige)(&R+mjMF_Y%dc1VEs2!bs{1ov(c*O>})iQ3UE+AiH>btU~&7e z#Y;$Hq19LC=0-tH{T%eD1JTy9hJ^)XeS^pHT-l@VC;B7DT0j5GJ3oy%BIM5eUjIOM zW`_7LVJ1@REt;O*-e+&^xRZa6XI7QnyaMgKohtwGP55IlTqa~?1;)3{7FlmilzL;F zVV=b(&G^lFDgKpNe4oB76s!EC1OLC=%`dWuD&5x_AwafGG`pEf4bZYodzd|mo-5U< z;-x5#%>}JyLx!Y=1G68Qr0#!~1F%Pa?~z98WaKMq@kMi>aTY>1`tIxWTTvtgmwD0@72m*pH1}0QE8K;adZM;IvuTU-X z2f^1wlZsaar{r@|_X0Jg+%^BOQh#>ET~C>0KV4O|urV5$*RJ~#hSw!m9Gn*m;`Hae zbBsF&psy^E{$FuG$~~s_sbsz>b{@t-?oMZS)S4q@DqQ*flrlTV;?%1B?RIZReYTB?KA?nkv) o-i6)5p&2IHnVK2{Hg!!mf4^mXK<(Di4e;lMl!9dOa|6Hs1xiClq5uE@ literal 0 HcmV?d00001 diff --git a/docs/exec/execution.md b/docs/exec/execution.md index 96280f12..01c96af5 100644 --- a/docs/exec/execution.md +++ b/docs/exec/execution.md @@ -7,8 +7,28 @@ icon: fontawesome/solid/play Add a flow diagram of the process expected for the procedure usage --- -## Quickstart Execution +## Dry-Run Execution - FPrime on Windows (WSL) If any of these steps seems confusing and needs more detail, check out the sections further on +### Start Hermes in a Powershell Terminal + +* Open a Windoes File Explorer and navigate to this location + + * C:\Users\\.vscode\extensions\jet-propulsion-laboratory.hermes-X.X.X-win32-x64\out\backend.exe + + * The version X.X.X of FPrime needs to be updated to what was downloaded during the Quickstart installation phase + + * The "your_user_name" needs to be updated + +* rename the file "backend" to "backend.exe" + +* In VS Code, open a Powershell terminal and send + + * C:\Users\\.vscode\extensions\jet-propulsion-laboratory.hermes-X.X.X-win32-x64\out\backend.exe + +* Hermes backend is now running! + + +### Connect VS Code to Hermes
@@ -17,7 +37,7 @@ If any of these steps seems confusing and needs more detail, check out the secti
  • Click the button at the bottom left corner of VS Code for Hermes
  • Switch from Hermes:Offline to Hermes: Remote
  • Select the remote host to connect to (should default to the correct location)
  • -
  • Click the left panel icon for Hermes
  • +
  • Click the left panel icon for Hermes (rover icon)
  • Create a profile if one doesn't exist yet
  • Update/verify the Connection Name, IP Address, and Websocket
  • Click the Play button to connect
  • @@ -44,10 +64,6 @@ If any of these steps seems confusing and needs more detail, check out the secti ## Create/Update a Profile -* Assumption: FPrime Backend is running - - * If a local FPrime needs to be started, follow the Quick Start instructions -

    Remote Host Connection

    @@ -63,7 +79,7 @@ If any of these steps seems confusing and needs more detail, check out the secti Address - 0.0.0.0/8000 + 0.0.0.0:8000 Dictionary @@ -81,7 +97,7 @@ If any of these steps seems confusing and needs more detail, check out the secti

    Hermes Offline to Remote

    @@ -89,6 +105,48 @@ If any of these steps seems confusing and needs more detail, check out the secti
    --- +## Connect Hermes and FPrime + +
    +
    +

    +
      +
    • Click the check button
    • +
    • Click the Play button
    • +
    • Switch to a .hermes.md and start sending your commands!
    • +
    +
    +
    +

    +

    + Hermes Connecting to FPrime +

    +
    +
    +
    +
    +

    +
      +
    • The indicator of successfully sent commands will show a green check in the code block and return EVRs/EHAs in the output under the code block.
    • +
    +
    +
    +

    +

    + FPrime Procedure Dryrun +

    +
    +
    +--- + + + !!! question "FAQs" diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md index 5972a3ce..8b774145 100644 --- a/docs/getting-started/quick-start.md +++ b/docs/getting-started/quick-start.md @@ -30,7 +30,10 @@ This guide will walk you through: By the end of this guide, you'll have Hermes connected to your F Prime deployment and ready to send commands and receive telemetry.

    -drawing + Hermes Offline to Remote

    ## Starting the Hermes Backend @@ -241,3 +244,38 @@ system. You can read about the [workflow](workflow.md) in VSCode and set up [telemetry monitoring](../tlm/index.md) infrastructure. + + + + + + \ No newline at end of file diff --git a/docs/procedures/procedure_templates/test_procedure.hermes.md b/docs/procedures/procedure_templates/test_procedure.hermes.md index 0b1ca74a..8253a5b8 100644 --- a/docs/procedures/procedure_templates/test_procedure.hermes.md +++ b/docs/procedures/procedure_templates/test_procedure.hermes.md @@ -84,17 +84,25 @@ Label 1: Retro JPL Logo * Complete the steps for runnng the test - - -```fprime - -``` ```fprime R00:00:00 CdhCore.cmdDisp.CMD_NO_OP ``` + + +
    Execution Outputs + +|Source|Sclk|Time|Severity|Component|Name|Message| +|---|---|---|---|---|---|---| +|fprime|1786665957.962138|1786665957962.137939|COMMAND|CdhCore.cmdDisp|OpCodeDispatched|Opcode CdhCore.cmdDisp.CMD_NO_OP dispatched to port 1| +|fprime|1786665957.962272|1786665957962.271973|ACTIVITY_HI|CdhCore.cmdDisp|NoOpReceived|Received a NO-OP command| +|fprime|1786665957.962346|1786665957962.345947|COMMAND|CdhCore.cmdDisp|OpCodeCompleted|Opcode CdhCore.cmdDisp.CMD_NO_OP completed| +
    + + + ```fprime R00:00:00 CdhCore.cmdDisp.CMD_NO_OP_STRING "Example Text to Print to the Terminal"