Skip to Content.
Sympa Menu

sm-commit - [SM-Commit] GIT changes to master grimoire by Pavel Vinogradov (f86235cb62ddcae3278cb85234e165b965766e9e)

sm-commit AT lists.ibiblio.org

Subject: Source Mage code commit list

List archive

Chronological Thread  
  • From: Pavel Vinogradov <scm AT sourcemage.org>
  • To: sm-commit AT lists.ibiblio.org
  • Subject: [SM-Commit] GIT changes to master grimoire by Pavel Vinogradov (f86235cb62ddcae3278cb85234e165b965766e9e)
  • Date: Tue, 21 Nov 2023 18:20:06 +0000

GIT changes to master grimoire by Pavel Vinogradov <public AT sourcemage.org>:

http/firefox/DEPENDS | 4
http/firefox/DETAILS | 4
http/firefox/HISTORY | 23
http/firefox/PRE_BUILD | 10
http/firefox/patches-for-python-3.12/0001-imp-removal.patch | 1090
http/firefox/patches-for-python-3.12/0002-setuptools-update.patch |85455
----------
http/firefox/patches-for-python-3.12/0003-no-distutils.patch | 362
http/firefox/patches-for-python-3.12/0004-no-distutils-part-2.patch | 128
8 files changed, 17 insertions(+), 87059 deletions(-)

New commits:
commit f86235cb62ddcae3278cb85234e165b965766e9e
Author: Pavel Vinogradov <public AT sourcemage.org>
Commit: Pavel Vinogradov <public AT sourcemage.org>

http/firefox: version 120.0, SECURITY_PATCH++

diff --git a/http/firefox/DEPENDS b/http/firefox/DEPENDS
index f4c3862..f9b6ab2 100755
--- a/http/firefox/DEPENDS
+++ b/http/firefox/DEPENDS
@@ -5,10 +5,6 @@ depends autoconf-2.13
&&
depends -sub "CLANG" llvm &&
depends perl &&
depends python3 &&
-if spell_ok python3 &&
- ! is_version_less "$(installed_version python3)" "3.12.0"; then
- depends six
-fi &&

depends icu "--with-system-icu" &&

diff --git a/http/firefox/DETAILS b/http/firefox/DETAILS
index 79c6bcb..5f189fe 100755
--- a/http/firefox/DETAILS
+++ b/http/firefox/DETAILS
@@ -1,6 +1,6 @@
SPELL=firefox
- VERSION=119.0.1
- SECURITY_PATCH=184
+ VERSION=120.0
+ SECURITY_PATCH=185
SOURCE="${SPELL}-${VERSION}.source.tar.xz"
# Watch: http://releases.mozilla.org/pub/firefox/releases/
/releases/([0-9.]+)/

SOURCE_URL[0]="http://releases.mozilla.org/pub/${SPELL}/releases/${VERSION}/source/${SOURCE}";
diff --git a/http/firefox/HISTORY b/http/firefox/HISTORY
index 22c8a07..dd38985 100644
--- a/http/firefox/HISTORY
+++ b/http/firefox/HISTORY
@@ -1,38 +1,43 @@
+2023-11-21 Pavel Vinogradov <public AT sourcemage.org>
+ * DETAILS: version 120.0, SECURITY_PATCH++
+ * DEPENDS, PRE_BUILD, patches-for-python-3.12/*: removed six
dependency,
+ patches and symlinking code
+
2023-11-12 Thomas Orgis <sobukus AT sourcemage.org>
* DEPENDS: handle atk deprecation (half-automated)

-2023-11-09 Pavel Vinogradov <public AT sorcemage.org>
+2023-11-09 Pavel Vinogradov <public AT sourcemage.org>
* DEPENDS, PRE_BUILD, patches-for-python-3.12/*: added code and
patches from
Mozilla and BLFS for successful building with python >= 3.12.0

-2023-11-07 Pavel Vinogradov <public AT sorcemage.org>
+2023-11-07 Pavel Vinogradov <public AT sourcemage.org>
* DETAILS: version 119.0.1
* patches/0028-icu-74.1.patch: added to build with icu 74.1

-2023-10-24 Pavel Vinogradov <public AT sorcemage.org>
+2023-10-24 Pavel Vinogradov <public AT sourcemage.org>
* DETAILS: version 119.0, SECURITY_PATCH++
* DEPENDS: nss >= 3.94
* patches/*: updated

-2023-10-12 Pavel Vinogradov <public AT sorcemage.org>
+2023-10-12 Pavel Vinogradov <public AT sourcemage.org>
* PRE_BUILD: reordered patching and checksums removal
* patches/*: updated

-2023-10-10 Pavel Vinogradov <public AT sorcemage.org>
+2023-10-10 Pavel Vinogradov <public AT sourcemage.org>
* DETAILS: version 118.0.2

-2023-09-27 Pavel Vinogradov <public AT sorcemage.org>
+2023-09-27 Pavel Vinogradov <public AT sourcemage.org>
* DETAILS: version 118.0, SECURITY_PATCH++
* DEPENDS: nss >= 3.93
* patches/*: updated

-2023-09-12 Pavel Vinogradov <public AT sorcemage.org>
+2023-09-12 Pavel Vinogradov <public AT sourcemage.org>
* DETAILS: version 117.0.1, SECURITY_PATCH++, (CVE-2023-4863)

-2023-09-09 Pavel Vinogradov <public AT sorcemage.org>
+2023-09-09 Pavel Vinogradov <public AT sourcemage.org>
* patches/*: updated

-2023-08-29 Pavel Vinogradov <public AT sorcemage.org>
+2023-08-29 Pavel Vinogradov <public AT sourcemage.org>
* DETAILS: version 117.0, SECURITY_PATCH++
* DEPENDS: nss >= 3.92
* patches/*: updated
diff --git a/http/firefox/PRE_BUILD b/http/firefox/PRE_BUILD
index 6a9e499..1b30247 100755
--- a/http/firefox/PRE_BUILD
+++ b/http/firefox/PRE_BUILD
@@ -58,12 +58,4 @@ apply_patch_dir patches &&

# clearing all checksums so the patches wouldn't cause troubles
find third_party/rust -name .cargo-checksum.json \
- -exec sed -i -e 's/\("files":{\)[^}]*/\1/' {} \; &&
-
-if is_depends_enabled "${SPELL}" "six"; then
- local PY_VER="$(installed_version python3 | cut -d . -f1,2)" &&
- apply_patch_dir "patches-for-python-3.12" &&
- for i in $(find -name six.py); do
- ln -sfv "${TRACK_ROOT}/usr/lib/python${PY_VER}/site-packages/six.py" "$i"
- done
-fi
\ No newline at end of file
+ -exec sed -i -e 's/\("files":{\)[^}]*/\1/' {} \;
diff --git a/http/firefox/patches-for-python-3.12/0001-imp-removal.patch
b/http/firefox/patches-for-python-3.12/0001-imp-removal.patch
deleted file mode 100644
index fbe33a5..0000000
--- a/http/firefox/patches-for-python-3.12/0001-imp-removal.patch
+++ /dev/null
@@ -1,1090 +0,0 @@
-
-# HG changeset patch
-# User serge-sans-paille <sguelton AT mozilla.com>
-# Date 1697534267 0
-# Node ID 7f8e4fcfcbfa83ee7504071ce3eb60e0e59319a5
-# Parent 6cb5031e76892d4e46a4018c82d59b1a992b596f
-Bug 1857516 - Get rid of deprecated imp python module in favor of importlib
r=saschanaz
-
-Following the official migration guide from
-https://docs.python.org/3/whatsnew/3.12.html#imp
-
-Differential Revision: https://phabricator.services.mozilla.com/D190465
-
-diff --git a/dom/bindings/mozwebidlcodegen/test/test_mozwebidlcodegen.py
b/dom/bindings/mozwebidlcodegen/test/test_mozwebidlcodegen.py
---- a/dom/bindings/mozwebidlcodegen/test/test_mozwebidlcodegen.py
-+++ b/dom/bindings/mozwebidlcodegen/test/test_mozwebidlcodegen.py
-@@ -1,23 +1,22 @@
- # This Source Code Form is subject to the terms of the Mozilla Public
- # License, v. 2.0. If a copy of the MPL was not distributed with this
- # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
--import imp
- import io
- import json
- import os
- import shutil
- import sys
- import tempfile
- import unittest
-
- import mozpack.path as mozpath
--from mozfile import NamedTemporaryFile
-+from mozfile import NamedTemporaryFile, load_source
- from mozunit import MockedOpen, main
- from mozwebidlcodegen import WebIDLCodegenManager, WebIDLCodegenManagerState
-
- OUR_DIR = mozpath.abspath(mozpath.dirname(__file__))
- TOPSRCDIR = mozpath.normpath(mozpath.join(OUR_DIR, "..", "..", "..", ".."))
-
-
- class TestWebIDLCodegenManager(unittest.TestCase):
-@@ -237,17 +236,17 @@ class TestWebIDLCodegenManager(unittest.
- #
- # Hacking imp to accept a MockedFile doesn't appear possible. So for
- # the first iteration we read from a temp file. The second iteration
- # doesn't need to import, so we are fine with a mocked file.
- fake_path = mozpath.join(OUR_DIR, "fakemodule.py")
- with NamedTemporaryFile("wt") as fh:
- fh.write("# Original content")
- fh.flush()
-- mod = imp.load_source("mozwebidlcodegen.fakemodule", fh.name)
-+ mod = load_source("mozwebidlcodegen.fakemodule", fh.name)
- mod.__file__ = fake_path
-
- args = self._get_manager_args()
- m1 = WebIDLCodegenManager(**args)
- with MockedOpen({fake_path: "# Original content"}):
- try:
- result = m1.generate_build_files()
- l = len(result.inputs)
-diff --git a/netwerk/dns/prepare_tlds.py b/netwerk/dns/prepare_tlds.py
---- a/netwerk/dns/prepare_tlds.py
-+++ b/netwerk/dns/prepare_tlds.py
-@@ -1,33 +1,31 @@
- # This Source Code Form is subject to the terms of the Mozilla Public
- # License, v. 2.0. If a copy of the MPL was not distributed with this
- # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
- import codecs
- import encodings.idna
--import imp
--import os
- import re
- import sys
--from make_dafsa import words_to_cxx, words_to_bin
-+
-+from make_dafsa import words_to_bin, words_to_cxx
-
- """
- Processes a file containing effective TLD data. See the following URL for a
- description of effective TLDs and of the file format that this script
- processes (although for the latter you're better off just reading this
file's
- short source code).
-
- http://wiki.mozilla.org/Gecko:Effective_TLD_Service
- """
-
-
- def getEffectiveTLDs(path):
- file = codecs.open(path, "r", "UTF-8")
-- entries = []
- domains = set()
- for line in file:
- # line always contains a line terminator unless the file is empty
- if len(line) == 0:
- raise StopIteration
- line = line.rstrip()
- # comment, empty, or superfluous line for explicitness purposes
- if line.startswith("//") or not line.strip():
-diff --git a/python/mach/mach/command_util.py
b/python/mach/mach/command_util.py
---- a/python/mach/mach/command_util.py
-+++ b/python/mach/mach/command_util.py
-@@ -1,22 +1,24 @@
- # This Source Code Form is subject to the terms of the Mozilla Public
- # License, v. 2.0. If a copy of the MPL was not distributed with this
- # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
- import argparse
- import ast
- import errno
--import imp
- import sys
-+import types
- import uuid
- from collections.abc import Iterable
- from pathlib import Path
- from typing import Dict, Optional, Union
-
-+from mozfile import load_source
-+
- from .base import MissingFileError
-
- INVALID_ENTRY_POINT = r"""
- Entry points should return a list of command providers or directories
- containing command providers. The following entry point is invalid:
-
- %s
-
-@@ -406,23 +408,23 @@ def load_commands_from_file(path: Union[
- This takes a path to a file and loads it as a Python module under the
- module name specified. If no name is specified, a random one will be
- chosen.
- """
- if module_name is None:
- # Ensure parent module is present otherwise we'll (likely) get
- # an error due to unknown parent.
- if "mach.commands" not in sys.modules:
-- mod = imp.new_module("mach.commands")
-+ mod = types.ModuleType("mach.commands")
- sys.modules["mach.commands"] = mod
-
- module_name = f"mach.commands.{uuid.uuid4().hex}"
-
- try:
-- imp.load_source(module_name, str(path))
-+ load_source(module_name, str(path))
- except IOError as e:
- if e.errno != errno.ENOENT:
- raise
-
- raise MissingFileError(f"{path} does not exist")
-
-
- def load_commands_from_spec(
-diff --git a/python/mach/mach/test/test_entry_point.py
b/python/mach/mach/test/test_entry_point.py
---- a/python/mach/mach/test/test_entry_point.py
-+++ b/python/mach/mach/test/test_entry_point.py
-@@ -1,13 +1,13 @@
- # This Source Code Form is subject to the terms of the Mozilla Public
- # License, v. 2.0. If a copy of the MPL was not distributed with this
- # file, You can obtain one at http://mozilla.org/MPL/2.0/.
--import imp
- import sys
-+import types
- from pathlib import Path
- from unittest.mock import patch
-
- from mozunit import main
-
- from mach.base import MachError
- from mach.test.conftest import TestBase
-
-@@ -33,17 +33,17 @@ class TestEntryPoints(TestBase):
- def _run_help(self):
- return self._run_mach(["help"], entry_point="mach.providers")
-
- @patch("pkg_resources.iter_entry_points")
- def test_load_entry_point_from_directory(self, mock):
- # Ensure parent module is present otherwise we'll (likely) get
- # an error due to unknown parent.
- if "mach.commands" not in sys.modules:
-- mod = imp.new_module("mach.commands")
-+ mod = types.ModuleType("mach.commands")
- sys.modules["mach.commands"] = mod
-
- mock.return_value = [Entry([self.provider_dir])]
- # Mach error raised due to conditions_invalid.py
- with self.assertRaises(MachError):
- self._run_help()
-
- @patch("pkg_resources.iter_entry_points")
-diff --git a/python/mozbuild/mozbuild/mach_commands.py
b/python/mozbuild/mozbuild/mach_commands.py
---- a/python/mozbuild/mozbuild/mach_commands.py
-+++ b/python/mozbuild/mozbuild/mach_commands.py
-@@ -23,16 +23,17 @@ from pathlib import Path
- import mozpack.path as mozpath
- from mach.decorators import (
- Command,
- CommandArgument,
- CommandArgumentGroup,
- SettingsProvider,
- SubCommand,
- )
-+from mozfile import load_source
-
- import mozbuild.settings # noqa need @SettingsProvider hook to execute
- from mozbuild.base import (
- BinaryNotFoundException,
- BuildEnvironmentNotFoundException,
- MozbuildObject,
- )
- from mozbuild.base import MachCommandConditions as conditions
-@@ -1110,21 +1111,20 @@ def android_gtest(
- adb_path = get_adb_path(command_context)
- if not libxul_path:
- libxul_path = os.path.join(
- command_context.topobjdir, "dist", "bin", "gtest", "libxul.so"
- )
-
- # run gtest via remotegtests.py
- exit_code = 0
-- import imp
-
- path = os.path.join("testing", "gtest", "remotegtests.py")
-- with open(path, "r") as fh:
-- imp.load_module("remotegtests", fh, path, (".py", "r",
imp.PY_SOURCE))
-+ load_source("remotegtests", path)
-+
- import remotegtests
-
- tester = remotegtests.RemoteGTests()
- if not tester.run_gtest(
- test_dir,
- shuffle,
- gtest_filter,
- package,
-diff --git
a/testing/marionette/harness/marionette_harness/marionette_test/testcases.py
b/testing/marionette/harness/marionette_harness/marionette_test/testcases.py
----
a/testing/marionette/harness/marionette_harness/marionette_test/testcases.py
-+++
b/testing/marionette/harness/marionette_harness/marionette_test/testcases.py
-@@ -1,24 +1,25 @@
- # This Source Code Form is subject to the terms of the Mozilla Public
- # License, v. 2.0. If a copy of the MPL was not distributed with this
- # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
--import imp
-+
- import os
- import re
- import sys
- import time
- import unittest
- import warnings
- import weakref
- from unittest.case import SkipTest
-
- import six
- from marionette_driver.errors import TimeoutException,
UnresponsiveInstanceException
-+from mozfile import load_source
- from mozlog import get_default_logger
-
-
- # With Python 3 both expectedFailure and unexpectedSuccess are
- # available in unittest/case.py but won't work here because both
- # do not inherit from BaseException. And that's currently needed
- # in our custom test status handling in `run()`.
- class expectedFailure(Exception):
-@@ -358,31 +359,30 @@ class MarionetteTestCase(CommonTestCase)
- filepath,
- suite,
- testloader,
- marionette,
- fixtures,
- testvars,
- **kwargs
- ):
-- # since we use imp.load_source to load test modules, if a module
-- # is loaded with the same name as another one the module would just
be
-- # reloaded.
-+ # since load_source caches modules, if a module is loaded with the
same
-+ # name as another one the module would just be reloaded.
- #
-- # We may end up by finding too many test in a module then since
-- # reload() only update the module dict (so old keys are still
there!)
-- # see https://docs.python.org/2/library/functions.html#reload
-+ # We may end up by finding too many test in a module then since
reload()
-+ # only update the module dict (so old keys are still there!) see
-+ # https://docs.python.org/2/library/functions.html#reload
- #
-- # we get rid of that by removing the module from sys.modules,
-- # so we ensure that it will be fully loaded by the
-- # imp.load_source call.
-+ # we get rid of that by removing the module from sys.modules, so we
-+ # ensure that it will be fully loaded by the imp.load_source call.
-+
- if mod_name in sys.modules:
- del sys.modules[mod_name]
-
-- test_mod = imp.load_source(mod_name, filepath)
-+ test_mod = load_source(mod_name, filepath)
-
- for name in dir(test_mod):
- obj = getattr(test_mod, name)
- if isinstance(obj, six.class_types) and issubclass(obj,
unittest.TestCase):
- testnames = testloader.getTestCaseNames(obj)
- for testname in testnames:
- suite.addTest(
- obj(
-diff --git a/testing/mochitest/mach_commands.py
b/testing/mochitest/mach_commands.py
---- a/testing/mochitest/mach_commands.py
-+++ b/testing/mochitest/mach_commands.py
-@@ -9,16 +9,17 @@ import sys
- import warnings
- from argparse import Namespace
- from collections import defaultdict
-
- import six
- from mach.decorators import Command, CommandArgument
- from mozbuild.base import MachCommandConditions as conditions
- from mozbuild.base import MozbuildObject
-+from mozfile import load_source
-
- here = os.path.abspath(os.path.dirname(__file__))
-
-
- ENG_BUILD_REQUIRED = """
- The mochitest command requires an engineering build. It may be the case that
- VARIANT=user or PRODUCTION=1 were set. Try re-building with VARIANT=eng:
-
-@@ -85,21 +86,18 @@ class MochitestRunner(MozbuildObject):
- resolver = self._spawn(TestResolver)
- tests = list(resolver.resolve_tests(paths=test_paths, cwd=cwd))
- return tests
-
- def run_desktop_test(self, command_context, tests=None, **kwargs):
- """Runs a mochitest."""
- # runtests.py is ambiguous, so we load the file/module manually.
- if "mochitest" not in sys.modules:
-- import imp
--
- path = os.path.join(self.mochitest_dir, "runtests.py")
-- with open(path, "r") as fh:
-- imp.load_module("mochitest", fh, path, (".py", "r",
imp.PY_SOURCE))
-+ load_source("mochitest", path)
-
- import mochitest
-
- # This is required to make other components happy. Sad, isn't it?
- os.chdir(self.topobjdir)
-
- # Automation installs its own stream handler to stdout. Since we
want
- # all logging to go through us, we just remove their handler.
-@@ -140,21 +138,19 @@ class MochitestRunner(MozbuildObject):
- self.log_manager.disable_unstructured()
- return result
-
- def run_android_test(self, command_context, tests, **kwargs):
- host_ret = verify_host_bin()
- if host_ret != 0:
- return host_ret
-
-- import imp
-+ path = os.path.join(self.mochitest_dir, "runtestsremote.py")
-+ load_source("runtestsremote", path)
-
-- path = os.path.join(self.mochitest_dir, "runtestsremote.py")
-- with open(path, "r") as fh:
-- imp.load_module("runtestsremote", fh, path, (".py", "r",
imp.PY_SOURCE))
- import runtestsremote
-
- options = Namespace(**kwargs)
-
- from manifestparser import TestManifest
-
- if tests and not options.manifestFile:
- manifest = TestManifest()
-@@ -185,24 +181,21 @@ def setup_argument_parser():
- if build_path not in sys.path:
- sys.path.append(build_path)
-
- mochitest_dir = os.path.join(build_obj.topobjdir, "_tests", "testing",
"mochitest")
-
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
-
-- import imp
--
- path = os.path.join(build_obj.topobjdir, mochitest_dir,
"runtests.py")
- if not os.path.exists(path):
- path = os.path.join(here, "runtests.py")
-
-- with open(path, "r") as fh:
-- imp.load_module("mochitest", fh, path, (".py", "r",
imp.PY_SOURCE))
-+ load_source("mochitest", path)
-
- from mochitest_options import MochitestArgumentParser
-
- if conditions.is_android(build_obj):
- # On Android, check for a connected device (and offer to start an
- # emulator if appropriate) before running tests. This check must
- # be done in this admittedly awkward place because
- # MochitestArgumentParser initialization fails if no device is
found.
-@@ -227,24 +220,21 @@ def setup_junit_argument_parser():
- sys.path.append(build_path)
-
- mochitest_dir = os.path.join(build_obj.topobjdir, "_tests", "testing",
"mochitest")
-
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
-
- # runtests.py contains MochitestDesktop, required by runjunit
-- import imp
--
- path = os.path.join(build_obj.topobjdir, mochitest_dir,
"runtests.py")
- if not os.path.exists(path):
- path = os.path.join(here, "runtests.py")
-
-- with open(path, "r") as fh:
-- imp.load_module("mochitest", fh, path, (".py", "r",
imp.PY_SOURCE))
-+ load_source("mochitest", path)
-
- import runjunit
- from mozrunner.devices.android_device import (
- InstallIntent,
- verify_android_device,
- )
-
- verify_android_device(
-diff --git a/testing/mozbase/mozfile/mozfile/mozfile.py
b/testing/mozbase/mozfile/mozfile/mozfile.py
---- a/testing/mozbase/mozfile/mozfile/mozfile.py
-+++ b/testing/mozbase/mozfile/mozfile/mozfile.py
-@@ -17,16 +17,17 @@ from textwrap import dedent
- from six.moves import urllib
-
- __all__ = [
- "extract_tarball",
- "extract_zip",
- "extract",
- "is_url",
- "load",
-+ "load_source",
- "copy_contents",
- "match",
- "move",
- "remove",
- "rmtree",
- "tree",
- "which",
- "NamedTemporaryFile",
-@@ -627,16 +628,29 @@ def load(resource):
-
- if not is_url(resource):
- # if no scheme is given, it is a file path
- return open(resource)
-
- return urllib.request.urlopen(resource)
-
-
-+# see https://docs.python.org/3/whatsnew/3.12.html#imp
-+def load_source(modname, filename):
-+ import importlib.machinery
-+ import importlib.util
-+
-+ loader = importlib.machinery.SourceFileLoader(modname, filename)
-+ spec = importlib.util.spec_from_file_location(modname, filename,
loader=loader)
-+ module = importlib.util.module_from_spec(spec)
-+ sys.modules[module.__name__] = module
-+ loader.exec_module(module)
-+ return module
-+
-+
- # We can't depend on mozpack.path here, so copy the 'match' function over.
-
- re_cache = {}
- # Python versions < 3.7 return r'\/' for re.escape('/').
- if re.escape("/") == "/":
- MATCH_STAR_STAR_RE = re.compile(r"(^|/)\\\*\\\*/")
- MATCH_STAR_STAR_END_RE = re.compile(r"(^|/)\\\*\\\*$")
- else:
-diff --git a/testing/mozharness/scripts/desktop_unittest.py
b/testing/mozharness/scripts/desktop_unittest.py
---- a/testing/mozharness/scripts/desktop_unittest.py
-+++ b/testing/mozharness/scripts/desktop_unittest.py
-@@ -7,29 +7,29 @@
- # ***** END LICENSE BLOCK *****
- """desktop_unittest.py
-
- author: Jordan Lund
- """
-
- import copy
- import glob
--import imp
- import json
- import multiprocessing
- import os
- import re
- import shutil
- import sys
- from datetime import datetime, timedelta
-
- # load modules from parent dir
- here = os.path.abspath(os.path.dirname(__file__))
- sys.path.insert(1, os.path.dirname(here))
-
-+from mozfile import load_source
- from mozharness.base.errors import BaseErrorList
- from mozharness.base.log import INFO, WARNING
- from mozharness.base.script import PreScriptAction
- from mozharness.base.vcs.vcsbase import MercurialScript
- from mozharness.mozilla.automation import TBPL_EXCEPTION, TBPL_RETRY
- from mozharness.mozilla.mozbase import MozbaseMixin
- from mozharness.mozilla.structuredlog import StructuredOutputParser
- from mozharness.mozilla.testing.codecoverage import (
-@@ -1193,17 +1193,17 @@ class DesktopUnittest(TestingMixin, Merc
- suite_category,
- flavor=flavor,
- config=self.config,
- error_list=error_list,
- log_obj=self.log_obj,
- )
-
- if suite_category == "reftest":
-- ref_formatter = imp.load_source(
-+ ref_formatter = load_source(
- "ReftestFormatter",
- os.path.abspath(
- os.path.join(dirs["abs_reftest_dir"],
"output.py")
- ),
- )
- parser.formatter = ref_formatter.ReftestFormatter()
-
- if self.query_minidump_stackwalk():
-diff --git a/testing/web-platform/manifestupdate.py
b/testing/web-platform/manifestupdate.py
---- a/testing/web-platform/manifestupdate.py
-+++ b/testing/web-platform/manifestupdate.py
-@@ -1,34 +1,32 @@
- # This Source Code Form is subject to the terms of the Mozilla Public
- # License, v. 2.0. If a copy of the MPL was not distributed with this
- # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
- import argparse
- import errno
- import hashlib
--import imp
- import os
- import sys
-
- import manifestdownload
- import six
- from mach.util import get_state_dir
-+from mozfile import load_source
- from mozlog.structured import commandline
- from six.moves import configparser
- from wptrunner import wptcommandline
-
- manifest = None
-
-
- def do_delayed_imports(wpt_dir):
- global manifest
-- imp.load_source(
-- "localpaths", os.path.join(wpt_dir, "tests", "tools",
"localpaths.py")
-- )
-+ load_source("localpaths", os.path.join(wpt_dir, "tests", "tools",
"localpaths.py"))
- sys.path.insert(0, os.path.join(wpt_dir, "tools", "manifest"))
- import manifest
-
-
- def create_parser():
- p = argparse.ArgumentParser()
- p.add_argument(
- "--rebuild", action="store_true", help="Rebuild manifest from
scratch"
-diff --git
a/testing/web-platform/tests/service-workers/service-worker/resources/scope1/redirect.py

b/testing/web-platform/tests/service-workers/service-worker/resources/scope1/redirect.py
----
a/testing/web-platform/tests/service-workers/service-worker/resources/scope1/redirect.py
-+++
b/testing/web-platform/tests/service-workers/service-worker/resources/scope1/redirect.py
-@@ -1,6 +1,8 @@
- import os
--import imp
-+
-+from tools.wpt.utils import load_source
-+
- # Use the file from the parent directory.
--mod = imp.load_source("_parent",
os.path.join(os.path.dirname(os.path.dirname(__file__)),
-+mod = load_source("_parent",
os.path.join(os.path.dirname(os.path.dirname(__file__)),
- os.path.basename(__file__)))
- main = mod.main
-diff --git
a/testing/web-platform/tests/service-workers/service-worker/resources/scope2/worker_interception_redirect_webworker.py

b/testing/web-platform/tests/service-workers/service-worker/resources/scope2/worker_interception_redirect_webworker.py
----
a/testing/web-platform/tests/service-workers/service-worker/resources/scope2/worker_interception_redirect_webworker.py
-+++
b/testing/web-platform/tests/service-workers/service-worker/resources/scope2/worker_interception_redirect_webworker.py
-@@ -1,6 +1,8 @@
- import os
--import imp
-+
-+from tools.wpt.utils import load_source
-+
- # Use the file from the parent directory.
--mod = imp.load_source("_parent",
os.path.join(os.path.dirname(os.path.dirname(__file__)),
-+mod = load_source("_parent",
os.path.join(os.path.dirname(os.path.dirname(__file__)),
- os.path.basename(__file__)))
- main = mod.main
-diff --git
a/testing/web-platform/tests/service-workers/service-worker/resources/subdir/worker_interception_redirect_webworker.py

b/testing/web-platform/tests/service-workers/service-worker/resources/subdir/worker_interception_redirect_webworker.py
----
a/testing/web-platform/tests/service-workers/service-worker/resources/subdir/worker_interception_redirect_webworker.py
-+++
b/testing/web-platform/tests/service-workers/service-worker/resources/subdir/worker_interception_redirect_webworker.py
-@@ -1,6 +1,8 @@
- import os
--import imp
-+
-+from tools.wpt.utils import load_source
-+
- # Use the file from the parent directory.
--mod = imp.load_source("_parent",
os.path.join(os.path.dirname(os.path.dirname(__file__)),
-+mod = load_source("_parent",
os.path.join(os.path.dirname(os.path.dirname(__file__)),
- os.path.basename(__file__)))
- main = mod.main
-diff --git a/testing/web-platform/tests/tools/runner/update_manifest.py
b/testing/web-platform/tests/tools/runner/update_manifest.py
---- a/testing/web-platform/tests/tools/runner/update_manifest.py
-+++ b/testing/web-platform/tests/tools/runner/update_manifest.py
-@@ -1,21 +1,23 @@
- # mypy: ignore-errors
-
--import imp
- import json
- import os
-
-+from tools.wpt.utils import load_source
-+
- here = os.path.dirname(__file__)
--localpaths = imp.load_source("localpaths",
os.path.abspath(os.path.join(here, os.pardir, "localpaths.py")))
-+localpaths = load_source("localpaths", os.path.abspath(os.path.join(here,
os.pardir, "localpaths.py")))
-
- root = localpaths.repo_root
-
- from manifest import manifest
-
-+
- def main(request, response):
- path = os.path.join(root, "MANIFEST.json")
-
- # TODO make this download rather than rebuilding from scratch when
possible
- manifest_file = manifest.load_and_update(root, path, "/",
parallel=False)
-
- supported_types = ["testharness", "reftest", "manual"]
- data = {"items": {},
-diff --git a/testing/web-platform/tests/tools/wpt/utils.py
b/testing/web-platform/tests/tools/wpt/utils.py
---- a/testing/web-platform/tests/tools/wpt/utils.py
-+++ b/testing/web-platform/tests/tools/wpt/utils.py
-@@ -1,17 +1,17 @@
- # mypy: allow-untyped-defs
-
- import errno
- import logging
- import os
--import sys
- import shutil
- import stat
- import subprocess
-+import sys
- import tarfile
- import time
- import zipfile
- from io import BytesIO
- from socket import error as SocketError # NOQA: N812
- from urllib.request import urlopen
-
- logger = logging.getLogger(__name__)
-@@ -161,8 +161,21 @@ def rmtree(path):
- def sha256sum(file_path):
- """Computes the SHA256 hash sum of a file"""
- from hashlib import sha256
- hash = sha256()
- with open(file_path, 'rb') as f:
- for chunk in iter(lambda: f.read(4096), b''):
- hash.update(chunk)
- return hash.hexdigest()
-+
-+
-+# see https://docs.python.org/3/whatsnew/3.12.html#imp
-+def load_source(modname, filename):
-+ import importlib.machinery
-+ import importlib.util
-+
-+ loader = importlib.machinery.SourceFileLoader(modname, filename)
-+ spec = importlib.util.spec_from_file_location(modname, filename,
loader=loader)
-+ module = importlib.util.module_from_spec(spec)
-+ sys.modules[module.__name__] = module
-+ loader.exec_module(module)
-+ return module
-diff --git
a/testing/web-platform/tests/tools/wptrunner/wptrunner/stability.py
b/testing/web-platform/tests/tools/wptrunner/wptrunner/stability.py
---- a/testing/web-platform/tests/tools/wptrunner/wptrunner/stability.py
-+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/stability.py
-@@ -1,30 +1,30 @@
- # mypy: allow-untyped-defs
-
- import copy
- import functools
--import imp
- import io
- import os
- from collections import OrderedDict, defaultdict
- from datetime import datetime
-
- from mozlog import reader
- from mozlog.formatters import JSONFormatter
--from mozlog.handlers import BaseHandler, StreamHandler, LogLevelFilter
-+from mozlog.handlers import BaseHandler, LogLevelFilter, StreamHandler
-+
-+from tools.wpt.utils import load_source
-
- from . import wptrunner
-
- here = os.path.dirname(__file__)
--localpaths = imp.load_source("localpaths",
os.path.abspath(os.path.join(here, os.pardir, os.pardir, "localpaths.py")))
-+localpaths = load_source("localpaths", os.path.abspath(os.path.join(here,
os.pardir, os.pardir, "localpaths.py")))
- from ci.tc.github_checks_output import get_gh_checks_outputter # type:
ignore
- from wpt.markdown import markdown_adjust, table # type: ignore
-
--
- # If a test takes more than (FLAKY_THRESHOLD*timeout) and does not
consistently
- # time out, it is considered slow (potentially flaky).
- FLAKY_THRESHOLD = 0.8
-
-
- class LogActionFilter(BaseHandler): # type: ignore
-
- """Handler that filters out messages not of a given set of actions.
-diff --git a/testing/web-platform/tests/xhr/resources/auth1/auth.py
b/testing/web-platform/tests/xhr/resources/auth1/auth.py
---- a/testing/web-platform/tests/xhr/resources/auth1/auth.py
-+++ b/testing/web-platform/tests/xhr/resources/auth1/auth.py
-@@ -1,12 +1,13 @@
--import imp
- import os
-
- from wptserve.utils import isomorphic_decode
-
-+from tools.wpt.utils import load_source
-+
- here = os.path.dirname(os.path.abspath(isomorphic_decode(__file__)))
-
- def main(request, response):
-- auth = imp.load_source(u"", os.path.join(here,
-+ auth = load_source(u"", os.path.join(here,
- u"..",
- u"authentication.py"))
- return auth.main(request, response)
-diff --git a/testing/web-platform/tests/xhr/resources/auth10/auth.py
b/testing/web-platform/tests/xhr/resources/auth10/auth.py
---- a/testing/web-platform/tests/xhr/resources/auth10/auth.py
-+++ b/testing/web-platform/tests/xhr/resources/auth10/auth.py
-@@ -1,12 +1,14 @@
--import imp
- import os
-
- from wptserve.utils import isomorphic_decode
-
-+from tools.wpt.utils import load_source
-+
- here = os.path.dirname(os.path.abspath(isomorphic_decode(__file__)))
-
-+
- def main(request, response):
-- auth = imp.load_source(u"", os.path.join(here,
-+ auth = load_source(u"", os.path.join(here,
- u"..",
- u"authentication.py"))
- return auth.main(request, response)
-diff --git a/testing/web-platform/tests/xhr/resources/auth11/auth.py
b/testing/web-platform/tests/xhr/resources/auth11/auth.py
---- a/testing/web-platform/tests/xhr/resources/auth11/auth.py
-+++ b/testing/web-platform/tests/xhr/resources/auth11/auth.py
-@@ -1,12 +1,13 @@
--import imp
- import os
-
- from wptserve.utils import isomorphic_decode
-
-+from tools.wpt.utils import load_source
-+
- here = os.path.dirname(os.path.abspath(isomorphic_decode(__file__)))
-
- def main(request, response):
-- auth = imp.load_source(u"", os.path.join(here,
-+ auth = load_source(u"", os.path.join(here,
- u"..",
- u"authentication.py"))
- return auth.main(request, response)
-diff --git a/testing/web-platform/tests/xhr/resources/auth2/auth.py
b/testing/web-platform/tests/xhr/resources/auth2/auth.py
---- a/testing/web-platform/tests/xhr/resources/auth2/auth.py
-+++ b/testing/web-platform/tests/xhr/resources/auth2/auth.py
-@@ -1,12 +1,14 @@
--import imp
- import os
-
- from wptserve.utils import isomorphic_decode
-
-+from tools.wpt.utils import load_source
-+
- here = os.path.dirname(os.path.abspath(isomorphic_decode(__file__)))
-
-+
- def main(request, response):
-- auth = imp.load_source(u"", os.path.join(here,
-+ auth = load_source(u"", os.path.join(here,
- u"..",
- u"authentication.py"))
- return auth.main(request, response)
-diff --git a/testing/web-platform/tests/xhr/resources/auth2/corsenabled.py
b/testing/web-platform/tests/xhr/resources/auth2/corsenabled.py
---- a/testing/web-platform/tests/xhr/resources/auth2/corsenabled.py
-+++ b/testing/web-platform/tests/xhr/resources/auth2/corsenabled.py
-@@ -1,18 +1,19 @@
--import imp
- import os
-
- from wptserve.utils import isomorphic_decode
-
-+from tools.wpt.utils import load_source
-+
- here = os.path.dirname(isomorphic_decode(__file__))
-
- def main(request, response):
- response.headers.set(b'Access-Control-Allow-Origin',
request.headers.get(b"origin"))
- response.headers.set(b'Access-Control-Allow-Credentials', b'true')
- response.headers.set(b'Access-Control-Allow-Methods', b'GET')
- response.headers.set(b'Access-Control-Allow-Headers', b'authorization,
x-user, x-pass')
- response.headers.set(b'Access-Control-Expose-Headers', b'x-challenge,
xhr-user, ses-user')
-- auth = imp.load_source(u"", os.path.abspath(os.path.join(here,
os.pardir, u"authentication.py")))
-+ auth = load_source(u"", os.path.abspath(os.path.join(here, os.pardir,
u"authentication.py")))
- if request.method == u"OPTIONS":
- return b""
- else:
- return auth.main(request, response)
-diff --git a/testing/web-platform/tests/xhr/resources/auth3/auth.py
b/testing/web-platform/tests/xhr/resources/auth3/auth.py
---- a/testing/web-platform/tests/xhr/resources/auth3/auth.py
-+++ b/testing/web-platform/tests/xhr/resources/auth3/auth.py
-@@ -1,12 +1,13 @@
--import imp
- import os
-
- from wptserve.utils import isomorphic_decode
-
-+from tools.wpt.utils import load_source
-+
- here = os.path.dirname(os.path.abspath(isomorphic_decode(__file__)))
-
- def main(request, response):
-- auth = imp.load_source(u"", os.path.join(here,
-+ auth = load_source(u"", os.path.join(here,
- u"..",
- u"authentication.py"))
- return auth.main(request, response)
-diff --git a/testing/web-platform/tests/xhr/resources/auth4/auth.py
b/testing/web-platform/tests/xhr/resources/auth4/auth.py
---- a/testing/web-platform/tests/xhr/resources/auth4/auth.py
-+++ b/testing/web-platform/tests/xhr/resources/auth4/auth.py
-@@ -1,12 +1,13 @@
--import imp
- import os
-
- from wptserve.utils import isomorphic_decode
-
-+from tools.wpt.utils import load_source
-+
- here = os.path.dirname(os.path.abspath(isomorphic_decode(__file__)))
-
- def main(request, response):
-- auth = imp.load_source(u"", os.path.join(here,
-+ auth = load_source(u"", os.path.join(here,
- u"..",
- u"authentication.py"))
- return auth.main(request, response)
-diff --git a/testing/web-platform/tests/xhr/resources/auth7/corsenabled.py
b/testing/web-platform/tests/xhr/resources/auth7/corsenabled.py
---- a/testing/web-platform/tests/xhr/resources/auth7/corsenabled.py
-+++ b/testing/web-platform/tests/xhr/resources/auth7/corsenabled.py
-@@ -1,20 +1,21 @@
--import imp
- import os
-
- from wptserve.utils import isomorphic_decode
-
-+from tools.wpt.utils import load_source
-+
- here = os.path.dirname(isomorphic_decode(__file__))
-
- def main(request, response):
- response.headers.set(b'Access-Control-Allow-Origin',
request.headers.get(b"origin"))
- response.headers.set(b'Access-Control-Allow-Credentials', b'true')
- response.headers.set(b'Access-Control-Allow-Methods', b'GET')
- response.headers.set(b'Access-Control-Allow-Headers', b'authorization,
x-user, x-pass')
- response.headers.set(b'Access-Control-Expose-Headers', b'x-challenge,
xhr-user, ses-user')
-- auth = imp.load_source(u"", os.path.join(here,
-+ auth = load_source(u"", os.path.join(here,
- os.pardir,
- u"authentication.py"))
- if request.method == u"OPTIONS":
- return b""
- else:
- return auth.main(request, response)
-diff --git
a/testing/web-platform/tests/xhr/resources/auth8/corsenabled-no-authorize.py
b/testing/web-platform/tests/xhr/resources/auth8/corsenabled-no-authorize.py
----
a/testing/web-platform/tests/xhr/resources/auth8/corsenabled-no-authorize.py
-+++
b/testing/web-platform/tests/xhr/resources/auth8/corsenabled-no-authorize.py
-@@ -1,20 +1,21 @@
--import imp
- import os
-
- from wptserve.utils import isomorphic_decode
-
-+from tools.wpt.utils import load_source
-+
- here = os.path.dirname(isomorphic_decode(__file__))
-
- def main(request, response):
- response.headers.set(b'Access-Control-Allow-Origin',
request.headers.get(b"origin"))
- response.headers.set(b'Access-Control-Allow-Credentials', b'true')
- response.headers.set(b'Access-Control-Allow-Methods', b'GET')
- response.headers.set(b'Access-Control-Allow-Headers', b'x-user, x-pass')
- response.headers.set(b'Access-Control-Expose-Headers', b'x-challenge,
xhr-user, ses-user')
-- auth = imp.load_source(u"", os.path.join(here,
-+ auth = load_source(u"", os.path.join(here,
- os.pardir,
- u"authentication.py"))
- if request.method == u"OPTIONS":
- return b""
- else:
- return auth.main(request, response)
-diff --git a/testing/web-platform/tests/xhr/resources/auth9/auth.py
b/testing/web-platform/tests/xhr/resources/auth9/auth.py
---- a/testing/web-platform/tests/xhr/resources/auth9/auth.py
-+++ b/testing/web-platform/tests/xhr/resources/auth9/auth.py
-@@ -1,12 +1,13 @@
--import imp
- import os
-
- from wptserve.utils import isomorphic_decode
-
-+from tools.wpt.utils import load_source
-+
- here = os.path.dirname(os.path.abspath(isomorphic_decode(__file__)))
-
- def main(request, response):
-- auth = imp.load_source(u"", os.path.join(here,
-+ auth = load_source(u"", os.path.join(here,
- u"..",
- u"authentication.py"))
- return auth.main(request, response)
-diff --git a/testing/web-platform/update/__init__.py
b/testing/web-platform/update/__init__.py
---- a/testing/web-platform/update/__init__.py
-+++ b/testing/web-platform/update/__init__.py
-@@ -1,23 +1,23 @@
- #!/usr/bin/env python
- #
- # This Source Code Form is subject to the terms of the Mozilla Public
- # License, v. 2.0. If a copy of the MPL was not distributed with this
- # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
--import imp
- import os
- import sys
-
-+from mozfile import load_source
- from mozlog import structuredlog
-
- here = os.path.split(__file__)[0]
-
--imp.load_source(
-+load_source(
- "localpaths", os.path.join(here, os.pardir, "tests", "tools",
"localpaths.py")
- )
-
- from wptrunner.update import WPTUpdate, setup_logging
- from wptrunner.update.base import exit_unclean
-
- from . import updatecommandline
- from .update import UpdateRunner
-diff --git a/tools/moztreedocs/mach_commands.py
b/tools/moztreedocs/mach_commands.py
---- a/tools/moztreedocs/mach_commands.py
-+++ b/tools/moztreedocs/mach_commands.py
-@@ -16,16 +16,17 @@ from functools import partial
- from pprint import pprint
-
- import mozpack.path as mozpath
- import sentry_sdk
- import yaml
- from mach.decorators import Command, CommandArgument, SubCommand
- from mach.registrar import Registrar
- from mozbuild.util import memoize
-+from mozfile import load_source
-
- here = os.path.abspath(os.path.dirname(__file__))
- topsrcdir = os.path.abspath(os.path.dirname(os.path.dirname(here)))
- DOC_ROOT = os.path.join(topsrcdir, "docs")
- BASE_LINK =
"http://gecko-docs.mozilla.org-l1.s3-website.us-west-2.amazonaws.com/";
-
-
- # Helps manage in-tree documentation.
-@@ -358,21 +359,18 @@ def manager():
- def toggle_no_autodoc():
- import moztreedocs
-
- moztreedocs._SphinxManager.NO_AUTODOC = True
-
-
- @memoize
- def _read_project_properties():
-- import imp
--
- path = os.path.normpath(manager().conf_py_path)
-- with open(path, "r") as fh:
-- conf = imp.load_module("doc_conf", fh, path, (".py", "r",
imp.PY_SOURCE))
-+ conf = load_source("doc_conf", path)
-
- # Prefer the Mozilla project name, falling back to Sphinx's
- # default variable if it isn't defined.
- project = getattr(conf, "moz_project_name", None)
- if not project:
- project = conf.project.replace(" ", "_")
-
- return {"project": project, "version": getattr(conf, "version", None)}
-diff --git a/tools/tryselect/test/test_mozharness_integration.py
b/tools/tryselect/test/test_mozharness_integration.py
---- a/tools/tryselect/test/test_mozharness_integration.py
-+++ b/tools/tryselect/test/test_mozharness_integration.py
-@@ -1,18 +1,18 @@
- # This Source Code Form is subject to the terms of the Mozilla Public
- # License, v. 2.0. If a copy of the MPL was not distributed with this
- # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
--import imp
- import json
- import os
-
- import mozunit
- import pytest
-+from mozfile import load_source
- from tryselect.tasks import build, resolve_tests_by_suite
-
- MOZHARNESS_SCRIPTS = {
- "android_emulator_unittest": {
- "class_name": "AndroidEmulatorTest",
- "configs": [
- "android/android_common.py",
- ],
-@@ -50,20 +50,20 @@ MOZHARNESS_SCRIPTS = {
- }
- """A suite being listed in a script's `xfail` list means it won't work
- properly with MOZHARNESS_TEST_PATHS (the mechanism |mach try fuzzy <path>|
- uses).
- """
-
-
- def get_mozharness_test_paths(name):
-- scriptdir = os.path.join(build.topsrcdir, "testing", "mozharness",
"scripts")
--
-- files = imp.find_module(name, [scriptdir])
-- mod = imp.load_module("scripts.{}".format(name), *files)
-+ scriptdir = os.path.join(build.topsrcdir, "testing", "mozharness")
-+ mod = load_source(
-+ "scripts." + name, os.path.join(scriptdir, "scripts", name + ".py")
-+ )
-
- class_name = MOZHARNESS_SCRIPTS[name]["class_name"]
- cls = getattr(mod, class_name)
- return cls(require_config_file=False)._get_mozharness_test_paths
-
-
- @pytest.fixture(scope="module")
- def all_suites():
-@@ -77,23 +77,25 @@ def all_suites():
- all_suites.append(
- {"flavor": flavor, "subsuite": subsuite, "srcdir_relpath":
"test"}
- )
-
- return all_suites
-
-
- def generate_suites_from_config(path):
-- configdir = os.path.join(build.topsrcdir, "testing", "mozharness",
"configs")
--
- parent, name = os.path.split(path)
- name = os.path.splitext(name)[0]
-
-- files = imp.find_module("{}".format(name), [os.path.join(configdir,
parent)])
-- mod = imp.load_module("config.{}".format(name), *files)
-+ configdir = os.path.join(
-+ build.topsrcdir, "testing", "mozharness", "configs", parent
-+ )
-+
-+ mod = load_source(name, os.path.join(configdir, name + ".py"))
-+
- config = mod.config
-
- for category in sorted(config["suite_definitions"]):
- key = "all_{}_suites".format(category)
- if key not in config:
- yield category,
- continue
-
-
diff --git
a/http/firefox/patches-for-python-3.12/0002-setuptools-update.patch
b/http/firefox/patches-for-python-3.12/0002-setuptools-update.patch
deleted file mode 100644
index 3b95eab..0000000
--- a/http/firefox/patches-for-python-3.12/0002-setuptools-update.patch
+++ /dev/null
@@ -1,85455 +0,0 @@
-
-# HG changeset patch
-# User Kagami Sascha Rosylight <krosylight AT mozilla.com>
-# Date 1697058122 0
-# Node ID 4c2f3a0a79343f6484997d369d5151a2359bca21
-# Parent eac298199d7b372295fb09c9d194b583dc9af12c
-Bug 1858260 - Upgrade setuptools to 68.0.0
r=sergesanspaille,mach-reviewers,ahal
-
-68.0.0 is the last version of setuptools that still supports Python 3.7,
currently our minimum supported Python version. (It's dropped at 68.1.0 as
it's not considered as a breaking change, which is surprising:
https://setuptools.pypa.io/en/stable/history.html)
-
-Differential Revision: https://phabricator.services.mozilla.com/D190674
-
-diff --git a/third_party/python/requirements.txt
b/third_party/python/requirements.txt
---- a/third_party/python/requirements.txt
-+++ b/third_party/python/requirements.txt
-@@ -524,19 +524,19 @@ requests==2.31.0 ; python_version >= "3.
- responses==0.10.6 ; python_version >= "3.7" and python_version < "4.0" \
-
--hash=sha256:502d9c0c8008439cfcdef7e251f507fcfdd503b56e8c0c87c3c3e3393953f790
\
-
--hash=sha256:97193c0183d63fba8cd3a041c75464e4b09ea0aff6328800d1546598567dde0b
- rsa==3.1.4 ; python_version >= "3.7" and python_version < "4.0" \
-
--hash=sha256:e2b0b05936c276b1edd2e1525553233b666df9e29b5c3ba223eed738277c82a0
- sentry-sdk==0.14.3 ; python_version >= "3.7" and python_version < "4.0" \
-
--hash=sha256:23808d571d2461a4ce3784ec12bbee5bdb8c026c143fe79d36cef8a6d653e71f
\
-
--hash=sha256:bb90a4e19c7233a580715fc986cc44be2c48fc10b31e71580a2037e1c94b6950
--setuptools==51.2.0 ; python_version >= "3.7" and python_version < "4.0" \
--
--hash=sha256:56948bf25c682e166cf2bfe7c1ad63e5745849b50d1ae7b0f8bff5decdcf34f2
\
--
--hash=sha256:7ef59b1790b3491f8d321f531eccc11517a07a4d7637e498465cd834d80d4c2c
-+setuptools==68.0.0 ; python_version >= "3.7" and python_version < "4.0" \
-+
--hash=sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f
\
-+
--hash=sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235
- six==1.13.0 ; python_version >= "3.7" and python_version < "4.0" \
-
--hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd
\
-
--hash=sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66
- slugid==2.0.0 ; python_version >= "3.7" and python_version < "4.0" \
-
--hash=sha256:a950d98b72691178bdd4d6c52743c4a2aa039207cf7a97d71060a111ff9ba297
\
-
--hash=sha256:aec8b0e01c4ad32e38e12d609eab3ec912fd129aaf6b2ded0199b56a5f8fd67c
- taskcluster-taskgraph==6.3.0 ; python_version >= "3.7" and python_version <
"4.0" \
-
--hash=sha256:43ce187215ab8658c06ad80f46c4606ce51b9986f4365d541416eecf9d6a2c28
\
-diff --git a/third_party/python/setuptools/_distutils_hack/__init__.py
b/third_party/python/setuptools/_distutils_hack/__init__.py
---- a/third_party/python/setuptools/_distutils_hack/__init__.py
-+++ b/third_party/python/setuptools/_distutils_hack/__init__.py
-@@ -1,123 +1,227 @@
-+# don't import any costly modules
- import sys
- import os
--import re
--import importlib
--import warnings
-
-
- is_pypy = '__pypy__' in sys.builtin_module_names
-
-
- 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
-+ import warnings
-+
- 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.")
-+ "that setuptools is always imported before distutils."
-+ )
-
-
- def clear_distutils():
- if 'distutils' not in sys.modules:
- return
-+ import warnings
-+
- warnings.warn("Setuptools is replacing distutils.")
-- mods = [name for name in sys.modules if re.match(r'distutils\b', name)]
-+ mods = [
-+ name
-+ for name in sys.modules
-+ if name == "distutils" or name.startswith("distutils.")
-+ ]
- 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')
-+ which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'local')
- return which == 'local'
-
-
- def ensure_local_distutils():
-+ import importlib
-+
- clear_distutils()
-- distutils = importlib.import_module('setuptools._distutils')
-- distutils.__name__ = 'distutils'
-- sys.modules['distutils'] = distutils
-
-- # sanity check that submodules load as expected
-+ # With the DistutilsMetaFinder in place,
-+ # perform an import to cause distutils to be
-+ # loaded from setuptools._distutils. Ref #2906.
-+ with shim():
-+ importlib.import_module('distutils')
-+
-+ # check that submodules load as expected
- core = importlib.import_module('distutils.core')
- assert '_distutils' in core.__file__, core.__file__
-+ assert 'setuptools._distutils.log' not in sys.modules
-
-
- 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 _TrivialRe:
-+ def __init__(self, *patterns):
-+ self._patterns = patterns
-+
-+ def match(self, string):
-+ return all(pat in string for pat in self._patterns)
-+
-+
- class DistutilsMetaFinder:
- def find_spec(self, fullname, path, target=None):
-- if path is not None:
-+ # optimization: only consider top level modules and those
-+ # found in the CPython test suite.
-+ if path is not None and not fullname.startswith('test.'):
- return
-
- method_name = 'spec_for_{fullname}'.format(**locals())
- method = getattr(self, method_name, lambda: None)
- return method()
-
- def spec_for_distutils(self):
-+ if self.is_cpython():
-+ return
-+
-+ import importlib
- import importlib.abc
- import importlib.util
-
-- class DistutilsLoader(importlib.abc.Loader):
-+ try:
-+ mod = importlib.import_module('setuptools._distutils')
-+ except Exception:
-+ # There are a couple of cases where setuptools._distutils
-+ # may not be present:
-+ # - An older Setuptools without a local distutils is
-+ # taking precedence. Ref #2957.
-+ # - Path manipulation during sitecustomize removes
-+ # setuptools from the path but only after the hook
-+ # has been loaded. Ref #2980.
-+ # In either case, fall back to stdlib behavior.
-+ return
-
-+ class DistutilsLoader(importlib.abc.Loader):
- def create_module(self, spec):
-- return importlib.import_module('setuptools._distutils')
-+ mod.__name__ = 'distutils'
-+ return mod
-
- def exec_module(self, module):
- pass
-
-- return importlib.util.spec_from_loader('distutils',
DistutilsLoader())
-+ return importlib.util.spec_from_loader(
-+ 'distutils', DistutilsLoader(), origin=mod.__file__
-+ )
-+
-+ @staticmethod
-+ def is_cpython():
-+ """
-+ Suppress supplying distutils for CPython (build and tests).
-+ Ref #2965 and #3007.
-+ """
-+ return os.path.isfile('pybuilddir.txt')
-
- def spec_for_pip(self):
- """
- Ensure stdlib distutils when running under pip.
- See pypa/pip#8761 for rationale.
- """
-- if self.pip_imported_during_build():
-+ if sys.version_info >= (3, 12) or self.pip_imported_during_build():
- return
- clear_distutils()
- self.spec_for_distutils = lambda: None
-
-- @staticmethod
-- def pip_imported_during_build():
-+ @classmethod
-+ def pip_imported_during_build(cls):
- """
- 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)
-+ cls.frame_file_is_setup(frame) for frame, line in
traceback.walk_stack(None)
- )
-
-+ @staticmethod
-+ def frame_file_is_setup(frame):
-+ """
-+ Return True if the indicated frame suggests a setup.py file.
-+ """
-+ # some frames may not have __file__ (#2940)
-+ return frame.f_globals.get('__file__', '').endswith('setup.py')
-+
-+ def spec_for_sensitive_tests(self):
-+ """
-+ Ensure stdlib distutils when running select tests under CPython.
-+
-+ python/cpython#91169
-+ """
-+ clear_distutils()
-+ self.spec_for_distutils = lambda: None
-+
-+ sensitive_tests = (
-+ [
-+ 'test.test_distutils',
-+ 'test.test_peg_generator',
-+ 'test.test_importlib',
-+ ]
-+ if sys.version_info < (3, 10)
-+ else [
-+ 'test.test_distutils',
-+ ]
-+ )
-+
-+
-+for name in DistutilsMetaFinder.sensitive_tests:
-+ setattr(
-+ DistutilsMetaFinder,
-+ f'spec_for_{name}',
-+ DistutilsMetaFinder.spec_for_sensitive_tests,
-+ )
-+
-
- DISTUTILS_FINDER = DistutilsMetaFinder()
-
-
- def add_shim():
-+ DISTUTILS_FINDER in sys.meta_path or insert_shim()
-+
-+
-+class shim:
-+ def __enter__(self):
-+ insert_shim()
-+
-+ def __exit__(self, exc, value, tb):
-+ _remove_shim()
-+
-+
-+def insert_shim():
- sys.meta_path.insert(0, DISTUTILS_FINDER)
-
-
--def remove_shim():
-+def _remove_shim():
- try:
- sys.meta_path.remove(DISTUTILS_FINDER)
- except ValueError:
- pass
-+
-+
-+if sys.version_info < (3, 12):
-+ # DistutilsMetaFinder can only be disabled in Python < 3.12 (PEP 632)
-+ remove_shim = _remove_shim
-diff --git a/third_party/python/setuptools/distutils-precedence.pth
b/third_party/python/setuptools/distutils-precedence.pth
---- a/third_party/python/setuptools/distutils-precedence.pth
-+++ b/third_party/python/setuptools/distutils-precedence.pth
-@@ -1,1 +1,1 @@
--import os; var = 'SETUPTOOLS_USE_DISTUTILS'; enabled = os.environ.get(var,
'stdlib') == 'local'; enabled and __import__('_distutils_hack').add_shim();
-+import os; var = 'SETUPTOOLS_USE_DISTUTILS'; enabled = os.environ.get(var,
'local') == 'local'; enabled and __import__('_distutils_hack').add_shim();
-diff --git a/third_party/python/setuptools/easy_install.py
b/third_party/python/setuptools/easy_install.py
-deleted file mode 100644
---- a/third_party/python/setuptools/easy_install.py
-+++ /dev/null
-@@ -1,5 +0,0 @@
--"""Run the EasyInstall command"""
--
--if __name__ == '__main__':
-- from setuptools.command.easy_install import main
-- main()
-diff --git a/third_party/python/setuptools/pkg_resources/__init__.py
b/third_party/python/setuptools/pkg_resources/__init__.py
---- a/third_party/python/setuptools/pkg_resources/__init__.py
-+++ b/third_party/python/setuptools/pkg_resources/__init__.py
-@@ -7,16 +7,19 @@ subdirectory thereof. The package resou
- 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.
-+
-+This module is deprecated. Users are directed to :mod:`importlib.resources`,
-+:mod:`importlib.metadata` and :pypi:`packaging` instead.
- """
-
- import sys
- import os
- import io
- import time
- import re
- import types
-@@ -29,17 +32,16 @@ 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
-@@ -49,39 +51,50 @@ except ImportError:
-
- 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.jaraco.text import (
-+ yield_lines,
-+ drop_comment,
-+ join_continuation,
-+)
-+
-+from pkg_resources.extern import platformdirs
- 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')
-+__import__('pkg_resources.extern.packaging.utils')
-
- 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
-@@ -97,28 +110,35 @@ iter_entry_points = None
- resource_listdir = None
- resource_filename = None
- resource_exists = None
- _distribution_finders = None
- _namespace_handlers = None
- _namespace_packages = None
-
-
-+warnings.warn(
-+ "pkg_resources is deprecated as an API. "
-+ "See https://setuptools.pypa.io/en/latest/pkg_resources.html";,
-+ DeprecationWarning,
-+ stacklevel=2
-+)
-+
-+
-+_PEP440_FALLBACK =
re.compile(r"^v?(?P<safe>(?:[0-9]+!)?[0-9]+(?:\.[0-9]+)*)", re.I)
-+
-+
- 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:
-- return packaging.version.LegacyVersion(v)
-+parse_version = packaging.version.Version
-
-
- _state_vars = {}
-
-
- def _declare_state(vartype, **kw):
- globals().update(kw)
- _state_vars.update(dict.fromkeys(kw, vartype))
-@@ -180,61 +200,97 @@ def get_supported_platform():
- 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',
-+ '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',
--
-+ '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',
-+ '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',
--
-+ 'Environment',
-+ 'WorkingSet',
-+ 'ResourceManager',
-+ 'Distribution',
-+ 'Requirement',
-+ 'EntryPoint',
- # Exceptions
-- 'ResolutionError', 'VersionConflict', 'DistributionNotFound',
-- 'UnknownExtra', 'ExtractionError',
--
-+ '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',
--
-+ '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',
--
-+ 'ensure_directory',
-+ 'normalize_path',
- # Distribution "precedence" constants
-- 'EGG_DIST', 'BINARY_DIST', 'SOURCE_DIST', 'CHECKOUT_DIST',
'DEVELOP_DIST',
--
-+ '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',
--
-+ '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',
-+ 'run_main',
-+ 'AvailableDistributions',
- ]
-
-
- class ResolutionError(Exception):
- """Abstract base for dependency resolution errors"""
-
- def __repr__(self):
- return self.__class__.__name__ + repr(self.args)
-@@ -283,18 +339,20 @@ class ContextualVersionConflict(VersionC
- @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}")
-+ _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]
-@@ -378,17 +436,18 @@ def get_build_platform():
- 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]),
-+ 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
-
-@@ -419,25 +478,28 @@ def compatible_platforms(provided, requi
- 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":
-+ 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):
-+ 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
-
-@@ -489,18 +551,18 @@ class IMetadataProvider:
- """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."""
-+ 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):
-@@ -538,16 +600,17 @@ class IResourceProvider(IMetadataProvide
- 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.normalized_to_canonical_keys = {}
- self.callbacks = []
-
- if entries is None:
- entries = sys.path
-
- for entry in entries:
- self.add_entry(entry)
-
-@@ -618,16 +681,24 @@ class WorkingSet:
- 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 None:
-+ canonical_key = self.normalized_to_canonical_keys.get(req.key)
-+
-+ if canonical_key is not None:
-+ req.key = canonical_key
-+ dist = self.by_key.get(canonical_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`
-
-@@ -686,24 +757,32 @@ class WorkingSet:
- 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
-+ normalized_name = packaging.utils.canonicalize_name(dist.key)
-+ self.normalized_to_canonical_keys[normalized_name] = dist.key
- if dist.key not in keys:
- keys.append(dist.key)
- if dist.key not in keys2:
- keys2.append(dist.key)
- self._added_new(dist)
-
-- def resolve(self, requirements, env=None, installer=None,
-- replace_conflicting=False, extras=None):
-+ def resolve(
-+ self,
-+ requirements,
-+ env=None,
-+ installer=None,
-+ 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
-@@ -742,60 +821,66 @@ class WorkingSet:
- 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)
-+ dist = self._resolve_dist(
-+ req, best, replace_conflicting, env, installer,
required_by, to_activate
-+ )
-
- # 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):
-+ def _resolve_dist(
-+ self, req, best, replace_conflicting, env, installer, required_by,
to_activate
-+ ):
-+ 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)
-+ return dist
-+
-+ 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
-@@ -838,19 +923,17 @@ class WorkingSet:
- 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
-@@ -904,25 +987,29 @@ class WorkingSet:
- 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[:]
-+ self.entries[:],
-+ self.entry_keys.copy(),
-+ self.by_key.copy(),
-+ self.normalized_to_canonical_keys.copy(),
-+ self.callbacks[:],
- )
-
-- def __setstate__(self, e_k_b_c):
-- entries, keys, by_key, callbacks = e_k_b_c
-+ def __setstate__(self, e_k_b_n_c):
-+ entries, keys, by_key, normalized_to_canonical_keys, callbacks =
e_k_b_n_c
- self.entries = entries[:]
- self.entry_keys = keys.copy()
- self.by_key = by_key.copy()
-+ self.normalized_to_canonical_keys =
normalized_to_canonical_keys.copy()
- self.callbacks = callbacks[:]
-
-
- class _ReqExtras(dict):
- """
- Map each requirement to the extras that demanded it.
- """
-
-@@ -940,18 +1027,18 @@ class _ReqExtras(dict):
- )
- 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):
-+ 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
-@@ -1008,26 +1095,24 @@ class Environment:
- 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
-- """
-+ """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):
-+ 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
-@@ -1104,30 +1189,29 @@ class ExtractionError(RuntimeError):
-
- 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
-- )
-+ 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):
-@@ -1139,42 +1223,42 @@ class ResourceManager:
- 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
-- )
-+ 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("""
-+ 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()
-+ """
-+ ).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`
-@@ -1263,19 +1347,17 @@ class ResourceManager:
- 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"
-- )
-+ 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
-@@ -1289,19 +1371,18 @@ class ResourceManager:
-
-
- 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')
-+ return os.environ.get('PYTHON_EGG_CACHE') or
platformdirs.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
'-'.
- """
-@@ -1315,16 +1396,48 @@ def safe_version(version):
- 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 _forgiving_version(version):
-+ """Fallback when ``safe_version`` is not safe enough
-+ >>> parse_version(_forgiving_version('0.23ubuntu1'))
-+ <Version('0.23.dev0+sanitized.ubuntu1')>
-+ >>> parse_version(_forgiving_version('0.23-'))
-+ <Version('0.23.dev0+sanitized')>
-+ >>> parse_version(_forgiving_version('0.-_'))
-+ <Version('0.dev0+sanitized')>
-+ >>> parse_version(_forgiving_version('42.+?1'))
-+ <Version('42.dev0+sanitized.1')>
-+ >>> parse_version(_forgiving_version('hello world'))
-+ <Version('0.dev0+sanitized.hello.world')>
-+ """
-+ version = version.replace(' ', '.')
-+ match = _PEP440_FALLBACK.search(version)
-+ if match:
-+ safe = match["safe"]
-+ rest = version[len(safe):]
-+ else:
-+ safe = "0"
-+ rest = version
-+ local = f"sanitized.{_safe_segment(rest)}".strip(".")
-+ return f"{safe}.dev0+{local}"
-+
-+
-+def _safe_segment(segment):
-+ """Convert an arbitrary string into a safe segment"""
-+ segment = re.sub('[^A-Za-z0-9.]+', '-', segment)
-+ segment = re.sub('-[^A-Za-z0-9]+', '-', segment)
-+ return re.sub(r'\.[^A-Za-z0-9]+', '.', segment).strip(".-")
-+
-+
- 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()
-
-@@ -1428,32 +1541,37 @@ class NullProvider:
- 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 {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
-+ 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"
- )
-@@ -1473,17 +1591,17 @@ class NullProvider:
- 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.readthedocs.io/en/latest/pkg_resources.html#basic-resource-access
-+
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')
-@@ -1523,35 +1641,34 @@ is not allowed.
- 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)
-+ 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(
-+ issue_warning(
- 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()'"
- )
-@@ -1570,17 +1687,17 @@ def _parents(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)
-+ super().__init__(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)
-@@ -1607,17 +1724,20 @@ class DefaultProvider(EggProvider):
- 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',
-+ 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()
-
-
-@@ -1667,16 +1787,17 @@ class ZipManifests(dict):
-
- 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
-@@ -1690,40 +1811,36 @@ class MemoizedZipManifests(ZipManifests)
-
- class ZipProvider(EggProvider):
- """Resource support for zips and eggs"""
-
- eagers = None
- _zip_manifests = MemoizedZipManifests()
-
- def __init__(self, module):
-- EggProvider.__init__(self, module)
-+ super().__init__(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)
-- )
-+ 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)
-- )
-+ 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(
-@@ -1741,36 +1858,32 @@ class ZipProvider(EggProvider):
- 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
-
-- def _extract_resource(self, manager, zip_path):
--
-+ # 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)
-- )
-+ 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')
-+ 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)
-- )
-+ 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),
- )
-@@ -1979,122 +2092,82 @@ def find_eggs_in_zip(importer, path_item
- # 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'):
-+ 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', 'foo', 'bar']
-- >>> 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 _by_version(name):
-- """
-- Parse each component of the filename
-- """
-- name, ext = os.path.splitext(name)
-- parts = itertools.chain(name.split('-'), [ext])
-- return [packaging.version.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')
-- )
-+ 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)
-- )
-+ entries = (os.path.join(path_item, child) for child in
safe_listdir(path_item))
-
- # scan for .egg and .egg-info in directory
-- path_item_entries = _by_version_descending(filtered)
-- for entry in path_item_entries:
-+ for entry in sorted(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_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()
-+ 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):
-@@ -2119,17 +2192,20 @@ def distributions_from_metadata(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,
-+ root,
-+ entry,
-+ metadata,
-+ precedence=DEVELOP_DIST,
- )
-
-
- def non_empty_lines(path):
- """
- Yield non-empty lines from file at path
- """
- with open(path) as f:
-@@ -2141,27 +2217,26 @@ def non_empty_lines(path):
-
- 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
-+ 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)
-+if hasattr(pkgutil, 'ImpImporter'):
-+ register_finder(pkgutil.ImpImporter, find_on_path)
-+
-+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
-
-@@ -2184,22 +2259,24 @@ 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
-+ spec = importer.find_spec(packageName)
- except AttributeError:
- # capture warnings due to #1111
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- loader = importer.find_module(packageName)
-+ else:
-+ loader = spec.loader if spec else None
-
- 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)
-@@ -2247,16 +2324,25 @@ def _rebuild_mod_path(orig_path, package
- module.__path__[:] = new_path
- else:
- module.__path__ = new_path
-
-
- def declare_namespace(packageName):
- """Declare that package 'packageName' is a namespace package"""
-
-+ msg = (
-+ f"Deprecated call to
`pkg_resources.declare_namespace({packageName!r})`.\n"
-+ "Implementing implicit namespace packages (as specified in PEP 420)
"
-+ "is preferred to `pkg_resources.declare_namespace`. "
-+ "See https://setuptools.pypa.io/en/latest/references/";
-+ "keywords.html#keyword-namespace-packages"
-+ )
-+ warnings.warn(msg, DeprecationWarning, stacklevel=2)
-+
- _imp.acquire_lock()
- try:
- if packageName in _namespace_packages:
- return
-
- path = sys.path
- parent, _, _ = packageName.rpartition('.')
-
-@@ -2303,34 +2389,33 @@ def file_ns_handler(importer, path_item,
- 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)
-+if hasattr(pkgutil, 'ImpImporter'):
-+ 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)
-+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))))
-+ 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
-@@ -2351,54 +2436,39 @@ 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)
-+ 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'))
-+ 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 yield_lines(strs):
-- """Yield non-empty/non-comment lines of a string or sequence"""
-- if isinstance(strs, str):
-- for s in strs.splitlines():
-- s = s.strip()
-- # skip blank lines/comments
-- if s and not s.startswith('#'):
-- yield s
-- else:
-- for ss in strs:
-- for s in yield_lines(ss):
-- yield s
--
--
- MODULE = re.compile(r"\w+(\.\w+)*$").match
- EGG_NAME = re.compile(
- r"""
- (?P<name>[^-]+) (
- -(?P<ver>[^-]+) (
- -py(?P<pyver>[^-]+) (
- -(?P<plat>.+)
- )?
-@@ -2541,32 +2611,41 @@ class EntryPoint:
- 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,
-+ 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
-@@ -2579,27 +2658,32 @@ class Distribution:
- 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
-+ 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._forgiving_parsed_version,
- self.precedence,
- self.key,
- self.location,
- self.py_version or '',
- self.platform or '',
- )
-
- def __hash__(self):
-@@ -2636,57 +2720,64 @@ class Distribution:
- 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)
-+ try:
-+ self._parsed_version = parse_version(self.version)
-+ except packaging.version.InvalidVersion as ex:
-+ info = f"(package: {self.project_name})"
-+ if hasattr(ex, "add_note"):
-+ ex.add_note(info) # PEP 678
-+ raise
-+ raise packaging.version.InvalidVersion(f"{str(ex)} {info}")
from None
-
- 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 _forgiving_parsed_version(self):
-+ try:
-+ return self.parsed_version
-+ except packaging.version.InvalidVersion as ex:
-+ self._parsed_version =
parse_version(_forgiving_version(self.version))
-+
-+ notes = "\n".join(getattr(ex, "__notes__", [])) # PEP 678
-+ msg = f"""!!\n\n
-+
*************************************************************************
-+ {str(ex)}\n{notes}
-+
-+ This is a long overdue deprecation.
-+ For the time being, `pkg_resources` will use
`{self._parsed_version}`
-+ as a replacement to avoid breaking existing environments,
-+ but no future compatibility is guaranteed.
-+
-+ If you maintain package {self.project_name} you should implement
-+ the relevant changes to adequate the project to PEP 440
immediately.
-+
*************************************************************************
-+ \n\n!!
-+ """
-+ warnings.warn(msg, DeprecationWarning)
-+
-+ return self._parsed_version
-
- @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)
-+ 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
-@@ -2705,18 +2796,17 @@ class Distribution:
- 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)
-+ 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
-
-@@ -2778,18 +2868,19 @@ class Distribution:
- 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
-+ 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:
-@@ -2809,27 +2900,23 @@ class Distribution:
- """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('_')
-- )
-+ | 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
-+ _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)
-@@ -2854,17 +2941,18 @@ class Distribution:
- 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)
-
-- def insert_on(self, path, loc=None, replace=False):
-+ # 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.
-@@ -2930,36 +3018,43 @@ class Distribution:
- 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):
-+ 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)):
-+ 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
-+ except SystemError:
-+ # TODO: remove this except clause when python/cpython#103632 is
fixed.
-+ 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)
-@@ -2989,16 +3084,17 @@ class EggInfoDistribution(Distribution):
- 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
-@@ -3024,22 +3120,22 @@ class DistInfoDistribution(Distribution)
- 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))
-+ common = types.MappingProxyType(dict.fromkeys(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)
-+ dm[s_extra] = [r for r in reqs_for_extra(extra) if r not in
common]
-
- return dm
-
-
- _distributionImpl = {
- '.egg': Distribution,
- '.egg-info': EggInfoDistribution,
- '.dist-info': DistInfoDistribution,
-@@ -3055,65 +3151,48 @@ def issue_warning(*args, **kw):
- 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`
-+ """
-+ 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)
-+ return map(Requirement, join_continuation(map(drop_comment,
yield_lines(strs))))
-
-
- 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.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
-- )
-+ 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
-@@ -3128,17 +3207,17 @@ class Requirement(packaging.requirements
- def __hash__(self):
- return self.__hash
-
- def __repr__(self):
- return "Requirement.parse(%r)" % str(self)
-
- @staticmethod
- def parse(s):
-- req, = parse_requirements(s)
-+ (req,) = parse_requirements(s)
- return req
-
-
- def _always_object(classes):
- """
- Ensure object appears in the mro even
- for old-style classes.
- """
-@@ -3231,16 +3310,25 @@ def _initialize(g=globals()):
- 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
-@@ -3257,29 +3345,17 @@ def _initialize_master_working_set():
- 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
-- )
-+ 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())
--
--
--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.
-- """
-diff --git a/third_party/python/setuptools/pkg_resources/_vendor/appdirs.py
b/third_party/python/setuptools/pkg_resources/_vendor/appdirs.py
-deleted file mode 100644
---- a/third_party/python/setuptools/pkg_resources/_vendor/appdirs.py
-+++ /dev/null
-@@ -1,608 +0,0 @@
--#!/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 <http://github.com/ActiveState/appdirs> 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 "<major>.<minor>".
-- 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
--
<http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
-- for a discussion of issues.
--
-- Typical user data directories are:
-- Mac OS X: ~/Library/Application Support/<AppName>
-- Unix: ~/.local/share/<AppName> # or in
$XDG_DATA_HOME, if defined
-- Win XP (not roaming): C:\Documents and
Settings\<username>\Application Data\<AppAuthor>\<AppName>
-- Win XP (roaming): C:\Documents and Settings\<username>\Local
Settings\Application Data\<AppAuthor>\<AppName>
-- Win 7 (not roaming):
C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>
-- Win 7 (roaming):
C:\Users\<username>\AppData\Roaming\<AppAuthor>\<AppName>
--
-- For Unix, we follow the XDG spec and support $XDG_DATA_HOME.
-- That means, by default "~/.local/share/<AppName>".
-- """
-- 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 "<major>.<minor>".
-- 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/<AppName>',
-- if XDG_DATA_DIRS is not set
--
-- Typical site data directories are:
-- Mac OS X: /Library/Application Support/<AppName>
-- Unix: /usr/local/share/<AppName> or /usr/share/<AppName>
-- Win XP: C:\Documents and Settings\All Users\Application
Data\<AppAuthor>\<AppName>
-- Vista: (Fail! "C:\ProgramData" is a hidden *system* directory
on Vista.)
-- Win 7: C:\ProgramData\<AppAuthor>\<AppName> # 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 "<major>.<minor>".
-- 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
--
<http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
-- for a discussion of issues.
--
-- Typical user config directories are:
-- Mac OS X: same as user_data_dir
-- Unix: ~/.config/<AppName> # 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/<AppName>".
-- """
-- 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 "<major>.<minor>".
-- 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/<AppName>', if XDG_CONFIG_DIRS is not set
--
-- Typical site config directories are:
-- Mac OS X: same as site_data_dir
-- Unix: /etc/xdg/<AppName> or $XDG_CONFIG_DIRS[i]/<AppName> 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 "<major>.<minor>".
-- 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/<AppName>
-- Unix: ~/.cache/<AppName> (XDG default)
-- Win XP: C:\Documents and Settings\<username>\Local
Settings\Application Data\<AppAuthor>\<AppName>\Cache
-- Vista:
C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\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\<ProfileName>\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 "<major>.<minor>".
-- 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
--
<http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
-- for a discussion of issues.
--
-- Typical user state directories are:
-- Mac OS X: same as user_data_dir
-- Unix: ~/.local/state/<AppName> # or in $XDG_STATE_HOME, if
defined
-- Win *: same as user_data_dir
--
-- For Unix, we follow this Debian proposal
<https://wiki.debian.org/XDGBaseDirectorySpecification#state>
-- to extend the XDG spec and support $XDG_STATE_HOME.
--
-- That means, by default "~/.local/state/<AppName>".
-- """
-- 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 "<major>.<minor>".
-- 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/<AppName>
-- Unix: ~/.cache/<AppName>/log # or under $XDG_CACHE_HOME if
defined
-- Win XP: C:\Documents and Settings\<username>\Local
Settings\Application Data\<AppAuthor>\<AppName>\Logs
-- Vista:
C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\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
-- # <http://bugs.activestate.com/show_bug.cgi?id=85099>.
-- 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
-- # <http://bugs.activestate.com/show_bug.cgi?id=85099>.
-- 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
-- # <http://bugs.activestate.com/show_bug.cgi?id=85099>.
-- 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/third_party/python/setuptools/pkg_resources/_vendor/importlib_resources/__init__.py

b/third_party/python/setuptools/pkg_resources/_vendor/importlib_resources/__init__.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/importlib_resources/__init__.py
-@@ -0,0 +1,36 @@
-+"""Read resources contained within a package."""
-+
-+from ._common import (
-+ as_file,
-+ files,
-+ Package,
-+)
-+
-+from ._legacy import (
-+ contents,
-+ open_binary,
-+ read_binary,
-+ open_text,
-+ read_text,
-+ is_resource,
-+ path,
-+ Resource,
-+)
-+
-+from .abc import ResourceReader
-+
-+
-+__all__ = [
-+ 'Package',
-+ 'Resource',
-+ 'ResourceReader',
-+ 'as_file',
-+ 'contents',
-+ 'files',
-+ 'is_resource',
-+ 'open_binary',
-+ 'open_text',
-+ 'path',
-+ 'read_binary',
-+ 'read_text',
-+]
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/importlib_resources/_adapters.py

b/third_party/python/setuptools/pkg_resources/_vendor/importlib_resources/_adapters.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/importlib_resources/_adapters.py
-@@ -0,0 +1,170 @@
-+from contextlib import suppress
-+from io import TextIOWrapper
-+
-+from . import abc
-+
-+
-+class SpecLoaderAdapter:
-+ """
-+ Adapt a package spec to adapt the underlying loader.
-+ """
-+
-+ def __init__(self, spec, adapter=lambda spec: spec.loader):
-+ self.spec = spec
-+ self.loader = adapter(spec)
-+
-+ def __getattr__(self, name):
-+ return getattr(self.spec, name)
-+
-+
-+class TraversableResourcesLoader:
-+ """
-+ Adapt a loader to provide TraversableResources.
-+ """
-+
-+ def __init__(self, spec):
-+ self.spec = spec
-+
-+ def get_resource_reader(self, name):
-+ return CompatibilityFiles(self.spec)._native()
-+
-+
-+def _io_wrapper(file, mode='r', *args, **kwargs):
-+ if mode == 'r':
-+ return TextIOWrapper(file, *args, **kwargs)
-+ elif mode == 'rb':
-+ return file
-+ raise ValueError(
-+ "Invalid mode value '{}', only 'r' and 'rb' are
supported".format(mode)
-+ )
-+
-+
-+class CompatibilityFiles:
-+ """
-+ Adapter for an existing or non-existent resource reader
-+ to provide a compatibility .files().
-+ """
-+
-+ class SpecPath(abc.Traversable):
-+ """
-+ Path tied to a module spec.
-+ Can be read and exposes the resource reader children.
-+ """
-+
-+ def __init__(self, spec, reader):
-+ self._spec = spec
-+ self._reader = reader
-+
-+ def iterdir(self):
-+ if not self._reader:
-+ return iter(())
-+ return iter(
-+ CompatibilityFiles.ChildPath(self._reader, path)
-+ for path in self._reader.contents()
-+ )
-+
-+ def is_file(self):
-+ return False
-+
-+ is_dir = is_file
-+
-+ def joinpath(self, other):
-+ if not self._reader:
-+ return CompatibilityFiles.OrphanPath(other)
-+ return CompatibilityFiles.ChildPath(self._reader, other)
-+
-+ @property
-+ def name(self):
-+ return self._spec.name
-+
-+ def open(self, mode='r', *args, **kwargs):
-+ return _io_wrapper(self._reader.open_resource(None), mode,
*args, **kwargs)
-+
-+ class ChildPath(abc.Traversable):
-+ """
-+ Path tied to a resource reader child.
-+ Can be read but doesn't expose any meaningful children.
-+ """
-+
-+ def __init__(self, reader, name):
-+ self._reader = reader
-+ self._name = name
-+
-+ def iterdir(self):
-+ return iter(())
-+
-+ def is_file(self):
-+ return self._reader.is_resource(self.name)
-+
-+ def is_dir(self):
-+ return not self.is_file()
-+
-+ def joinpath(self, other):
-+ return CompatibilityFiles.OrphanPath(self.name, other)
-+
-+ @property
-+ def name(self):
-+ return self._name
-+
-+ def open(self, mode='r', *args, **kwargs):
-+ return _io_wrapper(
-+ self._reader.open_resource(self.name), mode, *args, **kwargs
-+ )
-+
-+ class OrphanPath(abc.Traversable):
-+ """
-+ Orphan path, not tied to a module spec or resource reader.
-+ Can't be read and doesn't expose any meaningful children.
-+ """
-+
-+ def __init__(self, *path_parts):
-+ if len(path_parts) < 1:
-+ raise ValueError('Need at least one path part to construct
a path')
-+ self._path = path_parts
-+
-+ def iterdir(self):
-+ return iter(())
-+
-+ def is_file(self):
-+ return False
-+
-+ is_dir = is_file
-+
-+ def joinpath(self, other):
-+ return CompatibilityFiles.OrphanPath(*self._path, other)
-+
-+ @property
-+ def name(self):
-+ return self._path[-1]
-+
-+ def open(self, mode='r', *args, **kwargs):
-+ raise FileNotFoundError("Can't open orphan path")
-+
-+ def __init__(self, spec):
-+ self.spec = spec
-+
-+ @property
-+ def _reader(self):
-+ with suppress(AttributeError):
-+ return self.spec.loader.get_resource_reader(self.spec.name)
-+
-+ def _native(self):
-+ """
-+ Return the native reader if it supports files().
-+ """
-+ reader = self._reader
-+ return reader if hasattr(reader, 'files') else self
-+
-+ def __getattr__(self, attr):
-+ return getattr(self._reader, attr)
-+
-+ def files(self):
-+ return CompatibilityFiles.SpecPath(self.spec, self._reader)
-+
-+
-+def wrap_spec(package):
-+ """
-+ Construct a package spec with traversable compatibility
-+ on the spec/loader/reader.
-+ """
-+ return SpecLoaderAdapter(package.__spec__, TraversableResourcesLoader)
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/importlib_resources/_common.py

b/third_party/python/setuptools/pkg_resources/_vendor/importlib_resources/_common.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/importlib_resources/_common.py
-@@ -0,0 +1,207 @@
-+import os
-+import pathlib
-+import tempfile
-+import functools
-+import contextlib
-+import types
-+import importlib
-+import inspect
-+import warnings
-+import itertools
-+
-+from typing import Union, Optional, cast
-+from .abc import ResourceReader, Traversable
-+
-+from ._compat import wrap_spec
-+
-+Package = Union[types.ModuleType, str]
-+Anchor = Package
-+
-+
-+def package_to_anchor(func):
-+ """
-+ Replace 'package' parameter as 'anchor' and warn about the change.
-+
-+ Other errors should fall through.
-+
-+ >>> files('a', 'b')
-+ Traceback (most recent call last):
-+ TypeError: files() takes from 0 to 1 positional arguments but 2 were
given
-+ """
-+ undefined = object()
-+
-+ @functools.wraps(func)
-+ def wrapper(anchor=undefined, package=undefined):
-+ if package is not undefined:
-+ if anchor is not undefined:
-+ return func(anchor, package)
-+ warnings.warn(
-+ "First parameter to files is renamed to 'anchor'",
-+ DeprecationWarning,
-+ stacklevel=2,
-+ )
-+ return func(package)
-+ elif anchor is undefined:
-+ return func()
-+ return func(anchor)
-+
-+ return wrapper
-+
-+
-+@package_to_anchor
-+def files(anchor: Optional[Anchor] = None) -> Traversable:
-+ """
-+ Get a Traversable resource for an anchor.
-+ """
-+ return from_package(resolve(anchor))
-+
-+
-+def get_resource_reader(package: types.ModuleType) ->
Optional[ResourceReader]:
-+ """
-+ Return the package's loader if it's a ResourceReader.
-+ """
-+ # We can't use
-+ # a issubclass() check here because apparently abc.'s
__subclasscheck__()
-+ # hook wants to create a weak reference to the object, but
-+ # zipimport.zipimporter does not support weak references, resulting in a
-+ # TypeError. That seems terrible.
-+ spec = package.__spec__
-+ reader = getattr(spec.loader, 'get_resource_reader', None) # type:
ignore
-+ if reader is None:
-+ return None
-+ return reader(spec.name) # type: ignore
-+
-+
-+@functools.singledispatch
-+def resolve(cand: Optional[Anchor]) -> types.ModuleType:
-+ return cast(types.ModuleType, cand)
-+
-+
-+@resolve.register
-+def _(cand: str) -> types.ModuleType:
-+ return importlib.import_module(cand)
-+
-+
-+@resolve.register
-+def _(cand: None) -> types.ModuleType:
-+ return resolve(_infer_caller().f_globals['__name__'])
-+
-+
-+def _infer_caller():
-+ """
-+ Walk the stack and find the frame of the first caller not in this
module.
-+ """
-+
-+ def is_this_file(frame_info):
-+ return frame_info.filename == __file__
-+
-+ def is_wrapper(frame_info):
-+ return frame_info.function == 'wrapper'
-+
-+ not_this_file = itertools.filterfalse(is_this_file, inspect.stack())
-+ # also exclude 'wrapper' due to singledispatch in the call stack
-+ callers = itertools.filterfalse(is_wrapper, not_this_file)
-+ return next(callers).frame
-+
-+
-+def from_package(package: types.ModuleType):
-+ """
-+ Return a Traversable object for the given package.
-+
-+ """
-+ spec = wrap_spec(package)
-+ reader = spec.loader.get_resource_reader(spec.name)
-+ return reader.files()
-+
-+
-+@contextlib.contextmanager
-+def _tempfile(
-+ reader,
-+ suffix='',
-+ # gh-93353: Keep a reference to call os.remove() in late Python
-+ # finalization.
-+ *,
-+ _os_remove=os.remove,
-+):
-+ # Not using tempfile.NamedTemporaryFile as it leads to deeper 'try'
-+ # blocks due to the need to close the temporary file to work on Windows
-+ # properly.
-+ fd, raw_path = tempfile.mkstemp(suffix=suffix)
-+ try:
-+ try:
-+ os.write(fd, reader())
-+ finally:
-+ os.close(fd)
-+ del reader
-+ yield pathlib.Path(raw_path)
-+ finally:
-+ try:
-+ _os_remove(raw_path)
-+ except FileNotFoundError:
-+ pass
-+
-+
-+def _temp_file(path):
-+ return _tempfile(path.read_bytes, suffix=path.name)
-+
-+
-+def _is_present_dir(path: Traversable) -> bool:
-+ """
-+ Some Traversables implement ``is_dir()`` to raise an
-+ exception (i.e. ``FileNotFoundError``) when the
-+ directory doesn't exist. This function wraps that call
-+ to always return a boolean and only return True
-+ if there's a dir and it exists.
-+ """
-+ with contextlib.suppress(FileNotFoundError):
-+ return path.is_dir()
-+ return False
-+
-+
-+@functools.singledispatch
-+def as_file(path):
-+ """
-+ Given a Traversable object, return that object as a
-+ path on the local file system in a context manager.
-+ """
-+ return _temp_dir(path) if _is_present_dir(path) else _temp_file(path)
-+
-+
-+@as_file.register(pathlib.Path)
-+@contextlib.contextmanager
-+def _(path):
-+ """
-+ Degenerate behavior for pathlib.Path objects.
-+ """
-+ yield path
-+
-+
-+@contextlib.contextmanager
-+def _temp_path(dir: tempfile.TemporaryDirectory):
-+ """
-+ Wrap tempfile.TemporyDirectory to return a pathlib object.
-+ """
-+ with dir as result:
-+ yield pathlib.Path(result)
-+
-+
-+@contextlib.contextmanager
-+def _temp_dir(path):
-+ """
-+ Given a traversable dir, recursively replicate the whole tree
-+ to the file system in a context manager.
-+ """
-+ assert path.is_dir()
-+ with _temp_path(tempfile.TemporaryDirectory()) as temp_dir:
-+ yield _write_contents(temp_dir, path)
-+
-+
-+def _write_contents(target, source):
-+ child = target.joinpath(source.name)
-+ if source.is_dir():
-+ child.mkdir()
-+ for item in source.iterdir():
-+ _write_contents(child, item)
-+ else:
-+ child.write_bytes(source.read_bytes())
-+ return child
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/importlib_resources/_compat.py

b/third_party/python/setuptools/pkg_resources/_vendor/importlib_resources/_compat.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/importlib_resources/_compat.py
-@@ -0,0 +1,108 @@
-+# flake8: noqa
-+
-+import abc
-+import os
-+import sys
-+import pathlib
-+from contextlib import suppress
-+from typing import Union
-+
-+
-+if sys.version_info >= (3, 10):
-+ from zipfile import Path as ZipPath # type: ignore
-+else:
-+ from ..zipp import Path as ZipPath # type: ignore
-+
-+
-+try:
-+ from typing import runtime_checkable # type: ignore
-+except ImportError:
-+
-+ def runtime_checkable(cls): # type: ignore
-+ return cls
-+
-+
-+try:
-+ from typing import Protocol # type: ignore
-+except ImportError:
-+ Protocol = abc.ABC # type: ignore
-+
-+
-+class TraversableResourcesLoader:
-+ """
-+ Adapt loaders to provide TraversableResources and other
-+ compatibility.
-+
-+ Used primarily for Python 3.9 and earlier where the native
-+ loaders do not yet implement TraversableResources.
-+ """
-+
-+ def __init__(self, spec):
-+ self.spec = spec
-+
-+ @property
-+ def path(self):
-+ return self.spec.origin
-+
-+ def get_resource_reader(self, name):
-+ from . import readers, _adapters
-+
-+ def _zip_reader(spec):
-+ with suppress(AttributeError):
-+ return readers.ZipReader(spec.loader, spec.name)
-+
-+ def _namespace_reader(spec):
-+ with suppress(AttributeError, ValueError):
-+ return
readers.NamespaceReader(spec.submodule_search_locations)
-+
-+ def _available_reader(spec):
-+ with suppress(AttributeError):
-+ return spec.loader.get_resource_reader(spec.name)
-+
-+ def _native_reader(spec):
-+ reader = _available_reader(spec)
-+ return reader if hasattr(reader, 'files') else None
-+
-+ def _file_reader(spec):
-+ try:
-+ path = pathlib.Path(self.path)
-+ except TypeError:
-+ return None
-+ if path.exists():
-+ return readers.FileReader(self)
-+
-+ return (
-+ # native reader if it supplies 'files'
-+ _native_reader(self.spec)
-+ or
-+ # local ZipReader if a zip module
-+ _zip_reader(self.spec)
-+ or
-+ # local NamespaceReader if a namespace module
-+ _namespace_reader(self.spec)
-+ or
-+ # local FileReader
-+ _file_reader(self.spec)
-+ # fallback - adapt the spec ResourceReader to TraversableReader
-+ or _adapters.CompatibilityFiles(self.spec)
-+ )
-+
-+
-+def wrap_spec(package):
-+ """
-+ Construct a package spec with traversable compatibility
-+ on the spec/loader/reader.
-+
-+ Supersedes _adapters.wrap_spec to use TraversableResourcesLoader
-+ from above for older Python compatibility (<3.10).
-+ """
-+ from . import _adapters
-+
-+ return _adapters.SpecLoaderAdapter(package.__spec__,
TraversableResourcesLoader)
-+
-+
-+if sys.version_info >= (3, 9):
-+ StrPath = Union[str, os.PathLike[str]]
-+else:
-+ # PathLike is only subscriptable at runtime in 3.9+
-+ StrPath = Union[str, "os.PathLike[str]"]
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/importlib_resources/_itertools.py

b/third_party/python/setuptools/pkg_resources/_vendor/importlib_resources/_itertools.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/importlib_resources/_itertools.py
-@@ -0,0 +1,35 @@
-+from itertools import filterfalse
-+
-+from typing import (
-+ Callable,
-+ Iterable,
-+ Iterator,
-+ Optional,
-+ Set,
-+ TypeVar,
-+ Union,
-+)
-+
-+# Type and type variable definitions
-+_T = TypeVar('_T')
-+_U = TypeVar('_U')
-+
-+
-+def unique_everseen(
-+ iterable: Iterable[_T], key: Optional[Callable[[_T], _U]] = None
-+) -> Iterator[_T]:
-+ "List unique elements, preserving order. Remember all elements ever
seen."
-+ # unique_everseen('AAAABBBCCDAABBB') --> A B C D
-+ # unique_everseen('ABBCcAD', str.lower) --> A B C D
-+ seen: Set[Union[_T, _U]] = set()
-+ seen_add = seen.add
-+ if key is None:
-+ for element in filterfalse(seen.__contains__, iterable):
-+ seen_add(element)
-+ yield element
-+ else:
-+ for element in iterable:
-+ k = key(element)
-+ if k not in seen:
-+ seen_add(k)
-+ yield element
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/importlib_resources/_legacy.py

b/third_party/python/setuptools/pkg_resources/_vendor/importlib_resources/_legacy.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/importlib_resources/_legacy.py
-@@ -0,0 +1,120 @@
-+import functools
-+import os
-+import pathlib
-+import types
-+import warnings
-+
-+from typing import Union, Iterable, ContextManager, BinaryIO, TextIO, Any
-+
-+from . import _common
-+
-+Package = Union[types.ModuleType, str]
-+Resource = str
-+
-+
-+def deprecated(func):
-+ @functools.wraps(func)
-+ def wrapper(*args, **kwargs):
-+ warnings.warn(
-+ f"{func.__name__} is deprecated. Use files() instead. "
-+ "Refer to https://importlib-resources.readthedocs.io";
-+ "/en/latest/using.html#migrating-from-legacy for migration
advice.",
-+ DeprecationWarning,
-+ stacklevel=2,
-+ )
-+ return func(*args, **kwargs)
-+
-+ return wrapper
-+
-+
-+def normalize_path(path: Any) -> str:
-+ """Normalize a path by ensuring it is a string.
-+
-+ If the resulting string contains path separators, an exception is
raised.
-+ """
-+ str_path = str(path)
-+ parent, file_name = os.path.split(str_path)
-+ if parent:
-+ raise ValueError(f'{path!r} must be only a file name')
-+ return file_name
-+
-+
-+@deprecated
-+def open_binary(package: Package, resource: Resource) -> BinaryIO:
-+ """Return a file-like object opened for binary reading of the
resource."""
-+ return (_common.files(package) / normalize_path(resource)).open('rb')
-+
-+
-+@deprecated
-+def read_binary(package: Package, resource: Resource) -> bytes:
-+ """Return the binary contents of the resource."""
-+ return (_common.files(package) / normalize_path(resource)).read_bytes()
-+
-+
-+@deprecated
-+def open_text(
-+ package: Package,
-+ resource: Resource,
-+ encoding: str = 'utf-8',
-+ errors: str = 'strict',
-+) -> TextIO:
-+ """Return a file-like object opened for text reading of the resource."""
-+ return (_common.files(package) / normalize_path(resource)).open(
-+ 'r', encoding=encoding, errors=errors
-+ )
-+
-+
-+@deprecated
-+def read_text(
-+ package: Package,
-+ resource: Resource,
-+ encoding: str = 'utf-8',
-+ errors: str = 'strict',
-+) -> str:
-+ """Return the decoded string of the resource.
-+
-+ The decoding-related arguments have the same semantics as those of
-+ bytes.decode().
-+ """
-+ with open_text(package, resource, encoding, errors) as fp:
-+ return fp.read()
-+
-+
-+@deprecated
-+def contents(package: Package) -> Iterable[str]:
-+ """Return an iterable of entries in `package`.
-+
-+ Note that not all entries are resources. Specifically, directories are
-+ not considered resources. Use `is_resource()` on each entry returned
here
-+ to check if it is a resource or not.
-+ """
-+ return [path.name for path in _common.files(package).iterdir()]
-+
-+
-+@deprecated
-+def is_resource(package: Package, name: str) -> bool:
-+ """True if `name` is a resource inside `package`.
-+
-+ Directories are *not* resources.
-+ """
-+ resource = normalize_path(name)
-+ return any(
-+ traversable.name == resource and traversable.is_file()
-+ for traversable in _common.files(package).iterdir()
-+ )
-+
-+
-+@deprecated
-+def path(
-+ package: Package,
-+ resource: Resource,
-+) -> ContextManager[pathlib.Path]:
-+ """A context manager providing a file path object to the resource.
-+
-+ If the resource does not already exist on its own on the file system,
-+ a temporary file will be created. If the file was created, the file
-+ will be deleted upon exiting the context manager (no exception is
-+ raised if the file was deleted prior to the context manager
-+ exiting).
-+ """
-+ return _common.as_file(_common.files(package) /
normalize_path(resource))
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/importlib_resources/abc.py

b/third_party/python/setuptools/pkg_resources/_vendor/importlib_resources/abc.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/importlib_resources/abc.py
-@@ -0,0 +1,170 @@
-+import abc
-+import io
-+import itertools
-+import pathlib
-+from typing import Any, BinaryIO, Iterable, Iterator, NoReturn, Text,
Optional
-+
-+from ._compat import runtime_checkable, Protocol, StrPath
-+
-+
-+__all__ = ["ResourceReader", "Traversable", "TraversableResources"]
-+
-+
-+class ResourceReader(metaclass=abc.ABCMeta):
-+ """Abstract base class for loaders to provide resource reading
support."""
-+
-+ @abc.abstractmethod
-+ def open_resource(self, resource: Text) -> BinaryIO:
-+ """Return an opened, file-like object for binary reading.
-+
-+ The 'resource' argument is expected to represent only a file name.
-+ If the resource cannot be found, FileNotFoundError is raised.
-+ """
-+ # This deliberately raises FileNotFoundError instead of
-+ # NotImplementedError so that if this method is accidentally called,
-+ # it'll still do the right thing.
-+ raise FileNotFoundError
-+
-+ @abc.abstractmethod
-+ def resource_path(self, resource: Text) -> Text:
-+ """Return the file system path to the specified resource.
-+
-+ The 'resource' argument is expected to represent only a file name.
-+ If the resource does not exist on the file system, raise
-+ FileNotFoundError.
-+ """
-+ # This deliberately raises FileNotFoundError instead of
-+ # NotImplementedError so that if this method is accidentally called,
-+ # it'll still do the right thing.
-+ raise FileNotFoundError
-+
-+ @abc.abstractmethod
-+ def is_resource(self, path: Text) -> bool:
-+ """Return True if the named 'path' is a resource.
-+
-+ Files are resources, directories are not.
-+ """
-+ raise FileNotFoundError
-+
-+ @abc.abstractmethod
-+ def contents(self) -> Iterable[str]:
-+ """Return an iterable of entries in `package`."""
-+ raise FileNotFoundError
-+
-+
-+class TraversalError(Exception):
-+ pass
-+
-+
-+@runtime_checkable
-+class Traversable(Protocol):
-+ """
-+ An object with a subset of pathlib.Path methods suitable for
-+ traversing directories and opening files.
-+
-+ Any exceptions that occur when accessing the backing resource
-+ may propagate unaltered.
-+ """
-+
-+ @abc.abstractmethod
-+ def iterdir(self) -> Iterator["Traversable"]:
-+ """
-+ Yield Traversable objects in self
-+ """
-+
-+ def read_bytes(self) -> bytes:
-+ """
-+ Read contents of self as bytes
-+ """
-+ with self.open('rb') as strm:
-+ return strm.read()
-+
-+ def read_text(self, encoding: Optional[str] = None) -> str:
-+ """
-+ Read contents of self as text
-+ """
-+ with self.open(encoding=encoding) as strm:
-+ return strm.read()
-+
-+ @abc.abstractmethod
-+ def is_dir(self) -> bool:
-+ """
-+ Return True if self is a directory
-+ """
-+
-+ @abc.abstractmethod
-+ def is_file(self) -> bool:
-+ """
-+ Return True if self is a file
-+ """
-+
-+ def joinpath(self, *descendants: StrPath) -> "Traversable":
-+ """
-+ Return Traversable resolved with any descendants applied.
-+
-+ Each descendant should be a path segment relative to self
-+ and each may contain multiple levels separated by
-+ ``posixpath.sep`` (``/``).
-+ """
-+ if not descendants:
-+ return self
-+ names = itertools.chain.from_iterable(
-+ path.parts for path in map(pathlib.PurePosixPath, descendants)
-+ )
-+ target = next(names)
-+ matches = (
-+ traversable for traversable in self.iterdir() if
traversable.name == target
-+ )
-+ try:
-+ match = next(matches)
-+ except StopIteration:
-+ raise TraversalError(
-+ "Target not found during traversal.", target, list(names)
-+ )
-+ return match.joinpath(*names)
-+
-+ def __truediv__(self, child: StrPath) -> "Traversable":
-+ """
-+ Return Traversable child in self
-+ """
-+ return self.joinpath(child)
-+
-+ @abc.abstractmethod
-+ def open(self, mode='r', *args, **kwargs):
-+ """
-+ mode may be 'r' or 'rb' to open as text or binary. Return a handle
-+ suitable for reading (same as pathlib.Path.open).
-+
-+ When opening as text, accepts encoding parameters such as those
-+ accepted by io.TextIOWrapper.
-+ """
-+
-+ @property
-+ @abc.abstractmethod
-+ def name(self) -> str:
-+ """
-+ The base name of this object without any parent references.
-+ """
-+
-+
-+class TraversableResources(ResourceReader):
-+ """
-+ The required interface for providing traversable
-+ resources.
-+ """
-+
-+ @abc.abstractmethod
-+ def files(self) -> "Traversable":
-+ """Return a Traversable object for the loaded package."""
-+
-+ def open_resource(self, resource: StrPath) -> io.BufferedReader:
-+ return self.files().joinpath(resource).open('rb')
-+
-+ def resource_path(self, resource: Any) -> NoReturn:
-+ raise FileNotFoundError(resource)
-+
-+ def is_resource(self, path: StrPath) -> bool:
-+ return self.files().joinpath(path).is_file()
-+
-+ def contents(self) -> Iterator[str]:
-+ return (item.name for item in self.files().iterdir())
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/importlib_resources/readers.py

b/third_party/python/setuptools/pkg_resources/_vendor/importlib_resources/readers.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/importlib_resources/readers.py
-@@ -0,0 +1,120 @@
-+import collections
-+import pathlib
-+import operator
-+
-+from . import abc
-+
-+from ._itertools import unique_everseen
-+from ._compat import ZipPath
-+
-+
-+def remove_duplicates(items):
-+ return iter(collections.OrderedDict.fromkeys(items))
-+
-+
-+class FileReader(abc.TraversableResources):
-+ def __init__(self, loader):
-+ self.path = pathlib.Path(loader.path).parent
-+
-+ def resource_path(self, resource):
-+ """
-+ Return the file system path to prevent
-+ `resources.path()` from creating a temporary
-+ copy.
-+ """
-+ return str(self.path.joinpath(resource))
-+
-+ def files(self):
-+ return self.path
-+
-+
-+class ZipReader(abc.TraversableResources):
-+ def __init__(self, loader, module):
-+ _, _, name = module.rpartition('.')
-+ self.prefix = loader.prefix.replace('\\', '/') + name + '/'
-+ self.archive = loader.archive
-+
-+ def open_resource(self, resource):
-+ try:
-+ return super().open_resource(resource)
-+ except KeyError as exc:
-+ raise FileNotFoundError(exc.args[0])
-+
-+ def is_resource(self, path):
-+ # workaround for `zipfile.Path.is_file` returning true
-+ # for non-existent paths.
-+ target = self.files().joinpath(path)
-+ return target.is_file() and target.exists()
-+
-+ def files(self):
-+ return ZipPath(self.archive, self.prefix)
-+
-+
-+class MultiplexedPath(abc.Traversable):
-+ """
-+ Given a series of Traversable objects, implement a merged
-+ version of the interface across all objects. Useful for
-+ namespace packages which may be multihomed at a single
-+ name.
-+ """
-+
-+ def __init__(self, *paths):
-+ self._paths = list(map(pathlib.Path, remove_duplicates(paths)))
-+ if not self._paths:
-+ message = 'MultiplexedPath must contain at least one path'
-+ raise FileNotFoundError(message)
-+ if not all(path.is_dir() for path in self._paths):
-+ raise NotADirectoryError('MultiplexedPath only supports
directories')
-+
-+ def iterdir(self):
-+ files = (file for path in self._paths for file in path.iterdir())
-+ return unique_everseen(files, key=operator.attrgetter('name'))
-+
-+ def read_bytes(self):
-+ raise FileNotFoundError(f'{self} is not a file')
-+
-+ def read_text(self, *args, **kwargs):
-+ raise FileNotFoundError(f'{self} is not a file')
-+
-+ def is_dir(self):
-+ return True
-+
-+ def is_file(self):
-+ return False
-+
-+ def joinpath(self, *descendants):
-+ try:
-+ return super().joinpath(*descendants)
-+ except abc.TraversalError:
-+ # One of the paths did not resolve (a directory does not exist).
-+ # Just return something that will not exist.
-+ return self._paths[0].joinpath(*descendants)
-+
-+ def open(self, *args, **kwargs):
-+ raise FileNotFoundError(f'{self} is not a file')
-+
-+ @property
-+ def name(self):
-+ return self._paths[0].name
-+
-+ def __repr__(self):
-+ paths = ', '.join(f"'{path}'" for path in self._paths)
-+ return f'MultiplexedPath({paths})'
-+
-+
-+class NamespaceReader(abc.TraversableResources):
-+ def __init__(self, namespace_path):
-+ if 'NamespacePath' not in str(namespace_path):
-+ raise ValueError('Invalid path')
-+ self.path = MultiplexedPath(*list(namespace_path))
-+
-+ def resource_path(self, resource):
-+ """
-+ Return the file system path to prevent
-+ `resources.path()` from creating a temporary
-+ copy.
-+ """
-+ return str(self.path.joinpath(resource))
-+
-+ def files(self):
-+ return self.path
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/importlib_resources/simple.py

b/third_party/python/setuptools/pkg_resources/_vendor/importlib_resources/simple.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/importlib_resources/simple.py
-@@ -0,0 +1,106 @@
-+"""
-+Interface adapters for low-level readers.
-+"""
-+
-+import abc
-+import io
-+import itertools
-+from typing import BinaryIO, List
-+
-+from .abc import Traversable, TraversableResources
-+
-+
-+class SimpleReader(abc.ABC):
-+ """
-+ The minimum, low-level interface required from a resource
-+ provider.
-+ """
-+
-+ @property
-+ @abc.abstractmethod
-+ def package(self) -> str:
-+ """
-+ The name of the package for which this reader loads resources.
-+ """
-+
-+ @abc.abstractmethod
-+ def children(self) -> List['SimpleReader']:
-+ """
-+ Obtain an iterable of SimpleReader for available
-+ child containers (e.g. directories).
-+ """
-+
-+ @abc.abstractmethod
-+ def resources(self) -> List[str]:
-+ """
-+ Obtain available named resources for this virtual package.
-+ """
-+
-+ @abc.abstractmethod
-+ def open_binary(self, resource: str) -> BinaryIO:
-+ """
-+ Obtain a File-like for a named resource.
-+ """
-+
-+ @property
-+ def name(self):
-+ return self.package.split('.')[-1]
-+
-+
-+class ResourceContainer(Traversable):
-+ """
-+ Traversable container for a package's resources via its reader.
-+ """
-+
-+ def __init__(self, reader: SimpleReader):
-+ self.reader = reader
-+
-+ def is_dir(self):
-+ return True
-+
-+ def is_file(self):
-+ return False
-+
-+ def iterdir(self):
-+ files = (ResourceHandle(self, name) for name in
self.reader.resources)
-+ dirs = map(ResourceContainer, self.reader.children())
-+ return itertools.chain(files, dirs)
-+
-+ def open(self, *args, **kwargs):
-+ raise IsADirectoryError()
-+
-+
-+class ResourceHandle(Traversable):
-+ """
-+ Handle to a named resource in a ResourceReader.
-+ """
-+
-+ def __init__(self, parent: ResourceContainer, name: str):
-+ self.parent = parent
-+ self.name = name # type: ignore
-+
-+ def is_file(self):
-+ return True
-+
-+ def is_dir(self):
-+ return False
-+
-+ def open(self, mode='r', *args, **kwargs):
-+ stream = self.parent.reader.open_binary(self.name)
-+ if 'b' not in mode:
-+ stream = io.TextIOWrapper(*args, **kwargs)
-+ return stream
-+
-+ def joinpath(self, name):
-+ raise RuntimeError("Cannot traverse into a resource")
-+
-+
-+class TraversableReader(TraversableResources, SimpleReader):
-+ """
-+ A TraversableResources based on SimpleReader. Resource providers
-+ may derive from this class to provide the TraversableResources
-+ interface by supplying the SimpleReader interface.
-+ """
-+
-+ def files(self):
-+ return ResourceContainer(self)
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/jaraco/__init__.py
b/third_party/python/setuptools/pkg_resources/_vendor/jaraco/__init__.py
-new file mode 100644
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/jaraco/context.py
b/third_party/python/setuptools/pkg_resources/_vendor/jaraco/context.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/pkg_resources/_vendor/jaraco/context.py
-@@ -0,0 +1,288 @@
-+import os
-+import subprocess
-+import contextlib
-+import functools
-+import tempfile
-+import shutil
-+import operator
-+import warnings
-+
-+
-+@contextlib.contextmanager
-+def pushd(dir):
-+ """
-+ >>> tmp_path = getfixture('tmp_path')
-+ >>> with pushd(tmp_path):
-+ ... assert os.getcwd() == os.fspath(tmp_path)
-+ >>> assert os.getcwd() != os.fspath(tmp_path)
-+ """
-+
-+ orig = os.getcwd()
-+ os.chdir(dir)
-+ try:
-+ yield dir
-+ finally:
-+ os.chdir(orig)
-+
-+
-+@contextlib.contextmanager
-+def tarball_context(url, target_dir=None, runner=None, pushd=pushd):
-+ """
-+ Get a tarball, extract it, change to that directory, yield, then
-+ clean up.
-+ `runner` is the function to invoke commands.
-+ `pushd` is a context manager for changing the directory.
-+ """
-+ if target_dir is None:
-+ target_dir = os.path.basename(url).replace('.tar.gz',
'').replace('.tgz', '')
-+ if runner is None:
-+ runner = functools.partial(subprocess.check_call, shell=True)
-+ else:
-+ warnings.warn("runner parameter is deprecated", DeprecationWarning)
-+ # In the tar command, use --strip-components=1 to strip the first path
and
-+ # then
-+ # use -C to cause the files to be extracted to {target_dir}. This
ensures
-+ # that we always know where the files were extracted.
-+ runner('mkdir {target_dir}'.format(**vars()))
-+ try:
-+ getter = 'wget {url} -O -'
-+ extract = 'tar x{compression} --strip-components=1 -C {target_dir}'
-+ cmd = ' | '.join((getter, extract))
-+ runner(cmd.format(compression=infer_compression(url), **vars()))
-+ with pushd(target_dir):
-+ yield target_dir
-+ finally:
-+ runner('rm -Rf {target_dir}'.format(**vars()))
-+
-+
-+def infer_compression(url):
-+ """
-+ Given a URL or filename, infer the compression code for tar.
-+
-+ >>> infer_compression('http://foo/bar.tar.gz')
-+ 'z'
-+ >>> infer_compression('http://foo/bar.tgz')
-+ 'z'
-+ >>> infer_compression('file.bz')
-+ 'j'
-+ >>> infer_compression('file.xz')
-+ 'J'
-+ """
-+ # cheat and just assume it's the last two characters
-+ compression_indicator = url[-2:]
-+ mapping = dict(gz='z', bz='j', xz='J')
-+ # Assume 'z' (gzip) if no match
-+ return mapping.get(compression_indicator, 'z')
-+
-+
-+@contextlib.contextmanager
-+def temp_dir(remover=shutil.rmtree):
-+ """
-+ Create a temporary directory context. Pass a custom remover
-+ to override the removal behavior.
-+
-+ >>> import pathlib
-+ >>> with temp_dir() as the_dir:
-+ ... assert os.path.isdir(the_dir)
-+ ... _ =
pathlib.Path(the_dir).joinpath('somefile').write_text('contents')
-+ >>> assert not os.path.exists(the_dir)
-+ """
-+ temp_dir = tempfile.mkdtemp()
-+ try:
-+ yield temp_dir
-+ finally:
-+ remover(temp_dir)
-+
-+
-+@contextlib.contextmanager
-+def repo_context(url, branch=None, quiet=True, dest_ctx=temp_dir):
-+ """
-+ Check out the repo indicated by url.
-+
-+ If dest_ctx is supplied, it should be a context manager
-+ to yield the target directory for the check out.
-+ """
-+ exe = 'git' if 'git' in url else 'hg'
-+ with dest_ctx() as repo_dir:
-+ cmd = [exe, 'clone', url, repo_dir]
-+ if branch:
-+ cmd.extend(['--branch', branch])
-+ devnull = open(os.path.devnull, 'w')
-+ stdout = devnull if quiet else None
-+ subprocess.check_call(cmd, stdout=stdout)
-+ yield repo_dir
-+
-+
-+@contextlib.contextmanager
-+def null():
-+ """
-+ A null context suitable to stand in for a meaningful context.
-+
-+ >>> with null() as value:
-+ ... assert value is None
-+ """
-+ yield
-+
-+
-+class ExceptionTrap:
-+ """
-+ A context manager that will catch certain exceptions and provide an
-+ indication they occurred.
-+
-+ >>> with ExceptionTrap() as trap:
-+ ... raise Exception()
-+ >>> bool(trap)
-+ True
-+
-+ >>> with ExceptionTrap() as trap:
-+ ... pass
-+ >>> bool(trap)
-+ False
-+
-+ >>> with ExceptionTrap(ValueError) as trap:
-+ ... raise ValueError("1 + 1 is not 3")
-+ >>> bool(trap)
-+ True
-+ >>> trap.value
-+ ValueError('1 + 1 is not 3')
-+ >>> trap.tb
-+ <traceback object at ...>
-+
-+ >>> with ExceptionTrap(ValueError) as trap:
-+ ... raise Exception()
-+ Traceback (most recent call last):
-+ ...
-+ Exception
-+
-+ >>> bool(trap)
-+ False
-+ """
-+
-+ exc_info = None, None, None
-+
-+ def __init__(self, exceptions=(Exception,)):
-+ self.exceptions = exceptions
-+
-+ def __enter__(self):
-+ return self
-+
-+ @property
-+ def type(self):
-+ return self.exc_info[0]
-+
-+ @property
-+ def value(self):
-+ return self.exc_info[1]
-+
-+ @property
-+ def tb(self):
-+ return self.exc_info[2]
-+
-+ def __exit__(self, *exc_info):
-+ type = exc_info[0]
-+ matches = type and issubclass(type, self.exceptions)
-+ if matches:
-+ self.exc_info = exc_info
-+ return matches
-+
-+ def __bool__(self):
-+ return bool(self.type)
-+
-+ def raises(self, func, *, _test=bool):
-+ """
-+ Wrap func and replace the result with the truth
-+ value of the trap (True if an exception occurred).
-+
-+ First, give the decorator an alias to support Python 3.8
-+ Syntax.
-+
-+ >>> raises = ExceptionTrap(ValueError).raises
-+
-+ Now decorate a function that always fails.
-+
-+ >>> @raises
-+ ... def fail():
-+ ... raise ValueError('failed')
-+ >>> fail()
-+ True
-+ """
-+
-+ @functools.wraps(func)
-+ def wrapper(*args, **kwargs):
-+ with ExceptionTrap(self.exceptions) as trap:
-+ func(*args, **kwargs)
-+ return _test(trap)
-+
-+ return wrapper
-+
-+ def passes(self, func):
-+ """
-+ Wrap func and replace the result with the truth
-+ value of the trap (True if no exception).
-+
-+ First, give the decorator an alias to support Python 3.8
-+ Syntax.
-+
-+ >>> passes = ExceptionTrap(ValueError).passes
-+
-+ Now decorate a function that always fails.
-+
-+ >>> @passes
-+ ... def fail():
-+ ... raise ValueError('failed')
-+
-+ >>> fail()
-+ False
-+ """
-+ return self.raises(func, _test=operator.not_)
-+
-+
-+class suppress(contextlib.suppress, contextlib.ContextDecorator):
-+ """
-+ A version of contextlib.suppress with decorator support.
-+
-+ >>> @suppress(KeyError)
-+ ... def key_error():
-+ ... {}['']
-+ >>> key_error()
-+ """
-+
-+
-+class on_interrupt(contextlib.ContextDecorator):
-+ """
-+ Replace a KeyboardInterrupt with SystemExit(1)
-+
-+ >>> def do_interrupt():
-+ ... raise KeyboardInterrupt()
-+ >>> on_interrupt('error')(do_interrupt)()
-+ Traceback (most recent call last):
-+ ...
-+ SystemExit: 1
-+ >>> on_interrupt('error', code=255)(do_interrupt)()
-+ Traceback (most recent call last):
-+ ...
-+ SystemExit: 255
-+ >>> on_interrupt('suppress')(do_interrupt)()
-+ >>> with __import__('pytest').raises(KeyboardInterrupt):
-+ ... on_interrupt('ignore')(do_interrupt)()
-+ """
-+
-+ def __init__(
-+ self,
-+ action='error',
-+ # py3.7 compat
-+ # /,
-+ code=1,
-+ ):
-+ self.action = action
-+ self.code = code
-+
-+ def __enter__(self):
-+ return self
-+
-+ def __exit__(self, exctype, excinst, exctb):
-+ if exctype is not KeyboardInterrupt or self.action == 'ignore':
-+ return
-+ elif self.action == 'error':
-+ raise SystemExit(self.code) from excinst
-+ return self.action == 'suppress'
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/jaraco/functools.py
b/third_party/python/setuptools/pkg_resources/_vendor/jaraco/functools.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/pkg_resources/_vendor/jaraco/functools.py
-@@ -0,0 +1,556 @@
-+import functools
-+import time
-+import inspect
-+import collections
-+import types
-+import itertools
-+import warnings
-+
-+import pkg_resources.extern.more_itertools
-+
-+from typing import Callable, TypeVar
-+
-+
-+CallableT = TypeVar("CallableT", bound=Callable[..., object])
-+
-+
-+def compose(*funcs):
-+ """
-+ Compose any number of unary functions into a single unary function.
-+
-+ >>> import textwrap
-+ >>> expected = str.strip(textwrap.dedent(compose.__doc__))
-+ >>> strip_and_dedent = compose(str.strip, textwrap.dedent)
-+ >>> strip_and_dedent(compose.__doc__) == expected
-+ True
-+
-+ Compose also allows the innermost function to take arbitrary arguments.
-+
-+ >>> round_three = lambda x: round(x, ndigits=3)
-+ >>> f = compose(round_three, int.__truediv__)
-+ >>> [f(3*x, x+1) for x in range(1,10)]
-+ [1.5, 2.0, 2.25, 2.4, 2.5, 2.571, 2.625, 2.667, 2.7]
-+ """
-+
-+ def compose_two(f1, f2):
-+ return lambda *args, **kwargs: f1(f2(*args, **kwargs))
-+
-+ return functools.reduce(compose_two, funcs)
-+
-+
-+def method_caller(method_name, *args, **kwargs):
-+ """
-+ Return a function that will call a named method on the
-+ target object with optional positional and keyword
-+ arguments.
-+
-+ >>> lower = method_caller('lower')
-+ >>> lower('MyString')
-+ 'mystring'
-+ """
-+
-+ def call_method(target):
-+ func = getattr(target, method_name)
-+ return func(*args, **kwargs)
-+
-+ return call_method
-+
-+
-+def once(func):
-+ """
-+ Decorate func so it's only ever called the first time.
-+
-+ This decorator can ensure that an expensive or non-idempotent function
-+ will not be expensive on subsequent calls and is idempotent.
-+
-+ >>> add_three = once(lambda a: a+3)
-+ >>> add_three(3)
-+ 6
-+ >>> add_three(9)
-+ 6
-+ >>> add_three('12')
-+ 6
-+
-+ To reset the stored value, simply clear the property ``saved_result``.
-+
-+ >>> del add_three.saved_result
-+ >>> add_three(9)
-+ 12
-+ >>> add_three(8)
-+ 12
-+
-+ Or invoke 'reset()' on it.
-+
-+ >>> add_three.reset()
-+ >>> add_three(-3)
-+ 0
-+ >>> add_three(0)
-+ 0
-+ """
-+
-+ @functools.wraps(func)
-+ def wrapper(*args, **kwargs):
-+ if not hasattr(wrapper, 'saved_result'):
-+ wrapper.saved_result = func(*args, **kwargs)
-+ return wrapper.saved_result
-+
-+ wrapper.reset = lambda: vars(wrapper).__delitem__('saved_result')
-+ return wrapper
-+
-+
-+def method_cache(
-+ method: CallableT,
-+ cache_wrapper: Callable[
-+ [CallableT], CallableT
-+ ] = functools.lru_cache(), # type: ignore[assignment]
-+) -> CallableT:
-+ """
-+ Wrap lru_cache to support storing the cache data in the object
instances.
-+
-+ Abstracts the common paradigm where the method explicitly saves an
-+ underscore-prefixed protected property on first call and returns that
-+ subsequently.
-+
-+ >>> class MyClass:
-+ ... calls = 0
-+ ...
-+ ... @method_cache
-+ ... def method(self, value):
-+ ... self.calls += 1
-+ ... return value
-+
-+ >>> a = MyClass()
-+ >>> a.method(3)
-+ 3
-+ >>> for x in range(75):
-+ ... res = a.method(x)
-+ >>> a.calls
-+ 75
-+
-+ Note that the apparent behavior will be exactly like that of lru_cache
-+ except that the cache is stored on each instance, so values in one
-+ instance will not flush values from another, and when an instance is
-+ deleted, so are the cached values for that instance.
-+
-+ >>> b = MyClass()
-+ >>> for x in range(35):
-+ ... res = b.method(x)
-+ >>> b.calls
-+ 35
-+ >>> a.method(0)
-+ 0
-+ >>> a.calls
-+ 75
-+
-+ Note that if method had been decorated with ``functools.lru_cache()``,
-+ a.calls would have been 76 (due to the cached value of 0 having been
-+ flushed by the 'b' instance).
-+
-+ Clear the cache with ``.cache_clear()``
-+
-+ >>> a.method.cache_clear()
-+
-+ Same for a method that hasn't yet been called.
-+
-+ >>> c = MyClass()
-+ >>> c.method.cache_clear()
-+
-+ Another cache wrapper may be supplied:
-+
-+ >>> cache = functools.lru_cache(maxsize=2)
-+ >>> MyClass.method2 = method_cache(lambda self: 3, cache_wrapper=cache)
-+ >>> a = MyClass()
-+ >>> a.method2()
-+ 3
-+
-+ Caution - do not subsequently wrap the method with another decorator,
such
-+ as ``@property``, which changes the semantics of the function.
-+
-+ See also
-+
http://code.activestate.com/recipes/577452-a-memoize-decorator-for-instance-methods/
-+ for another implementation and additional justification.
-+ """
-+
-+ def wrapper(self: object, *args: object, **kwargs: object) -> object:
-+ # it's the first call, replace the method with a cached, bound
method
-+ bound_method: CallableT = types.MethodType( # type:
ignore[assignment]
-+ method, self
-+ )
-+ cached_method = cache_wrapper(bound_method)
-+ setattr(self, method.__name__, cached_method)
-+ return cached_method(*args, **kwargs)
-+
-+ # Support cache clear even before cache has been created.
-+ wrapper.cache_clear = lambda: None # type: ignore[attr-defined]
-+
-+ return ( # type: ignore[return-value]
-+ _special_method_cache(method, cache_wrapper) or wrapper
-+ )
-+
-+
-+def _special_method_cache(method, cache_wrapper):
-+ """
-+ Because Python treats special methods differently, it's not
-+ possible to use instance attributes to implement the cached
-+ methods.
-+
-+ Instead, install the wrapper method under a different name
-+ and return a simple proxy to that wrapper.
-+
-+ https://github.com/jaraco/jaraco.functools/issues/5
-+ """
-+ name = method.__name__
-+ special_names = '__getattr__', '__getitem__'
-+ if name not in special_names:
-+ return
-+
-+ wrapper_name = '__cached' + name
-+
-+ def proxy(self, *args, **kwargs):
-+ if wrapper_name not in vars(self):
-+ bound = types.MethodType(method, self)
-+ cache = cache_wrapper(bound)
-+ setattr(self, wrapper_name, cache)
-+ else:
-+ cache = getattr(self, wrapper_name)
-+ return cache(*args, **kwargs)
-+
-+ return proxy
-+
-+
-+def apply(transform):
-+ """
-+ Decorate a function with a transform function that is
-+ invoked on results returned from the decorated function.
-+
-+ >>> @apply(reversed)
-+ ... def get_numbers(start):
-+ ... "doc for get_numbers"
-+ ... return range(start, start+3)
-+ >>> list(get_numbers(4))
-+ [6, 5, 4]
-+ >>> get_numbers.__doc__
-+ 'doc for get_numbers'
-+ """
-+
-+ def wrap(func):
-+ return functools.wraps(func)(compose(transform, func))
-+
-+ return wrap
-+
-+
-+def result_invoke(action):
-+ r"""
-+ Decorate a function with an action function that is
-+ invoked on the results returned from the decorated
-+ function (for its side-effect), then return the original
-+ result.
-+
-+ >>> @result_invoke(print)
-+ ... def add_two(a, b):
-+ ... return a + b
-+ >>> x = add_two(2, 3)
-+ 5
-+ >>> x
-+ 5
-+ """
-+
-+ def wrap(func):
-+ @functools.wraps(func)
-+ def wrapper(*args, **kwargs):
-+ result = func(*args, **kwargs)
-+ action(result)
-+ return result
-+
-+ return wrapper
-+
-+ return wrap
-+
-+
-+def invoke(f, *args, **kwargs):
-+ """
-+ Call a function for its side effect after initialization.
-+
-+ The benefit of using the decorator instead of simply invoking a function
-+ after defining it is that it makes explicit the author's intent for the
-+ function to be called immediately. Whereas if one simply calls the
-+ function immediately, it's less obvious if that was intentional or
-+ incidental. It also avoids repeating the name - the two actions,
defining
-+ the function and calling it immediately are modeled separately, but
linked
-+ by the decorator construct.
-+
-+ The benefit of having a function construct (opposed to just invoking
some
-+ behavior inline) is to serve as a scope in which the behavior occurs. It
-+ avoids polluting the global namespace with local variables, provides an
-+ anchor on which to attach documentation (docstring), keeps the behavior
-+ logically separated (instead of conceptually separated or not separated
at
-+ all), and provides potential to re-use the behavior for testing or other
-+ purposes.
-+
-+ This function is named as a pithy way to communicate, "call this
function
-+ primarily for its side effect", or "while defining this function, also
-+ take it aside and call it". It exists because there's no Python
construct
-+ for "define and call" (nor should there be, as decorators serve this
need
-+ just fine). The behavior happens immediately and synchronously.
-+
-+ >>> @invoke
-+ ... def func(): print("called")
-+ called
-+ >>> func()
-+ called
-+
-+ Use functools.partial to pass parameters to the initial call
-+
-+ >>> @functools.partial(invoke, name='bingo')
-+ ... def func(name): print("called with", name)
-+ called with bingo
-+ """
-+ f(*args, **kwargs)
-+ return f
-+
-+
-+def call_aside(*args, **kwargs):
-+ """
-+ Deprecated name for invoke.
-+ """
-+ warnings.warn("call_aside is deprecated, use invoke",
DeprecationWarning)
-+ return invoke(*args, **kwargs)
-+
-+
-+class Throttler:
-+ """
-+ Rate-limit a function (or other callable)
-+ """
-+
-+ def __init__(self, func, max_rate=float('Inf')):
-+ if isinstance(func, Throttler):
-+ func = func.func
-+ self.func = func
-+ self.max_rate = max_rate
-+ self.reset()
-+
-+ def reset(self):
-+ self.last_called = 0
-+
-+ def __call__(self, *args, **kwargs):
-+ self._wait()
-+ return self.func(*args, **kwargs)
-+
-+ def _wait(self):
-+ "ensure at least 1/max_rate seconds from last call"
-+ elapsed = time.time() - self.last_called
-+ must_wait = 1 / self.max_rate - elapsed
-+ time.sleep(max(0, must_wait))
-+ self.last_called = time.time()
-+
-+ def __get__(self, obj, type=None):
-+ return first_invoke(self._wait, functools.partial(self.func, obj))
-+
-+
-+def first_invoke(func1, func2):
-+ """
-+ Return a function that when invoked will invoke func1 without
-+ any parameters (for its side-effect) and then invoke func2
-+ with whatever parameters were passed, returning its result.
-+ """
-+
-+ def wrapper(*args, **kwargs):
-+ func1()
-+ return func2(*args, **kwargs)
-+
-+ return wrapper
-+
-+
-+def retry_call(func, cleanup=lambda: None, retries=0, trap=()):
-+ """
-+ Given a callable func, trap the indicated exceptions
-+ for up to 'retries' times, invoking cleanup on the
-+ exception. On the final attempt, allow any exceptions
-+ to propagate.
-+ """
-+ attempts = itertools.count() if retries == float('inf') else
range(retries)
-+ for attempt in attempts:
-+ try:
-+ return func()
-+ except trap:
-+ cleanup()
-+
-+ return func()
-+
-+
-+def retry(*r_args, **r_kwargs):
-+ """
-+ Decorator wrapper for retry_call. Accepts arguments to retry_call
-+ except func and then returns a decorator for the decorated function.
-+
-+ Ex:
-+
-+ >>> @retry(retries=3)
-+ ... def my_func(a, b):
-+ ... "this is my funk"
-+ ... print(a, b)
-+ >>> my_func.__doc__
-+ 'this is my funk'
-+ """
-+
-+ def decorate(func):
-+ @functools.wraps(func)
-+ def wrapper(*f_args, **f_kwargs):
-+ bound = functools.partial(func, *f_args, **f_kwargs)
-+ return retry_call(bound, *r_args, **r_kwargs)
-+
-+ return wrapper
-+
-+ return decorate
-+
-+
-+def print_yielded(func):
-+ """
-+ Convert a generator into a function that prints all yielded elements
-+
-+ >>> @print_yielded
-+ ... def x():
-+ ... yield 3; yield None
-+ >>> x()
-+ 3
-+ None
-+ """
-+ print_all = functools.partial(map, print)
-+ print_results = compose(more_itertools.consume, print_all, func)
-+ return functools.wraps(func)(print_results)
-+
-+
-+def pass_none(func):
-+ """
-+ Wrap func so it's not called if its first param is None
-+
-+ >>> print_text = pass_none(print)
-+ >>> print_text('text')
-+ text
-+ >>> print_text(None)
-+ """
-+
-+ @functools.wraps(func)
-+ def wrapper(param, *args, **kwargs):
-+ if param is not None:
-+ return func(param, *args, **kwargs)
-+
-+ return wrapper
-+
-+
-+def assign_params(func, namespace):
-+ """
-+ Assign parameters from namespace where func solicits.
-+
-+ >>> def func(x, y=3):
-+ ... print(x, y)
-+ >>> assigned = assign_params(func, dict(x=2, z=4))
-+ >>> assigned()
-+ 2 3
-+
-+ The usual errors are raised if a function doesn't receive
-+ its required parameters:
-+
-+ >>> assigned = assign_params(func, dict(y=3, z=4))
-+ >>> assigned()
-+ Traceback (most recent call last):
-+ TypeError: func() ...argument...
-+
-+ It even works on methods:
-+
-+ >>> class Handler:
-+ ... def meth(self, arg):
-+ ... print(arg)
-+ >>> assign_params(Handler().meth, dict(arg='crystal', foo='clear'))()
-+ crystal
-+ """
-+ sig = inspect.signature(func)
-+ params = sig.parameters.keys()
-+ call_ns = {k: namespace[k] for k in params if k in namespace}
-+ return functools.partial(func, **call_ns)
-+
-+
-+def save_method_args(method):
-+ """
-+ Wrap a method such that when it is called, the args and kwargs are
-+ saved on the method.
-+
-+ >>> class MyClass:
-+ ... @save_method_args
-+ ... def method(self, a, b):
-+ ... print(a, b)
-+ >>> my_ob = MyClass()
-+ >>> my_ob.method(1, 2)
-+ 1 2
-+ >>> my_ob._saved_method.args
-+ (1, 2)
-+ >>> my_ob._saved_method.kwargs
-+ {}
-+ >>> my_ob.method(a=3, b='foo')
-+ 3 foo
-+ >>> my_ob._saved_method.args
-+ ()
-+ >>> my_ob._saved_method.kwargs == dict(a=3, b='foo')
-+ True
-+
-+ The arguments are stored on the instance, allowing for
-+ different instance to save different args.
-+
-+ >>> your_ob = MyClass()
-+ >>> your_ob.method({str('x'): 3}, b=[4])
-+ {'x': 3} [4]
-+ >>> your_ob._saved_method.args
-+ ({'x': 3},)
-+ >>> my_ob._saved_method.args
-+ ()
-+ """
-+ args_and_kwargs = collections.namedtuple('args_and_kwargs', 'args
kwargs')
-+
-+ @functools.wraps(method)
-+ def wrapper(self, *args, **kwargs):
-+ attr_name = '_saved_' + method.__name__
-+ attr = args_and_kwargs(args, kwargs)
-+ setattr(self, attr_name, attr)
-+ return method(self, *args, **kwargs)
-+
-+ return wrapper
-+
-+
-+def except_(*exceptions, replace=None, use=None):
-+ """
-+ Replace the indicated exceptions, if raised, with the indicated
-+ literal replacement or evaluated expression (if present).
-+
-+ >>> safe_int = except_(ValueError)(int)
-+ >>> safe_int('five')
-+ >>> safe_int('5')
-+ 5
-+
-+ Specify a literal replacement with ``replace``.
-+
-+ >>> safe_int_r = except_(ValueError, replace=0)(int)
-+ >>> safe_int_r('five')
-+ 0
-+
-+ Provide an expression to ``use`` to pass through particular parameters.
-+
-+ >>> safe_int_pt = except_(ValueError, use='args[0]')(int)
-+ >>> safe_int_pt('five')
-+ 'five'
-+
-+ """
-+
-+ def decorate(func):
-+ @functools.wraps(func)
-+ def wrapper(*args, **kwargs):
-+ try:
-+ return func(*args, **kwargs)
-+ except exceptions:
-+ try:
-+ return eval(use)
-+ except TypeError:
-+ return replace
-+
-+ return wrapper
-+
-+ return decorate
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/jaraco/text/__init__.py
b/third_party/python/setuptools/pkg_resources/_vendor/jaraco/text/__init__.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/jaraco/text/__init__.py
-@@ -0,0 +1,599 @@
-+import re
-+import itertools
-+import textwrap
-+import functools
-+
-+try:
-+ from importlib.resources import files # type: ignore
-+except ImportError: # pragma: nocover
-+ from pkg_resources.extern.importlib_resources import files # type:
ignore
-+
-+from pkg_resources.extern.jaraco.functools import compose, method_cache
-+from pkg_resources.extern.jaraco.context import ExceptionTrap
-+
-+
-+def substitution(old, new):
-+ """
-+ Return a function that will perform a substitution on a string
-+ """
-+ return lambda s: s.replace(old, new)
-+
-+
-+def multi_substitution(*substitutions):
-+ """
-+ Take a sequence of pairs specifying substitutions, and create
-+ a function that performs those substitutions.
-+
-+ >>> multi_substitution(('foo', 'bar'), ('bar', 'baz'))('foo')
-+ 'baz'
-+ """
-+ substitutions = itertools.starmap(substitution, substitutions)
-+ # compose function applies last function first, so reverse the
-+ # substitutions to get the expected order.
-+ substitutions = reversed(tuple(substitutions))
-+ return compose(*substitutions)
-+
-+
-+class FoldedCase(str):
-+ """
-+ A case insensitive string class; behaves just like str
-+ except compares equal when the only variation is case.
-+
-+ >>> s = FoldedCase('hello world')
-+
-+ >>> s == 'Hello World'
-+ True
-+
-+ >>> 'Hello World' == s
-+ True
-+
-+ >>> s != 'Hello World'
-+ False
-+
-+ >>> s.index('O')
-+ 4
-+
-+ >>> s.split('O')
-+ ['hell', ' w', 'rld']
-+
-+ >>> sorted(map(FoldedCase, ['GAMMA', 'alpha', 'Beta']))
-+ ['alpha', 'Beta', 'GAMMA']
-+
-+ Sequence membership is straightforward.
-+
-+ >>> "Hello World" in [s]
-+ True
-+ >>> s in ["Hello World"]
-+ True
-+
-+ You may test for set inclusion, but candidate and elements
-+ must both be folded.
-+
-+ >>> FoldedCase("Hello World") in {s}
-+ True
-+ >>> s in {FoldedCase("Hello World")}
-+ True
-+
-+ String inclusion works as long as the FoldedCase object
-+ is on the right.
-+
-+ >>> "hello" in FoldedCase("Hello World")
-+ True
-+
-+ But not if the FoldedCase object is on the left:
-+
-+ >>> FoldedCase('hello') in 'Hello World'
-+ False
-+
-+ In that case, use ``in_``:
-+
-+ >>> FoldedCase('hello').in_('Hello World')
-+ True
-+
-+ >>> FoldedCase('hello') > FoldedCase('Hello')
-+ False
-+ """
-+
-+ def __lt__(self, other):
-+ return self.lower() < other.lower()
-+
-+ def __gt__(self, other):
-+ return self.lower() > other.lower()
-+
-+ def __eq__(self, other):
-+ return self.lower() == other.lower()
-+
-+ def __ne__(self, other):
-+ return self.lower() != other.lower()
-+
-+ def __hash__(self):
-+ return hash(self.lower())
-+
-+ def __contains__(self, other):
-+ return super().lower().__contains__(other.lower())
-+
-+ def in_(self, other):
-+ "Does self appear in other?"
-+ return self in FoldedCase(other)
-+
-+ # cache lower since it's likely to be called frequently.
-+ @method_cache
-+ def lower(self):
-+ return super().lower()
-+
-+ def index(self, sub):
-+ return self.lower().index(sub.lower())
-+
-+ def split(self, splitter=' ', maxsplit=0):
-+ pattern = re.compile(re.escape(splitter), re.I)
-+ return pattern.split(self, maxsplit)
-+
-+
-+# Python 3.8 compatibility
-+_unicode_trap = ExceptionTrap(UnicodeDecodeError)
-+
-+
-+@_unicode_trap.passes
-+def is_decodable(value):
-+ r"""
-+ Return True if the supplied value is decodable (using the default
-+ encoding).
-+
-+ >>> is_decodable(b'\xff')
-+ False
-+ >>> is_decodable(b'\x32')
-+ True
-+ """
-+ value.decode()
-+
-+
-+def is_binary(value):
-+ r"""
-+ Return True if the value appears to be binary (that is, it's a byte
-+ string and isn't decodable).
-+
-+ >>> is_binary(b'\xff')
-+ True
-+ >>> is_binary('\xff')
-+ False
-+ """
-+ return isinstance(value, bytes) and not is_decodable(value)
-+
-+
-+def trim(s):
-+ r"""
-+ Trim something like a docstring to remove the whitespace that
-+ is common due to indentation and formatting.
-+
-+ >>> trim("\n\tfoo = bar\n\t\tbar = baz\n")
-+ 'foo = bar\n\tbar = baz'
-+ """
-+ return textwrap.dedent(s).strip()
-+
-+
-+def wrap(s):
-+ """
-+ Wrap lines of text, retaining existing newlines as
-+ paragraph markers.
-+
-+ >>> print(wrap(lorem_ipsum))
-+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
-+ eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
-+ minim veniam, quis nostrud exercitation ullamco laboris nisi ut
-+ aliquip ex ea commodo consequat. Duis aute irure dolor in
-+ reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
-+ pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
-+ culpa qui officia deserunt mollit anim id est laborum.
-+ <BLANKLINE>
-+ Curabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam
-+ varius, turpis et commodo pharetra, est eros bibendum elit, nec luctus
-+ magna felis sollicitudin mauris. Integer in mauris eu nibh euismod
-+ gravida. Duis ac tellus et risus vulputate vehicula. Donec lobortis
-+ risus a elit. Etiam tempor. Ut ullamcorper, ligula eu tempor congue,
-+ eros est euismod turpis, id tincidunt sapien risus a quam. Maecenas
-+ fermentum consequat mi. Donec fermentum. Pellentesque malesuada nulla
-+ a mi. Duis sapien sem, aliquet nec, commodo eget, consequat quis,
-+ neque. Aliquam faucibus, elit ut dictum aliquet, felis nisl adipiscing
-+ sapien, sed malesuada diam lacus eget erat. Cras mollis scelerisque
-+ nunc. Nullam arcu. Aliquam consequat. Curabitur augue lorem, dapibus
-+ quis, laoreet et, pretium ac, nisi. Aenean magna nisl, mollis quis,
-+ molestie eu, feugiat in, orci. In hac habitasse platea dictumst.
-+ """
-+ paragraphs = s.splitlines()
-+ wrapped = ('\n'.join(textwrap.wrap(para)) for para in paragraphs)
-+ return '\n\n'.join(wrapped)
-+
-+
-+def unwrap(s):
-+ r"""
-+ Given a multi-line string, return an unwrapped version.
-+
-+ >>> wrapped = wrap(lorem_ipsum)
-+ >>> wrapped.count('\n')
-+ 20
-+ >>> unwrapped = unwrap(wrapped)
-+ >>> unwrapped.count('\n')
-+ 1
-+ >>> print(unwrapped)
-+ Lorem ipsum dolor sit amet, consectetur adipiscing ...
-+ Curabitur pretium tincidunt lacus. Nulla gravida orci ...
-+
-+ """
-+ paragraphs = re.split(r'\n\n+', s)
-+ cleaned = (para.replace('\n', ' ') for para in paragraphs)
-+ return '\n'.join(cleaned)
-+
-+
-+
-+
-+class Splitter(object):
-+ """object that will split a string with the given arguments for each
call
-+
-+ >>> s = Splitter(',')
-+ >>> s('hello, world, this is your, master calling')
-+ ['hello', ' world', ' this is your', ' master calling']
-+ """
-+
-+ def __init__(self, *args):
-+ self.args = args
-+
-+ def __call__(self, s):
-+ return s.split(*self.args)
-+
-+
-+def indent(string, prefix=' ' * 4):
-+ """
-+ >>> indent('foo')
-+ ' foo'
-+ """
-+ return prefix + string
-+
-+
-+class WordSet(tuple):
-+ """
-+ Given an identifier, return the words that identifier represents,
-+ whether in camel case, underscore-separated, etc.
-+
-+ >>> WordSet.parse("camelCase")
-+ ('camel', 'Case')
-+
-+ >>> WordSet.parse("under_sep")
-+ ('under', 'sep')
-+
-+ Acronyms should be retained
-+
-+ >>> WordSet.parse("firstSNL")
-+ ('first', 'SNL')
-+
-+ >>> WordSet.parse("you_and_I")
-+ ('you', 'and', 'I')
-+
-+ >>> WordSet.parse("A simple test")
-+ ('A', 'simple', 'test')
-+
-+ Multiple caps should not interfere with the first cap of another word.
-+
-+ >>> WordSet.parse("myABCClass")
-+ ('my', 'ABC', 'Class')
-+
-+ The result is a WordSet, so you can get the form you need.
-+
-+ >>> WordSet.parse("myABCClass").underscore_separated()
-+ 'my_ABC_Class'
-+
-+ >>> WordSet.parse('a-command').camel_case()
-+ 'ACommand'
-+
-+ >>> WordSet.parse('someIdentifier').lowered().space_separated()
-+ 'some identifier'
-+
-+ Slices of the result should return another WordSet.
-+
-+ >>> WordSet.parse('taken-out-of-context')[1:].underscore_separated()
-+ 'out_of_context'
-+
-+ >>> WordSet.from_class_name(WordSet()).lowered().space_separated()
-+ 'word set'
-+
-+ >>> example = WordSet.parse('figured it out')
-+ >>> example.headless_camel_case()
-+ 'figuredItOut'
-+ >>> example.dash_separated()
-+ 'figured-it-out'
-+
-+ """
-+
-+ _pattern = re.compile('([A-Z]?[a-z]+)|([A-Z]+(?![a-z]))')
-+
-+ def capitalized(self):
-+ return WordSet(word.capitalize() for word in self)
-+
-+ def lowered(self):
-+ return WordSet(word.lower() for word in self)
-+
-+ def camel_case(self):
-+ return ''.join(self.capitalized())
-+
-+ def headless_camel_case(self):
-+ words = iter(self)
-+ first = next(words).lower()
-+ new_words = itertools.chain((first,), WordSet(words).camel_case())
-+ return ''.join(new_words)
-+
-+ def underscore_separated(self):
-+ return '_'.join(self)
-+
-+ def dash_separated(self):
-+ return '-'.join(self)
-+
-+ def space_separated(self):
-+ return ' '.join(self)
-+
-+ def trim_right(self, item):
-+ """
-+ Remove the item from the end of the set.
-+
-+ >>> WordSet.parse('foo bar').trim_right('foo')
-+ ('foo', 'bar')
-+ >>> WordSet.parse('foo bar').trim_right('bar')
-+ ('foo',)
-+ >>> WordSet.parse('').trim_right('bar')
-+ ()
-+ """
-+ return self[:-1] if self and self[-1] == item else self
-+
-+ def trim_left(self, item):
-+ """
-+ Remove the item from the beginning of the set.
-+
-+ >>> WordSet.parse('foo bar').trim_left('foo')
-+ ('bar',)
-+ >>> WordSet.parse('foo bar').trim_left('bar')
-+ ('foo', 'bar')
-+ >>> WordSet.parse('').trim_left('bar')
-+ ()
-+ """
-+ return self[1:] if self and self[0] == item else self
-+
-+ def trim(self, item):
-+ """
-+ >>> WordSet.parse('foo bar').trim('foo')
-+ ('bar',)
-+ """
-+ return self.trim_left(item).trim_right(item)
-+
-+ def __getitem__(self, item):
-+ result = super(WordSet, self).__getitem__(item)
-+ if isinstance(item, slice):
-+ result = WordSet(result)
-+ return result
-+
-+ @classmethod
-+ def parse(cls, identifier):
-+ matches = cls._pattern.finditer(identifier)
-+ return WordSet(match.group(0) for match in matches)
-+
-+ @classmethod
-+ def from_class_name(cls, subject):
-+ return cls.parse(subject.__class__.__name__)
-+
-+
-+# for backward compatibility
-+words = WordSet.parse
-+
-+
-+def simple_html_strip(s):
-+ r"""
-+ Remove HTML from the string `s`.
-+
-+ >>> str(simple_html_strip(''))
-+ ''
-+
-+ >>> print(simple_html_strip('A <bold>stormy</bold> day in paradise'))
-+ A stormy day in paradise
-+
-+ >>> print(simple_html_strip('Somebody <!-- do not --> tell the truth.'))
-+ Somebody tell the truth.
-+
-+ >>> print(simple_html_strip('What about<br/>\nmultiple lines?'))
-+ What about
-+ multiple lines?
-+ """
-+ html_stripper = re.compile('(<!--.*?-->)|(<[^>]*>)|([^<]+)', re.DOTALL)
-+ texts = (match.group(3) or '' for match in html_stripper.finditer(s))
-+ return ''.join(texts)
-+
-+
-+class SeparatedValues(str):
-+ """
-+ A string separated by a separator. Overrides __iter__ for getting
-+ the values.
-+
-+ >>> list(SeparatedValues('a,b,c'))
-+ ['a', 'b', 'c']
-+
-+ Whitespace is stripped and empty values are discarded.
-+
-+ >>> list(SeparatedValues(' a, b , c, '))
-+ ['a', 'b', 'c']
-+ """
-+
-+ separator = ','
-+
-+ def __iter__(self):
-+ parts = self.split(self.separator)
-+ return filter(None, (part.strip() for part in parts))
-+
-+
-+class Stripper:
-+ r"""
-+ Given a series of lines, find the common prefix and strip it from them.
-+
-+ >>> lines = [
-+ ... 'abcdefg\n',
-+ ... 'abc\n',
-+ ... 'abcde\n',
-+ ... ]
-+ >>> res = Stripper.strip_prefix(lines)
-+ >>> res.prefix
-+ 'abc'
-+ >>> list(res.lines)
-+ ['defg\n', '\n', 'de\n']
-+
-+ If no prefix is common, nothing should be stripped.
-+
-+ >>> lines = [
-+ ... 'abcd\n',
-+ ... '1234\n',
-+ ... ]
-+ >>> res = Stripper.strip_prefix(lines)
-+ >>> res.prefix = ''
-+ >>> list(res.lines)
-+ ['abcd\n', '1234\n']
-+ """
-+
-+ def __init__(self, prefix, lines):
-+ self.prefix = prefix
-+ self.lines = map(self, lines)
-+
-+ @classmethod
-+ def strip_prefix(cls, lines):
-+ prefix_lines, lines = itertools.tee(lines)
-+ prefix = functools.reduce(cls.common_prefix, prefix_lines)
-+ return cls(prefix, lines)
-+
-+ def __call__(self, line):
-+ if not self.prefix:
-+ return line
-+ null, prefix, rest = line.partition(self.prefix)
-+ return rest
-+
-+ @staticmethod
-+ def common_prefix(s1, s2):
-+ """
-+ Return the common prefix of two lines.
-+ """
-+ index = min(len(s1), len(s2))
-+ while s1[:index] != s2[:index]:
-+ index -= 1
-+ return s1[:index]
-+
-+
-+def remove_prefix(text, prefix):
-+ """
-+ Remove the prefix from the text if it exists.
-+
-+ >>> remove_prefix('underwhelming performance', 'underwhelming ')
-+ 'performance'
-+
-+ >>> remove_prefix('something special', 'sample')
-+ 'something special'
-+ """
-+ null, prefix, rest = text.rpartition(prefix)
-+ return rest
-+
-+
-+def remove_suffix(text, suffix):
-+ """
-+ Remove the suffix from the text if it exists.
-+
-+ >>> remove_suffix('name.git', '.git')
-+ 'name'
-+
-+ >>> remove_suffix('something special', 'sample')
-+ 'something special'
-+ """
-+ rest, suffix, null = text.partition(suffix)
-+ return rest
-+
-+
-+def normalize_newlines(text):
-+ r"""
-+ Replace alternate newlines with the canonical newline.
-+
-+ >>> normalize_newlines('Lorem Ipsum\u2029')
-+ 'Lorem Ipsum\n'
-+ >>> normalize_newlines('Lorem Ipsum\r\n')
-+ 'Lorem Ipsum\n'
-+ >>> normalize_newlines('Lorem Ipsum\x85')
-+ 'Lorem Ipsum\n'
-+ """
-+ newlines = ['\r\n', '\r', '\n', '\u0085', '\u2028', '\u2029']
-+ pattern = '|'.join(newlines)
-+ return re.sub(pattern, '\n', text)
-+
-+
-+def _nonblank(str):
-+ return str and not str.startswith('#')
-+
-+
-+@functools.singledispatch
-+def yield_lines(iterable):
-+ r"""
-+ Yield valid lines of a string or iterable.
-+
-+ >>> list(yield_lines(''))
-+ []
-+ >>> list(yield_lines(['foo', 'bar']))
-+ ['foo', 'bar']
-+ >>> list(yield_lines('foo\nbar'))
-+ ['foo', 'bar']
-+ >>> list(yield_lines('\nfoo\n#bar\nbaz #comment'))
-+ ['foo', 'baz #comment']
-+ >>> list(yield_lines(['foo\nbar', 'baz', 'bing\n\n\n']))
-+ ['foo', 'bar', 'baz', 'bing']
-+ """
-+ return itertools.chain.from_iterable(map(yield_lines, iterable))
-+
-+
-+@yield_lines.register(str)
-+def _(text):
-+ return filter(_nonblank, map(str.strip, text.splitlines()))
-+
-+
-+def drop_comment(line):
-+ """
-+ Drop comments.
-+
-+ >>> drop_comment('foo # bar')
-+ 'foo'
-+
-+ A hash without a space may be in a URL.
-+
-+ >>> drop_comment('http://example.com/foo#bar')
-+ 'http://example.com/foo#bar'
-+ """
-+ return line.partition(' #')[0]
-+
-+
-+def join_continuation(lines):
-+ r"""
-+ Join lines continued by a trailing backslash.
-+
-+ >>> list(join_continuation(['foo \\', 'bar', 'baz']))
-+ ['foobar', 'baz']
-+ >>> list(join_continuation(['foo \\', 'bar', 'baz']))
-+ ['foobar', 'baz']
-+ >>> list(join_continuation(['foo \\', 'bar \\', 'baz']))
-+ ['foobarbaz']
-+
-+ Not sure why, but...
-+ The character preceeding the backslash is also elided.
-+
-+ >>> list(join_continuation(['goo\\', 'dly']))
-+ ['godly']
-+
-+ A terrible idea, but...
-+ If no line is available to continue, suppress the lines.
-+
-+ >>> list(join_continuation(['foo', 'bar\\', 'baz\\']))
-+ ['foo']
-+ """
-+ lines = iter(lines)
-+ for item in lines:
-+ while item.endswith('\\'):
-+ try:
-+ item = item[:-2].strip() + next(lines)
-+ except StopIteration:
-+ return
-+ yield item
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/more_itertools/__init__.py

b/third_party/python/setuptools/pkg_resources/_vendor/more_itertools/__init__.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/more_itertools/__init__.py
-@@ -0,0 +1,6 @@
-+"""More routines for operating on iterables, beyond itertools"""
-+
-+from .more import * # noqa
-+from .recipes import * # noqa
-+
-+__version__ = '9.1.0'
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/more_itertools/more.py
b/third_party/python/setuptools/pkg_resources/_vendor/more_itertools/more.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/more_itertools/more.py
-@@ -0,0 +1,4391 @@
-+import warnings
-+
-+from collections import Counter, defaultdict, deque, abc
-+from collections.abc import Sequence
-+from functools import partial, reduce, wraps
-+from heapq import 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, ge, le
-+from sys import hexversion, maxsize
-+from time import monotonic
-+
-+from .recipes import (
-+ _marker,
-+ _zip_equal,
-+ UnequalIterablesError,
-+ consume,
-+ flatten,
-+ pairwise,
-+ powerset,
-+ take,
-+ unique_everseen,
-+ all_equal,
-+)
-+
-+__all__ = [
-+ 'AbortThread',
-+ 'SequenceView',
-+ 'UnequalIterablesError',
-+ 'adjacent',
-+ 'all_unique',
-+ 'always_iterable',
-+ 'always_reversible',
-+ 'bucket',
-+ 'callback_iter',
-+ 'chunked',
-+ 'chunked_even',
-+ 'circular_shifts',
-+ 'collapse',
-+ 'combination_index',
-+ 'consecutive_groups',
-+ 'constrained_batches',
-+ 'consumer',
-+ 'count_cycle',
-+ 'countable',
-+ 'difference',
-+ 'distinct_combinations',
-+ 'distinct_permutations',
-+ 'distribute',
-+ 'divide',
-+ 'duplicates_everseen',
-+ 'duplicates_justseen',
-+ 'exactly_n',
-+ 'filter_except',
-+ 'first',
-+ 'gray_product',
-+ 'groupby_transform',
-+ 'ichunked',
-+ 'iequals',
-+ 'ilen',
-+ 'interleave',
-+ 'interleave_evenly',
-+ 'interleave_longest',
-+ 'intersperse',
-+ 'is_sorted',
-+ 'islice_extended',
-+ 'iterate',
-+ 'last',
-+ 'locate',
-+ 'longest_common_prefix',
-+ 'lstrip',
-+ 'make_decorator',
-+ 'map_except',
-+ 'map_if',
-+ 'map_reduce',
-+ 'mark_ends',
-+ 'minmax',
-+ 'nth_or_last',
-+ 'nth_permutation',
-+ 'nth_product',
-+ 'numeric_range',
-+ 'one',
-+ 'only',
-+ 'padded',
-+ 'partitions',
-+ 'peekable',
-+ 'permutation_index',
-+ 'product_index',
-+ 'raise_',
-+ 'repeat_each',
-+ 'repeat_last',
-+ 'replace',
-+ 'rlocate',
-+ 'rstrip',
-+ 'run_length',
-+ 'sample',
-+ 'seekable',
-+ 'set_partitions',
-+ 'side_effect',
-+ 'sliced',
-+ 'sort_together',
-+ 'split_after',
-+ 'split_at',
-+ 'split_before',
-+ 'split_into',
-+ 'split_when',
-+ 'spy',
-+ 'stagger',
-+ 'strip',
-+ 'strictly_n',
-+ 'substrings',
-+ 'substrings_indexes',
-+ 'time_limited',
-+ 'unique_in_window',
-+ 'unique_to_each',
-+ 'unzip',
-+ 'value_chain',
-+ 'windowed',
-+ 'windowed_complete',
-+ 'with_iter',
-+ 'zip_broadcast',
-+ 'zip_equal',
-+ 'zip_offset',
-+]
-+
-+
-+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:
-+ if n is None:
-+ raise ValueError('n must not be None when using strict mode.')
-+
-+ 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 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 raise_(exception, *args):
-+ raise exception(*args)
-+
-+
-+def strictly_n(iterable, n, too_short=None, too_long=None):
-+ """Validate that *iterable* has exactly *n* items and return them if
-+ it does. If it has fewer than *n* items, call function *too_short*
-+ with those items. If it has more than *n* items, call function
-+ *too_long* with the first ``n + 1`` items.
-+
-+ >>> iterable = ['a', 'b', 'c', 'd']
-+ >>> n = 4
-+ >>> list(strictly_n(iterable, n))
-+ ['a', 'b', 'c', 'd']
-+
-+ By default, *too_short* and *too_long* are functions that raise
-+ ``ValueError``.
-+
-+ >>> list(strictly_n('ab', 3)) # doctest: +IGNORE_EXCEPTION_DETAIL
-+ Traceback (most recent call last):
-+ ...
-+ ValueError: too few items in iterable (got 2)
-+
-+ >>> list(strictly_n('abc', 2)) # doctest: +IGNORE_EXCEPTION_DETAIL
-+ Traceback (most recent call last):
-+ ...
-+ ValueError: too many items in iterable (got at least 3)
-+
-+ You can instead supply functions that do something else.
-+ *too_short* will be called with the number of items in *iterable*.
-+ *too_long* will be called with `n + 1`.
-+
-+ >>> def too_short(item_count):
-+ ... raise RuntimeError
-+ >>> it = strictly_n('abcd', 6, too_short=too_short)
-+ >>> list(it) # doctest: +IGNORE_EXCEPTION_DETAIL
-+ Traceback (most recent call last):
-+ ...
-+ RuntimeError
-+
-+ >>> def too_long(item_count):
-+ ... print('The boss is going to hear about this')
-+ >>> it = strictly_n('abcdef', 4, too_long=too_long)
-+ >>> list(it)
-+ The boss is going to hear about this
-+ ['a', 'b', 'c', 'd']
-+
-+ """
-+ if too_short is None:
-+ too_short = lambda item_count: raise_(
-+ ValueError,
-+ 'Too few items in iterable (got {})'.format(item_count),
-+ )
-+
-+ if too_long is None:
-+ too_long = lambda item_count: raise_(
-+ ValueError,
-+ 'Too many items in iterable (got at least
{})'.format(item_count),
-+ )
-+
-+ it = iter(iterable)
-+ for i in range(n):
-+ try:
-+ item = next(it)
-+ except StopIteration:
-+ too_short(i)
-+ return
-+ else:
-+ yield item
-+
-+ try:
-+ next(it)
-+ except StopIteration:
-+ pass
-+ else:
-+ too_long(n + 1)
-+
-+
-+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, x_1, e, x_2...
-+ # islice(..., 1, None) -> x_0, 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 == 0:
-+ return
-+ elif 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 interleave_evenly(iterables, lengths=None):
-+ """
-+ Interleave multiple iterables so that their elements are evenly
distributed
-+ throughout the output sequence.
-+
-+ >>> iterables = [1, 2, 3, 4, 5], ['a', 'b']
-+ >>> list(interleave_evenly(iterables))
-+ [1, 2, 'a', 3, 4, 'b', 5]
-+
-+ >>> iterables = [[1, 2, 3], [4, 5], [6, 7, 8]]
-+ >>> list(interleave_evenly(iterables))
-+ [1, 6, 4, 2, 7, 3, 8, 5]
-+
-+ This function requires iterables of known length. Iterables without
-+ ``__len__()`` can be used by manually specifying lengths with *lengths*:
-+
-+ >>> from itertools import combinations, repeat
-+ >>> iterables = [combinations(range(4), 2), ['a', 'b', 'c']]
-+ >>> lengths = [4 * (4 - 1) // 2, 3]
-+ >>> list(interleave_evenly(iterables, lengths=lengths))
-+ [(0, 1), (0, 2), 'a', (0, 3), (1, 2), 'b', (1, 3), (2, 3), 'c']
-+
-+ Based on Bresenham's algorithm.
-+ """
-+ if lengths is None:
-+ try:
-+ lengths = [len(it) for it in iterables]
-+ except TypeError:
-+ raise ValueError(
-+ 'Iterable lengths could not be determined automatically. '
-+ 'Specify them with the lengths keyword.'
-+ )
-+ elif len(iterables) != len(lengths):
-+ raise ValueError('Mismatching number of iterables and lengths.')
-+
-+ dims = len(lengths)
-+
-+ # sort iterables by length, descending
-+ lengths_permute = sorted(
-+ range(dims), key=lambda i: lengths[i], reverse=True
-+ )
-+ lengths_desc = [lengths[i] for i in lengths_permute]
-+ iters_desc = [iter(iterables[i]) for i in lengths_permute]
-+
-+ # the longest iterable is the primary one (Bresenham: the longest
-+ # distance along an axis)
-+ delta_primary, deltas_secondary = lengths_desc[0], lengths_desc[1:]
-+ iter_primary, iters_secondary = iters_desc[0], iters_desc[1:]
-+ errors = [delta_primary // dims] * len(deltas_secondary)
-+
-+ to_yield = sum(lengths)
-+ while to_yield:
-+ yield next(iter_primary)
-+ to_yield -= 1
-+ # update errors for each secondary iterable
-+ errors = [e - delta for e, delta in zip(errors, deltas_secondary)]
-+
-+ # those iterables for which the error is negative are yielded
-+ # ("diagonal step" in Bresenham)
-+ for i, e in enumerate(errors):
-+ if e < 0:
-+ yield next(iters_secondary[i])
-+ to_yield -= 1
-+ errors[i] += delta_primary
-+
-+
-+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.
-+ To 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:
-+ buf = list(it)
-+ if buf:
-+ yield buf
-+ 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_each(iterable, n=2):
-+ """Repeat each element in *iterable* *n* times.
-+
-+ >>> list(repeat_each('ABC', 3))
-+ ['A', 'A', 'A', 'B', 'B', 'B', 'C', 'C', 'C']
-+ """
-+ return chain.from_iterable(map(repeat, iterable, repeat(n)))
-+
-+
-+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
-+ )
-+
-+
-+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,
-+ )
-+
-+ return _zip_equal(*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 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 longest_common_prefix(iterables):
-+ """Yield elements of the longest common prefix amongst given
*iterables*.
-+
-+ >>> ''.join(longest_common_prefix(['abcd', 'abc', 'abf']))
-+ 'ab'
-+
-+ """
-+ return (c[0] for c in takewhile(all_equal, zip(*iterables)))
-+
-+
-+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, map(func, 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
-+
-+
-+class _IChunk:
-+ def __init__(self, iterable, n):
-+ self._it = islice(iterable, n)
-+ self._cache = deque()
-+
-+ def fill_cache(self):
-+ self._cache.extend(self._it)
-+
-+ def __iter__(self):
-+ return self
-+
-+ def __next__(self):
-+ try:
-+ return next(self._it)
-+ except StopIteration:
-+ if self._cache:
-+ return self._cache.popleft()
-+ else:
-+ raise
-+
-+
-+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 = peekable(iter(iterable))
-+ ichunk_marker = object()
-+ while True:
-+ # Check to see whether we're at the end of the source iterable
-+ item = source.peek(ichunk_marker)
-+ if item is ichunk_marker:
-+ return
-+
-+ chunk = _IChunk(source, n)
-+ yield chunk
-+
-+ # Advance the source iterable and fill previous chunk's cache
-+ chunk.fill_cache()
-+
-+
-+def iequals(*iterables):
-+ """Return ``True`` if all given *iterables* are equal to each other,
-+ which means that they contain the same elements in the same order.
-+
-+ The function is useful for comparing iterables of different data types
-+ or iterables that do not support equality checks.
-+
-+ >>> iequals("abc", ['a', 'b', 'c'], ('a', 'b', 'c'), iter("abc"))
-+ True
-+
-+ >>> iequals("abc", "acb")
-+ False
-+
-+ Not to be confused with :func:`all_equals`, which checks whether all
-+ elements of iterable are equal to each other.
-+
-+ """
-+ return all(map(all_equal, zip_longest(*iterables, fillvalue=object())))
-+
-+
-+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 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 map_if(iterable, pred, func, func_else=lambda x: x):
-+ """Evaluate each item from *iterable* using *pred*. If the result is
-+ equivalent to ``True``, transform the item with *func* and yield it.
-+ Otherwise, transform the item with *func_else* and yield it.
-+
-+ *pred*, *func*, and *func_else* should each be functions that accept
-+ one argument. By default, *func_else* is the identity function.
-+
-+ >>> from math import sqrt
-+ >>> iterable = list(range(-5, 5))
-+ >>> iterable
-+ [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4]
-+ >>> list(map_if(iterable, lambda x: x > 3, lambda x: 'toobig'))
-+ [-5, -4, -3, -2, -1, 0, 1, 2, 3, 'toobig']
-+ >>> list(map_if(iterable, lambda x: x >= 0,
-+ ... lambda x: f'{sqrt(x):.2f}', lambda x: None))
-+ [None, None, None, None, None, '0.00', '1.00', '1.41', '1.73', '2.00']
-+ """
-+ for item in iterable:
-+ yield func(item) if pred(item) else func_else(item)
-+
-+
-+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, strict=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
-+
-+ If *strict*, tests for strict sorting, that is, returns ``False`` if
equal
-+ elements are found:
-+
-+ >>> is_sorted([1, 2, 2])
-+ True
-+ >>> is_sorted([1, 2, 2], strict=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 = (le if reverse else ge) if strict else (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
-+ # Lazily import concurrent.future
-+ self._executor = __import__(
-+
).futures.__import__("concurrent.futures").futures.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 versions 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
-+
-+
-+def chunked_even(iterable, n):
-+ """Break *iterable* into lists of approximately length *n*.
-+ Items are distributed such the lengths of the lists differ by at most
-+ 1 item.
-+
-+ >>> iterable = [1, 2, 3, 4, 5, 6, 7]
-+ >>> n = 3
-+ >>> list(chunked_even(iterable, n)) # List lengths: 3, 2, 2
-+ [[1, 2, 3], [4, 5], [6, 7]]
-+ >>> list(chunked(iterable, n)) # List lengths: 3, 3, 1
-+ [[1, 2, 3], [4, 5, 6], [7]]
-+
-+ """
-+
-+ len_method = getattr(iterable, '__len__', None)
-+
-+ if len_method is None:
-+ return _chunked_even_online(iterable, n)
-+ else:
-+ return _chunked_even_finite(iterable, len_method(), n)
-+
-+
-+def _chunked_even_online(iterable, n):
-+ buffer = []
-+ maxbuf = n + (n - 2) * (n - 1)
-+ for x in iterable:
-+ buffer.append(x)
-+ if len(buffer) == maxbuf:
-+ yield buffer[:n]
-+ buffer = buffer[n:]
-+ yield from _chunked_even_finite(buffer, len(buffer), n)
-+
-+
-+def _chunked_even_finite(iterable, N, n):
-+ if N < 1:
-+ return
-+
-+ # Lists are either size `full_size <= n` or `partial_size = full_size -
1`
-+ q, r = divmod(N, n)
-+ num_lists = q + (1 if r > 0 else 0)
-+ q, r = divmod(N, num_lists)
-+ full_size = q + (1 if r > 0 else 0)
-+ partial_size = full_size - 1
-+ num_full = N - partial_size * num_lists
-+ num_partial = num_lists - num_full
-+
-+ buffer = []
-+ iterator = iter(iterable)
-+
-+ # Yield num_full lists of full_size
-+ for x in iterator:
-+ buffer.append(x)
-+ if len(buffer) == full_size:
-+ yield buffer
-+ buffer = []
-+ num_full -= 1
-+ if num_full <= 0:
-+ break
-+
-+ # Yield num_partial lists of partial_size
-+ for x in iterator:
-+ buffer.append(x)
-+ if len(buffer) == partial_size:
-+ yield buffer
-+ buffer = []
-+ num_partial -= 1
-+
-+
-+def zip_broadcast(*objects, scalar_types=(str, bytes), strict=False):
-+ """A version of :func:`zip` that "broadcasts" any scalar
-+ (i.e., non-iterable) items into output tuples.
-+
-+ >>> iterable_1 = [1, 2, 3]
-+ >>> iterable_2 = ['a', 'b', 'c']
-+ >>> scalar = '_'
-+ >>> list(zip_broadcast(iterable_1, iterable_2, scalar))
-+ [(1, 'a', '_'), (2, 'b', '_'), (3, 'c', '_')]
-+
-+ The *scalar_types* keyword argument determines what types are considered
-+ scalar. It is set to ``(str, bytes)`` by default. Set it to ``None`` to
-+ treat strings and byte strings as iterable:
-+
-+ >>> list(zip_broadcast('abc', 0, 'xyz', scalar_types=None))
-+ [('a', 0, 'x'), ('b', 0, 'y'), ('c', 0, 'z')]
-+
-+ If the *strict* keyword argument is ``True``, then
-+ ``UnequalIterablesError`` will be raised if any of the iterables have
-+ different lengths.
-+ """
-+
-+ def is_scalar(obj):
-+ if scalar_types and isinstance(obj, scalar_types):
-+ return True
-+ try:
-+ iter(obj)
-+ except TypeError:
-+ return True
-+ else:
-+ return False
-+
-+ size = len(objects)
-+ if not size:
-+ return
-+
-+ iterables, iterable_positions = [], []
-+ scalars, scalar_positions = [], []
-+ for i, obj in enumerate(objects):
-+ if is_scalar(obj):
-+ scalars.append(obj)
-+ scalar_positions.append(i)
-+ else:
-+ iterables.append(iter(obj))
-+ iterable_positions.append(i)
-+
-+ if len(scalars) == size:
-+ yield tuple(objects)
-+ return
-+
-+ zipper = _zip_equal if strict else zip
-+ for item in zipper(*iterables):
-+ new_item = [None] * size
-+
-+ for i, elem in zip(iterable_positions, item):
-+ new_item[i] = elem
-+
-+ for i, elem in zip(scalar_positions, scalars):
-+ new_item[i] = elem
-+
-+ yield tuple(new_item)
-+
-+
-+def unique_in_window(iterable, n, key=None):
-+ """Yield the items from *iterable* that haven't been seen recently.
-+ *n* is the size of the lookback window.
-+
-+ >>> iterable = [0, 1, 0, 2, 3, 0]
-+ >>> n = 3
-+ >>> list(unique_in_window(iterable, n))
-+ [0, 1, 2, 3, 0]
-+
-+ The *key* function, if provided, will be used to determine uniqueness:
-+
-+ >>> list(unique_in_window('abAcda', 3, key=lambda x: x.lower()))
-+ ['a', 'b', 'c', 'd', 'a']
-+
-+ The items in *iterable* must be hashable.
-+
-+ """
-+ if n <= 0:
-+ raise ValueError('n must be greater than 0')
-+
-+ window = deque(maxlen=n)
-+ uniques = set()
-+ use_key = key is not None
-+
-+ for item in iterable:
-+ k = key(item) if use_key else item
-+ if k in uniques:
-+ continue
-+
-+ if len(uniques) == n:
-+ uniques.discard(window[0])
-+
-+ uniques.add(k)
-+ window.append(k)
-+
-+ yield item
-+
-+
-+def duplicates_everseen(iterable, key=None):
-+ """Yield duplicate elements after their first appearance.
-+
-+ >>> list(duplicates_everseen('mississippi'))
-+ ['s', 'i', 's', 's', 'i', 'p', 'i']
-+ >>> list(duplicates_everseen('AaaBbbCccAaa', str.lower))
-+ ['a', 'a', 'b', 'b', 'c', 'c', 'A', 'a', 'a']
-+
-+ This function is analagous to :func:`unique_everseen` and is subject to
-+ the same performance considerations.
-+
-+ """
-+ seen_set = set()
-+ seen_list = []
-+ use_key = key is not None
-+
-+ for element in iterable:
-+ k = key(element) if use_key else element
-+ try:
-+ if k not in seen_set:
-+ seen_set.add(k)
-+ else:
-+ yield element
-+ except TypeError:
-+ if k not in seen_list:
-+ seen_list.append(k)
-+ else:
-+ yield element
-+
-+
-+def duplicates_justseen(iterable, key=None):
-+ """Yields serially-duplicate elements after their first appearance.
-+
-+ >>> list(duplicates_justseen('mississippi'))
-+ ['s', 's', 'p']
-+ >>> list(duplicates_justseen('AaaBbbCccAaa', str.lower))
-+ ['a', 'a', 'b', 'b', 'c', 'c', 'a', 'a']
-+
-+ This function is analagous to :func:`unique_justseen`.
-+
-+ """
-+ return flatten(
-+ map(
-+ lambda group_tuple: islice_extended(group_tuple[1])[1:],
-+ groupby(iterable, key),
-+ )
-+ )
-+
-+
-+def minmax(iterable_or_value, *others, key=None, default=_marker):
-+ """Returns both the smallest and largest items in an iterable
-+ or the largest of two or more arguments.
-+
-+ >>> minmax([3, 1, 5])
-+ (1, 5)
-+
-+ >>> minmax(4, 2, 6)
-+ (2, 6)
-+
-+ If a *key* function is provided, it will be used to transform the input
-+ items for comparison.
-+
-+ >>> minmax([5, 30], key=str) # '30' sorts before '5'
-+ (30, 5)
-+
-+ If a *default* value is provided, it will be returned if there are no
-+ input items.
-+
-+ >>> minmax([], default=(0, 0))
-+ (0, 0)
-+
-+ Otherwise ``ValueError`` is raised.
-+
-+ This function is based on the
-+ `recipe <http://code.activestate.com/recipes/577916/>`__ by
-+ Raymond Hettinger and takes care to minimize the number of comparisons
-+ performed.
-+ """
-+ iterable = (iterable_or_value, *others) if others else iterable_or_value
-+
-+ it = iter(iterable)
-+
-+ try:
-+ lo = hi = next(it)
-+ except StopIteration as e:
-+ if default is _marker:
-+ raise ValueError(
-+ '`minmax()` argument is an empty iterable. '
-+ 'Provide a `default` value to suppress this error.'
-+ ) from e
-+ return default
-+
-+ # Different branches depending on the presence of key. This saves a lot
-+ # of unimportant copies which would slow the "key=None" branch
-+ # significantly down.
-+ if key is None:
-+ for x, y in zip_longest(it, it, fillvalue=lo):
-+ if y < x:
-+ x, y = y, x
-+ if x < lo:
-+ lo = x
-+ if hi < y:
-+ hi = y
-+
-+ else:
-+ lo_key = hi_key = key(lo)
-+
-+ for x, y in zip_longest(it, it, fillvalue=lo):
-+ x_key, y_key = key(x), key(y)
-+
-+ if y_key < x_key:
-+ x, y, x_key, y_key = y, x, y_key, x_key
-+ if x_key < lo_key:
-+ lo, lo_key = x, x_key
-+ if hi_key < y_key:
-+ hi, hi_key = y, y_key
-+
-+ return lo, hi
-+
-+
-+def constrained_batches(
-+ iterable, max_size, max_count=None, get_len=len, strict=True
-+):
-+ """Yield batches of items from *iterable* with a combined size limited
by
-+ *max_size*.
-+
-+ >>> iterable = [b'12345', b'123', b'12345678', b'1', b'1', b'12', b'1']
-+ >>> list(constrained_batches(iterable, 10))
-+ [(b'12345', b'123'), (b'12345678', b'1', b'1'), (b'12', b'1')]
-+
-+ If a *max_count* is supplied, the number of items per batch is also
-+ limited:
-+
-+ >>> iterable = [b'12345', b'123', b'12345678', b'1', b'1', b'12', b'1']
-+ >>> list(constrained_batches(iterable, 10, max_count = 2))
-+ [(b'12345', b'123'), (b'12345678', b'1'), (b'1', b'12'), (b'1',)]
-+
-+ If a *get_len* function is supplied, use that instead of :func:`len` to
-+ determine item size.
-+
-+ If *strict* is ``True``, raise ``ValueError`` if any single item is
bigger
-+ than *max_size*. Otherwise, allow single items to exceed *max_size*.
-+ """
-+ if max_size <= 0:
-+ raise ValueError('maximum size must be greater than zero')
-+
-+ batch = []
-+ batch_size = 0
-+ batch_count = 0
-+ for item in iterable:
-+ item_len = get_len(item)
-+ if strict and item_len > max_size:
-+ raise ValueError('item size exceeds maximum size')
-+
-+ reached_count = batch_count == max_count
-+ reached_size = item_len + batch_size > max_size
-+ if batch_count and (reached_size or reached_count):
-+ yield tuple(batch)
-+ batch.clear()
-+ batch_size = 0
-+ batch_count = 0
-+
-+ batch.append(item)
-+ batch_size += item_len
-+ batch_count += 1
-+
-+ if batch:
-+ yield tuple(batch)
-+
-+
-+def gray_product(*iterables):
-+ """Like :func:`itertools.product`, but return tuples in an order such
-+ that only one element in the generated tuple changes from one iteration
-+ to the next.
-+
-+ >>> list(gray_product('AB','CD'))
-+ [('A', 'C'), ('B', 'C'), ('B', 'D'), ('A', 'D')]
-+
-+ This function consumes all of the input iterables before producing
output.
-+ If any of the input iterables have fewer than two items, ``ValueError``
-+ is raised.
-+
-+ For information on the algorithm, see
-+ `this section
<https://www-cs-faculty.stanford.edu/~knuth/fasc2a.ps.gz>`__
-+ of Donald Knuth's *The Art of Computer Programming*.
-+ """
-+ all_iterables = tuple(tuple(x) for x in iterables)
-+ iterable_count = len(all_iterables)
-+ for iterable in all_iterables:
-+ if len(iterable) < 2:
-+ raise ValueError("each iterable must have two or more items")
-+
-+ # This is based on "Algorithm H" from section 7.2.1.1, page 20.
-+ # a holds the indexes of the source iterables for the n-tuple to be
yielded
-+ # f is the array of "focus pointers"
-+ # o is the array of "directions"
-+ a = [0] * iterable_count
-+ f = list(range(iterable_count + 1))
-+ o = [1] * iterable_count
-+ while True:
-+ yield tuple(all_iterables[i][a[i]] for i in range(iterable_count))
-+ j = f[0]
-+ f[0] = 0
-+ if j == iterable_count:
-+ break
-+ a[j] = a[j] + o[j]
-+ if a[j] == 0 or a[j] == len(all_iterables[j]) - 1:
-+ o[j] = -o[j]
-+ f[j] = f[j + 1]
-+ f[j + 1] = j + 1
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/more_itertools/recipes.py

b/third_party/python/setuptools/pkg_resources/_vendor/more_itertools/recipes.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/more_itertools/recipes.py
-@@ -0,0 +1,930 @@
-+"""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 math
-+import operator
-+import warnings
-+
-+from collections import deque
-+from collections.abc import Sized
-+from functools import reduce
-+from itertools import (
-+ chain,
-+ combinations,
-+ compress,
-+ count,
-+ cycle,
-+ groupby,
-+ islice,
-+ product,
-+ repeat,
-+ starmap,
-+ tee,
-+ zip_longest,
-+)
-+from random import randrange, sample, choice
-+from sys import hexversion
-+
-+__all__ = [
-+ 'all_equal',
-+ 'batched',
-+ 'before_and_after',
-+ 'consume',
-+ 'convolve',
-+ 'dotproduct',
-+ 'first_true',
-+ 'factor',
-+ 'flatten',
-+ 'grouper',
-+ 'iter_except',
-+ 'iter_index',
-+ 'matmul',
-+ 'ncycles',
-+ 'nth',
-+ 'nth_combination',
-+ 'padnone',
-+ 'pad_none',
-+ 'pairwise',
-+ 'partition',
-+ 'polynomial_from_roots',
-+ 'powerset',
-+ 'prepend',
-+ 'quantify',
-+ 'random_combination_with_replacement',
-+ 'random_combination',
-+ 'random_permutation',
-+ 'random_product',
-+ 'repeatfunc',
-+ 'roundrobin',
-+ 'sieve',
-+ 'sliding_window',
-+ 'subslices',
-+ 'tabulate',
-+ 'tail',
-+ 'take',
-+ 'transpose',
-+ 'triplewise',
-+ 'unique_everseen',
-+ 'unique_justseen',
-+]
-+
-+_marker = object()
-+
-+
-+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']
-+
-+ """
-+ # If the given iterable has a length, then we can use islice to get its
-+ # final elements. Note that if the iterable is not actually Iterable,
-+ # either islice or deque will throw a TypeError. This is why we don't
-+ # check if it is Iterable.
-+ if isinstance(iterable, Sized):
-+ yield from islice(iterable, max(0, len(iterable) - n), None)
-+ else:
-+ yield from 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 "<stdin>", line 1, in <module>
-+ 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 "<stdin>", line 1, in <module>
-+ 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__
-+
-+
-+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):
-+ # 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 grouper(iterable, n, incomplete='fill', fillvalue=None):
-+ """Group elements from *iterable* into fixed-length groups of length
*n*.
-+
-+ >>> list(grouper('ABCDEF', 3))
-+ [('A', 'B', 'C'), ('D', 'E', 'F')]
-+
-+ The keyword arguments *incomplete* and *fillvalue* control what happens
for
-+ iterables whose length is not a multiple of *n*.
-+
-+ When *incomplete* is `'fill'`, the last group will contain instances of
-+ *fillvalue*.
-+
-+ >>> list(grouper('ABCDEFG', 3, incomplete='fill', fillvalue='x'))
-+ [('A', 'B', 'C'), ('D', 'E', 'F'), ('G', 'x', 'x')]
-+
-+ When *incomplete* is `'ignore'`, the last group will not be emitted.
-+
-+ >>> list(grouper('ABCDEFG', 3, incomplete='ignore', fillvalue='x'))
-+ [('A', 'B', 'C'), ('D', 'E', 'F')]
-+
-+ When *incomplete* is `'strict'`, a subclass of `ValueError` will be
raised.
-+
-+ >>> it = grouper('ABCDEFG', 3, incomplete='strict')
-+ >>> list(it) # doctest: +IGNORE_EXCEPTION_DETAIL
-+ Traceback (most recent call last):
-+ ...
-+ UnequalIterablesError
-+
-+ """
-+ args = [iter(iterable)] * n
-+ if incomplete == 'fill':
-+ return zip_longest(*args, fillvalue=fillvalue)
-+ if incomplete == 'strict':
-+ return _zip_equal(*args)
-+ if incomplete == 'ignore':
-+ return zip(*args)
-+ else:
-+ raise ValueError('Expected fill, strict, or ignore')
-+
-+
-+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]
-+
-+ Multiple exceptions can be specified as a stopping condition:
-+
-+ >>> l = [1, 2, 3, '...', 4, 5, 6]
-+ >>> list(iter_except(lambda: 1 + l.pop(), (IndexError, TypeError)))
-+ [7, 6, 5]
-+ >>> list(iter_except(lambda: 1 + l.pop(), (IndexError, TypeError)))
-+ [4, 3, 2]
-+ >>> list(iter_except(lambda: 1 + l.pop(), (IndexError, TypeError)))
-+ []
-+
-+ """
-+ 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))
-+
-+
-+def before_and_after(predicate, it):
-+ """A variant of :func:`takewhile` that allows complete access to the
-+ remainder of the iterator.
-+
-+ >>> it = iter('ABCdEfGhI')
-+ >>> all_upper, remainder = before_and_after(str.isupper, it)
-+ >>> ''.join(all_upper)
-+ 'ABC'
-+ >>> ''.join(remainder) # takewhile() would lose the 'd'
-+ 'dEfGhI'
-+
-+ Note that the first iterator must be fully consumed before the second
-+ iterator can generate valid results.
-+ """
-+ it = iter(it)
-+ transition = []
-+
-+ def true_iterator():
-+ for elem in it:
-+ if predicate(elem):
-+ yield elem
-+ else:
-+ transition.append(elem)
-+ return
-+
-+ # Note: this is different from itertools recipes to allow nesting
-+ # before_and_after remainders into before_and_after again. See tests
-+ # for an example.
-+ remainder_iterator = chain(transition, it)
-+
-+ return true_iterator(), remainder_iterator
-+
-+
-+def triplewise(iterable):
-+ """Return overlapping triplets from *iterable*.
-+
-+ >>> list(triplewise('ABCDE'))
-+ [('A', 'B', 'C'), ('B', 'C', 'D'), ('C', 'D', 'E')]
-+
-+ """
-+ for (a, _), (b, c) in pairwise(pairwise(iterable)):
-+ yield a, b, c
-+
-+
-+def sliding_window(iterable, n):
-+ """Return a sliding window of width *n* over *iterable*.
-+
-+ >>> list(sliding_window(range(6), 4))
-+ [(0, 1, 2, 3), (1, 2, 3, 4), (2, 3, 4, 5)]
-+
-+ If *iterable* has fewer than *n* items, then nothing is yielded:
-+
-+ >>> list(sliding_window(range(3), 4))
-+ []
-+
-+ For a variant with more features, see :func:`windowed`.
-+ """
-+ it = iter(iterable)
-+ window = deque(islice(it, n), maxlen=n)
-+ if len(window) == n:
-+ yield tuple(window)
-+ for x in it:
-+ window.append(x)
-+ yield tuple(window)
-+
-+
-+def subslices(iterable):
-+ """Return all contiguous non-empty subslices of *iterable*.
-+
-+ >>> list(subslices('ABC'))
-+ [['A'], ['A', 'B'], ['A', 'B', 'C'], ['B'], ['B', 'C'], ['C']]
-+
-+ This is similar to :func:`substrings`, but emits items in a different
-+ order.
-+ """
-+ seq = list(iterable)
-+ slices = starmap(slice, combinations(range(len(seq) + 1), 2))
-+ return map(operator.getitem, repeat(seq), slices)
-+
-+
-+def polynomial_from_roots(roots):
-+ """Compute a polynomial's coefficients from its roots.
-+
-+ >>> roots = [5, -4, 3] # (x - 5) * (x + 4) * (x - 3)
-+ >>> polynomial_from_roots(roots) # x^3 - 4 * x^2 - 17 * x + 60
-+ [1, -4, -17, 60]
-+ """
-+ # Use math.prod for Python 3.8+,
-+ prod = getattr(math, 'prod', lambda x: reduce(operator.mul, x, 1))
-+ roots = list(map(operator.neg, roots))
-+ return [
-+ sum(map(prod, combinations(roots, k))) for k in range(len(roots) +
1)
-+ ]
-+
-+
-+def iter_index(iterable, value, start=0):
-+ """Yield the index of each place in *iterable* that *value* occurs,
-+ beginning with index *start*.
-+
-+ See :func:`locate` for a more general means of finding the indexes
-+ associated with particular values.
-+
-+ >>> list(iter_index('AABCADEAF', 'A'))
-+ [0, 1, 4, 7]
-+ """
-+ try:
-+ seq_index = iterable.index
-+ except AttributeError:
-+ # Slow path for general iterables
-+ it = islice(iterable, start, None)
-+ for i, element in enumerate(it, start):
-+ if element is value or element == value:
-+ yield i
-+ else:
-+ # Fast path for sequences
-+ i = start - 1
-+ try:
-+ while True:
-+ i = seq_index(value, i + 1)
-+ yield i
-+ except ValueError:
-+ pass
-+
-+
-+def sieve(n):
-+ """Yield the primes less than n.
-+
-+ >>> list(sieve(30))
-+ [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
-+ """
-+ isqrt = getattr(math, 'isqrt', lambda x: int(math.sqrt(x)))
-+ data = bytearray((0, 1)) * (n // 2)
-+ data[:3] = 0, 0, 0
-+ limit = isqrt(n) + 1
-+ for p in compress(range(limit), data):
-+ data[p * p : n : p + p] = bytes(len(range(p * p, n, p + p)))
-+ data[2] = 1
-+ return iter_index(data, 1) if n > 2 else iter([])
-+
-+
-+def batched(iterable, n):
-+ """Batch data into lists of length *n*. The last batch may be shorter.
-+
-+ >>> list(batched('ABCDEFG', 3))
-+ [['A', 'B', 'C'], ['D', 'E', 'F'], ['G']]
-+
-+ This recipe is from the ``itertools`` docs. This library also provides
-+ :func:`chunked`, which has a different implementation.
-+ """
-+ if hexversion >= 0x30C00A0: # Python 3.12.0a0
-+ warnings.warn(
-+ (
-+ 'batched will be removed in a future version of '
-+ 'more-itertools. Use the standard library '
-+ 'itertools.batched function instead'
-+ ),
-+ DeprecationWarning,
-+ )
-+
-+ it = iter(iterable)
-+ while True:
-+ batch = list(islice(it, n))
-+ if not batch:
-+ break
-+ yield batch
-+
-+
-+def transpose(it):
-+ """Swap the rows and columns of the input.
-+
-+ >>> list(transpose([(1, 2, 3), (11, 22, 33)]))
-+ [(1, 11), (2, 22), (3, 33)]
-+
-+ The caller should ensure that the dimensions of the input are
compatible.
-+ """
-+ # TODO: when 3.9 goes end-of-life, add stric=True to this.
-+ return zip(*it)
-+
-+
-+def matmul(m1, m2):
-+ """Multiply two matrices.
-+ >>> list(matmul([(7, 5), (3, 5)], [(2, 5), (7, 9)]))
-+ [[49, 80], [41, 60]]
-+
-+ The caller should ensure that the dimensions of the input matrices are
-+ compatible with each other.
-+ """
-+ n = len(m2[0])
-+ return batched(starmap(dotproduct, product(m1, transpose(m2))), n)
-+
-+
-+def factor(n):
-+ """Yield the prime factors of n.
-+ >>> list(factor(360))
-+ [2, 2, 2, 3, 3, 5]
-+ """
-+ isqrt = getattr(math, 'isqrt', lambda x: int(math.sqrt(x)))
-+ for prime in sieve(isqrt(n) + 1):
-+ while True:
-+ quotient, remainder = divmod(n, prime)
-+ if remainder:
-+ break
-+ yield prime
-+ n = quotient
-+ if n == 1:
-+ return
-+ if n >= 2:
-+ yield n
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/packaging/__about__.py
b/third_party/python/setuptools/pkg_resources/_vendor/packaging/__about__.py
-deleted file mode 100644
----
a/third_party/python/setuptools/pkg_resources/_vendor/packaging/__about__.py
-+++ /dev/null
-@@ -1,27 +0,0 @@
--# 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 __future__ import absolute_import, division, print_function
--
--__all__ = [
-- "__title__",
-- "__summary__",
-- "__uri__",
-- "__version__",
-- "__author__",
-- "__email__",
-- "__license__",
-- "__copyright__",
--]
--
--__title__ = "packaging"
--__summary__ = "Core utilities for Python packages"
--__uri__ = "https://github.com/pypa/packaging";
--
--__version__ = "20.4"
--
--__author__ = "Donald Stufft and individual contributors"
--__email__ = "donald AT stufft.io"
--
--__license__ = "BSD-2-Clause or Apache-2.0"
--__copyright__ = "Copyright 2014-2019 %s" % __author__
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/packaging/__init__.py
b/third_party/python/setuptools/pkg_resources/_vendor/packaging/__init__.py
----
a/third_party/python/setuptools/pkg_resources/_vendor/packaging/__init__.py
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/packaging/__init__.py
-@@ -1,26 +1,15 @@
- # 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 __future__ import absolute_import, division, print_function
-+
-+__title__ = "packaging"
-+__summary__ = "Core utilities for Python packages"
-+__uri__ = "https://github.com/pypa/packaging";
-
--from .__about__ import (
-- __author__,
-- __copyright__,
-- __email__,
-- __license__,
-- __summary__,
-- __title__,
-- __uri__,
-- __version__,
--)
-+__version__ = "23.1"
-
--__all__ = [
-- "__title__",
-- "__summary__",
-- "__uri__",
-- "__version__",
-- "__author__",
-- "__email__",
-- "__license__",
-- "__copyright__",
--]
-+__author__ = "Donald Stufft and individual contributors"
-+__email__ = "donald AT stufft.io"
-+
-+__license__ = "BSD-2-Clause or Apache-2.0"
-+__copyright__ = "2014-2019 %s" % __author__
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/packaging/_compat.py
b/third_party/python/setuptools/pkg_resources/_vendor/packaging/_compat.py
-deleted file mode 100644
----
a/third_party/python/setuptools/pkg_resources/_vendor/packaging/_compat.py
-+++ /dev/null
-@@ -1,38 +0,0 @@
--# 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 __future__ import absolute_import, division, print_function
--
--import sys
--
--from ._typing import TYPE_CHECKING
--
--if TYPE_CHECKING: # pragma: no cover
-- from typing import Any, Dict, Tuple, Type
--
--
--PY2 = sys.version_info[0] == 2
--PY3 = sys.version_info[0] == 3
--
--# flake8: noqa
--
--if PY3:
-- string_types = (str,)
--else:
-- string_types = (basestring,)
--
--
--def with_metaclass(meta, *bases):
-- # type: (Type[Any], Tuple[Type[Any], ...]) -> Any
-- """
-- Create a base class with a metaclass.
-- """
-- # This requires a bit of explanation: the basic idea is to make a dummy
-- # metaclass for one level of class instantiation that replaces itself
with
-- # the actual metaclass.
-- class metaclass(meta): # type: ignore
-- def __new__(cls, name, this_bases, d):
-- # type: (Type[Any], str, Tuple[Any], Dict[Any, Any]) -> Any
-- return meta(name, bases, d)
--
-- return type.__new__(metaclass, "temporary_class", (), {})
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/packaging/_elffile.py
b/third_party/python/setuptools/pkg_resources/_vendor/packaging/_elffile.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/packaging/_elffile.py
-@@ -0,0 +1,108 @@
-+"""
-+ELF file parser.
-+
-+This provides a class ``ELFFile`` that parses an ELF executable in a similar
-+interface to ``ZipFile``. Only the read interface is implemented.
-+
-+Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca
-+ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html
-+"""
-+
-+import enum
-+import os
-+import struct
-+from typing import IO, Optional, Tuple
-+
-+
-+class ELFInvalid(ValueError):
-+ pass
-+
-+
-+class EIClass(enum.IntEnum):
-+ C32 = 1
-+ C64 = 2
-+
-+
-+class EIData(enum.IntEnum):
-+ Lsb = 1
-+ Msb = 2
-+
-+
-+class EMachine(enum.IntEnum):
-+ I386 = 3
-+ S390 = 22
-+ Arm = 40
-+ X8664 = 62
-+ AArc64 = 183
-+
-+
-+class ELFFile:
-+ """
-+ Representation of an ELF executable.
-+ """
-+
-+ def __init__(self, f: IO[bytes]) -> None:
-+ self._f = f
-+
-+ try:
-+ ident = self._read("16B")
-+ except struct.error:
-+ raise ELFInvalid("unable to parse identification")
-+ magic = bytes(ident[:4])
-+ if magic != b"\x7fELF":
-+ raise ELFInvalid(f"invalid magic: {magic!r}")
-+
-+ self.capacity = ident[4] # Format for program header (bitness).
-+ self.encoding = ident[5] # Data structure encoding (endianness).
-+
-+ 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, self._p_fmt, self._p_idx = {
-+ (1, 1): ("<HHIIIIIHHH", "<IIIIIIII", (0, 1, 4)), # 32-bit
LSB.
-+ (1, 2): (">HHIIIIIHHH", ">IIIIIIII", (0, 1, 4)), # 32-bit
MSB.
-+ (2, 1): ("<HHIQQQIHHH", "<IIQQQQQQ", (0, 2, 5)), # 64-bit
LSB.
-+ (2, 2): (">HHIQQQIHHH", ">IIQQQQQQ", (0, 2, 5)), # 64-bit
MSB.
-+ }[(self.capacity, self.encoding)]
-+ except KeyError:
-+ raise ELFInvalid(
-+ f"unrecognized capacity ({self.capacity}) or "
-+ f"encoding ({self.encoding})"
-+ )
-+
-+ try:
-+ (
-+ _,
-+ self.machine, # Architecture type.
-+ _,
-+ _,
-+ self._e_phoff, # Offset of program header.
-+ _,
-+ self.flags, # Processor-specific flags.
-+ _,
-+ self._e_phentsize, # Size of section.
-+ self._e_phnum, # Number of sections.
-+ ) = self._read(e_fmt)
-+ except struct.error as e:
-+ raise ELFInvalid("unable to parse machine and section
information") from e
-+
-+ def _read(self, fmt: str) -> Tuple[int, ...]:
-+ return struct.unpack(fmt, self._f.read(struct.calcsize(fmt)))
-+
-+ @property
-+ def interpreter(self) -> Optional[str]:
-+ """
-+ The path recorded in the ``PT_INTERP`` section header.
-+ """
-+ for index in range(self._e_phnum):
-+ self._f.seek(self._e_phoff + self._e_phentsize * index)
-+ try:
-+ data = self._read(self._p_fmt)
-+ except struct.error:
-+ continue
-+ if data[self._p_idx[0]] != 3: # Not PT_INTERP.
-+ continue
-+ self._f.seek(data[self._p_idx[1]])
-+ return
os.fsdecode(self._f.read(data[self._p_idx[2]])).strip("\0")
-+ return None
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/packaging/_manylinux.py
b/third_party/python/setuptools/pkg_resources/_vendor/packaging/_manylinux.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/packaging/_manylinux.py
-@@ -0,0 +1,240 @@
-+import collections
-+import contextlib
-+import functools
-+import os
-+import re
-+import sys
-+import warnings
-+from typing import Dict, Generator, Iterator, NamedTuple, Optional, Tuple
-+
-+from ._elffile import EIClass, EIData, ELFFile, EMachine
-+
-+EF_ARM_ABIMASK = 0xFF000000
-+EF_ARM_ABI_VER5 = 0x05000000
-+EF_ARM_ABI_FLOAT_HARD = 0x00000400
-+
-+
-+# `os.PathLike` not a generic type until Python 3.9, so sticking with `str`
-+# as the type for `path` until then.
-+@contextlib.contextmanager
-+def _parse_elf(path: str) -> Generator[Optional[ELFFile], None, None]:
-+ try:
-+ with open(path, "rb") as f:
-+ yield ELFFile(f)
-+ except (OSError, TypeError, ValueError):
-+ yield None
-+
-+
-+def _is_linux_armhf(executable: str) -> bool:
-+ # hard-float ABI can be detected from the ELF header of the running
-+ # process
-+ # https://static.docs.arm.com/ihi0044/g/aaelf32.pdf
-+ with _parse_elf(executable) as f:
-+ return (
-+ f is not None
-+ and f.capacity == EIClass.C32
-+ and f.encoding == EIData.Lsb
-+ and f.machine == EMachine.Arm
-+ and f.flags & EF_ARM_ABIMASK == EF_ARM_ABI_VER5
-+ and f.flags & EF_ARM_ABI_FLOAT_HARD == EF_ARM_ABI_FLOAT_HARD
-+ )
-+
-+
-+def _is_linux_i686(executable: str) -> bool:
-+ with _parse_elf(executable) as f:
-+ return (
-+ f is not None
-+ and f.capacity == EIClass.C32
-+ and f.encoding == EIData.Lsb
-+ and f.machine == EMachine.I386
-+ )
-+
-+
-+def _have_compatible_abi(executable: str, arch: str) -> bool:
-+ if arch == "armv7l":
-+ return _is_linux_armhf(executable)
-+ if arch == "i686":
-+ return _is_linux_i686(executable)
-+ 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:
-+ # Should be a string like "glibc 2.17".
-+ version_string: str = getattr(os, "confstr")("CS_GNU_LIBC_VERSION")
-+ assert version_string is not None
-+ _, version = version_string.rsplit()
-+ 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<major>[0-9]+)\.(?P<minor>[0-9]+)", version_str)
-+ if not m:
-+ warnings.warn(
-+ f"Expected glibc version with 2 components major.minor,"
-+ f" got: {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(sys.executable, 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/third_party/python/setuptools/pkg_resources/_vendor/packaging/_musllinux.py
b/third_party/python/setuptools/pkg_resources/_vendor/packaging/_musllinux.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/packaging/_musllinux.py
-@@ -0,0 +1,80 @@
-+"""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 functools
-+import re
-+import subprocess
-+import sys
-+from typing import Iterator, NamedTuple, Optional
-+
-+from ._elffile import ELFFile
-+
-+
-+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
-+ """
-+ try:
-+ with open(executable, "rb") as f:
-+ ld = ELFFile(f).interpreter
-+ except (OSError, TypeError, ValueError):
-+ return None
-+ if ld is None or "musl" not in 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/third_party/python/setuptools/pkg_resources/_vendor/packaging/_parser.py
b/third_party/python/setuptools/pkg_resources/_vendor/packaging/_parser.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/packaging/_parser.py
-@@ -0,0 +1,353 @@
-+"""Handwritten parser of dependency specifiers.
-+
-+The docstring for each __parse_* function contains ENBF-inspired grammar
representing
-+the implementation.
-+"""
-+
-+import ast
-+from typing import Any, List, NamedTuple, Optional, Tuple, Union
-+
-+from ._tokenizer import DEFAULT_RULES, Tokenizer
-+
-+
-+class Node:
-+ def __init__(self, value: str) -> None:
-+ self.value = value
-+
-+ def __str__(self) -> str:
-+ return 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)
-+
-+
-+MarkerVar = Union[Variable, Value]
-+MarkerItem = Tuple[MarkerVar, Op, MarkerVar]
-+# MarkerAtom = Union[MarkerItem, List["MarkerAtom"]]
-+# MarkerList = List[Union["MarkerList", MarkerAtom, str]]
-+# mypy does not support recursive type definition
-+# https://github.com/python/mypy/issues/731
-+MarkerAtom = Any
-+MarkerList = List[Any]
-+
-+
-+class ParsedRequirement(NamedTuple):
-+ name: str
-+ url: str
-+ extras: List[str]
-+ specifier: str
-+ marker: Optional[MarkerList]
-+
-+
-+#
--------------------------------------------------------------------------------------
-+# Recursive descent parser for dependency specifier
-+#
--------------------------------------------------------------------------------------
-+def parse_requirement(source: str) -> ParsedRequirement:
-+ return _parse_requirement(Tokenizer(source, rules=DEFAULT_RULES))
-+
-+
-+def _parse_requirement(tokenizer: Tokenizer) -> ParsedRequirement:
-+ """
-+ requirement = WS? IDENTIFIER WS? extras WS? requirement_details
-+ """
-+ tokenizer.consume("WS")
-+
-+ name_token = tokenizer.expect(
-+ "IDENTIFIER", expected="package name at the start of dependency
specifier"
-+ )
-+ name = name_token.text
-+ tokenizer.consume("WS")
-+
-+ extras = _parse_extras(tokenizer)
-+ tokenizer.consume("WS")
-+
-+ url, specifier, marker = _parse_requirement_details(tokenizer)
-+ tokenizer.expect("END", expected="end of dependency specifier")
-+
-+ return ParsedRequirement(name, url, extras, specifier, marker)
-+
-+
-+def _parse_requirement_details(
-+ tokenizer: Tokenizer,
-+) -> Tuple[str, str, Optional[MarkerList]]:
-+ """
-+ requirement_details = AT URL (WS requirement_marker?)?
-+ | specifier WS? (requirement_marker)?
-+ """
-+
-+ specifier = ""
-+ url = ""
-+ marker = None
-+
-+ if tokenizer.check("AT"):
-+ tokenizer.read()
-+ tokenizer.consume("WS")
-+
-+ url_start = tokenizer.position
-+ url = tokenizer.expect("URL", expected="URL after @").text
-+ if tokenizer.check("END", peek=True):
-+ return (url, specifier, marker)
-+
-+ tokenizer.expect("WS", expected="whitespace after URL")
-+
-+ # The input might end after whitespace.
-+ if tokenizer.check("END", peek=True):
-+ return (url, specifier, marker)
-+
-+ marker = _parse_requirement_marker(
-+ tokenizer, span_start=url_start, after="URL and whitespace"
-+ )
-+ else:
-+ specifier_start = tokenizer.position
-+ specifier = _parse_specifier(tokenizer)
-+ tokenizer.consume("WS")
-+
-+ if tokenizer.check("END", peek=True):
-+ return (url, specifier, marker)
-+
-+ marker = _parse_requirement_marker(
-+ tokenizer,
-+ span_start=specifier_start,
-+ after=(
-+ "version specifier"
-+ if specifier
-+ else "name and no valid version specifier"
-+ ),
-+ )
-+
-+ return (url, specifier, marker)
-+
-+
-+def _parse_requirement_marker(
-+ tokenizer: Tokenizer, *, span_start: int, after: str
-+) -> MarkerList:
-+ """
-+ requirement_marker = SEMICOLON marker WS?
-+ """
-+
-+ if not tokenizer.check("SEMICOLON"):
-+ tokenizer.raise_syntax_error(
-+ f"Expected end or semicolon (after {after})",
-+ span_start=span_start,
-+ )
-+ tokenizer.read()
-+
-+ marker = _parse_marker(tokenizer)
-+ tokenizer.consume("WS")
-+
-+ return marker
-+
-+
-+def _parse_extras(tokenizer: Tokenizer) -> List[str]:
-+ """
-+ extras = (LEFT_BRACKET wsp* extras_list? wsp* RIGHT_BRACKET)?
-+ """
-+ if not tokenizer.check("LEFT_BRACKET", peek=True):
-+ return []
-+
-+ with tokenizer.enclosing_tokens(
-+ "LEFT_BRACKET",
-+ "RIGHT_BRACKET",
-+ around="extras",
-+ ):
-+ tokenizer.consume("WS")
-+ extras = _parse_extras_list(tokenizer)
-+ tokenizer.consume("WS")
-+
-+ return extras
-+
-+
-+def _parse_extras_list(tokenizer: Tokenizer) -> List[str]:
-+ """
-+ extras_list = identifier (wsp* ',' wsp* identifier)*
-+ """
-+ extras: List[str] = []
-+
-+ if not tokenizer.check("IDENTIFIER"):
-+ return extras
-+
-+ extras.append(tokenizer.read().text)
-+
-+ while True:
-+ tokenizer.consume("WS")
-+ if tokenizer.check("IDENTIFIER", peek=True):
-+ tokenizer.raise_syntax_error("Expected comma between extra
names")
-+ elif not tokenizer.check("COMMA"):
-+ break
-+
-+ tokenizer.read()
-+ tokenizer.consume("WS")
-+
-+ extra_token = tokenizer.expect("IDENTIFIER", expected="extra name
after comma")
-+ extras.append(extra_token.text)
-+
-+ return extras
-+
-+
-+def _parse_specifier(tokenizer: Tokenizer) -> str:
-+ """
-+ specifier = LEFT_PARENTHESIS WS? version_many WS? RIGHT_PARENTHESIS
-+ | WS? version_many WS?
-+ """
-+ with tokenizer.enclosing_tokens(
-+ "LEFT_PARENTHESIS",
-+ "RIGHT_PARENTHESIS",
-+ around="version specifier",
-+ ):
-+ tokenizer.consume("WS")
-+ parsed_specifiers = _parse_version_many(tokenizer)
-+ tokenizer.consume("WS")
-+
-+ return parsed_specifiers
-+
-+
-+def _parse_version_many(tokenizer: Tokenizer) -> str:
-+ """
-+ version_many = (SPECIFIER (WS? COMMA WS? SPECIFIER)*)?
-+ """
-+ parsed_specifiers = ""
-+ while tokenizer.check("SPECIFIER"):
-+ span_start = tokenizer.position
-+ parsed_specifiers += tokenizer.read().text
-+ if tokenizer.check("VERSION_PREFIX_TRAIL", peek=True):
-+ tokenizer.raise_syntax_error(
-+ ".* suffix can only be used with `==` or `!=` operators",
-+ span_start=span_start,
-+ span_end=tokenizer.position + 1,
-+ )
-+ if tokenizer.check("VERSION_LOCAL_LABEL_TRAIL", peek=True):
-+ tokenizer.raise_syntax_error(
-+ "Local version label can only be used with `==` or `!=`
operators",
-+ span_start=span_start,
-+ span_end=tokenizer.position,
-+ )
-+ tokenizer.consume("WS")
-+ if not tokenizer.check("COMMA"):
-+ break
-+ parsed_specifiers += tokenizer.read().text
-+ tokenizer.consume("WS")
-+
-+ return parsed_specifiers
-+
-+
-+#
--------------------------------------------------------------------------------------
-+# Recursive descent parser for marker expression
-+#
--------------------------------------------------------------------------------------
-+def parse_marker(source: str) -> MarkerList:
-+ return _parse_marker(Tokenizer(source, rules=DEFAULT_RULES))
-+
-+
-+def _parse_marker(tokenizer: Tokenizer) -> MarkerList:
-+ """
-+ marker = marker_atom (BOOLOP marker_atom)+
-+ """
-+ expression = [_parse_marker_atom(tokenizer)]
-+ while tokenizer.check("BOOLOP"):
-+ token = tokenizer.read()
-+ expr_right = _parse_marker_atom(tokenizer)
-+ expression.extend((token.text, expr_right))
-+ return expression
-+
-+
-+def _parse_marker_atom(tokenizer: Tokenizer) -> MarkerAtom:
-+ """
-+ marker_atom = WS? LEFT_PARENTHESIS WS? marker WS? RIGHT_PARENTHESIS WS?
-+ | WS? marker_item WS?
-+ """
-+
-+ tokenizer.consume("WS")
-+ if tokenizer.check("LEFT_PARENTHESIS", peek=True):
-+ with tokenizer.enclosing_tokens(
-+ "LEFT_PARENTHESIS",
-+ "RIGHT_PARENTHESIS",
-+ around="marker expression",
-+ ):
-+ tokenizer.consume("WS")
-+ marker: MarkerAtom = _parse_marker(tokenizer)
-+ tokenizer.consume("WS")
-+ else:
-+ marker = _parse_marker_item(tokenizer)
-+ tokenizer.consume("WS")
-+ return marker
-+
-+
-+def _parse_marker_item(tokenizer: Tokenizer) -> MarkerItem:
-+ """
-+ marker_item = WS? marker_var WS? marker_op WS? marker_var WS?
-+ """
-+ tokenizer.consume("WS")
-+ marker_var_left = _parse_marker_var(tokenizer)
-+ tokenizer.consume("WS")
-+ marker_op = _parse_marker_op(tokenizer)
-+ tokenizer.consume("WS")
-+ marker_var_right = _parse_marker_var(tokenizer)
-+ tokenizer.consume("WS")
-+ return (marker_var_left, marker_op, marker_var_right)
-+
-+
-+def _parse_marker_var(tokenizer: Tokenizer) -> MarkerVar:
-+ """
-+ marker_var = VARIABLE | QUOTED_STRING
-+ """
-+ if tokenizer.check("VARIABLE"):
-+ return process_env_var(tokenizer.read().text.replace(".", "_"))
-+ elif tokenizer.check("QUOTED_STRING"):
-+ return process_python_str(tokenizer.read().text)
-+ else:
-+ tokenizer.raise_syntax_error(
-+ message="Expected a marker variable or quoted string"
-+ )
-+
-+
-+def process_env_var(env_var: str) -> Variable:
-+ if (
-+ env_var == "platform_python_implementation"
-+ or env_var == "python_implementation"
-+ ):
-+ return Variable("platform_python_implementation")
-+ else:
-+ return Variable(env_var)
-+
-+
-+def process_python_str(python_str: str) -> Value:
-+ value = ast.literal_eval(python_str)
-+ return Value(str(value))
-+
-+
-+def _parse_marker_op(tokenizer: Tokenizer) -> Op:
-+ """
-+ marker_op = IN | NOT IN | OP
-+ """
-+ if tokenizer.check("IN"):
-+ tokenizer.read()
-+ return Op("in")
-+ elif tokenizer.check("NOT"):
-+ tokenizer.read()
-+ tokenizer.expect("WS", expected="whitespace after 'not'")
-+ tokenizer.expect("IN", expected="'in' after 'not'")
-+ return Op("not in")
-+ elif tokenizer.check("OP"):
-+ return Op(tokenizer.read().text)
-+ else:
-+ return tokenizer.raise_syntax_error(
-+ "Expected marker operator, one of "
-+ "<=, <, !=, ==, >=, >, ~=, ===, in, not in"
-+ )
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/packaging/_structures.py

b/third_party/python/setuptools/pkg_resources/_vendor/packaging/_structures.py
----
a/third_party/python/setuptools/pkg_resources/_vendor/packaging/_structures.py
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/packaging/_structures.py
-@@ -1,86 +1,61 @@
- # 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 __future__ import absolute_import, division, print_function
-
-
--class InfinityType(object):
-- def __repr__(self):
-- # type: () -> str
-+class InfinityType:
-+ def __repr__(self) -> str:
- return "Infinity"
-
-- def __hash__(self):
-- # type: () -> int
-+ def __hash__(self) -> int:
- return hash(repr(self))
-
-- def __lt__(self, other):
-- # type: (object) -> bool
-+ def __lt__(self, other: object) -> bool:
- return False
-
-- def __le__(self, other):
-- # type: (object) -> bool
-+ def __le__(self, other: object) -> bool:
- return False
-
-- def __eq__(self, other):
-- # type: (object) -> bool
-+ def __eq__(self, other: object) -> bool:
- return isinstance(other, self.__class__)
-
-- def __ne__(self, other):
-- # type: (object) -> bool
-- return not isinstance(other, self.__class__)
--
-- def __gt__(self, other):
-- # type: (object) -> bool
-+ def __gt__(self, other: object) -> bool:
- return True
-
-- def __ge__(self, other):
-- # type: (object) -> bool
-+ def __ge__(self, other: object) -> bool:
- return True
-
-- def __neg__(self):
-- # type: (object) -> NegativeInfinityType
-+ def __neg__(self: object) -> "NegativeInfinityType":
- return NegativeInfinity
-
-
- Infinity = InfinityType()
-
-
--class NegativeInfinityType(object):
-- def __repr__(self):
-- # type: () -> str
-+class NegativeInfinityType:
-+ def __repr__(self) -> str:
- return "-Infinity"
-
-- def __hash__(self):
-- # type: () -> int
-+ def __hash__(self) -> int:
- return hash(repr(self))
-
-- def __lt__(self, other):
-- # type: (object) -> bool
-- return True
--
-- def __le__(self, other):
-- # type: (object) -> bool
-+ def __lt__(self, other: object) -> bool:
- return True
-
-- def __eq__(self, other):
-- # type: (object) -> bool
-+ def __le__(self, other: object) -> bool:
-+ return True
-+
-+ def __eq__(self, other: object) -> bool:
- return isinstance(other, self.__class__)
-
-- def __ne__(self, other):
-- # type: (object) -> bool
-- return not isinstance(other, self.__class__)
--
-- def __gt__(self, other):
-- # type: (object) -> bool
-+ def __gt__(self, other: object) -> bool:
- return False
-
-- def __ge__(self, other):
-- # type: (object) -> bool
-+ def __ge__(self, other: object) -> bool:
- return False
-
-- def __neg__(self):
-- # type: (object) -> InfinityType
-+ def __neg__(self: object) -> InfinityType:
- return Infinity
-
-
- NegativeInfinity = NegativeInfinityType()
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/packaging/_tokenizer.py
b/third_party/python/setuptools/pkg_resources/_vendor/packaging/_tokenizer.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/packaging/_tokenizer.py
-@@ -0,0 +1,192 @@
-+import contextlib
-+import re
-+from dataclasses import dataclass
-+from typing import Dict, Iterator, NoReturn, Optional, Tuple, Union
-+
-+from .specifiers import Specifier
-+
-+
-+@dataclass
-+class Token:
-+ name: str
-+ text: str
-+ position: int
-+
-+
-+class ParserSyntaxError(Exception):
-+ """The provided source text could not be parsed correctly."""
-+
-+ def __init__(
-+ self,
-+ message: str,
-+ *,
-+ source: str,
-+ span: Tuple[int, int],
-+ ) -> None:
-+ self.span = span
-+ self.message = message
-+ self.source = source
-+
-+ super().__init__()
-+
-+ def __str__(self) -> str:
-+ marker = " " * self.span[0] + "~" * (self.span[1] - self.span[0]) +
"^"
-+ return "\n ".join([self.message, self.source, marker])
-+
-+
-+DEFAULT_RULES: "Dict[str, Union[str, re.Pattern[str]]]" = {
-+ "LEFT_PARENTHESIS": r"\(",
-+ "RIGHT_PARENTHESIS": r"\)",
-+ "LEFT_BRACKET": r"\[",
-+ "RIGHT_BRACKET": r"\]",
-+ "SEMICOLON": r";",
-+ "COMMA": r",",
-+ "QUOTED_STRING": re.compile(
-+ r"""
-+ (
-+ ('[^']*')
-+ |
-+ ("[^"]*")
-+ )
-+ """,
-+ re.VERBOSE,
-+ ),
-+ "OP": r"(===|==|~=|!=|<=|>=|<|>)",
-+ "BOOLOP": r"\b(or|and)\b",
-+ "IN": r"\bin\b",
-+ "NOT": r"\bnot\b",
-+ "VARIABLE": re.compile(
-+ r"""
-+ \b(
-+ python_version
-+ |python_full_version
-+ |os[._]name
-+ |sys[._]platform
-+ |platform_(release|system)
-+ |platform[._](version|machine|python_implementation)
-+ |python_implementation
-+ |implementation_(name|version)
-+ |extra
-+ )\b
-+ """,
-+ re.VERBOSE,
-+ ),
-+ "SPECIFIER": re.compile(
-+ Specifier._operator_regex_str + Specifier._version_regex_str,
-+ re.VERBOSE | re.IGNORECASE,
-+ ),
-+ "AT": r"\@",
-+ "URL": r"[^ \t]+",
-+ "IDENTIFIER": r"\b[a-zA-Z0-9][a-zA-Z0-9._-]*\b",
-+ "VERSION_PREFIX_TRAIL": r"\.\*",
-+ "VERSION_LOCAL_LABEL_TRAIL": r"\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*",
-+ "WS": r"[ \t]+",
-+ "END": r"$",
-+}
-+
-+
-+class Tokenizer:
-+ """Context-sensitive token parsing.
-+
-+ Provides methods to examine the input stream to check whether the next
token
-+ matches.
-+ """
-+
-+ def __init__(
-+ self,
-+ source: str,
-+ *,
-+ rules: "Dict[str, Union[str, re.Pattern[str]]]",
-+ ) -> None:
-+ self.source = source
-+ self.rules: Dict[str, re.Pattern[str]] = {
-+ name: re.compile(pattern) for name, pattern in rules.items()
-+ }
-+ self.next_token: Optional[Token] = None
-+ self.position = 0
-+
-+ def consume(self, name: str) -> None:
-+ """Move beyond provided token name, if at current position."""
-+ if self.check(name):
-+ self.read()
-+
-+ def check(self, name: str, *, peek: bool = False) -> bool:
-+ """Check whether the next token has the provided name.
-+
-+ By default, if the check succeeds, the token *must* be read before
-+ another check. If `peek` is set to `True`, the token is not loaded
and
-+ would need to be checked again.
-+ """
-+ assert (
-+ self.next_token is None
-+ ), f"Cannot check for {name!r}, already have {self.next_token!r}"
-+ assert name in self.rules, f"Unknown token name: {name!r}"
-+
-+ expression = self.rules[name]
-+
-+ match = expression.match(self.source, self.position)
-+ if match is None:
-+ return False
-+ if not peek:
-+ self.next_token = Token(name, match[0], self.position)
-+ return True
-+
-+ def expect(self, name: str, *, expected: str) -> Token:
-+ """Expect a certain token name next, failing with a syntax error
otherwise.
-+
-+ The token is *not* read.
-+ """
-+ if not self.check(name):
-+ raise self.raise_syntax_error(f"Expected {expected}")
-+ return self.read()
-+
-+ def read(self) -> Token:
-+ """Consume the next token and return it."""
-+ token = self.next_token
-+ assert token is not None
-+
-+ self.position += len(token.text)
-+ self.next_token = None
-+
-+ return token
-+
-+ def raise_syntax_error(
-+ self,
-+ message: str,
-+ *,
-+ span_start: Optional[int] = None,
-+ span_end: Optional[int] = None,
-+ ) -> NoReturn:
-+ """Raise ParserSyntaxError at the given position."""
-+ span = (
-+ self.position if span_start is None else span_start,
-+ self.position if span_end is None else span_end,
-+ )
-+ raise ParserSyntaxError(
-+ message,
-+ source=self.source,
-+ span=span,
-+ )
-+
-+ @contextlib.contextmanager
-+ def enclosing_tokens(
-+ self, open_token: str, close_token: str, *, around: str
-+ ) -> Iterator[None]:
-+ if self.check(open_token):
-+ open_position = self.position
-+ self.read()
-+ else:
-+ open_position = None
-+
-+ yield
-+
-+ if open_position is None:
-+ return
-+
-+ if not self.check(close_token):
-+ self.raise_syntax_error(
-+ f"Expected matching {close_token} for {open_token}, after
{around}",
-+ span_start=open_position,
-+ )
-+
-+ self.read()
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/packaging/_typing.py
b/third_party/python/setuptools/pkg_resources/_vendor/packaging/_typing.py
-deleted file mode 100644
----
a/third_party/python/setuptools/pkg_resources/_vendor/packaging/_typing.py
-+++ /dev/null
-@@ -1,48 +0,0 @@
--"""For neatly implementing static typing in packaging.
--
--`mypy` - the static type analysis tool we use - uses the `typing` module,
which
--provides core functionality fundamental to mypy's functioning.
--
--Generally, `typing` would be imported at runtime and used in that fashion -
--it acts as a no-op at runtime and does not have any run-time overhead by
--design.
--
--As it turns out, `typing` is not vendorable - it uses separate sources for
--Python 2/Python 3. Thus, this codebase can not expect it to be present.
--To work around this, mypy allows the typing import to be behind a False-y
--optional to prevent it from running at runtime and type-comments can be used
--to remove the need for the types to be accessible directly during runtime.
--
--This module provides the False-y guard in a nicely named fashion so that a
--curious maintainer can reach here to read this.
--
--In packaging, all static-typing related imports should be guarded as
follows:
--
-- from packaging._typing import TYPE_CHECKING
--
-- if TYPE_CHECKING:
-- from typing import ...
--
--Ref: https://github.com/python/mypy/issues/3216
--"""
--
--__all__ = ["TYPE_CHECKING", "cast"]
--
--# The TYPE_CHECKING constant defined by the typing module is False at
runtime
--# but True while type checking.
--if False: # pragma: no cover
-- from typing import TYPE_CHECKING
--else:
-- TYPE_CHECKING = False
--
--# typing's cast syntax requires calling typing.cast at runtime, but we don't
--# want to import typing at runtime. Here, we inform the type checkers that
--# we're importing `typing.cast` as `cast` and re-implement typing.cast's
--# runtime behavior in a block that is ignored by type checkers.
--if TYPE_CHECKING: # pragma: no cover
-- # not executed at runtime
-- from typing import cast
--else:
-- # executed at runtime
-- def cast(type_, value): # noqa
-- return value
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/packaging/markers.py
b/third_party/python/setuptools/pkg_resources/_vendor/packaging/markers.py
----
a/third_party/python/setuptools/pkg_resources/_vendor/packaging/markers.py
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/packaging/markers.py
-@@ -1,40 +1,40 @@
- # 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 __future__ import absolute_import, division, print_function
-
- import operator
- import os
- import platform
- import sys
--
--from pkg_resources.extern.pyparsing import ParseException, ParseResults,
stringStart, stringEnd
--from pkg_resources.extern.pyparsing import ZeroOrMore, Group, Forward,
QuotedString
--from pkg_resources.extern.pyparsing import Literal as L # noqa
-+from typing import Any, Callable, Dict, List, Optional, Tuple, Union
-
--from ._compat import string_types
--from ._typing import TYPE_CHECKING
--from .specifiers import Specifier, InvalidSpecifier
--
--if TYPE_CHECKING: # pragma: no cover
-- from typing import Any, Callable, Dict, List, Optional, Tuple, Union
--
-- Operator = Callable[[str, str], bool]
--
-+from ._parser import (
-+ MarkerAtom,
-+ MarkerList,
-+ Op,
-+ Value,
-+ Variable,
-+ parse_marker as _parse_marker,
-+)
-+from ._tokenizer import ParserSyntaxError
-+from .specifiers import InvalidSpecifier, Specifier
-+from .utils import canonicalize_name
-
- __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):
-@@ -45,121 +45,37 @@ class UndefinedComparison(ValueError):
-
- class UndefinedEnvironmentName(ValueError):
- """
- A name was attempted to be used that does not exist inside of the
- environment.
- """
-
-
--class Node(object):
-- def __init__(self, value):
-- # type: (Any) -> None
-- self.value = value
--
-- def __str__(self):
-- # type: () -> str
-- return str(self.value)
--
-- def __repr__(self):
-- # type: () -> str
-- return "<{0}({1!r})>".format(self.__class__.__name__, str(self))
--
-- def serialize(self):
-- # type: () -> str
-- raise NotImplementedError
--
--
--class Variable(Node):
-- def serialize(self):
-- # type: () -> str
-- return str(self)
--
--
--class Value(Node):
-- def serialize(self):
-- # type: () -> str
-- return '"{0}"'.format(self)
--
--
--class Op(Node):
-- def serialize(self):
-- # type: () -> str
-- return str(self)
-+def _normalize_extra_values(results: Any) -> Any:
-+ """
-+ Normalize extra values.
-+ """
-+ if isinstance(results[0], tuple):
-+ lhs, op, rhs = results[0]
-+ if isinstance(lhs, Variable) and lhs.value == "extra":
-+ normalized_extra = canonicalize_name(rhs.value)
-+ rhs = Value(normalized_extra)
-+ elif isinstance(rhs, Variable) and rhs.value == "extra":
-+ normalized_extra = canonicalize_name(lhs.value)
-+ lhs = Value(normalized_extra)
-+ results[0] = lhs, op, rhs
-+ return results
-
-
--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("<")
--)
-+def _format_marker(
-+ marker: Union[List[str], MarkerAtom, str], first: Optional[bool] = True
-+) -> str:
-
--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):
-- # type: (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, first=True):
-- # type: (Union[List[str], Tuple[Node, ...], str], Optional[bool]) -> str
--
-- assert isinstance(marker, (list, tuple, string_types))
-+ 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
-@@ -174,155 +90,163 @@ def _format_marker(marker, first=True):
- else:
- return "(" + " ".join(inner) + ")"
- elif isinstance(marker, tuple):
- return " ".join([m.serialize() for m in marker])
- else:
- return marker
-
-
--_operators = {
-+_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,
--} # type: Dict[str, Operator]
-+}
-
-
--def _eval_op(lhs, op, rhs):
-- # type: (str, Op, str) -> bool
-+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)
-+ return spec.contains(lhs, prereleases=True)
-
-- oper = _operators.get(op.serialize()) # type: Optional[Operator]
-+ oper: Optional[Operator] = _operators.get(op.serialize())
- if oper is None:
-- raise UndefinedComparison(
-- "Undefined {0!r} on {1!r} and {2!r}.".format(op, lhs, rhs)
-- )
-+ raise UndefinedComparison(f"Undefined {op!r} on {lhs!r} and
{rhs!r}.")
-
- return oper(lhs, rhs)
-
-
--class Undefined(object):
-- pass
-+def _normalize(*values: str, key: str) -> Tuple[str, ...]:
-+ # PEP 685 – Comparison of extra names for optional distribution
dependencies
-+ # https://peps.python.org/pep-0685/
-+ # > When comparing extra names, tools MUST normalize the names being
-+ # > compared using the semantics outlined in PEP 503 for names
-+ if key == "extra":
-+ return tuple(canonicalize_name(v) for v in values)
-
--
--_undefined = Undefined()
-+ # other environment markers don't have such standards
-+ return values
-
-
--def _get_env(environment, name):
-- # type: (Dict[str, str], str) -> str
-- value = environment.get(name, _undefined) # type: Union[str, Undefined]
--
-- if isinstance(value, Undefined):
-- raise UndefinedEnvironmentName(
-- "{0!r} does not exist in evaluation environment.".format(name)
-- )
--
-- return value
--
--
--def _evaluate_markers(markers, environment):
-- # type: (List[Any], Dict[str, str]) -> bool
-- groups = [[]] # type: List[List[bool]]
-+def _evaluate_markers(markers: MarkerList, environment: Dict[str, str]) ->
bool:
-+ groups: List[List[bool]] = [[]]
-
- for marker in markers:
-- assert isinstance(marker, (list, tuple, string_types))
-+ 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)
-+ environment_key = lhs.value
-+ lhs_value = environment[environment_key]
- rhs_value = rhs.value
- else:
- lhs_value = lhs.value
-- rhs_value = _get_env(environment, rhs.value)
-+ environment_key = rhs.value
-+ rhs_value = environment[environment_key]
-
-+ lhs_value, rhs_value = _normalize(lhs_value, rhs_value,
key=environment_key)
- 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):
-- # type: (sys._version_info) -> str
-+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():
-- # type: () -> Dict[str, str]
-- if hasattr(sys, "implementation"):
-- # Ignoring the `sys.implementation` reference for type checking due
to
-- # mypy not liking that the attribute doesn't exist in Python 2.7
when
-- # run with the `--py27` flag.
-- iver = format_full_version(sys.implementation.version) # type:
ignore
-- implementation_name = sys.implementation.name # type: ignore
-- else:
-- iver = "0"
-- implementation_name = ""
--
-+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(object):
-- def __init__(self, marker):
-- # type: (str) -> None
-+class Marker:
-+ def __init__(self, marker: str) -> None:
-+ # Note: We create a Marker object without calling this constructor
in
-+ # packaging.requirements.Requirement. If any additional logic
is
-+ # added here, make sure to mirror/adapt Requirement.
- try:
-- self._markers = _coerce_parse_result(MARKER.parseString(marker))
-- except ParseException as e:
-- err_str = "Invalid marker: {0!r}, parse error at {1!r}".format(
-- marker, marker[e.loc : e.loc + 8]
-- )
-- raise InvalidMarker(err_str)
-+ self._markers = _normalize_extra_values(_parse_marker(marker))
-+ # The attribute `_markers` can be described in terms of a
recursive type:
-+ # MarkerList = List[Union[Tuple[Node, ...], str, MarkerList]]
-+ #
-+ # For example, the following expression:
-+ # python_version > "3.6" or (python_version == "3.6" and
os_name == "unix")
-+ #
-+ # is parsed into:
-+ # [
-+ # (<Variable('python_version')>, <Op('>')>, <Value('3.6')>),
-+ # 'and',
-+ # [
-+ # (<Variable('python_version')>, <Op('==')>,
<Value('3.6')>),
-+ # 'or',
-+ # (<Variable('os_name')>, <Op('==')>, <Value('unix')>)
-+ # ]
-+ # ]
-+ except ParserSyntaxError as e:
-+ raise InvalidMarker(str(e)) from e
-
-- def __str__(self):
-- # type: () -> str
-+ def __str__(self) -> str:
- return _format_marker(self._markers)
-
-- def __repr__(self):
-- # type: () -> str
-- return "<Marker({0!r})>".format(str(self))
-+ def __repr__(self) -> str:
-+ return f"<Marker('{self}')>"
-+
-+ def __hash__(self) -> int:
-+ return hash((self.__class__.__name__, str(self)))
-
-- def evaluate(self, environment=None):
-- # type: (Optional[Dict[str, str]]) -> bool
-+ def __eq__(self, other: Any) -> bool:
-+ if not isinstance(other, Marker):
-+ return NotImplemented
-+
-+ return str(self) == str(other)
-+
-+ 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()
-+ current_environment["extra"] = ""
- if environment is not None:
- current_environment.update(environment)
-+ # The API used to allow setting extra to None. We need to
handle this
-+ # case for backwards compatibility.
-+ if current_environment["extra"] is None:
-+ current_environment["extra"] = ""
-
- return _evaluate_markers(self._markers, current_environment)
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/packaging/metadata.py
b/third_party/python/setuptools/pkg_resources/_vendor/packaging/metadata.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/packaging/metadata.py
-@@ -0,0 +1,408 @@
-+import email.feedparser
-+import email.header
-+import email.message
-+import email.parser
-+import email.policy
-+import sys
-+import typing
-+from typing import Dict, List, Optional, Tuple, Union, cast
-+
-+if sys.version_info >= (3, 8): # pragma: no cover
-+ from typing import TypedDict
-+else: # pragma: no cover
-+ if typing.TYPE_CHECKING:
-+ from typing_extensions import TypedDict
-+ else:
-+ try:
-+ from typing_extensions import TypedDict
-+ except ImportError:
-+
-+ class TypedDict:
-+ def __init_subclass__(*_args, **_kwargs):
-+ pass
-+
-+
-+# The RawMetadata class attempts to make as few assumptions about the
underlying
-+# serialization formats as possible. The idea is that as long as a
serialization
-+# formats offer some very basic primitives in *some* way then we can support
-+# serializing to and from that format.
-+class RawMetadata(TypedDict, total=False):
-+ """A dictionary of raw core metadata.
-+
-+ Each field in core metadata maps to a key of this dictionary (when data
is
-+ provided). The key is lower-case and underscores are used instead of
dashes
-+ compared to the equivalent core metadata field. Any core metadata field
that
-+ can be specified multiple times or can hold multiple values in a single
-+ field have a key with a plural name.
-+
-+ Core metadata fields that can be specified multiple times are stored as
a
-+ list or dict depending on which is appropriate for the field. Any fields
-+ which hold multiple values in a single field are stored as a list.
-+
-+ """
-+
-+ # Metadata 1.0 - PEP 241
-+ metadata_version: str
-+ name: str
-+ version: str
-+ platforms: List[str]
-+ summary: str
-+ description: str
-+ keywords: List[str]
-+ home_page: str
-+ author: str
-+ author_email: str
-+ license: str
-+
-+ # Metadata 1.1 - PEP 314
-+ supported_platforms: List[str]
-+ download_url: str
-+ classifiers: List[str]
-+ requires: List[str]
-+ provides: List[str]
-+ obsoletes: List[str]
-+
-+ # Metadata 1.2 - PEP 345
-+ maintainer: str
-+ maintainer_email: str
-+ requires_dist: List[str]
-+ provides_dist: List[str]
-+ obsoletes_dist: List[str]
-+ requires_python: str
-+ requires_external: List[str]
-+ project_urls: Dict[str, str]
-+
-+ # Metadata 2.0
-+ # PEP 426 attempted to completely revamp the metadata format
-+ # but got stuck without ever being able to build consensus on
-+ # it and ultimately ended up withdrawn.
-+ #
-+ # However, a number of tools had started emiting METADATA with
-+ # `2.0` Metadata-Version, so for historical reasons, this version
-+ # was skipped.
-+
-+ # Metadata 2.1 - PEP 566
-+ description_content_type: str
-+ provides_extra: List[str]
-+
-+ # Metadata 2.2 - PEP 643
-+ dynamic: List[str]
-+
-+ # Metadata 2.3 - PEP 685
-+ # No new fields were added in PEP 685, just some edge case were
-+ # tightened up to provide better interoptability.
-+
-+
-+_STRING_FIELDS = {
-+ "author",
-+ "author_email",
-+ "description",
-+ "description_content_type",
-+ "download_url",
-+ "home_page",
-+ "license",
-+ "maintainer",
-+ "maintainer_email",
-+ "metadata_version",
-+ "name",
-+ "requires_python",
-+ "summary",
-+ "version",
-+}
-+
-+_LIST_STRING_FIELDS = {
-+ "classifiers",
-+ "dynamic",
-+ "obsoletes",
-+ "obsoletes_dist",
-+ "platforms",
-+ "provides",
-+ "provides_dist",
-+ "provides_extra",
-+ "requires",
-+ "requires_dist",
-+ "requires_external",
-+ "supported_platforms",
-+}
-+
-+
-+def _parse_keywords(data: str) -> List[str]:
-+ """Split a string of comma-separate keyboards into a list of
keywords."""
-+ return [k.strip() for k in data.split(",")]
-+
-+
-+def _parse_project_urls(data: List[str]) -> Dict[str, str]:
-+ """Parse a list of label/URL string pairings separated by a comma."""
-+ urls = {}
-+ for pair in data:
-+ # Our logic is slightly tricky here as we want to try and do
-+ # *something* reasonable with malformed data.
-+ #
-+ # The main thing that we have to worry about, is data that does
-+ # not have a ',' at all to split the label from the Value. There
-+ # isn't a singular right answer here, and we will fail validation
-+ # later on (if the caller is validating) so it doesn't *really*
-+ # matter, but since the missing value has to be an empty str
-+ # and our return value is dict[str, str], if we let the key
-+ # be the missing value, then they'd have multiple '' values that
-+ # overwrite each other in a accumulating dict.
-+ #
-+ # The other potentional issue is that it's possible to have the
-+ # same label multiple times in the metadata, with no solid "right"
-+ # answer with what to do in that case. As such, we'll do the only
-+ # thing we can, which is treat the field as unparseable and add it
-+ # to our list of unparsed fields.
-+ parts = [p.strip() for p in pair.split(",", 1)]
-+ parts.extend([""] * (max(0, 2 - len(parts)))) # Ensure 2 items
-+
-+ # TODO: The spec doesn't say anything about if the keys should be
-+ # considered case sensitive or not... logically they should
-+ # be case-preserving and case-insensitive, but doing that
-+ # would open up more cases where we might have duplicate
-+ # entries.
-+ label, url = parts
-+ if label in urls:
-+ # The label already exists in our set of urls, so this field
-+ # is unparseable, and we can just add the whole thing to our
-+ # unparseable data and stop processing it.
-+ raise KeyError("duplicate labels in project urls")
-+ urls[label] = url
-+
-+ return urls
-+
-+
-+def _get_payload(msg: email.message.Message, source: Union[bytes, str]) ->
str:
-+ """Get the body of the message."""
-+ # If our source is a str, then our caller has managed encodings for us,
-+ # and we don't need to deal with it.
-+ if isinstance(source, str):
-+ payload: str = msg.get_payload()
-+ return payload
-+ # If our source is a bytes, then we're managing the encoding and we need
-+ # to deal with it.
-+ else:
-+ bpayload: bytes = msg.get_payload(decode=True)
-+ try:
-+ return bpayload.decode("utf8", "strict")
-+ except UnicodeDecodeError:
-+ raise ValueError("payload in an invalid encoding")
-+
-+
-+# The various parse_FORMAT functions here are intended to be as lenient as
-+# possible in their parsing, while still returning a correctly typed
-+# RawMetadata.
-+#
-+# To aid in this, we also generally want to do as little touching of the
-+# data as possible, except where there are possibly some historic holdovers
-+# that make valid data awkward to work with.
-+#
-+# While this is a lower level, intermediate format than our ``Metadata``
-+# class, some light touch ups can make a massive difference in usability.
-+
-+# Map METADATA fields to RawMetadata.
-+_EMAIL_TO_RAW_MAPPING = {
-+ "author": "author",
-+ "author-email": "author_email",
-+ "classifier": "classifiers",
-+ "description": "description",
-+ "description-content-type": "description_content_type",
-+ "download-url": "download_url",
-+ "dynamic": "dynamic",
-+ "home-page": "home_page",
-+ "keywords": "keywords",
-+ "license": "license",
-+ "maintainer": "maintainer",
-+ "maintainer-email": "maintainer_email",
-+ "metadata-version": "metadata_version",
-+ "name": "name",
-+ "obsoletes": "obsoletes",
-+ "obsoletes-dist": "obsoletes_dist",
-+ "platform": "platforms",
-+ "project-url": "project_urls",
-+ "provides": "provides",
-+ "provides-dist": "provides_dist",
-+ "provides-extra": "provides_extra",
-+ "requires": "requires",
-+ "requires-dist": "requires_dist",
-+ "requires-external": "requires_external",
-+ "requires-python": "requires_python",
-+ "summary": "summary",
-+ "supported-platform": "supported_platforms",
-+ "version": "version",
-+}
-+
-+
-+def parse_email(data: Union[bytes, str]) -> Tuple[RawMetadata, Dict[str,
List[str]]]:
-+ """Parse a distribution's metadata.
-+
-+ This function returns a two-item tuple of dicts. The first dict is of
-+ recognized fields from the core metadata specification. Fields that can
be
-+ parsed and translated into Python's built-in types are converted
-+ appropriately. All other fields are left as-is. Fields that are allowed
to
-+ appear multiple times are stored as lists.
-+
-+ The second dict contains all other fields from the metadata. This
includes
-+ any unrecognized fields. It also includes any fields which are expected
to
-+ be parsed into a built-in type but were not formatted appropriately.
Finally,
-+ any fields that are expected to appear only once but are repeated are
-+ included in this dict.
-+
-+ """
-+ raw: Dict[str, Union[str, List[str], Dict[str, str]]] = {}
-+ unparsed: Dict[str, List[str]] = {}
-+
-+ if isinstance(data, str):
-+ parsed =
email.parser.Parser(policy=email.policy.compat32).parsestr(data)
-+ else:
-+ parsed =
email.parser.BytesParser(policy=email.policy.compat32).parsebytes(data)
-+
-+ # We have to wrap parsed.keys() in a set, because in the case of
multiple
-+ # values for a key (a list), the key will appear multiple times in the
-+ # list of keys, but we're avoiding that by using get_all().
-+ for name in frozenset(parsed.keys()):
-+ # Header names in RFC are case insensitive, so we'll normalize to
all
-+ # lower case to make comparisons easier.
-+ name = name.lower()
-+
-+ # We use get_all() here, even for fields that aren't multiple use,
-+ # because otherwise someone could have e.g. two Name fields, and we
-+ # would just silently ignore it rather than doing something about
it.
-+ headers = parsed.get_all(name)
-+
-+ # The way the email module works when parsing bytes is that it
-+ # unconditionally decodes the bytes as ascii using the
surrogateescape
-+ # handler. When you pull that data back out (such as with get_all()
),
-+ # it looks to see if the str has any surrogate escapes, and if it
does
-+ # it wraps it in a Header object instead of returning the string.
-+ #
-+ # As such, we'll look for those Header objects, and fix up the
encoding.
-+ value = []
-+ # Flag if we have run into any issues processing the headers, thus
-+ # signalling that the data belongs in 'unparsed'.
-+ valid_encoding = True
-+ for h in headers:
-+ # It's unclear if this can return more types than just a Header
or
-+ # a str, so we'll just assert here to make sure.
-+ assert isinstance(h, (email.header.Header, str))
-+
-+ # If it's a header object, we need to do our little dance to get
-+ # the real data out of it. In cases where there is invalid data
-+ # we're going to end up with mojibake, but there's no obvious,
good
-+ # way around that without reimplementing parts of the Header
object
-+ # ourselves.
-+ #
-+ # That should be fine since, if mojibacked happens, this key is
-+ # going into the unparsed dict anyways.
-+ if isinstance(h, email.header.Header):
-+ # The Header object stores it's data as chunks, and each
chunk
-+ # can be independently encoded, so we'll need to check each
-+ # of them.
-+ chunks: List[Tuple[bytes, Optional[str]]] = []
-+ for bin, encoding in email.header.decode_header(h):
-+ try:
-+ bin.decode("utf8", "strict")
-+ except UnicodeDecodeError:
-+ # Enable mojibake.
-+ encoding = "latin1"
-+ valid_encoding = False
-+ else:
-+ encoding = "utf8"
-+ chunks.append((bin, encoding))
-+
-+ # Turn our chunks back into a Header object, then let that
-+ # Header object do the right thing to turn them into a
-+ # string for us.
-+ value.append(str(email.header.make_header(chunks)))
-+ # This is already a string, so just add it.
-+ else:
-+ value.append(h)
-+
-+ # We've processed all of our values to get them into a list of str,
-+ # but we may have mojibake data, in which case this is an unparsed
-+ # field.
-+ if not valid_encoding:
-+ unparsed[name] = value
-+ continue
-+
-+ raw_name = _EMAIL_TO_RAW_MAPPING.get(name)
-+ if raw_name is None:
-+ # This is a bit of a weird situation, we've encountered a key
that
-+ # we don't know what it means, so we don't know whether it's
meant
-+ # to be a list or not.
-+ #
-+ # Since we can't really tell one way or another, we'll just
leave it
-+ # as a list, even though it may be a single item list, because
that's
-+ # what makes the most sense for email headers.
-+ unparsed[name] = value
-+ continue
-+
-+ # If this is one of our string fields, then we'll check to see if
our
-+ # value is a list of a single item. If it is then we'll assume that
-+ # it was emitted as a single string, and unwrap the str from inside
-+ # the list.
-+ #
-+ # If it's any other kind of data, then we haven't the faintest clue
-+ # what we should parse it as, and we have to just add it to our list
-+ # of unparsed stuff.
-+ if raw_name in _STRING_FIELDS and len(value) == 1:
-+ raw[raw_name] = value[0]
-+ # If this is one of our list of string fields, then we can just
assign
-+ # the value, since email *only* has strings, and our get_all() call
-+ # above ensures that this is a list.
-+ elif raw_name in _LIST_STRING_FIELDS:
-+ raw[raw_name] = value
-+ # Special Case: Keywords
-+ # The keywords field is implemented in the metadata spec as a str,
-+ # but it conceptually is a list of strings, and is serialized using
-+ # ", ".join(keywords), so we'll do some light data massaging to turn
-+ # this into what it logically is.
-+ elif raw_name == "keywords" and len(value) == 1:
-+ raw[raw_name] = _parse_keywords(value[0])
-+ # Special Case: Project-URL
-+ # The project urls is implemented in the metadata spec as a list of
-+ # specially-formatted strings that represent a key and a value,
which
-+ # is fundamentally a mapping, however the email format doesn't
support
-+ # mappings in a sane way, so it was crammed into a list of strings
-+ # instead.
-+ #
-+ # We will do a little light data massaging to turn this into a map
as
-+ # it logically should be.
-+ elif raw_name == "project_urls":
-+ try:
-+ raw[raw_name] = _parse_project_urls(value)
-+ except KeyError:
-+ unparsed[name] = value
-+ # Nothing that we've done has managed to parse this, so it'll just
-+ # throw it in our unparseable data and move on.
-+ else:
-+ unparsed[name] = value
-+
-+ # We need to support getting the Description from the message payload in
-+ # addition to getting it from the the headers. This does mean, though,
there
-+ # is the possibility of it being set both ways, in which case we put
both
-+ # in 'unparsed' since we don't know which is right.
-+ try:
-+ payload = _get_payload(parsed, data)
-+ except ValueError:
-+ unparsed.setdefault("description", []).append(
-+ parsed.get_payload(decode=isinstance(data, bytes))
-+ )
-+ else:
-+ if payload:
-+ # Check to see if we've already got a description, if so then
both
-+ # it, and this body move to unparseable.
-+ if "description" in raw:
-+ description_header = cast(str, raw.pop("description"))
-+ unparsed.setdefault("description", []).extend(
-+ [description_header, payload]
-+ )
-+ elif "description" in unparsed:
-+ unparsed["description"].append(payload)
-+ else:
-+ raw["description"] = payload
-+
-+ # We need to cast our `raw` to a metadata, because a TypedDict only
support
-+ # literal key names, but we're computing our key names on purpose, but
the
-+ # way this function is implemented, our `TypedDict` can only have valid
key
-+ # names.
-+ return cast(RawMetadata, raw), unparsed
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/packaging/requirements.py

b/third_party/python/setuptools/pkg_resources/_vendor/packaging/requirements.py
----
a/third_party/python/setuptools/pkg_resources/_vendor/packaging/requirements.py
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/packaging/requirements.py
-@@ -1,145 +1,95 @@
- # 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 __future__ import absolute_import, division, print_function
-
--import string
--import re
-+import urllib.parse
-+from typing import Any, List, Optional, Set
-
--from pkg_resources.extern.pyparsing import stringStart, stringEnd,
originalTextFor, ParseException
--from pkg_resources.extern.pyparsing import ZeroOrMore, Word, Optional,
Regex, Combine
--from pkg_resources.extern.pyparsing import Literal as L # noqa
--from urllib import parse as urlparse
--
--from ._typing import TYPE_CHECKING
--from .markers import MARKER_EXPR, Marker
--from .specifiers import LegacySpecifier, Specifier, SpecifierSet
--
--if TYPE_CHECKING: # pragma: no cover
-- from typing import List
-+from ._parser import parse_requirement as _parse_requirement
-+from ._tokenizer import ParserSyntaxError
-+from .markers import Marker, _normalize_extra_values
-+from .specifiers import 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(object):
-+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):
-- # type: (str) -> None
-+ def __init__(self, requirement_string: str) -> None:
- try:
-- req = REQUIREMENT.parseString(requirement_string)
-- except ParseException as e:
-- raise InvalidRequirement(
-- 'Parse error at "{0!r}": {1}'.format(
-- requirement_string[e.loc : e.loc + 8], e.msg
-- )
-- )
-+ parsed = _parse_requirement(requirement_string)
-+ except ParserSyntaxError as e:
-+ raise InvalidRequirement(str(e)) from e
-
-- self.name = req.name
-- if req.url:
-- parsed_url = urlparse.urlparse(req.url)
-+ self.name: str = parsed.name
-+ if parsed.url:
-+ parsed_url = urllib.parse.urlparse(parsed.url)
- if parsed_url.scheme == "file":
-- if urlparse.urlunparse(parsed_url) != req.url:
-+ if urllib.parse.urlunparse(parsed_url) != parsed.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("Invalid URL: {0}".format(req.url))
-- self.url = req.url
-+ raise InvalidRequirement(f"Invalid URL: {parsed.url}")
-+ self.url: Optional[str] = parsed.url
- else:
- self.url = None
-- self.extras = set(req.extras.asList() if req.extras else [])
-- self.specifier = SpecifierSet(req.specifier)
-- self.marker = req.marker if req.marker else None
-+ self.extras: Set[str] = set(parsed.extras if parsed.extras else [])
-+ self.specifier: SpecifierSet = SpecifierSet(parsed.specifier)
-+ self.marker: Optional[Marker] = None
-+ if parsed.marker is not None:
-+ self.marker = Marker.__new__(Marker)
-+ self.marker._markers = _normalize_extra_values(parsed.marker)
-
-- def __str__(self):
-- # type: () -> str
-- parts = [self.name] # type: List[str]
-+ def __str__(self) -> str:
-+ parts: List[str] = [self.name]
-
- if self.extras:
-- parts.append("[{0}]".format(",".join(sorted(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("@ {0}".format(self.url))
-+ parts.append(f"@ {self.url}")
- if self.marker:
- parts.append(" ")
-
- if self.marker:
-- parts.append("; {0}".format(self.marker))
-+ parts.append(f"; {self.marker}")
-
- return "".join(parts)
-
-- def __repr__(self):
-- # type: () -> str
-- return "<Requirement({0!r})>".format(str(self))
-+ def __repr__(self) -> str:
-+ return f"<Requirement('{self}')>"
-+
-+ def __hash__(self) -> int:
-+ return hash((self.__class__.__name__, str(self)))
-+
-+ def __eq__(self, other: Any) -> bool:
-+ if not isinstance(other, Requirement):
-+ return NotImplemented
-+
-+ return (
-+ self.name == other.name
-+ and self.extras == other.extras
-+ and self.specifier == other.specifier
-+ and self.url == other.url
-+ and self.marker == other.marker
-+ )
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/packaging/specifiers.py
b/third_party/python/setuptools/pkg_resources/_vendor/packaging/specifiers.py
----
a/third_party/python/setuptools/pkg_resources/_vendor/packaging/specifiers.py
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/packaging/specifiers.py
-@@ -1,399 +1,185 @@
- # 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 __future__ import absolute_import, division, print_function
-+"""
-+.. testsetup::
-+
-+ from packaging.specifiers import Specifier, SpecifierSet,
InvalidSpecifier
-+ from packaging.version import Version
-+"""
-
- import abc
--import functools
- import itertools
- import re
--
--from ._compat import string_types, with_metaclass
--from ._typing import TYPE_CHECKING
--from .utils import canonicalize_version
--from .version import Version, LegacyVersion, parse
-+from typing import (
-+ Callable,
-+ Iterable,
-+ Iterator,
-+ List,
-+ Optional,
-+ Set,
-+ Tuple,
-+ TypeVar,
-+ Union,
-+)
-
--if TYPE_CHECKING: # pragma: no cover
-- from typing import (
-- List,
-- Dict,
-- Union,
-- Iterable,
-- Iterator,
-- Optional,
-- Callable,
-- Tuple,
-- FrozenSet,
-- )
-+from .utils import canonicalize_version
-+from .version import Version
-
-- ParsedVersion = Union[Version, LegacyVersion]
-- UnparsedVersion = Union[Version, LegacyVersion, str]
-- CallableOperator = Callable[[ParsedVersion, str], bool]
-+UnparsedVersion = Union[Version, str]
-+UnparsedVersionVar = TypeVar("UnparsedVersionVar", bound=UnparsedVersion)
-+CallableOperator = Callable[[Version, str], bool]
-+
-+
-+def _coerce_version(version: UnparsedVersion) -> Version:
-+ if not isinstance(version, Version):
-+ version = Version(version)
-+ return version
-
-
- class InvalidSpecifier(ValueError):
- """
-- An invalid specifier was found, users should refer to PEP 440.
-+ Raised when attempting to create a :class:`Specifier` with a specifier
-+ string that is invalid.
-+
-+ >>> Specifier("lolwat")
-+ Traceback (most recent call last):
-+ ...
-+ packaging.specifiers.InvalidSpecifier: Invalid specifier: 'lolwat'
- """
-
-
--class BaseSpecifier(with_metaclass(abc.ABCMeta, object)): # type: ignore
-+class BaseSpecifier(metaclass=abc.ABCMeta):
- @abc.abstractmethod
-- def __str__(self):
-- # type: () -> str
-+ def __str__(self) -> str:
- """
-- Returns the str representation of this Specifier like object. This
-+ Returns the str representation of this Specifier-like object. This
- should be representative of the Specifier itself.
- """
-
- @abc.abstractmethod
-- def __hash__(self):
-- # type: () -> int
-- """
-- Returns a hash value for this Specifier like object.
-+ def __hash__(self) -> int:
- """
--
-- @abc.abstractmethod
-- def __eq__(self, other):
-- # type: (object) -> bool
-- """
-- Returns a boolean representing whether or not the two Specifier like
-- objects are equal.
-+ Returns a hash value for this Specifier-like object.
- """
-
- @abc.abstractmethod
-- def __ne__(self, other):
-- # type: (object) -> bool
-+ def __eq__(self, other: object) -> bool:
- """
-- Returns a boolean representing whether or not the two Specifier like
-- objects are not equal.
-+ Returns a boolean representing whether or not the two Specifier-like
-+ objects are equal.
-+
-+ :param other: The other object to check against.
- """
-
-- @abc.abstractproperty
-- def prereleases(self):
-- # type: () -> Optional[bool]
-- """
-- Returns whether or not pre-releases as a whole are allowed by this
-- specifier.
-+ @property
-+ @abc.abstractmethod
-+ def prereleases(self) -> Optional[bool]:
-+ """Whether or not pre-releases as a whole are allowed.
-+
-+ This can be set to either ``True`` or ``False`` to explicitly
enable or disable
-+ prereleases or it can be set to ``None`` (the default) to use
default semantics.
- """
-
- @prereleases.setter
-- def prereleases(self, value):
-- # type: (bool) -> None
-- """
-- Sets whether or not pre-releases as a whole are allowed by this
-- specifier.
-+ def prereleases(self, value: bool) -> None:
-+ """Setter for :attr:`prereleases`.
-+
-+ :param value: The value to set.
- """
-
- @abc.abstractmethod
-- def contains(self, item, prereleases=None):
-- # type: (str, Optional[bool]) -> bool
-+ 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, prereleases=None):
-- # type: (Iterable[UnparsedVersion], Optional[bool]) ->
Iterable[UnparsedVersion]
-+ def filter(
-+ self, iterable: Iterable[UnparsedVersionVar], prereleases:
Optional[bool] = None
-+ ) -> Iterator[UnparsedVersionVar]:
- """
- 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 = {} # type: Dict[str, str]
--
-- def __init__(self, spec="", prereleases=None):
-- # type: (str, Optional[bool]) -> None
-- match = self._regex.search(spec)
-- if not match:
-- raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec))
--
-- self._spec = (
-- match.group("operator").strip(),
-- match.group("version").strip(),
-- ) # type: Tuple[str, str]
--
-- # Store whether or not this Specifier should accept prereleases
-- self._prereleases = prereleases
--
-- def __repr__(self):
-- # type: () -> str
-- pre = (
-- ", prereleases={0!r}".format(self.prereleases)
-- if self._prereleases is not None
-- else ""
-- )
--
-- return "<{0}({1!r}{2})>".format(self.__class__.__name__, str(self),
pre)
--
-- def __str__(self):
-- # type: () -> str
-- return "{0}{1}".format(*self._spec)
--
-- @property
-- def _canonical_spec(self):
-- # type: () -> Tuple[str, Union[Version, str]]
-- return self._spec[0], canonicalize_version(self._spec[1])
--
-- def __hash__(self):
-- # type: () -> int
-- return hash(self._canonical_spec)
--
-- def __eq__(self, other):
-- # type: (object) -> bool
-- if isinstance(other, string_types):
-- 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
-+class Specifier(BaseSpecifier):
-+ """This class abstracts handling of version specifiers.
-
-- def __ne__(self, other):
-- # type: (object) -> bool
-- if isinstance(other, string_types):
-- 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):
-- # type: (str) -> CallableOperator
-- operator_callable = getattr(
-- self, "_compare_{0}".format(self._operators[op])
-- ) # type: CallableOperator
-- return operator_callable
--
-- def _coerce_version(self, version):
-- # type: (UnparsedVersion) -> ParsedVersion
-- if not isinstance(version, (LegacyVersion, Version)):
-- version = parse(version)
-- return version
--
-- @property
-- def operator(self):
-- # type: () -> str
-- return self._spec[0]
--
-- @property
-- def version(self):
-- # type: () -> str
-- return self._spec[1]
--
-- @property
-- def prereleases(self):
-- # type: () -> Optional[bool]
-- return self._prereleases
--
-- @prereleases.setter
-- def prereleases(self, value):
-- # type: (bool) -> None
-- self._prereleases = value
--
-- def __contains__(self, item):
-- # type: (str) -> bool
-- return self.contains(item)
--
-- def contains(self, item, prereleases=None):
-- # type: (UnparsedVersion, Optional[bool]) -> 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)
-+ .. tip::
-
-- # 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 = self._get_operator(self.operator) # type:
CallableOperator
-- return operator_callable(normalized_item, self.version)
--
-- def filter(self, iterable, prereleases=None):
-- # type: (Iterable[UnparsedVersion], Optional[bool]) ->
Iterable[UnparsedVersion]
--
-- 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 incase 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<operator>(==|!=|<=|>=|<|>))
-- \s*
-- (?P<version>
-- [^,;\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.
-- )
-- """
-+ It is generally not required to instantiate this manually. You
should instead
-+ prefer to work with :class:`SpecifierSet` instead, which can parse
-+ comma-separated version specifiers (which is what package metadata
contains).
-+ """
-
-- _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 _coerce_version(self, version):
-- # type: (Union[ParsedVersion, str]) -> LegacyVersion
-- if not isinstance(version, LegacyVersion):
-- version = LegacyVersion(str(version))
-- return version
--
-- def _compare_equal(self, prospective, spec):
-- # type: (LegacyVersion, str) -> bool
-- return prospective == self._coerce_version(spec)
--
-- def _compare_not_equal(self, prospective, spec):
-- # type: (LegacyVersion, str) -> bool
-- return prospective != self._coerce_version(spec)
--
-- def _compare_less_than_equal(self, prospective, spec):
-- # type: (LegacyVersion, str) -> bool
-- return prospective <= self._coerce_version(spec)
--
-- def _compare_greater_than_equal(self, prospective, spec):
-- # type: (LegacyVersion, str) -> bool
-- return prospective >= self._coerce_version(spec)
--
-- def _compare_less_than(self, prospective, spec):
-- # type: (LegacyVersion, str) -> bool
-- return prospective < self._coerce_version(spec)
--
-- def _compare_greater_than(self, prospective, spec):
-- # type: (LegacyVersion, str) -> bool
-- return prospective > self._coerce_version(spec)
--
--
--def _require_version_compare(
-- fn # type: (Callable[[Specifier, ParsedVersion, str], bool])
--):
-- # type: (...) -> Callable[[Specifier, ParsedVersion, str], bool]
-- @functools.wraps(fn)
-- def wrapped(self, prospective, spec):
-- # type: (Specifier, ParsedVersion, str) -> bool
-- if not isinstance(prospective, Version):
-- return False
-- return fn(self, prospective, spec)
--
-- return wrapped
--
--
--class Specifier(_IndividualSpecifier):
--
-- _regex_str = r"""
-+ _operator_regex_str = r"""
- (?P<operator>(~=|==|!=|<=|>=|<|>|===))
-+ """
-+ _version_regex_str = r"""
- (?P<version>
- (?:
- # 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.
-+ [^\s;)]* # The arbitrary version can be just about
anything,
-+ # we match everything except for whitespace, a
-+ # semi-colon for marker support, and a closing
paren
-+ # since versions can be enclosed in them.
- )
- |
- (?:
- # 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.
-+ # You cannot use a wild card and a pre-release,
post-release, a dev or
-+ # local version together so group them with a | and make
them optional.
- (?:
-+ \.\* # Wild card syntax of .*
-+ |
-+ (?: # pre release
-+ [-_\.]?
-+ (alpha|beta|preview|pre|a|b|c|rc)
-+ [-_\.]?
-+ [0-9]*
-+ )?
-+ (?: # post release
-+ (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
-+ )?
- (?:[-_\.]?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)
-+ (alpha|beta|preview|pre|a|b|c|rc)
- [-_\.]?
- [0-9]*
- )?
- (?: # post release
- (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
- )?
- (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
- )
-@@ -408,137 +194,270 @@ class Specifier(_IndividualSpecifier):
- # don't match here.
-
- \s*
- v?
- (?:[0-9]+!)? # epoch
- [0-9]+(?:\.[0-9]+)* # release
- (?: # pre release
- [-_\.]?
-- (a|b|c|rc|alpha|beta|pre|preview)
-+ (alpha|beta|preview|pre|a|b|c|rc)
- [-_\.]?
- [0-9]*
- )?
- (?: # post release
- (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
- )?
- (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
- )
- )
- """
-
-- _regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE |
re.IGNORECASE)
-+ _regex = re.compile(
-+ r"^\s*" + _operator_regex_str + _version_regex_str + r"\s*$",
-+ re.VERBOSE | re.IGNORECASE,
-+ )
-
- _operators = {
- "~=": "compatible",
- "==": "equal",
- "!=": "not_equal",
- "<=": "less_than_equal",
- ">=": "greater_than_equal",
- "<": "less_than",
- ">": "greater_than",
- "===": "arbitrary",
- }
-
-- @_require_version_compare
-- def _compare_compatible(self, prospective, spec):
-- # type: (ParsedVersion, str) -> bool
-+ def __init__(self, spec: str = "", prereleases: Optional[bool] = None)
-> None:
-+ """Initialize a Specifier instance.
-+
-+ :param spec:
-+ The string representation of a specifier which will be parsed
and
-+ normalized before use.
-+ :param prereleases:
-+ This tells the specifier if it should accept prerelease
versions if
-+ applicable or not. The default of ``None`` will autodetect it
from the
-+ given specifiers.
-+ :raises InvalidSpecifier:
-+ If the given specifier is invalid (i.e. bad syntax).
-+ """
-+ 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
-+
-+ # https://github.com/python/mypy/pull/13475#pullrequestreview-1079784515
-+ @property # type: ignore[override]
-+ 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 Version(version).is_prerelease:
-+ return True
-+
-+ return False
-+
-+ @prereleases.setter
-+ def prereleases(self, value: bool) -> None:
-+ self._prereleases = value
-+
-+ @property
-+ def operator(self) -> str:
-+ """The operator of this specifier.
-+
-+ >>> Specifier("==1.2.3").operator
-+ '=='
-+ """
-+ return self._spec[0]
-+
-+ @property
-+ def version(self) -> str:
-+ """The version of this specifier.
-+
-+ >>> Specifier("==1.2.3").version
-+ '1.2.3'
-+ """
-+ return self._spec[1]
-+
-+ def __repr__(self) -> str:
-+ """A representation of the Specifier that shows all internal state.
-+
-+ >>> Specifier('>=1.0.0')
-+ <Specifier('>=1.0.0')>
-+ >>> Specifier('>=1.0.0', prereleases=False)
-+ <Specifier('>=1.0.0', prereleases=False)>
-+ >>> Specifier('>=1.0.0', prereleases=True)
-+ <Specifier('>=1.0.0', prereleases=True)>
-+ """
-+ pre = (
-+ f", prereleases={self.prereleases!r}"
-+ if self._prereleases is not None
-+ else ""
-+ )
-+
-+ return f"<{self.__class__.__name__}({str(self)!r}{pre})>"
-+
-+ def __str__(self) -> str:
-+ """A string representation of the Specifier that can be
round-tripped.
-+
-+ >>> str(Specifier('>=1.0.0'))
-+ '>=1.0.0'
-+ >>> str(Specifier('>=1.0.0', prereleases=False))
-+ '>=1.0.0'
-+ """
-+ return "{}{}".format(*self._spec)
-+
-+ @property
-+ def _canonical_spec(self) -> Tuple[str, str]:
-+ canonical_version = canonicalize_version(
-+ self._spec[1],
-+ strip_trailing_zero=(self._spec[0] != "~="),
-+ )
-+ return self._spec[0], canonical_version
-+
-+ def __hash__(self) -> int:
-+ return hash(self._canonical_spec)
-+
-+ def __eq__(self, other: object) -> bool:
-+ """Whether or not the two Specifier-like objects are equal.
-+
-+ :param other: The other object to check against.
-+
-+ The value of :attr:`prereleases` is ignored.
-+
-+ >>> Specifier("==1.2.3") == Specifier("== 1.2.3.0")
-+ True
-+ >>> (Specifier("==1.2.3", prereleases=False) ==
-+ ... Specifier("==1.2.3", prereleases=True))
-+ True
-+ >>> Specifier("==1.2.3") == "==1.2.3"
-+ True
-+ >>> Specifier("==1.2.3") == Specifier("==1.2.4")
-+ False
-+ >>> Specifier("==1.2.3") == Specifier("~=1.2.3")
-+ False
-+ """
-+ 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 _get_operator(self, op: str) -> CallableOperator:
-+ operator_callable: CallableOperator = getattr(
-+ self, f"_compare_{self._operators[op]}"
-+ )
-+ return operator_callable
-+
-+ def _compare_compatible(self, prospective: Version, 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 post and dev releases and we want to treat the pre-release
as
-- # it's own separate segment.
-+ # ignore suffix segments.
- prefix = ".".join(
-- list(
-- itertools.takewhile(
-- lambda x: (not x.startswith("post") and not
x.startswith("dev")),
-- _version_split(spec),
-- )
-- )[:-1]
-+ 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, spec):
-- # type: (ParsedVersion, str) -> bool
-+ def _compare_equal(self, prospective: Version, 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)
-+ normalized_prospective = canonicalize_version(
-+ prospective.public, strip_trailing_zero=False
-+ )
-+ # Get the normalized version string ignoring the trailing .*
-+ normalized_spec = canonicalize_version(spec[:-2],
strip_trailing_zero=False)
- # 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_spec = _version_split(normalized_spec)
-
- # 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))
-+ split_prospective = _version_split(normalized_prospective)
-+
-+ # 0-pad the prospective version before shortening it to get the
correct
-+ # shortened version.
-+ padded_prospective, _ = _pad_version(split_prospective,
split_spec)
-
- # 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)]
-+ shortened_prospective = padded_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
-+ return shortened_prospective == split_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, spec):
-- # type: (ParsedVersion, str) -> bool
-+ def _compare_not_equal(self, prospective: Version, spec: str) -> bool:
- return not self._compare_equal(prospective, spec)
-
-- @_require_version_compare
-- def _compare_less_than_equal(self, prospective, spec):
-- # type: (ParsedVersion, str) -> bool
-+ def _compare_less_than_equal(self, prospective: Version, 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, spec):
-- # type: (ParsedVersion, str) -> bool
-+ def _compare_greater_than_equal(self, prospective: Version, 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, spec_str):
-- # type: (ParsedVersion, str) -> bool
-+ def _compare_less_than(self, prospective: Version, 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.
-@@ -553,19 +472,17 @@ class Specifier(_IndividualSpecifier):
- 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, spec_str):
-- # type: (ParsedVersion, str) -> bool
-+ def _compare_greater_than(self, prospective: Version, 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.
-@@ -586,69 +503,169 @@ class Specifier(_IndividualSpecifier):
- 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, spec):
-- # type: (Version, str) -> bool
-+ def _compare_arbitrary(self, prospective: Version, spec: str) -> bool:
- return str(prospective).lower() == str(spec).lower()
-
-- @property
-- def prereleases(self):
-- # type: () -> bool
-+ def __contains__(self, item: Union[str, Version]) -> bool:
-+ """Return whether or not the item is contained in this specifier.
-+
-+ :param item: The item to check for.
-+
-+ This is used for the ``in`` operator and behaves the same as
-+ :meth:`contains` with no ``prereleases`` argument passed.
-+
-+ >>> "1.2.3" in Specifier(">=1.2.3")
-+ True
-+ >>> Version("1.2.3") in Specifier(">=1.2.3")
-+ True
-+ >>> "1.0.0" in Specifier(">=1.2.3")
-+ False
-+ >>> "1.3.0a1" in Specifier(">=1.2.3")
-+ False
-+ >>> "1.3.0a1" in Specifier(">=1.2.3", prereleases=True)
-+ True
-+ """
-+ return self.contains(item)
-+
-+ def contains(
-+ self, item: UnparsedVersion, prereleases: Optional[bool] = None
-+ ) -> bool:
-+ """Return whether or not the item is contained in this specifier.
-
-- # 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
-+ :param item:
-+ The item to check for, which can be a version string or a
-+ :class:`Version` instance.
-+ :param prereleases:
-+ Whether or not to match prereleases with this Specifier. If set
to
-+ ``None`` (the default), it uses :attr:`prereleases` to determine
-+ whether or not prereleases are allowed.
-+
-+ >>> Specifier(">=1.2.3").contains("1.2.3")
-+ True
-+ >>> Specifier(">=1.2.3").contains(Version("1.2.3"))
-+ True
-+ >>> Specifier(">=1.2.3").contains("1.0.0")
-+ False
-+ >>> Specifier(">=1.2.3").contains("1.3.0a1")
-+ False
-+ >>> Specifier(">=1.2.3", prereleases=True).contains("1.3.0a1")
-+ True
-+ >>> Specifier(">=1.2.3").contains("1.3.0a1", prereleases=True)
-+ True
-+ """
-+
-+ # Determine if prereleases are to be allowed or not.
-+ if prereleases is None:
-+ prereleases = self.prereleases
-+
-+ # Normalize item to a Version, this allows us to have a shortcut for
-+ # "2.0" in Specifier(">=2")
-+ normalized_item = _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
-
-- # 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]
-+ # 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[UnparsedVersionVar], prereleases:
Optional[bool] = None
-+ ) -> Iterator[UnparsedVersionVar]:
-+ """Filter items in the given iterable, that match the specifier.
-+
-+ :param iterable:
-+ An iterable that can contain version strings and
:class:`Version` instances.
-+ The items in the iterable will be filtered according to the
specifier.
-+ :param prereleases:
-+ Whether or not to allow prereleases in the returned iterator.
If set to
-+ ``None`` (the default), it will be intelligently decide whether
to allow
-+ prereleases or not (based on the :attr:`prereleases` attribute,
and
-+ whether the only versions matching are prereleases).
-+
-+ This method is smarter than just ``filter(Specifier().contains,
[...])``
-+ because it implements the rule from :pep:`440` that a prerelease
item
-+ SHOULD be accepted if no other versions match the given specifier.
-
-- # Parse the version, and if it is a pre-release than this
-- # specifier allows pre-releases.
-- if parse(version).is_prerelease:
-- return True
-+ >>> list(Specifier(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
-+ ['1.3']
-+ >>> list(Specifier(">=1.2.3").filter(["1.2", "1.2.3", "1.3",
Version("1.4")]))
-+ ['1.2.3', '1.3', <Version('1.4')>]
-+ >>> list(Specifier(">=1.2.3").filter(["1.2", "1.5a1"]))
-+ ['1.5a1']
-+ >>> list(Specifier(">=1.2.3").filter(["1.3", "1.5a1"],
prereleases=True))
-+ ['1.3', '1.5a1']
-+ >>> list(Specifier(">=1.2.3", prereleases=True).filter(["1.3",
"1.5a1"]))
-+ ['1.3', '1.5a1']
-+ """
-+
-+ yielded = False
-+ found_prereleases = []
-+
-+ kw = {"prereleases": prereleases if prereleases is not None else
True}
-
-- return False
-+ # Attempt to iterate over all the values in the iterable and if any
of
-+ # them match, yield them.
-+ for version in iterable:
-+ parsed_version = _coerce_version(version)
-
-- @prereleases.setter
-- def prereleases(self, value):
-- # type: (bool) -> None
-- self._prereleases = value
-+ 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
-
-
- _prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$")
-
-
--def _version_split(version):
-- # type: (str) -> List[str]
-- result = [] # type: List[str]
-+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 _pad_version(left, right):
-- # type: (List[str], List[str]) -> Tuple[List[str], List[str]]
-+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]) :])
-@@ -657,60 +674,126 @@ def _pad_version(left, right):
- # 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="", prereleases=None):
-- # type: (str, Optional[bool]) -> None
-+ """This class abstracts handling of a set of version specifiers.
-+
-+ It can be passed a single specifier (``>=3.0``), a comma-separated list
of
-+ specifiers (``>=3.0,!=3.1``), or no specifier at all.
-+ """
-+
-+ def __init__(
-+ self, specifiers: str = "", prereleases: Optional[bool] = None
-+ ) -> None:
-+ """Initialize a SpecifierSet instance.
-
-- # Split on , to break each individual specifier into it's own item,
and
-+ :param specifiers:
-+ The string representation of a specifier or a comma-separated
list of
-+ specifiers which will be parsed and normalized before use.
-+ :param prereleases:
-+ This tells the SpecifierSet if it should accept prerelease
versions if
-+ applicable or not. The default of ``None`` will autodetect it
from the
-+ given specifiers.
-+
-+ :raises InvalidSpecifier:
-+ If the given ``specifiers`` are not parseable than this
exception will be
-+ raised.
-+ """
-+
-+ # 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()
-+ # Specifier.
-+ parsed: Set[Specifier] = set()
- for specifier in split_specifiers:
-- try:
-- parsed.add(Specifier(specifier))
-- except InvalidSpecifier:
-- parsed.add(LegacySpecifier(specifier))
-+ parsed.add(Specifier(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):
-- # type: () -> str
-+ @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 __repr__(self) -> str:
-+ """A representation of the specifier set that shows all internal
state.
-+
-+ Note that the ordering of the individual specifiers within the set
may not
-+ match the input string.
-+
-+ >>> SpecifierSet('>=1.0.0,!=2.0.0')
-+ <SpecifierSet('!=2.0.0,>=1.0.0')>
-+ >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=False)
-+ <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=False)>
-+ >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=True)
-+ <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=True)>
-+ """
- pre = (
-- ", prereleases={0!r}".format(self.prereleases)
-+ f", prereleases={self.prereleases!r}"
- if self._prereleases is not None
- else ""
- )
-
-- return "<SpecifierSet({0!r}{1})>".format(str(self), pre)
-+ return f"<SpecifierSet({str(self)!r}{pre})>"
-+
-+ def __str__(self) -> str:
-+ """A string representation of the specifier set that can be
round-tripped.
-
-- def __str__(self):
-- # type: () -> str
-+ Note that the ordering of the individual specifiers within the set
may not
-+ match the input string.
-+
-+ >>> str(SpecifierSet(">=1.0.0,!=1.0.1"))
-+ '!=1.0.1,>=1.0.0'
-+ >>> str(SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False))
-+ '!=1.0.1,>=1.0.0'
-+ """
- return ",".join(sorted(str(s) for s in self._specs))
-
-- def __hash__(self):
-- # type: () -> int
-+ def __hash__(self) -> int:
- return hash(self._specs)
-
-- def __and__(self, other):
-- # type: (Union[SpecifierSet, str]) -> SpecifierSet
-- if isinstance(other, string_types):
-+ def __and__(self, other: Union["SpecifierSet", str]) -> "SpecifierSet":
-+ """Return a SpecifierSet which is a combination of the two sets.
-+
-+ :param other: The other object to combine with.
-+
-+ >>> SpecifierSet(">=1.0.0,!=1.0.1") & '<=2.0.0,!=2.0.1'
-+ <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>
-+ >>> SpecifierSet(">=1.0.0,!=1.0.1") &
SpecifierSet('<=2.0.0,!=2.0.1')
-+ <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>
-+ """
-+ 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:
-@@ -722,142 +805,204 @@ class SpecifierSet(BaseSpecifier):
- else:
- raise ValueError(
- "Cannot combine SpecifierSets with True and False
prerelease "
- "overrides."
- )
-
- return specifier
-
-- def __eq__(self, other):
-- # type: (object) -> bool
-- if isinstance(other, (string_types, _IndividualSpecifier)):
-+ def __eq__(self, other: object) -> bool:
-+ """Whether or not the two SpecifierSet-like objects are equal.
-+
-+ :param other: The other object to check against.
-+
-+ The value of :attr:`prereleases` is ignored.
-+
-+ >>> SpecifierSet(">=1.0.0,!=1.0.1") ==
SpecifierSet(">=1.0.0,!=1.0.1")
-+ True
-+ >>> (SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False) ==
-+ ... SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True))
-+ True
-+ >>> SpecifierSet(">=1.0.0,!=1.0.1") == ">=1.0.0,!=1.0.1"
-+ True
-+ >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0")
-+ False
-+ >>> SpecifierSet(">=1.0.0,!=1.0.1") ==
SpecifierSet(">=1.0.0,!=1.0.2")
-+ False
-+ """
-+ if isinstance(other, (str, Specifier)):
- other = SpecifierSet(str(other))
- elif not isinstance(other, SpecifierSet):
- return NotImplemented
-
- return self._specs == other._specs
-
-- def __ne__(self, other):
-- # type: (object) -> bool
-- if isinstance(other, (string_types, _IndividualSpecifier)):
-- other = SpecifierSet(str(other))
-- elif not isinstance(other, SpecifierSet):
-- return NotImplemented
--
-- return self._specs != other._specs
--
-- def __len__(self):
-- # type: () -> int
-+ def __len__(self) -> int:
-+ """Returns the number of specifiers in this specifier set."""
- return len(self._specs)
-
-- def __iter__(self):
-- # type: () -> Iterator[FrozenSet[_IndividualSpecifier]]
-+ def __iter__(self) -> Iterator[Specifier]:
-+ """
-+ Returns an iterator over all the underlying :class:`Specifier`
instances
-+ in this specifier set.
-+
-+ >>> sorted(SpecifierSet(">=1.0.0,!=1.0.1"), key=str)
-+ [<Specifier('!=1.0.1')>, <Specifier('>=1.0.0')>]
-+ """
- return iter(self._specs)
-
-- @property
-- def prereleases(self):
-- # type: () -> Optional[bool]
-+ def __contains__(self, item: UnparsedVersion) -> bool:
-+ """Return whether or not the item is contained in this specifier.
-
-- # 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
-+ :param item: The item to check for.
-+
-+ This is used for the ``in`` operator and behaves the same as
-+ :meth:`contains` with no ``prereleases`` argument passed.
-
-- # 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):
-- # type: (bool) -> None
-- self._prereleases = value
--
-- def __contains__(self, item):
-- # type: (Union[ParsedVersion, str]) -> bool
-+ >>> "1.2.3" in SpecifierSet(">=1.0.0,!=1.0.1")
-+ True
-+ >>> Version("1.2.3") in SpecifierSet(">=1.0.0,!=1.0.1")
-+ True
-+ >>> "1.0.1" in SpecifierSet(">=1.0.0,!=1.0.1")
-+ False
-+ >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1")
-+ False
-+ >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True)
-+ True
-+ """
- return self.contains(item)
-
-- def contains(self, item, prereleases=None):
-- # type: (Union[ParsedVersion, str], Optional[bool]) -> bool
-+ def contains(
-+ self,
-+ item: UnparsedVersion,
-+ prereleases: Optional[bool] = None,
-+ installed: Optional[bool] = None,
-+ ) -> bool:
-+ """Return whether or not the item is contained in this SpecifierSet.
-+
-+ :param item:
-+ The item to check for, which can be a version string or a
-+ :class:`Version` instance.
-+ :param prereleases:
-+ Whether or not to match prereleases with this SpecifierSet. If
set to
-+ ``None`` (the default), it uses :attr:`prereleases` to determine
-+ whether or not prereleases are allowed.
-
-- # Ensure that our item is a Version or LegacyVersion instance.
-- if not isinstance(item, (LegacyVersion, Version)):
-- item = parse(item)
-+ >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.2.3")
-+ True
-+ >>> SpecifierSet(">=1.0.0,!=1.0.1").contains(Version("1.2.3"))
-+ True
-+ >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.0.1")
-+ False
-+ >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1")
-+ False
-+ >>> SpecifierSet(">=1.0.0,!=1.0.1",
prereleases=True).contains("1.3.0a1")
-+ True
-+ >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1",
prereleases=True)
-+ True
-+ """
-+ # Ensure that our item is a Version instance.
-+ if not isinstance(item, Version):
-+ item = Version(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
-
-+ if installed and item.is_prerelease:
-+ item = Version(item.base_version)
-+
- # 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, # type: Iterable[Union[ParsedVersion, str]]
-- prereleases=None, # type: Optional[bool]
-- ):
-- # type: (...) -> Iterable[Union[ParsedVersion, str]]
-+ self, iterable: Iterable[UnparsedVersionVar], prereleases:
Optional[bool] = None
-+ ) -> Iterator[UnparsedVersionVar]:
-+ """Filter items in the given iterable, that match the specifiers in
this set.
-+
-+ :param iterable:
-+ An iterable that can contain version strings and
:class:`Version` instances.
-+ The items in the iterable will be filtered according to the
specifier.
-+ :param prereleases:
-+ Whether or not to allow prereleases in the returned iterator.
If set to
-+ ``None`` (the default), it will be intelligently decide whether
to allow
-+ prereleases or not (based on the :attr:`prereleases` attribute,
and
-+ whether the only versions matching are prereleases).
-+
-+ This method is smarter than just
``filter(SpecifierSet(...).contains, [...])``
-+ because it implements the rule from :pep:`440` that a prerelease
item
-+ SHOULD be accepted if no other versions match the given specifier.
-
-+ >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
-+ ['1.3']
-+ >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3",
Version("1.4")]))
-+ ['1.3', <Version('1.4')>]
-+ >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.5a1"]))
-+ []
-+ >>> list(SpecifierSet(">=1.2.3").filter(["1.3", "1.5a1"],
prereleases=True))
-+ ['1.3', '1.5a1']
-+ >>> list(SpecifierSet(">=1.2.3", prereleases=True).filter(["1.3",
"1.5a1"]))
-+ ['1.3', '1.5a1']
-+
-+ An "empty" SpecifierSet will filter items based on the presence of
prerelease
-+ versions in the set.
-+
-+ >>> list(SpecifierSet("").filter(["1.3", "1.5a1"]))
-+ ['1.3']
-+ >>> list(SpecifierSet("").filter(["1.5a1"]))
-+ ['1.5a1']
-+ >>> list(SpecifierSet("", prereleases=True).filter(["1.3",
"1.5a1"]))
-+ ['1.3', '1.5a1']
-+ >>> list(SpecifierSet("").filter(["1.3", "1.5a1"],
prereleases=True))
-+ ['1.3', '1.5a1']
-+ """
- # 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
-+ return iter(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.
-+ # releases.
- else:
-- filtered = [] # type: List[Union[ParsedVersion, str]]
-- found_prereleases = [] # type: List[Union[ParsedVersion, str]]
-+ filtered: List[UnparsedVersionVar] = []
-+ found_prereleases: List[UnparsedVersionVar] = []
-
- 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
-+ parsed_version = _coerce_version(item)
-
- # 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 iter(found_prereleases)
-
-- return filtered
-+ return iter(filtered)
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/packaging/tags.py
b/third_party/python/setuptools/pkg_resources/_vendor/packaging/tags.py
---- a/third_party/python/setuptools/pkg_resources/_vendor/packaging/tags.py
-+++ b/third_party/python/setuptools/pkg_resources/_vendor/packaging/tags.py
-@@ -1,182 +1,143 @@
- # 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 __future__ import absolute_import
--
--import distutils.util
--
--try:
-- from importlib.machinery import EXTENSION_SUFFIXES
--except ImportError: # pragma: no cover
-- import imp
--
-- EXTENSION_SUFFIXES = [x[0] for x in imp.get_suffixes()]
-- del imp
- import logging
--import os
- import platform
--import re
--import struct
-+import subprocess
- import sys
- import sysconfig
--import warnings
--
--from ._typing import TYPE_CHECKING, cast
-+from importlib.machinery import EXTENSION_SUFFIXES
-+from typing import (
-+ Dict,
-+ FrozenSet,
-+ Iterable,
-+ Iterator,
-+ List,
-+ Optional,
-+ Sequence,
-+ Tuple,
-+ Union,
-+ cast,
-+)
-
--if TYPE_CHECKING: # pragma: no cover
-- from typing import (
-- Dict,
-- FrozenSet,
-- IO,
-- Iterable,
-- Iterator,
-- List,
-- Optional,
-- Sequence,
-- Tuple,
-- Union,
-- )
--
-- PythonVersion = Sequence[int]
-- MacVersion = Tuple[int, int]
-- GlibcVersion = Tuple[int, int]
--
-+from . import _manylinux, _musllinux
-
- logger = logging.getLogger(__name__)
-
--INTERPRETER_SHORT_NAMES = {
-+PythonVersion = Sequence[int]
-+MacVersion = Tuple[int, int]
-+
-+INTERPRETER_SHORT_NAMES: Dict[str, str] = {
- "python": "py", # Generic.
- "cpython": "cp",
- "pypy": "pp",
- "ironpython": "ip",
- "jython": "jy",
--} # type: Dict[str, str]
-+}
-
-
--_32_BIT_INTERPRETER = sys.maxsize <= 2 ** 32
-+_32_BIT_INTERPRETER = sys.maxsize <= 2**32
-
-
--class Tag(object):
-+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"]
-+ __slots__ = ["_interpreter", "_abi", "_platform", "_hash"]
-
-- def __init__(self, interpreter, abi, platform):
-- # type: (str, str, str) -> None
-+ 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):
-- # type: () -> str
-+ def interpreter(self) -> str:
- return self._interpreter
-
- @property
-- def abi(self):
-- # type: () -> str
-+ def abi(self) -> str:
- return self._abi
-
- @property
-- def platform(self):
-- # type: () -> str
-+ def platform(self) -> str:
- return self._platform
-
-- def __eq__(self, other):
-- # type: (object) -> bool
-+ def __eq__(self, other: object) -> bool:
- if not isinstance(other, Tag):
- return NotImplemented
-
- return (
-- (self.platform == other.platform)
-- and (self.abi == other.abi)
-- and (self.interpreter == other.interpreter)
-+ (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):
-- # type: () -> int
-- return hash((self._interpreter, self._abi, self._platform))
-+ def __hash__(self) -> int:
-+ return self._hash
-
-- def __str__(self):
-- # type: () -> str
-- return "{}-{}-{}".format(self._interpreter, self._abi,
self._platform)
-+ def __str__(self) -> str:
-+ return f"{self._interpreter}-{self._abi}-{self._platform}"
-
-- def __repr__(self):
-- # type: () -> str
-- return "<{self} @ {self_id}>".format(self=self, self_id=id(self))
-+ def __repr__(self) -> str:
-+ return f"<{self} @ {id(self)}>"
-
-
--def parse_tag(tag):
-- # type: (str) -> FrozenSet[Tag]
-+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 _warn_keyword_parameter(func_name, kwargs):
-- # type: (str, Dict[str, bool]) -> bool
-- """
-- Backwards-compatibility with Python 2.7 to allow treating 'warn' as
keyword-only.
-- """
-- if not kwargs:
-- return False
-- elif len(kwargs) > 1 or "warn" not in kwargs:
-- kwargs.pop("warn", None)
-- arg = next(iter(kwargs.keys()))
-- raise TypeError(
-- "{}() got an unexpected keyword argument
{!r}".format(func_name, arg)
-- )
-- return kwargs["warn"]
--
--
--def _get_config_var(name, warn=False):
-- # type: (str, bool) -> Union[int, str, None]
-- value = sysconfig.get_config_var(name)
-+def _get_config_var(name: str, warn: bool = False) -> Union[int, str, None]:
-+ value: Union[int, str, None] = 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):
-- # type: (str) -> str
-- return string.replace(".", "_").replace("-", "_")
-+def _normalize_string(string: str) -> str:
-+ return string.replace(".", "_").replace("-", "_").replace(" ", "_")
-
-
--def _abi3_applies(python_version):
-- # type: (PythonVersion) -> bool
-+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, warn=False):
-- # type: (PythonVersion, bool) -> List[str]
-+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.
-@@ -192,179 +153,206 @@ def _cpython_abis(py_version, warn=False
- 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("cp{version}".format(version=version))
-+ 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=None, # type: Optional[PythonVersion]
-- abis=None, # type: Optional[Iterable[str]]
-- platforms=None, # type: Optional[Iterable[str]]
-- **kwargs # type: bool
--):
-- # type: (...) -> Iterator[Tag]
-+ 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<python_version>-<abi>-<platform>
- - cp<python_version>-abi3-<platform>
- - cp<python_version>-none-<platform>
- - cp<less than python_version>-abi3-<platform> # 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.
- """
-- warn = _warn_keyword_parameter("cpython_tags", kwargs)
- if not python_version:
- python_version = sys.version_info[:2]
-
-- interpreter = "cp{}".format(_version_nodot(python_version[:2]))
-+ interpreter = f"cp{_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())
-+ platforms = list(platforms or platform_tags())
- for abi in abis:
- for platform_ in platforms:
- yield Tag(interpreter, abi, platform_)
- if _abi3_applies(python_version):
-- for tag in (Tag(interpreter, "abi3", platform_) for platform_ in
platforms):
-- yield tag
-- for tag in (Tag(interpreter, "none", platform_) for platform_ in
platforms):
-- yield tag
-+ 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():
-- # type: () -> Iterator[str]
-- abi = sysconfig.get_config_var("SOABI")
-- if abi:
-- yield _normalize_string(abi)
-+def _generic_abi() -> List[str]:
-+ """
-+ Return the ABI tag based on EXT_SUFFIX.
-+ """
-+ # The following are examples of `EXT_SUFFIX`.
-+ # We want to keep the parts which are related to the ABI and remove the
-+ # parts which are related to the platform:
-+ # - linux: '.cpython-310-x86_64-linux-gnu.so' => cp310
-+ # - mac: '.cpython-310-darwin.so' => cp310
-+ # - win: '.cp310-win_amd64.pyd' => cp310
-+ # - win: '.pyd' => cp37 (uses
_cpython_abis())
-+ # - pypy: '.pypy38-pp73-x86_64-linux-gnu.so' => pypy38_pp73
-+ # - graalpy: '.graalpy-38-native-x86_64-darwin.dylib'
-+ # => graalpy_38_native
-+
-+ ext_suffix = _get_config_var("EXT_SUFFIX", warn=True)
-+ if not isinstance(ext_suffix, str) or ext_suffix[0] != ".":
-+ raise SystemError("invalid sysconfig.get_config_var('EXT_SUFFIX')")
-+ parts = ext_suffix.split(".")
-+ if len(parts) < 3:
-+ # CPython3.7 and earlier uses ".pyd" on Windows.
-+ return _cpython_abis(sys.version_info[:2])
-+ soabi = parts[1]
-+ if soabi.startswith("cpython"):
-+ # non-windows
-+ abi = "cp" + soabi.split("-")[1]
-+ elif soabi.startswith("cp"):
-+ # windows
-+ abi = soabi.split("-")[0]
-+ elif soabi.startswith("pypy"):
-+ abi = "-".join(soabi.split("-")[:2])
-+ elif soabi.startswith("graalpy"):
-+ abi = "-".join(soabi.split("-")[:3])
-+ elif soabi:
-+ # pyston, ironpython, others?
-+ abi = soabi
-+ else:
-+ return []
-+ return [_normalize_string(abi)]
-
-
- def generic_tags(
-- interpreter=None, # type: Optional[str]
-- abis=None, # type: Optional[Iterable[str]]
-- platforms=None, # type: Optional[Iterable[str]]
-- **kwargs # type: bool
--):
-- # type: (...) -> Iterator[Tag]
-+ 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:
- - <interpreter>-<abi>-<platform>
-
- The "none" ABI will be added if it was not explicitly provided.
- """
-- warn = _warn_keyword_parameter("generic_tags", kwargs)
- 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)
-+ else:
-+ abis = list(abis)
-+ platforms = list(platforms or platform_tags())
- 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):
-- # type: (PythonVersion) -> Iterator[str]
-+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])
-+ yield f"py{_version_nodot(py_version[:2])}"
-+ yield f"py{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)))
-+ yield f"py{_version_nodot((py_version[0], minor))}"
-
-
- def compatible_tags(
-- python_version=None, # type: Optional[PythonVersion]
-- interpreter=None, # type: Optional[str]
-- platforms=None, # type: Optional[Iterable[str]]
--):
-- # type: (...) -> Iterator[Tag]
-+ 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-<platform>
- - <interpreter>-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())
-+ 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, is_32bit=_32_BIT_INTERPRETER):
-- # type: (str, bool) -> str
-+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, cpu_arch):
-- # type: (MacVersion, str) -> List[str]
-+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):
-@@ -377,375 +365,182 @@ def _mac_binary_formats(version, cpu_arc
- return []
- formats.append("fat64")
-
- elif cpu_arch == "ppc":
- if version > (10, 6):
- return []
- formats.extend(["fat32", "fat"])
-
-- formats.append("universal")
-+ 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=None, arch=None):
-- # type: (Optional[MacVersion], Optional[str]) -> Iterator[str]
-+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() # type: ignore
-+ version_str, _, cpu_arch = platform.mac_ver()
- if version is None:
- version = cast("MacVersion", tuple(map(int,
version_str.split(".")[:2])))
-+ if version == (10, 16):
-+ # When built against an older macOS SDK, Python will report
macOS 10.16
-+ # instead of the real version.
-+ version_str = subprocess.run(
-+ [
-+ sys.executable,
-+ "-sS",
-+ "-c",
-+ "import platform; print(platform.mac_ver()[0])",
-+ ],
-+ check=True,
-+ env={"SYSTEM_VERSION_COMPAT": "0"},
-+ stdout=subprocess.PIPE,
-+ universal_newlines=True,
-+ ).stdout
-+ 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
-- for minor_version in range(version[1], -1, -1):
-- compat_version = version[0], 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,
-- )
--
--
--# From PEP 513.
--def _is_manylinux_compatible(name, glibc_version):
-- # type: (str, GlibcVersion) -> bool
-- # Check for presence of _manylinux module.
-- try:
-- import _manylinux # noqa
--
-- return bool(getattr(_manylinux, name + "_compatible"))
-- except (ImportError, AttributeError):
-- # Fall through to heuristic check below.
-- pass
--
-- return _have_compatible_glibc(*glibc_version)
--
-
--def _glibc_version_string():
-- # type: () -> Optional[str]
-- # Returns glibc version string, or None if not using glibc.
-- return _glibc_version_string_confstr() or _glibc_version_string_ctypes()
--
-+ 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
-+ )
-
--def _glibc_version_string_confstr():
-- # type: () -> 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/fcf1d003bf4f0100c9d0921ff3d70e1127ca1b71/Lib/platform.py#L175-L183
-- try:
-- # os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc
2.17".
-- version_string = os.confstr( # type: ignore[attr-defined] # noqa:
F821
-- "CS_GNU_LIBC_VERSION"
-- )
-- assert version_string is not None
-- _, version = version_string.split() # type: Tuple[str, str]
-- except (AssertionError, AttributeError, OSError, ValueError):
-- # os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad
value)...
-- return None
-- return version
--
-+ 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
-+ )
-
--def _glibc_version_string_ctypes():
-- # type: () -> 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.
-- #
-- # Note: typeshed is wrong here so we are ignoring this line.
-- process_namespace = ctypes.CDLL(None) # type: ignore
-- 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 = gnu_get_libc_version() # type: str
-- # py2 / py3 compatibility:
-- if not isinstance(version_str, str):
-- version_str = version_str.decode("ascii")
--
-- return version_str
--
--
--# Separated out from have_compatible_glibc for easier unit testing.
--def _check_glibc_version(version_str, required_major, minimum_minor):
-- # type: (str, int, int) -> bool
-- # Parse string and check against requested 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<major>[0-9]+)\.(?P<minor>[0-9]+)", version_str)
-- if not m:
-- warnings.warn(
-- "Expected glibc version with 2 components major.minor,"
-- " got: %s" % version_str,
-- RuntimeWarning,
-- )
-- return False
-- return (
-- int(m.group("major")) == required_major
-- and int(m.group("minor")) >= minimum_minor
-- )
--
--
--def _have_compatible_glibc(required_major, minimum_minor):
-- # type: (int, int) -> bool
-- version_str = _glibc_version_string()
-- if version_str is None:
-- return False
-- return _check_glibc_version(version_str, required_major, minimum_minor)
-+ 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,
-+ )
-
-
--# 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(object):
-- #
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):
-- # type: (IO[bytes]) -> None
-- def unpack(fmt):
-- # type: (str) -> int
-- try:
-- (result,) = struct.unpack(
-- fmt, file.read(struct.calcsize(fmt))
-- ) # type: (int, )
-- except struct.error:
-- raise _ELFFileHeader._InvalidELFFileHeader()
-- return result
--
-- 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" if self.e_ident_data == self.ELFDATA2LSB else ">H"
-- format_i = "<I" if self.e_ident_data == self.ELFDATA2LSB else ">I"
-- format_q = "<Q" if self.e_ident_data == self.ELFDATA2LSB else ">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():
-- # type: () -> Optional[_ELFFileHeader]
-- try:
-- with open(sys.executable, "rb") as f:
-- elf_header = _ELFFileHeader(f)
-- except (IOError, OSError, TypeError,
_ELFFileHeader._InvalidELFFileHeader):
-- return None
-- return elf_header
--
--
--def _is_linux_armhf():
-- # type: () -> 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():
-- # type: () -> 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_manylinux_abi(arch):
-- # type: (str) -> bool
-- if arch == "armv7l":
-- return _is_linux_armhf()
-- if arch == "i686":
-- return _is_linux_i686()
-- return True
--
--
--def _linux_platforms(is_32bit=_32_BIT_INTERPRETER):
-- # type: (bool) -> Iterator[str]
-- linux = _normalize_string(distutils.util.get_platform())
-+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"
-- manylinux_support = []
- _, arch = linux.split("_", 1)
-- if _have_compatible_manylinux_abi(arch):
-- if arch in {"x86_64", "i686", "aarch64", "armv7l", "ppc64",
"ppc64le", "s390x"}:
-- manylinux_support.append(
-- ("manylinux2014", (2, 17))
-- ) # CentOS 7 w/ glibc 2.17 (PEP 599)
-- if arch in {"x86_64", "i686"}:
-- manylinux_support.append(
-- ("manylinux2010", (2, 12))
-- ) # CentOS 6 w/ glibc 2.12 (PEP 571)
-- manylinux_support.append(
-- ("manylinux1", (2, 5))
-- ) # CentOS 5 w/ glibc 2.5 (PEP 513)
-- manylinux_support_iter = iter(manylinux_support)
-- for name, glibc_version in manylinux_support_iter:
-- if _is_manylinux_compatible(name, glibc_version):
-- yield linux.replace("linux", name)
-- break
-- # Support for a later manylinux implies support for an earlier version.
-- for name, _ in manylinux_support_iter:
-- yield linux.replace("linux", name)
-+ yield from _manylinux.platform_tags(linux, arch)
-+ yield from _musllinux.platform_tags(arch)
- yield linux
-
-
--def _generic_platforms():
-- # type: () -> Iterator[str]
-- yield _normalize_string(distutils.util.get_platform())
-+def _generic_platforms() -> Iterator[str]:
-+ yield _normalize_string(sysconfig.get_platform())
-
-
--def _platform_tags():
-- # type: () -> Iterator[str]
-+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():
-- # type: () -> str
-+def interpreter_name() -> str:
- """
- Returns the name of the running interpreter.
-+
-+ Some implementations have a reserved, two-letter abbreviation which will
-+ be returned when appropriate.
- """
-- try:
-- name = sys.implementation.name # type: ignore
-- except AttributeError: # pragma: no cover
-- # Python 2.7 compatibility.
-- name = platform.python_implementation().lower()
-+ name = sys.implementation.name
- return INTERPRETER_SHORT_NAMES.get(name) or name
-
-
--def interpreter_version(**kwargs):
-- # type: (bool) -> str
-+def interpreter_version(*, warn: bool = False) -> str:
- """
- Returns the version of the running interpreter.
- """
-- warn = _warn_keyword_parameter("interpreter_version", kwargs)
- 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):
-- # type: (PythonVersion) -> str
-- if any(v >= 10 for v in version):
-- sep = "_"
-- else:
-- sep = ""
-- return sep.join(map(str, version))
-+def _version_nodot(version: PythonVersion) -> str:
-+ return "".join(map(str, version))
-
-
--def sys_tags(**kwargs):
-- # type: (bool) -> Iterator[Tag]
-+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.
- """
-- warn = _warn_keyword_parameter("sys_tags", kwargs)
-
- interp_name = interpreter_name()
- if interp_name == "cp":
-- for tag in cpython_tags(warn=warn):
-- yield tag
-+ yield from cpython_tags(warn=warn)
- else:
-- for tag in generic_tags():
-- yield tag
-+ yield from generic_tags()
-
-- for tag in compatible_tags():
-- yield tag
-+ if interp_name == "pp":
-+ interp = "pp3"
-+ elif interp_name == "cp":
-+ interp = "cp" + interpreter_version(warn=warn)
-+ else:
-+ interp = None
-+ yield from compatible_tags(interpreter=interp)
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/packaging/utils.py
b/third_party/python/setuptools/pkg_resources/_vendor/packaging/utils.py
---- a/third_party/python/setuptools/pkg_resources/_vendor/packaging/utils.py
-+++ b/third_party/python/setuptools/pkg_resources/_vendor/packaging/utils.py
-@@ -1,65 +1,141 @@
- # 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 __future__ import absolute_import, division, print_function
-
- import re
-+from typing import FrozenSet, NewType, Tuple, Union, cast
-
--from ._typing import TYPE_CHECKING, cast
-+from .tags import Tag, parse_tag
- from .version import InvalidVersion, Version
-
--if TYPE_CHECKING: # pragma: no cover
-- from typing import NewType, Union
-+BuildTag = Union[Tuple[()], Tuple[int, str]]
-+NormalizedName = NewType("NormalizedName", str)
-+
-
-- NormalizedName = NewType("NormalizedName", str)
--
--_canonicalize_regex = re.compile(r"[-_.]+")
-+class InvalidWheelFilename(ValueError):
-+ """
-+ An invalid wheel filename was found, users should refer to PEP 427.
-+ """
-
-
--def canonicalize_name(name):
-- # type: (str) -> NormalizedName
-+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)
-+ return cast(NormalizedName, value)
-
-
--def canonicalize_version(_version):
-- # type: (str) -> Union[Version, str]
-+def canonicalize_version(
-+ version: Union[Version, str], *, strip_trailing_zero: bool = True
-+) -> str:
- """
- This is very similar to Version.__str__, but has one subtle difference
- with the way it handles the release segment.
- """
--
-- try:
-- version = Version(_version)
-- except InvalidVersion:
-- # Legacy versions cannot be normalized
-- return _version
-+ if isinstance(version, str):
-+ try:
-+ parsed = Version(version)
-+ except InvalidVersion:
-+ # Legacy versions cannot be normalized
-+ return version
-+ else:
-+ parsed = version
-
- parts = []
-
- # Epoch
-- if version.epoch != 0:
-- parts.append("{0}!".format(version.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
version.release)))
-+ release_segment = ".".join(str(x) for x in parsed.release)
-+ if strip_trailing_zero:
-+ # NB: This strips trailing '.0's to normalize
-+ release_segment = re.sub(r"(\.0)+$", "", release_segment)
-+ parts.append(release_segment)
-
- # Pre-release
-- if version.pre is not None:
-- parts.append("".join(str(x) for x in version.pre))
-+ if parsed.pre is not None:
-+ parts.append("".join(str(x) for x in parsed.pre))
-
- # Post-release
-- if version.post is not None:
-- parts.append(".post{0}".format(version.post))
-+ if parsed.post is not None:
-+ parts.append(f".post{parsed.post}")
-
- # Development release
-- if version.dev is not None:
-- parts.append(".dev{0}".format(version.dev))
-+ if parsed.dev is not None:
-+ parts.append(f".dev{parsed.dev}")
-
- # Local version segment
-- if version.local is not None:
-- parts.append("+{0}".format(version.local))
-+ 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/third_party/python/setuptools/pkg_resources/_vendor/packaging/version.py
b/third_party/python/setuptools/pkg_resources/_vendor/packaging/version.py
----
a/third_party/python/setuptools/pkg_resources/_vendor/packaging/version.py
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/packaging/version.py
-@@ -1,243 +1,121 @@
- # 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 __future__ import absolute_import, division, print_function
-+"""
-+.. testsetup::
-+
-+ from packaging.version import parse, Version
-+"""
-
- import collections
- import itertools
- import re
--
--from ._structures import Infinity, NegativeInfinity
--from ._typing import TYPE_CHECKING
-+from typing import Any, Callable, Optional, SupportsInt, Tuple, Union
-
--if TYPE_CHECKING: # pragma: no cover
-- from typing import Callable, Iterator, List, Optional, SupportsInt,
Tuple, Union
-+from ._structures import Infinity, InfinityType, NegativeInfinity,
NegativeInfinityType
-
-- from ._structures import InfinityType, NegativeInfinityType
-+__all__ = ["VERSION_PATTERN", "parse", "Version", "InvalidVersion"]
-
-- 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],
-- ],
-- ...,
-+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
-- ]
--
--__all__ = ["parse", "Version", "LegacyVersion", "InvalidVersion",
"VERSION_PATTERN"]
--
-+ ...,
-+ ],
-+]
-+CmpKey = Tuple[
-+ int, Tuple[int, ...], PrePostDevType, PrePostDevType, PrePostDevType,
LocalType
-+]
-+VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]
-
- _Version = collections.namedtuple(
- "_Version", ["epoch", "release", "dev", "pre", "post", "local"]
- )
-
-
--def parse(version):
-- # type: (str) -> Union[LegacyVersion, Version]
-+def parse(version: str) -> "Version":
-+ """Parse the given version string.
-+
-+ >>> parse('1.0.dev1')
-+ <Version('1.0.dev1')>
-+
-+ :param version: The version string to parse.
-+ :raises InvalidVersion: When the version string is not a valid 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)
-+ return Version(version)
-
-
- class InvalidVersion(ValueError):
-- """
-- An invalid version was found, users should refer to PEP 440.
-+ """Raised when a version string is not a valid version.
-+
-+ >>> Version("invalid")
-+ Traceback (most recent call last):
-+ ...
-+ packaging.version.InvalidVersion: Invalid version: 'invalid'
- """
-
-
--class _BaseVersion(object):
-- _key = None # type: Union[CmpKey, LegacyCmpKey]
-+class _BaseVersion:
-+ _key: Tuple[Any, ...]
-
-- def __hash__(self):
-- # type: () -> int
-+ def __hash__(self) -> int:
- return hash(self._key)
-
-- def __lt__(self, other):
-- # type: (_BaseVersion) -> bool
-- return self._compare(other, lambda s, o: s < o)
--
-- def __le__(self, other):
-- # type: (_BaseVersion) -> bool
-- return self._compare(other, lambda s, o: s <= o)
--
-- def __eq__(self, other):
-- # type: (object) -> bool
-- return self._compare(other, lambda s, o: s == o)
--
-- def __ge__(self, other):
-- # type: (_BaseVersion) -> bool
-- return self._compare(other, lambda s, o: s >= o)
--
-- def __gt__(self, other):
-- # type: (_BaseVersion) -> bool
-- return self._compare(other, lambda s, o: s > o)
--
-- def __ne__(self, other):
-- # type: (object) -> bool
-- return self._compare(other, lambda s, o: s != o)
--
-- def _compare(self, other, method):
-- # type: (object, VersionComparisonMethod) -> Union[bool,
NotImplemented]
-+ # 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 method(self._key, other._key)
--
--
--class LegacyVersion(_BaseVersion):
-- def __init__(self, version):
-- # type: (str) -> None
-- self._version = str(version)
-- self._key = _legacy_cmpkey(self._version)
--
-- def __str__(self):
-- # type: () -> str
-- return self._version
-+ return self._key < other._key
-
-- def __repr__(self):
-- # type: () -> str
-- return "<LegacyVersion({0})>".format(repr(str(self)))
--
-- @property
-- def public(self):
-- # type: () -> str
-- return self._version
--
-- @property
-- def base_version(self):
-- # type: () -> str
-- return self._version
-+ def __le__(self, other: "_BaseVersion") -> bool:
-+ if not isinstance(other, _BaseVersion):
-+ return NotImplemented
-
-- @property
-- def epoch(self):
-- # type: () -> int
-- return -1
--
-- @property
-- def release(self):
-- # type: () -> None
-- return None
--
-- @property
-- def pre(self):
-- # type: () -> None
-- return None
-+ return self._key <= other._key
-
-- @property
-- def post(self):
-- # type: () -> None
-- return None
--
-- @property
-- def dev(self):
-- # type: () -> None
-- return None
-+ def __eq__(self, other: object) -> bool:
-+ if not isinstance(other, _BaseVersion):
-+ return NotImplemented
-
-- @property
-- def local(self):
-- # type: () -> None
-- return None
--
-- @property
-- def is_prerelease(self):
-- # type: () -> bool
-- return False
-+ return self._key == other._key
-
-- @property
-- def is_postrelease(self):
-- # type: () -> bool
-- return False
--
-- @property
-- def is_devrelease(self):
-- # type: () -> bool
-- return False
--
--
--_legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)",
re.VERBOSE)
-+ def __ge__(self, other: "_BaseVersion") -> bool:
-+ if not isinstance(other, _BaseVersion):
-+ return NotImplemented
-
--_legacy_version_replacement_map = {
-- "pre": "c",
-- "preview": "c",
-- "-": "final-",
-- "rc": "c",
-- "dev": "@",
--}
--
--
--def _parse_version_parts(s):
-- # type: (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
-+ return self._key >= other._key
-
-- if part[:1] in "0123456789":
-- # pad for numeric comparison
-- yield part.zfill(8)
-- else:
-- yield "*" + part
-+ def __gt__(self, other: "_BaseVersion") -> bool:
-+ if not isinstance(other, _BaseVersion):
-+ return NotImplemented
-
-- # ensure that alpha/beta/candidate are before final
-- yield "*final"
--
--
--def _legacy_cmpkey(version):
-- # type: (str) -> LegacyCmpKey
-+ return self._key > other._key
-
-- # 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
-+ def __ne__(self, other: object) -> bool:
-+ if not isinstance(other, _BaseVersion):
-+ return NotImplemented
-
-- # This scheme is taken from pkg_resources.parse_version setuptools
prior to
-- # it's adoption of the packaging library.
-- parts = [] # type: 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)
-+ return self._key != other._key
-
-
- # Deliberately not anchored to the start and end of the string, to make it
- # easier for 3rd party code to reuse
--VERSION_PATTERN = r"""
-+_VERSION_PATTERN = r"""
- v?
- (?:
- (?:(?P<epoch>[0-9]+)!)? # epoch
- (?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
- (?P<pre> # pre-release
- [-_\.]?
- (?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
- [-_\.]?
-@@ -258,28 +136,71 @@ VERSION_PATTERN = r"""
- (?P<dev_l>dev)
- [-_\.]?
- (?P<dev_n>[0-9]+)?
- )?
- )
- (?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
- """
-
-+VERSION_PATTERN = _VERSION_PATTERN
-+"""
-+A string containing the regular expression used to match a valid version.
-+
-+The pattern is not anchored at either end, and is intended for embedding in
larger
-+expressions (for example, matching a version number as part of a file
name). The
-+regular expression should be compiled with the ``re.VERBOSE`` and
``re.IGNORECASE``
-+flags set.
-+
-+:meta hide-value:
-+"""
-+
-
- class Version(_BaseVersion):
-+ """This class abstracts handling of a project's versions.
-+
-+ A :class:`Version` instance is comparison aware and can be compared and
-+ sorted using the standard Python interfaces.
-+
-+ >>> v1 = Version("1.0a5")
-+ >>> v2 = Version("1.0")
-+ >>> v1
-+ <Version('1.0a5')>
-+ >>> v2
-+ <Version('1.0')>
-+ >>> v1 < v2
-+ True
-+ >>> v1 == v2
-+ False
-+ >>> v1 > v2
-+ False
-+ >>> v1 >= v2
-+ False
-+ >>> v1 <= v2
-+ True
-+ """
-
- _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE |
re.IGNORECASE)
-+ _key: CmpKey
-
-- def __init__(self, version):
-- # type: (str) -> None
-+ def __init__(self, version: str) -> None:
-+ """Initialize a Version object.
-+
-+ :param version:
-+ The string representation of a version which will be parsed and
normalized
-+ before use.
-+ :raises InvalidVersion:
-+ If the ``version`` does not conform to PEP 440 in any way then
this
-+ exception will be raised.
-+ """
-
- # Validate the version and parse it into pieces
- match = self._regex.search(version)
- if not match:
-- raise InvalidVersion("Invalid version: '{0}'".format(version))
-+ 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")
-@@ -293,140 +214,250 @@ class Version(_BaseVersion):
- self._version.epoch,
- self._version.release,
- self._version.pre,
- self._version.post,
- self._version.dev,
- self._version.local,
- )
-
-- def __repr__(self):
-- # type: () -> str
-- return "<Version({0})>".format(repr(str(self)))
-+ def __repr__(self) -> str:
-+ """A representation of the Version that shows all internal state.
-
-- def __str__(self):
-- # type: () -> str
-+ >>> Version('1.0.0')
-+ <Version('1.0.0')>
-+ """
-+ return f"<Version('{self}')>"
-+
-+ def __str__(self) -> str:
-+ """A string representation of the version that can be
rounded-tripped.
-+
-+ >>> str(Version("1.0a5"))
-+ '1.0a5'
-+ """
- parts = []
-
- # Epoch
- if self.epoch != 0:
-- parts.append("{0}!".format(self.epoch))
-+ 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(".post{0}".format(self.post))
-+ parts.append(f".post{self.post}")
-
- # Development release
- if self.dev is not None:
-- parts.append(".dev{0}".format(self.dev))
-+ parts.append(f".dev{self.dev}")
-
- # Local version segment
- if self.local is not None:
-- parts.append("+{0}".format(self.local))
-+ parts.append(f"+{self.local}")
-
- return "".join(parts)
-
- @property
-- def epoch(self):
-- # type: () -> int
-- _epoch = self._version.epoch # type: int
-+ def epoch(self) -> int:
-+ """The epoch of the version.
-+
-+ >>> Version("2.0.0").epoch
-+ 0
-+ >>> Version("1!2.0.0").epoch
-+ 1
-+ """
-+ _epoch: int = self._version.epoch
- return _epoch
-
- @property
-- def release(self):
-- # type: () -> Tuple[int, ...]
-- _release = self._version.release # type: Tuple[int, ...]
-+ def release(self) -> Tuple[int, ...]:
-+ """The components of the "release" segment of the version.
-+
-+ >>> Version("1.2.3").release
-+ (1, 2, 3)
-+ >>> Version("2.0.0").release
-+ (2, 0, 0)
-+ >>> Version("1!2.0.0.post0").release
-+ (2, 0, 0)
-+
-+ Includes trailing zeroes but not the epoch or any pre-release /
development /
-+ post-release suffixes.
-+ """
-+ _release: Tuple[int, ...] = self._version.release
- return _release
-
- @property
-- def pre(self):
-- # type: () -> Optional[Tuple[str, int]]
-- _pre = self._version.pre # type: Optional[Tuple[str, int]]
-+ def pre(self) -> Optional[Tuple[str, int]]:
-+ """The pre-release segment of the version.
-+
-+ >>> print(Version("1.2.3").pre)
-+ None
-+ >>> Version("1.2.3a1").pre
-+ ('a', 1)
-+ >>> Version("1.2.3b1").pre
-+ ('b', 1)
-+ >>> Version("1.2.3rc1").pre
-+ ('rc', 1)
-+ """
-+ _pre: Optional[Tuple[str, int]] = self._version.pre
- return _pre
-
- @property
-- def post(self):
-- # type: () -> Optional[Tuple[str, int]]
-+ def post(self) -> Optional[int]:
-+ """The post-release number of the version.
-+
-+ >>> print(Version("1.2.3").post)
-+ None
-+ >>> Version("1.2.3.post1").post
-+ 1
-+ """
- return self._version.post[1] if self._version.post else None
-
- @property
-- def dev(self):
-- # type: () -> Optional[Tuple[str, int]]
-+ def dev(self) -> Optional[int]:
-+ """The development number of the version.
-+
-+ >>> print(Version("1.2.3").dev)
-+ None
-+ >>> Version("1.2.3.dev1").dev
-+ 1
-+ """
- return self._version.dev[1] if self._version.dev else None
-
- @property
-- def local(self):
-- # type: () -> Optional[str]
-+ def local(self) -> Optional[str]:
-+ """The local version segment of the version.
-+
-+ >>> print(Version("1.2.3").local)
-+ None
-+ >>> Version("1.2.3+abc").local
-+ 'abc'
-+ """
- if self._version.local:
- return ".".join(str(x) for x in self._version.local)
- else:
- return None
-
- @property
-- def public(self):
-- # type: () -> str
-+ def public(self) -> str:
-+ """The public portion of the version.
-+
-+ >>> Version("1.2.3").public
-+ '1.2.3'
-+ >>> Version("1.2.3+abc").public
-+ '1.2.3'
-+ >>> Version("1.2.3+abc.dev1").public
-+ '1.2.3'
-+ """
- return str(self).split("+", 1)[0]
-
- @property
-- def base_version(self):
-- # type: () -> str
-+ def base_version(self) -> str:
-+ """The "base version" of the version.
-+
-+ >>> Version("1.2.3").base_version
-+ '1.2.3'
-+ >>> Version("1.2.3+abc").base_version
-+ '1.2.3'
-+ >>> Version("1!1.2.3+abc.dev1").base_version
-+ '1!1.2.3'
-+
-+ The "base version" is the public version of the project without any
pre or post
-+ release markers.
-+ """
- parts = []
-
- # Epoch
- if self.epoch != 0:
-- parts.append("{0}!".format(self.epoch))
-+ 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):
-- # type: () -> bool
-+ def is_prerelease(self) -> bool:
-+ """Whether this version is a pre-release.
-+
-+ >>> Version("1.2.3").is_prerelease
-+ False
-+ >>> Version("1.2.3a1").is_prerelease
-+ True
-+ >>> Version("1.2.3b1").is_prerelease
-+ True
-+ >>> Version("1.2.3rc1").is_prerelease
-+ True
-+ >>> Version("1.2.3dev1").is_prerelease
-+ True
-+ """
- return self.dev is not None or self.pre is not None
-
- @property
-- def is_postrelease(self):
-- # type: () -> bool
-+ def is_postrelease(self) -> bool:
-+ """Whether this version is a post-release.
-+
-+ >>> Version("1.2.3").is_postrelease
-+ False
-+ >>> Version("1.2.3.post1").is_postrelease
-+ True
-+ """
- return self.post is not None
-
- @property
-- def is_devrelease(self):
-- # type: () -> bool
-+ def is_devrelease(self) -> bool:
-+ """Whether this version is a development release.
-+
-+ >>> Version("1.2.3").is_devrelease
-+ False
-+ >>> Version("1.2.3.dev1").is_devrelease
-+ True
-+ """
- return self.dev is not None
-
- @property
-- def major(self):
-- # type: () -> int
-+ def major(self) -> int:
-+ """The first item of :attr:`release` or ``0`` if unavailable.
-+
-+ >>> Version("1.2.3").major
-+ 1
-+ """
- return self.release[0] if len(self.release) >= 1 else 0
-
- @property
-- def minor(self):
-- # type: () -> int
-+ def minor(self) -> int:
-+ """The second item of :attr:`release` or ``0`` if unavailable.
-+
-+ >>> Version("1.2.3").minor
-+ 2
-+ >>> Version("1").minor
-+ 0
-+ """
- return self.release[1] if len(self.release) >= 2 else 0
-
- @property
-- def micro(self):
-- # type: () -> int
-+ def micro(self) -> int:
-+ """The third item of :attr:`release` or ``0`` if unavailable.
-+
-+ >>> Version("1.2.3").micro
-+ 3
-+ >>> Version("1").micro
-+ 0
-+ """
- return self.release[2] if len(self.release) >= 3 else 0
-
-
- def _parse_letter_version(
-- letter, # type: str
-- number, # type: Union[str, bytes, SupportsInt]
--):
-- # type: (...) -> Optional[Tuple[str, int]]
-+ 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
-@@ -453,78 +484,76 @@ def _parse_letter_version(
- return letter, int(number)
-
- return None
-
-
- _local_version_separators = re.compile(r"[\._-]")
-
-
--def _parse_local_version(local):
-- # type: (str) -> Optional[LocalType]
-+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, # type: int
-- release, # type: Tuple[int, ...]
-- pre, # type: Optional[Tuple[str, int]]
-- post, # type: Optional[Tuple[str, int]]
-- dev, # type: Optional[Tuple[str, int]]
-- local, # type: Optional[Tuple[SubLocalType]]
--):
-- # type: (...) -> 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 = NegativeInfinity # type: PrePostDevType
-+ _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 = NegativeInfinity # type: PrePostDevType
-+ _post: PrePostDevType = NegativeInfinity
-
- else:
- _post = post
-
- # Versions without a development segment should sort after those with
one.
- if dev is None:
-- _dev = Infinity # type: PrePostDevType
-+ _dev: PrePostDevType = Infinity
-
- else:
- _dev = dev
-
- if local is None:
- # Versions without a local segment should sort before those with
one.
-- _local = NegativeInfinity # type: LocalType
-+ _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
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/platformdirs/__init__.py

b/third_party/python/setuptools/pkg_resources/_vendor/platformdirs/__init__.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/platformdirs/__init__.py
-@@ -0,0 +1,342 @@
-+"""
-+Utilities for determining application-specific dirs. See
<https://github.com/platformdirs/platformdirs> for details and
-+usage.
-+"""
-+from __future__ import annotations
-+
-+import os
-+import sys
-+from pathlib import Path
-+
-+if sys.version_info >= (3, 8): # pragma: no cover (py38+)
-+ from typing import Literal
-+else: # pragma: no cover (py38+)
-+ from ..typing_extensions import Literal
-+
-+from .api import PlatformDirsABC
-+from .version import __version__
-+from .version import __version_tuple__ as __version_info__
-+
-+
-+def _set_platform_dir_class() -> type[PlatformDirsABC]:
-+ if sys.platform == "win32":
-+ from .windows import Windows as Result
-+ elif sys.platform == "darwin":
-+ from .macos import MacOS as Result
-+ else:
-+ from .unix import Unix as Result
-+
-+ if os.getenv("ANDROID_DATA") == "/data" and os.getenv("ANDROID_ROOT")
== "/system":
-+
-+ if os.getenv("SHELL") or os.getenv("PREFIX"):
-+ return Result
-+
-+ from .android import _android_folder
-+
-+ if _android_folder() is not None:
-+ from .android import Android
-+
-+ return Android # return to avoid redefinition of result
-+
-+ return Result
-+
-+
-+PlatformDirs = _set_platform_dir_class() #: Currently active platform
-+AppDirs = PlatformDirs #: Backwards compatibility with appdirs
-+
-+
-+def user_data_dir(
-+ appname: str | None = None,
-+ appauthor: str | None | Literal[False] = None,
-+ version: str | None = None,
-+ roaming: bool = False,
-+) -> str:
-+ """
-+ :param appname: See `appname
<platformdirs.api.PlatformDirsABC.appname>`.
-+ :param appauthor: See `appauthor
<platformdirs.api.PlatformDirsABC.appauthor>`.
-+ :param version: See `version
<platformdirs.api.PlatformDirsABC.version>`.
-+ :param roaming: See `roaming
<platformdirs.api.PlatformDirsABC.version>`.
-+ :returns: data directory tied to the user
-+ """
-+ return PlatformDirs(appname=appname, appauthor=appauthor,
version=version, roaming=roaming).user_data_dir
-+
-+
-+def site_data_dir(
-+ appname: str | None = None,
-+ appauthor: str | None | Literal[False] = None,
-+ version: str | None = None,
-+ multipath: bool = False,
-+) -> str:
-+ """
-+ :param appname: See `appname
<platformdirs.api.PlatformDirsABC.appname>`.
-+ :param appauthor: See `appauthor
<platformdirs.api.PlatformDirsABC.appauthor>`.
-+ :param version: See `version
<platformdirs.api.PlatformDirsABC.version>`.
-+ :param multipath: See `roaming
<platformdirs.api.PlatformDirsABC.multipath>`.
-+ :returns: data directory shared by users
-+ """
-+ return PlatformDirs(appname=appname, appauthor=appauthor,
version=version, multipath=multipath).site_data_dir
-+
-+
-+def user_config_dir(
-+ appname: str | None = None,
-+ appauthor: str | None | Literal[False] = None,
-+ version: str | None = None,
-+ roaming: bool = False,
-+) -> str:
-+ """
-+ :param appname: See `appname
<platformdirs.api.PlatformDirsABC.appname>`.
-+ :param appauthor: See `appauthor
<platformdirs.api.PlatformDirsABC.appauthor>`.
-+ :param version: See `version
<platformdirs.api.PlatformDirsABC.version>`.
-+ :param roaming: See `roaming
<platformdirs.api.PlatformDirsABC.version>`.
-+ :returns: config directory tied to the user
-+ """
-+ return PlatformDirs(appname=appname, appauthor=appauthor,
version=version, roaming=roaming).user_config_dir
-+
-+
-+def site_config_dir(
-+ appname: str | None = None,
-+ appauthor: str | None | Literal[False] = None,
-+ version: str | None = None,
-+ multipath: bool = False,
-+) -> str:
-+ """
-+ :param appname: See `appname
<platformdirs.api.PlatformDirsABC.appname>`.
-+ :param appauthor: See `appauthor
<platformdirs.api.PlatformDirsABC.appauthor>`.
-+ :param version: See `version
<platformdirs.api.PlatformDirsABC.version>`.
-+ :param multipath: See `roaming
<platformdirs.api.PlatformDirsABC.multipath>`.
-+ :returns: config directory shared by the users
-+ """
-+ return PlatformDirs(appname=appname, appauthor=appauthor,
version=version, multipath=multipath).site_config_dir
-+
-+
-+def user_cache_dir(
-+ appname: str | None = None,
-+ appauthor: str | None | Literal[False] = None,
-+ version: str | None = None,
-+ opinion: bool = True,
-+) -> str:
-+ """
-+ :param appname: See `appname
<platformdirs.api.PlatformDirsABC.appname>`.
-+ :param appauthor: See `appauthor
<platformdirs.api.PlatformDirsABC.appauthor>`.
-+ :param version: See `version
<platformdirs.api.PlatformDirsABC.version>`.
-+ :param opinion: See `roaming
<platformdirs.api.PlatformDirsABC.opinion>`.
-+ :returns: cache directory tied to the user
-+ """
-+ return PlatformDirs(appname=appname, appauthor=appauthor,
version=version, opinion=opinion).user_cache_dir
-+
-+
-+def user_state_dir(
-+ appname: str | None = None,
-+ appauthor: str | None | Literal[False] = None,
-+ version: str | None = None,
-+ roaming: bool = False,
-+) -> str:
-+ """
-+ :param appname: See `appname
<platformdirs.api.PlatformDirsABC.appname>`.
-+ :param appauthor: See `appauthor
<platformdirs.api.PlatformDirsABC.appauthor>`.
-+ :param version: See `version
<platformdirs.api.PlatformDirsABC.version>`.
-+ :param roaming: See `roaming
<platformdirs.api.PlatformDirsABC.version>`.
-+ :returns: state directory tied to the user
-+ """
-+ return PlatformDirs(appname=appname, appauthor=appauthor,
version=version, roaming=roaming).user_state_dir
-+
-+
-+def user_log_dir(
-+ appname: str | None = None,
-+ appauthor: str | None | Literal[False] = None,
-+ version: str | None = None,
-+ opinion: bool = True,
-+) -> str:
-+ """
-+ :param appname: See `appname
<platformdirs.api.PlatformDirsABC.appname>`.
-+ :param appauthor: See `appauthor
<platformdirs.api.PlatformDirsABC.appauthor>`.
-+ :param version: See `version
<platformdirs.api.PlatformDirsABC.version>`.
-+ :param opinion: See `roaming
<platformdirs.api.PlatformDirsABC.opinion>`.
-+ :returns: log directory tied to the user
-+ """
-+ return PlatformDirs(appname=appname, appauthor=appauthor,
version=version, opinion=opinion).user_log_dir
-+
-+
-+def user_documents_dir() -> str:
-+ """
-+ :returns: documents directory tied to the user
-+ """
-+ return PlatformDirs().user_documents_dir
-+
-+
-+def user_runtime_dir(
-+ appname: str | None = None,
-+ appauthor: str | None | Literal[False] = None,
-+ version: str | None = None,
-+ opinion: bool = True,
-+) -> str:
-+ """
-+ :param appname: See `appname
<platformdirs.api.PlatformDirsABC.appname>`.
-+ :param appauthor: See `appauthor
<platformdirs.api.PlatformDirsABC.appauthor>`.
-+ :param version: See `version
<platformdirs.api.PlatformDirsABC.version>`.
-+ :param opinion: See `opinion
<platformdirs.api.PlatformDirsABC.opinion>`.
-+ :returns: runtime directory tied to the user
-+ """
-+ return PlatformDirs(appname=appname, appauthor=appauthor,
version=version, opinion=opinion).user_runtime_dir
-+
-+
-+def user_data_path(
-+ appname: str | None = None,
-+ appauthor: str | None | Literal[False] = None,
-+ version: str | None = None,
-+ roaming: bool = False,
-+) -> Path:
-+ """
-+ :param appname: See `appname
<platformdirs.api.PlatformDirsABC.appname>`.
-+ :param appauthor: See `appauthor
<platformdirs.api.PlatformDirsABC.appauthor>`.
-+ :param version: See `version
<platformdirs.api.PlatformDirsABC.version>`.
-+ :param roaming: See `roaming
<platformdirs.api.PlatformDirsABC.version>`.
-+ :returns: data path tied to the user
-+ """
-+ return PlatformDirs(appname=appname, appauthor=appauthor,
version=version, roaming=roaming).user_data_path
-+
-+
-+def site_data_path(
-+ appname: str | None = None,
-+ appauthor: str | None | Literal[False] = None,
-+ version: str | None = None,
-+ multipath: bool = False,
-+) -> Path:
-+ """
-+ :param appname: See `appname
<platformdirs.api.PlatformDirsABC.appname>`.
-+ :param appauthor: See `appauthor
<platformdirs.api.PlatformDirsABC.appauthor>`.
-+ :param version: See `version
<platformdirs.api.PlatformDirsABC.version>`.
-+ :param multipath: See `multipath
<platformdirs.api.PlatformDirsABC.multipath>`.
-+ :returns: data path shared by users
-+ """
-+ return PlatformDirs(appname=appname, appauthor=appauthor,
version=version, multipath=multipath).site_data_path
-+
-+
-+def user_config_path(
-+ appname: str | None = None,
-+ appauthor: str | None | Literal[False] = None,
-+ version: str | None = None,
-+ roaming: bool = False,
-+) -> Path:
-+ """
-+ :param appname: See `appname
<platformdirs.api.PlatformDirsABC.appname>`.
-+ :param appauthor: See `appauthor
<platformdirs.api.PlatformDirsABC.appauthor>`.
-+ :param version: See `version
<platformdirs.api.PlatformDirsABC.version>`.
-+ :param roaming: See `roaming
<platformdirs.api.PlatformDirsABC.version>`.
-+ :returns: config path tied to the user
-+ """
-+ return PlatformDirs(appname=appname, appauthor=appauthor,
version=version, roaming=roaming).user_config_path
-+
-+
-+def site_config_path(
-+ appname: str | None = None,
-+ appauthor: str | None | Literal[False] = None,
-+ version: str | None = None,
-+ multipath: bool = False,
-+) -> Path:
-+ """
-+ :param appname: See `appname
<platformdirs.api.PlatformDirsABC.appname>`.
-+ :param appauthor: See `appauthor
<platformdirs.api.PlatformDirsABC.appauthor>`.
-+ :param version: See `version
<platformdirs.api.PlatformDirsABC.version>`.
-+ :param multipath: See `roaming
<platformdirs.api.PlatformDirsABC.multipath>`.
-+ :returns: config path shared by the users
-+ """
-+ return PlatformDirs(appname=appname, appauthor=appauthor,
version=version, multipath=multipath).site_config_path
-+
-+
-+def user_cache_path(
-+ appname: str | None = None,
-+ appauthor: str | None | Literal[False] = None,
-+ version: str | None = None,
-+ opinion: bool = True,
-+) -> Path:
-+ """
-+ :param appname: See `appname
<platformdirs.api.PlatformDirsABC.appname>`.
-+ :param appauthor: See `appauthor
<platformdirs.api.PlatformDirsABC.appauthor>`.
-+ :param version: See `version
<platformdirs.api.PlatformDirsABC.version>`.
-+ :param opinion: See `roaming
<platformdirs.api.PlatformDirsABC.opinion>`.
-+ :returns: cache path tied to the user
-+ """
-+ return PlatformDirs(appname=appname, appauthor=appauthor,
version=version, opinion=opinion).user_cache_path
-+
-+
-+def user_state_path(
-+ appname: str | None = None,
-+ appauthor: str | None | Literal[False] = None,
-+ version: str | None = None,
-+ roaming: bool = False,
-+) -> Path:
-+ """
-+ :param appname: See `appname
<platformdirs.api.PlatformDirsABC.appname>`.
-+ :param appauthor: See `appauthor
<platformdirs.api.PlatformDirsABC.appauthor>`.
-+ :param version: See `version
<platformdirs.api.PlatformDirsABC.version>`.
-+ :param roaming: See `roaming
<platformdirs.api.PlatformDirsABC.version>`.
-+ :returns: state path tied to the user
-+ """
-+ return PlatformDirs(appname=appname, appauthor=appauthor,
version=version, roaming=roaming).user_state_path
-+
-+
-+def user_log_path(
-+ appname: str | None = None,
-+ appauthor: str | None | Literal[False] = None,
-+ version: str | None = None,
-+ opinion: bool = True,
-+) -> Path:
-+ """
-+ :param appname: See `appname
<platformdirs.api.PlatformDirsABC.appname>`.
-+ :param appauthor: See `appauthor
<platformdirs.api.PlatformDirsABC.appauthor>`.
-+ :param version: See `version
<platformdirs.api.PlatformDirsABC.version>`.
-+ :param opinion: See `roaming
<platformdirs.api.PlatformDirsABC.opinion>`.
-+ :returns: log path tied to the user
-+ """
-+ return PlatformDirs(appname=appname, appauthor=appauthor,
version=version, opinion=opinion).user_log_path
-+
-+
-+def user_documents_path() -> Path:
-+ """
-+ :returns: documents path tied to the user
-+ """
-+ return PlatformDirs().user_documents_path
-+
-+
-+def user_runtime_path(
-+ appname: str | None = None,
-+ appauthor: str | None | Literal[False] = None,
-+ version: str | None = None,
-+ opinion: bool = True,
-+) -> Path:
-+ """
-+ :param appname: See `appname
<platformdirs.api.PlatformDirsABC.appname>`.
-+ :param appauthor: See `appauthor
<platformdirs.api.PlatformDirsABC.appauthor>`.
-+ :param version: See `version
<platformdirs.api.PlatformDirsABC.version>`.
-+ :param opinion: See `opinion
<platformdirs.api.PlatformDirsABC.opinion>`.
-+ :returns: runtime path tied to the user
-+ """
-+ return PlatformDirs(appname=appname, appauthor=appauthor,
version=version, opinion=opinion).user_runtime_path
-+
-+
-+__all__ = [
-+ "__version__",
-+ "__version_info__",
-+ "PlatformDirs",
-+ "AppDirs",
-+ "PlatformDirsABC",
-+ "user_data_dir",
-+ "user_config_dir",
-+ "user_cache_dir",
-+ "user_state_dir",
-+ "user_log_dir",
-+ "user_documents_dir",
-+ "user_runtime_dir",
-+ "site_data_dir",
-+ "site_config_dir",
-+ "user_data_path",
-+ "user_config_path",
-+ "user_cache_path",
-+ "user_state_path",
-+ "user_log_path",
-+ "user_documents_path",
-+ "user_runtime_path",
-+ "site_data_path",
-+ "site_config_path",
-+]
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/platformdirs/__main__.py

b/third_party/python/setuptools/pkg_resources/_vendor/platformdirs/__main__.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/platformdirs/__main__.py
-@@ -0,0 +1,46 @@
-+from __future__ import annotations
-+
-+from platformdirs import PlatformDirs, __version__
-+
-+PROPS = (
-+ "user_data_dir",
-+ "user_config_dir",
-+ "user_cache_dir",
-+ "user_state_dir",
-+ "user_log_dir",
-+ "user_documents_dir",
-+ "user_runtime_dir",
-+ "site_data_dir",
-+ "site_config_dir",
-+)
-+
-+
-+def main() -> None:
-+ app_name = "MyApp"
-+ app_author = "MyCompany"
-+
-+ print(f"-- platformdirs {__version__} --")
-+
-+ print("-- app dirs (with optional 'version')")
-+ dirs = PlatformDirs(app_name, app_author, version="1.0")
-+ for prop in PROPS:
-+ print(f"{prop}: {getattr(dirs, prop)}")
-+
-+ print("\n-- app dirs (without optional 'version')")
-+ dirs = PlatformDirs(app_name, app_author)
-+ for prop in PROPS:
-+ print(f"{prop}: {getattr(dirs, prop)}")
-+
-+ print("\n-- app dirs (without optional 'appauthor')")
-+ dirs = PlatformDirs(app_name)
-+ for prop in PROPS:
-+ print(f"{prop}: {getattr(dirs, prop)}")
-+
-+ print("\n-- app dirs (with disabled 'appauthor')")
-+ dirs = PlatformDirs(app_name, appauthor=False)
-+ for prop in PROPS:
-+ print(f"{prop}: {getattr(dirs, prop)}")
-+
-+
-+if __name__ == "__main__":
-+ main()
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/platformdirs/android.py
b/third_party/python/setuptools/pkg_resources/_vendor/platformdirs/android.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/platformdirs/android.py
-@@ -0,0 +1,120 @@
-+from __future__ import annotations
-+
-+import os
-+import re
-+import sys
-+from functools import lru_cache
-+from typing import cast
-+
-+from .api import PlatformDirsABC
-+
-+
-+class Android(PlatformDirsABC):
-+ """
-+ Follows the guidance `from here
<https://android.stackexchange.com/a/216132>`_. Makes use of the
-+ `appname <platformdirs.api.PlatformDirsABC.appname>` and
-+ `version <platformdirs.api.PlatformDirsABC.version>`.
-+ """
-+
-+ @property
-+ def user_data_dir(self) -> str:
-+ """:return: data directory tied to the user, e.g.
``/data/user/<userid>/<packagename>/files/<AppName>``"""
-+ return self._append_app_name_and_version(cast(str,
_android_folder()), "files")
-+
-+ @property
-+ def site_data_dir(self) -> str:
-+ """:return: data directory shared by users, same as
`user_data_dir`"""
-+ return self.user_data_dir
-+
-+ @property
-+ def user_config_dir(self) -> str:
-+ """
-+ :return: config directory tied to the user, e.g.
``/data/user/<userid>/<packagename>/shared_prefs/<AppName>``
-+ """
-+ return self._append_app_name_and_version(cast(str,
_android_folder()), "shared_prefs")
-+
-+ @property
-+ def site_config_dir(self) -> str:
-+ """:return: config directory shared by the users, same as
`user_config_dir`"""
-+ return self.user_config_dir
-+
-+ @property
-+ def user_cache_dir(self) -> str:
-+ """:return: cache directory tied to the user, e.g. e.g.
``/data/user/<userid>/<packagename>/cache/<AppName>``"""
-+ return self._append_app_name_and_version(cast(str,
_android_folder()), "cache")
-+
-+ @property
-+ def user_state_dir(self) -> str:
-+ """:return: state directory tied to the user, same as
`user_data_dir`"""
-+ return self.user_data_dir
-+
-+ @property
-+ def user_log_dir(self) -> str:
-+ """
-+ :return: log directory tied to the user, same as `user_cache_dir`
if not opinionated else ``log`` in it,
-+ e.g. ``/data/user/<userid>/<packagename>/cache/<AppName>/log``
-+ """
-+ path = self.user_cache_dir
-+ if self.opinion:
-+ path = os.path.join(path, "log")
-+ return path
-+
-+ @property
-+ def user_documents_dir(self) -> str:
-+ """
-+ :return: documents directory tied to the user e.g.
``/storage/emulated/0/Documents``
-+ """
-+ return _android_documents_folder()
-+
-+ @property
-+ def user_runtime_dir(self) -> str:
-+ """
-+ :return: runtime directory tied to the user, same as
`user_cache_dir` if not opinionated else ``tmp`` in it,
-+ e.g. ``/data/user/<userid>/<packagename>/cache/<AppName>/tmp``
-+ """
-+ path = self.user_cache_dir
-+ if self.opinion:
-+ path = os.path.join(path, "tmp")
-+ return path
-+
-+
-+@lru_cache(maxsize=1)
-+def _android_folder() -> str | None:
-+ """:return: base folder for the Android OS or None if cannot be found"""
-+ try:
-+ # First try to get path to android app via pyjnius
-+ from jnius import autoclass
-+
-+ Context = autoclass("android.content.Context") # noqa: N806
-+ result: str | None =
Context.getFilesDir().getParentFile().getAbsolutePath()
-+ except Exception:
-+ # if fails find an android folder looking path on the sys.path
-+ pattern = re.compile(r"/data/(data|user/\d+)/(.+)/files")
-+ for path in sys.path:
-+ if pattern.match(path):
-+ result = path.split("/files")[0]
-+ break
-+ else:
-+ result = None
-+ return result
-+
-+
-+@lru_cache(maxsize=1)
-+def _android_documents_folder() -> str:
-+ """:return: documents folder for the Android OS"""
-+ # Get directories with pyjnius
-+ try:
-+ from jnius import autoclass
-+
-+ Context = autoclass("android.content.Context") # noqa: N806
-+ Environment = autoclass("android.os.Environment") # noqa: N806
-+ documents_dir: str =
Context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath()
-+ except Exception:
-+ documents_dir = "/storage/emulated/0/Documents"
-+
-+ return documents_dir
-+
-+
-+__all__ = [
-+ "Android",
-+]
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/platformdirs/api.py
b/third_party/python/setuptools/pkg_resources/_vendor/platformdirs/api.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/pkg_resources/_vendor/platformdirs/api.py
-@@ -0,0 +1,156 @@
-+from __future__ import annotations
-+
-+import os
-+import sys
-+from abc import ABC, abstractmethod
-+from pathlib import Path
-+
-+if sys.version_info >= (3, 8): # pragma: no branch
-+ from typing import Literal # pragma: no cover
-+
-+
-+class PlatformDirsABC(ABC):
-+ """
-+ Abstract base class for platform directories.
-+ """
-+
-+ def __init__(
-+ self,
-+ appname: str | None = None,
-+ appauthor: str | None | Literal[False] = None,
-+ version: str | None = None,
-+ roaming: bool = False,
-+ multipath: bool = False,
-+ opinion: bool = True,
-+ ):
-+ """
-+ Create a new platform directory.
-+
-+ :param appname: See `appname`.
-+ :param appauthor: See `appauthor`.
-+ :param version: See `version`.
-+ :param roaming: See `roaming`.
-+ :param multipath: See `multipath`.
-+ :param opinion: See `opinion`.
-+ """
-+ self.appname = appname #: The name of application.
-+ self.appauthor = appauthor
-+ """
-+ The name of the app author or distributing body for this
application. Typically, it is the owning company name.
-+ Defaults to `appname`. You may pass ``False`` to disable it.
-+ """
-+ self.version = version
-+ """
-+ 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 ``<major>.<minor>``.
-+ """
-+ self.roaming = roaming
-+ """
-+ Whether to use the roaming appdata directory on Windows. That means
that for users on a Windows network setup
-+ for roaming profiles, this user data will be synced on login (see
-+ `here
<http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>`_).
-+ """
-+ self.multipath = multipath
-+ """
-+ An optional parameter only applicable to Unix/Linux which indicates
that the entire list of data dirs should be
-+ returned. By default, the first item would only be returned.
-+ """
-+ self.opinion = opinion #: A flag to indicating to use opinionated
values.
-+
-+ def _append_app_name_and_version(self, *base: str) -> str:
-+ params = list(base[1:])
-+ if self.appname:
-+ params.append(self.appname)
-+ if self.version:
-+ params.append(self.version)
-+ return os.path.join(base[0], *params)
-+
-+ @property
-+ @abstractmethod
-+ def user_data_dir(self) -> str:
-+ """:return: data directory tied to the user"""
-+
-+ @property
-+ @abstractmethod
-+ def site_data_dir(self) -> str:
-+ """:return: data directory shared by users"""
-+
-+ @property
-+ @abstractmethod
-+ def user_config_dir(self) -> str:
-+ """:return: config directory tied to the user"""
-+
-+ @property
-+ @abstractmethod
-+ def site_config_dir(self) -> str:
-+ """:return: config directory shared by the users"""
-+
-+ @property
-+ @abstractmethod
-+ def user_cache_dir(self) -> str:
-+ """:return: cache directory tied to the user"""
-+
-+ @property
-+ @abstractmethod
-+ def user_state_dir(self) -> str:
-+ """:return: state directory tied to the user"""
-+
-+ @property
-+ @abstractmethod
-+ def user_log_dir(self) -> str:
-+ """:return: log directory tied to the user"""
-+
-+ @property
-+ @abstractmethod
-+ def user_documents_dir(self) -> str:
-+ """:return: documents directory tied to the user"""
-+
-+ @property
-+ @abstractmethod
-+ def user_runtime_dir(self) -> str:
-+ """:return: runtime directory tied to the user"""
-+
-+ @property
-+ def user_data_path(self) -> Path:
-+ """:return: data path tied to the user"""
-+ return Path(self.user_data_dir)
-+
-+ @property
-+ def site_data_path(self) -> Path:
-+ """:return: data path shared by users"""
-+ return Path(self.site_data_dir)
-+
-+ @property
-+ def user_config_path(self) -> Path:
-+ """:return: config path tied to the user"""
-+ return Path(self.user_config_dir)
-+
-+ @property
-+ def site_config_path(self) -> Path:
-+ """:return: config path shared by the users"""
-+ return Path(self.site_config_dir)
-+
-+ @property
-+ def user_cache_path(self) -> Path:
-+ """:return: cache path tied to the user"""
-+ return Path(self.user_cache_dir)
-+
-+ @property
-+ def user_state_path(self) -> Path:
-+ """:return: state path tied to the user"""
-+ return Path(self.user_state_dir)
-+
-+ @property
-+ def user_log_path(self) -> Path:
-+ """:return: log path tied to the user"""
-+ return Path(self.user_log_dir)
-+
-+ @property
-+ def user_documents_path(self) -> Path:
-+ """:return: documents path tied to the user"""
-+ return Path(self.user_documents_dir)
-+
-+ @property
-+ def user_runtime_path(self) -> Path:
-+ """:return: runtime path tied to the user"""
-+ return Path(self.user_runtime_dir)
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/platformdirs/macos.py
b/third_party/python/setuptools/pkg_resources/_vendor/platformdirs/macos.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/platformdirs/macos.py
-@@ -0,0 +1,64 @@
-+from __future__ import annotations
-+
-+import os
-+
-+from .api import PlatformDirsABC
-+
-+
-+class MacOS(PlatformDirsABC):
-+ """
-+ Platform directories for the macOS operating system. Follows the
guidance from `Apple documentation
-+
<https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/MacOSXDirectories/MacOSXDirectories.html>`_.
-+ Makes use of the `appname <platformdirs.api.PlatformDirsABC.appname>`
and
-+ `version <platformdirs.api.PlatformDirsABC.version>`.
-+ """
-+
-+ @property
-+ def user_data_dir(self) -> str:
-+ """:return: data directory tied to the user, e.g.
``~/Library/Application Support/$appname/$version``"""
-+ return
self._append_app_name_and_version(os.path.expanduser("~/Library/Application
Support/"))
-+
-+ @property
-+ def site_data_dir(self) -> str:
-+ """:return: data directory shared by users, e.g.
``/Library/Application Support/$appname/$version``"""
-+ return self._append_app_name_and_version("/Library/Application
Support")
-+
-+ @property
-+ def user_config_dir(self) -> str:
-+ """:return: config directory tied to the user, e.g.
``~/Library/Preferences/$appname/$version``"""
-+ return
self._append_app_name_and_version(os.path.expanduser("~/Library/Preferences/"))
-+
-+ @property
-+ def site_config_dir(self) -> str:
-+ """:return: config directory shared by the users, e.g.
``/Library/Preferences/$appname``"""
-+ return self._append_app_name_and_version("/Library/Preferences")
-+
-+ @property
-+ def user_cache_dir(self) -> str:
-+ """:return: cache directory tied to the user, e.g.
``~/Library/Caches/$appname/$version``"""
-+ return
self._append_app_name_and_version(os.path.expanduser("~/Library/Caches"))
-+
-+ @property
-+ def user_state_dir(self) -> str:
-+ """:return: state directory tied to the user, same as
`user_data_dir`"""
-+ return self.user_data_dir
-+
-+ @property
-+ def user_log_dir(self) -> str:
-+ """:return: log directory tied to the user, e.g.
``~/Library/Logs/$appname/$version``"""
-+ return
self._append_app_name_and_version(os.path.expanduser("~/Library/Logs"))
-+
-+ @property
-+ def user_documents_dir(self) -> str:
-+ """:return: documents directory tied to the user, e.g.
``~/Documents``"""
-+ return os.path.expanduser("~/Documents")
-+
-+ @property
-+ def user_runtime_dir(self) -> str:
-+ """:return: runtime directory tied to the user, e.g.
``~/Library/Caches/TemporaryItems/$appname/$version``"""
-+ return
self._append_app_name_and_version(os.path.expanduser("~/Library/Caches/TemporaryItems"))
-+
-+
-+__all__ = [
-+ "MacOS",
-+]
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/platformdirs/unix.py
b/third_party/python/setuptools/pkg_resources/_vendor/platformdirs/unix.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/platformdirs/unix.py
-@@ -0,0 +1,181 @@
-+from __future__ import annotations
-+
-+import os
-+import sys
-+from configparser import ConfigParser
-+from pathlib import Path
-+
-+from .api import PlatformDirsABC
-+
-+if sys.platform.startswith("linux"): # pragma: no branch # no op check,
only to please the type checker
-+ from os import getuid
-+else:
-+
-+ def getuid() -> int:
-+ raise RuntimeError("should only be used on Linux")
-+
-+
-+class Unix(PlatformDirsABC):
-+ """
-+ On Unix/Linux, we follow the
-+ `XDG Basedir Spec
<https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html>`_.
The spec allows
-+ overriding directories with environment variables. The examples show
are the default values, alongside the name of
-+ the environment variable that overrides them. Makes use of the
-+ `appname <platformdirs.api.PlatformDirsABC.appname>`,
-+ `version <platformdirs.api.PlatformDirsABC.version>`,
-+ `multipath <platformdirs.api.PlatformDirsABC.multipath>`,
-+ `opinion <platformdirs.api.PlatformDirsABC.opinion>`.
-+ """
-+
-+ @property
-+ def user_data_dir(self) -> str:
-+ """
-+ :return: data directory tied to the user, e.g.
``~/.local/share/$appname/$version`` or
-+ ``$XDG_DATA_HOME/$appname/$version``
-+ """
-+ path = os.environ.get("XDG_DATA_HOME", "")
-+ if not path.strip():
-+ path = os.path.expanduser("~/.local/share")
-+ return self._append_app_name_and_version(path)
-+
-+ @property
-+ def site_data_dir(self) -> str:
-+ """
-+ :return: data directories shared by users (if `multipath
<platformdirs.api.PlatformDirsABC.multipath>` is
-+ enabled and ``XDG_DATA_DIR`` is set and a multi path the response
is also a multi path separated by the OS
-+ path separator), e.g. ``/usr/local/share/$appname/$version`` or
``/usr/share/$appname/$version``
-+ """
-+ # XDG default for $XDG_DATA_DIRS; only first, if multipath is False
-+ path = os.environ.get("XDG_DATA_DIRS", "")
-+ if not path.strip():
-+ path = f"/usr/local/share{os.pathsep}/usr/share"
-+ return self._with_multi_path(path)
-+
-+ def _with_multi_path(self, path: str) -> str:
-+ path_list = path.split(os.pathsep)
-+ if not self.multipath:
-+ path_list = path_list[0:1]
-+ path_list =
[self._append_app_name_and_version(os.path.expanduser(p)) for p in path_list]
-+ return os.pathsep.join(path_list)
-+
-+ @property
-+ def user_config_dir(self) -> str:
-+ """
-+ :return: config directory tied to the user, e.g.
``~/.config/$appname/$version`` or
-+ ``$XDG_CONFIG_HOME/$appname/$version``
-+ """
-+ path = os.environ.get("XDG_CONFIG_HOME", "")
-+ if not path.strip():
-+ path = os.path.expanduser("~/.config")
-+ return self._append_app_name_and_version(path)
-+
-+ @property
-+ def site_config_dir(self) -> str:
-+ """
-+ :return: config directories shared by users (if `multipath
<platformdirs.api.PlatformDirsABC.multipath>`
-+ is enabled and ``XDG_DATA_DIR`` is set and a multi path the
response is also a multi path separated by the OS
-+ path separator), e.g. ``/etc/xdg/$appname/$version``
-+ """
-+ # XDG default for $XDG_CONFIG_DIRS only first, if multipath is False
-+ path = os.environ.get("XDG_CONFIG_DIRS", "")
-+ if not path.strip():
-+ path = "/etc/xdg"
-+ return self._with_multi_path(path)
-+
-+ @property
-+ def user_cache_dir(self) -> str:
-+ """
-+ :return: cache directory tied to the user, e.g.
``~/.cache/$appname/$version`` or
-+ ``~/$XDG_CACHE_HOME/$appname/$version``
-+ """
-+ path = os.environ.get("XDG_CACHE_HOME", "")
-+ if not path.strip():
-+ path = os.path.expanduser("~/.cache")
-+ return self._append_app_name_and_version(path)
-+
-+ @property
-+ def user_state_dir(self) -> str:
-+ """
-+ :return: state directory tied to the user, e.g.
``~/.local/state/$appname/$version`` or
-+ ``$XDG_STATE_HOME/$appname/$version``
-+ """
-+ path = os.environ.get("XDG_STATE_HOME", "")
-+ if not path.strip():
-+ path = os.path.expanduser("~/.local/state")
-+ return self._append_app_name_and_version(path)
-+
-+ @property
-+ def user_log_dir(self) -> str:
-+ """
-+ :return: log directory tied to the user, same as `user_state_dir`
if not opinionated else ``log`` in it
-+ """
-+ path = self.user_state_dir
-+ if self.opinion:
-+ path = os.path.join(path, "log")
-+ return path
-+
-+ @property
-+ def user_documents_dir(self) -> str:
-+ """
-+ :return: documents directory tied to the user, e.g. ``~/Documents``
-+ """
-+ documents_dir = _get_user_dirs_folder("XDG_DOCUMENTS_DIR")
-+ if documents_dir is None:
-+ documents_dir = os.environ.get("XDG_DOCUMENTS_DIR", "").strip()
-+ if not documents_dir:
-+ documents_dir = os.path.expanduser("~/Documents")
-+
-+ return documents_dir
-+
-+ @property
-+ def user_runtime_dir(self) -> str:
-+ """
-+ :return: runtime directory tied to the user, e.g. ``/run/user/$(id
-u)/$appname/$version`` or
-+ ``$XDG_RUNTIME_DIR/$appname/$version``
-+ """
-+ path = os.environ.get("XDG_RUNTIME_DIR", "")
-+ if not path.strip():
-+ path = f"/run/user/{getuid()}"
-+ return self._append_app_name_and_version(path)
-+
-+ @property
-+ def site_data_path(self) -> Path:
-+ """:return: data path shared by users. Only return first item, even
if ``multipath`` is set to ``True``"""
-+ return self._first_item_as_path_if_multipath(self.site_data_dir)
-+
-+ @property
-+ def site_config_path(self) -> Path:
-+ """:return: config path shared by the users. Only return first
item, even if ``multipath`` is set to ``True``"""
-+ return self._first_item_as_path_if_multipath(self.site_config_dir)
-+
-+ def _first_item_as_path_if_multipath(self, directory: str) -> Path:
-+ if self.multipath:
-+ # If multipath is True, the first path is returned.
-+ directory = directory.split(os.pathsep)[0]
-+ return Path(directory)
-+
-+
-+def _get_user_dirs_folder(key: str) -> str | None:
-+ """Return directory from user-dirs.dirs config file. See
https://freedesktop.org/wiki/Software/xdg-user-dirs/""";
-+ user_dirs_config_path = os.path.join(Unix().user_config_dir,
"user-dirs.dirs")
-+ if os.path.exists(user_dirs_config_path):
-+ parser = ConfigParser()
-+
-+ with open(user_dirs_config_path) as stream:
-+ # Add fake section header, so ConfigParser doesn't complain
-+ parser.read_string(f"[top]\n{stream.read()}")
-+
-+ if key not in parser["top"]:
-+ return None
-+
-+ path = parser["top"][key].strip('"')
-+ # Handle relative home paths
-+ path = path.replace("$HOME", os.path.expanduser("~"))
-+ return path
-+
-+ return None
-+
-+
-+__all__ = [
-+ "Unix",
-+]
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/platformdirs/version.py
b/third_party/python/setuptools/pkg_resources/_vendor/platformdirs/version.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/platformdirs/version.py
-@@ -0,0 +1,4 @@
-+# file generated by setuptools_scm
-+# don't change, don't track in version control
-+__version__ = version = '2.6.2'
-+__version_tuple__ = version_tuple = (2, 6, 2)
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/platformdirs/windows.py
b/third_party/python/setuptools/pkg_resources/_vendor/platformdirs/windows.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/platformdirs/windows.py
-@@ -0,0 +1,184 @@
-+from __future__ import annotations
-+
-+import ctypes
-+import os
-+import sys
-+from functools import lru_cache
-+from typing import Callable
-+
-+from .api import PlatformDirsABC
-+
-+
-+class Windows(PlatformDirsABC):
-+ """`MSDN on where to store app data files
-+
<http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120>`_.
-+ Makes use of the
-+ `appname <platformdirs.api.PlatformDirsABC.appname>`,
-+ `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`,
-+ `version <platformdirs.api.PlatformDirsABC.version>`,
-+ `roaming <platformdirs.api.PlatformDirsABC.roaming>`,
-+ `opinion <platformdirs.api.PlatformDirsABC.opinion>`."""
-+
-+ @property
-+ def user_data_dir(self) -> str:
-+ """
-+ :return: data directory tied to the user, e.g.
-+ ``%USERPROFILE%\\AppData\\Local\\$appauthor\\$appname`` (not
roaming) or
-+ ``%USERPROFILE%\\AppData\\Roaming\\$appauthor\\$appname`` (roaming)
-+ """
-+ const = "CSIDL_APPDATA" if self.roaming else "CSIDL_LOCAL_APPDATA"
-+ path = os.path.normpath(get_win_folder(const))
-+ return self._append_parts(path)
-+
-+ def _append_parts(self, path: str, *, opinion_value: str | None = None)
-> str:
-+ params = []
-+ if self.appname:
-+ if self.appauthor is not False:
-+ author = self.appauthor or self.appname
-+ params.append(author)
-+ params.append(self.appname)
-+ if opinion_value is not None and self.opinion:
-+ params.append(opinion_value)
-+ if self.version:
-+ params.append(self.version)
-+ return os.path.join(path, *params)
-+
-+ @property
-+ def site_data_dir(self) -> str:
-+ """:return: data directory shared by users, e.g.
``C:\\ProgramData\\$appauthor\\$appname``"""
-+ path = os.path.normpath(get_win_folder("CSIDL_COMMON_APPDATA"))
-+ return self._append_parts(path)
-+
-+ @property
-+ def user_config_dir(self) -> str:
-+ """:return: config directory tied to the user, same as
`user_data_dir`"""
-+ return self.user_data_dir
-+
-+ @property
-+ def site_config_dir(self) -> str:
-+ """:return: config directory shared by the users, same as
`site_data_dir`"""
-+ return self.site_data_dir
-+
-+ @property
-+ def user_cache_dir(self) -> str:
-+ """
-+ :return: cache directory tied to the user (if opinionated with
``Cache`` folder within ``$appname``) e.g.
-+
``%USERPROFILE%\\AppData\\Local\\$appauthor\\$appname\\Cache\\$version``
-+ """
-+ path = os.path.normpath(get_win_folder("CSIDL_LOCAL_APPDATA"))
-+ return self._append_parts(path, opinion_value="Cache")
-+
-+ @property
-+ def user_state_dir(self) -> str:
-+ """:return: state directory tied to the user, same as
`user_data_dir`"""
-+ return self.user_data_dir
-+
-+ @property
-+ def user_log_dir(self) -> str:
-+ """
-+ :return: log directory tied to the user, same as `user_data_dir` if
not opinionated else ``Logs`` in it
-+ """
-+ path = self.user_data_dir
-+ if self.opinion:
-+ path = os.path.join(path, "Logs")
-+ return path
-+
-+ @property
-+ def user_documents_dir(self) -> str:
-+ """
-+ :return: documents directory tied to the user e.g.
``%USERPROFILE%\\Documents``
-+ """
-+ return os.path.normpath(get_win_folder("CSIDL_PERSONAL"))
-+
-+ @property
-+ def user_runtime_dir(self) -> str:
-+ """
-+ :return: runtime directory tied to the user, e.g.
-+ ``%USERPROFILE%\\AppData\\Local\\Temp\\$appauthor\\$appname``
-+ """
-+ path =
os.path.normpath(os.path.join(get_win_folder("CSIDL_LOCAL_APPDATA"), "Temp"))
-+ return self._append_parts(path)
-+
-+
-+def get_win_folder_from_env_vars(csidl_name: str) -> str:
-+ """Get folder from environment variables."""
-+ if csidl_name == "CSIDL_PERSONAL": # does not have an environment name
-+ return os.path.join(os.path.normpath(os.environ["USERPROFILE"]),
"Documents")
-+
-+ env_var_name = {
-+ "CSIDL_APPDATA": "APPDATA",
-+ "CSIDL_COMMON_APPDATA": "ALLUSERSPROFILE",
-+ "CSIDL_LOCAL_APPDATA": "LOCALAPPDATA",
-+ }.get(csidl_name)
-+ if env_var_name is None:
-+ raise ValueError(f"Unknown CSIDL name: {csidl_name}")
-+ result = os.environ.get(env_var_name)
-+ if result is None:
-+ raise ValueError(f"Unset environment variable: {env_var_name}")
-+ return result
-+
-+
-+def get_win_folder_from_registry(csidl_name: str) -> str:
-+ """Get folder from the registry.
-+
-+ 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.
-+ """
-+ shell_folder_name = {
-+ "CSIDL_APPDATA": "AppData",
-+ "CSIDL_COMMON_APPDATA": "Common AppData",
-+ "CSIDL_LOCAL_APPDATA": "Local AppData",
-+ "CSIDL_PERSONAL": "Personal",
-+ }.get(csidl_name)
-+ if shell_folder_name is None:
-+ raise ValueError(f"Unknown CSIDL name: {csidl_name}")
-+ if sys.platform != "win32": # only needed for mypy type checker to
know that this code runs only on Windows
-+ raise NotImplementedError
-+ import winreg
-+
-+ key = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
-+ directory, _ = winreg.QueryValueEx(key, shell_folder_name)
-+ return str(directory)
-+
-+
-+def get_win_folder_via_ctypes(csidl_name: str) -> str:
-+ """Get folder with ctypes."""
-+ csidl_const = {
-+ "CSIDL_APPDATA": 26,
-+ "CSIDL_COMMON_APPDATA": 35,
-+ "CSIDL_LOCAL_APPDATA": 28,
-+ "CSIDL_PERSONAL": 5,
-+ }.get(csidl_name)
-+ if csidl_const is None:
-+ raise ValueError(f"Unknown CSIDL name: {csidl_name}")
-+
-+ buf = ctypes.create_unicode_buffer(1024)
-+ windll = getattr(ctypes, "windll") # noqa: B009 # using getattr to
avoid false positive with mypy type checker
-+ windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
-+
-+ # Downgrade to short path name if it has highbit chars.
-+ if any(ord(c) > 255 for c in buf):
-+ buf2 = ctypes.create_unicode_buffer(1024)
-+ if windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
-+ buf = buf2
-+
-+ return buf.value
-+
-+
-+def _pick_get_win_folder() -> Callable[[str], str]:
-+ if hasattr(ctypes, "windll"):
-+ return get_win_folder_via_ctypes
-+ try:
-+ import winreg # noqa: F401
-+ except ImportError:
-+ return get_win_folder_from_env_vars
-+ else:
-+ return get_win_folder_from_registry
-+
-+
-+get_win_folder = lru_cache(maxsize=None)(_pick_get_win_folder())
-+
-+__all__ = [
-+ "Windows",
-+]
-diff --git
a/third_party/python/setuptools/pkg_resources/_vendor/pyparsing.py
b/third_party/python/setuptools/pkg_resources/_vendor/pyparsing.py
-deleted file mode 100644
---- a/third_party/python/setuptools/pkg_resources/_vendor/pyparsing.py
-+++ /dev/null
-@@ -1,5742 +0,0 @@
--# 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{"<salutation>, <addressee>!"}), built up using L{Word}, L{Literal}, and
L{And} elements
--(L{'+'<ParserElement.__add__>} 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<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{'+'<And>}, L{'|'<MatchFirst>}, L{'^'<Or>}, and L{'&'<Each>}
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 <ptmcg AT users.sourceforge.net>"
--
--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<And._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.<resultsName>} - 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) # -> <class
'pyparsing.ParseResults'> ['sldkj', 'lsdkj', 'sldkj']
--
-- # Use asList() to create an actual list
-- result_list = result.asList()
-- print(type(result_list), result_list) # -> <class '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)) # -> <class
'pyparsing.ParseResults'> (['12', '/', '31', '/', '1999'], {'day': [('1999',
4)], 'year': [('12', 0)], 'month': [('31', 2)]})
--
-- result_dict = result.asDict()
-- print(type(result_dict), repr(result_dict)) # -> <class '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,
-- "</", resTag, ">" ]
--
-- out += [ nl, indent, "</", selfTag, ">" ]
-- 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}<ParserElement.parseString>} for more
information
-- on parsing strings containing C{<TAB>}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<loc<len(s) and s[loc-1] == '\n' else loc - s.rfind("\n",
0, loc)
--
--def lineno(loc,strg):
-- """Returns current line number within a string, counting newlines as
line separators.
-- The first line 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}<ParserElement.parseString>} for more
information
-- on parsing strings containing C{<TAB>}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 = "<parse action>"
-- 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, <TAB> 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 = "<unknown>" # 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}<parseString>} for more information
-- on parsing strings containing C{<TAB>}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}<setParseAction>}.
--
-- See examples in L{I{copy}<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}<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}<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}<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 <TAB>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{<TAB>}s to spaces before
parsing the input string.
-- Must be called before C{parseString} when the input grammar
contains elements that
-- match C{<TAB>} 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
<exprname> at loc <n>(<line>,<col>)"}
-- 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
(loc<instrlen and instring[loc] in bodychars):
-- throwException = True
--
-- if throwException:
-- raise ParseException(instring, loc, self.errmsg, self)
--
-- return loc, instring[start:loc]
--
-- def __str__( self ):
-- try:
-- return super(Word,self).__str__()
-- except Exception:
-- pass
--
--
-- if self.strRepr is None:
--
-- def charsAsStr(s):
-- if len(s)>4:
-- 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<name>...)}), these will be preserved as
-- named parse results.
--
-- Example::
-- realnum = Regex(r"[+-]?\d+\.\d*")
-- date = Regex(r'(?P<year>\d{4})-(?P<month>\d\d?)-(?P<day>\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 = {
-- " " : "<SPC>",
-- "\t": "<TAB>",
-- "\n": "<LF>",
-- "\r": "<CR>",
-- "\f": "<FF>",
-- }
-- 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):
-- if instring[loc] == "\n":
-- return loc+1, "\n"
-- else:
-- raise ParseException(instring, loc, self.errmsg, self)
-- elif loc == len(instring):
-- return loc+1, []
-- else:
-- raise ParseException(instring, loc, self.errmsg, self)
--
--class StringStart(_PositionToken):
-- """
-- Matches if current position is at the beginning of the parse string
-- """
-- def __init__( self ):
-- super(StringStart,self).__init__()
-- self.errmsg = "Expected start of text"
--
-- def parseImpl( self, instring, loc, doActions=True ):
-- if loc != 0:
-- # see if entire string up to here is just whitespace and
ignoreables
-- if loc != self.preParse( instring, 0 ):
-- raise ParseException(instring, loc, self.errmsg, self)
-- return loc, []
--
--class StringEnd(_PositionToken):
-- """
-- Matches if current position is at the end of the parse string
-- """
-- def __init__( self ):
-- super(StringEnd,self).__init__()
-- self.errmsg = "Expected end of text"
--
-- def parseImpl( self, instring, loc, doActions=True ):
-- if loc < len(instring):
-- raise ParseException(instring, loc, self.errmsg, self)
-- elif loc == len(instring):
-- return loc+1, []
-- elif 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<instrlen:
-- if (instring[loc] in self.wordChars or
-- instring[loc-1] not in self.wordChars):
-- raise ParseException(instring, loc, self.errmsg, self)
-- return loc, []
--
--
--class ParseExpression(ParserElement):
-- """
-- Abstract subclass of ParserElement, for combining and post-processing
parsed tokens.
-- """
-- def __init__( self, exprs, savelist = False ):
-- super(ParseExpression,self).__init__(savelist)
-- if isinstance( exprs, _generatorType ):
-- exprs = list(exprs)
--
-- if isinstance( exprs, basestring ):
-- self.exprs = [ ParserElement._literalStringClass( exprs ) ]
-- elif isinstance( exprs, Iterable ):
-- exprs = list(exprs)
-- # if sequence of strings provided, wrap with Literal
-- if all(isinstance(expr, basestring) for expr in exprs):
-- exprs = map(ParserElement._literalStringClass, exprs)
-- self.exprs = list(exprs)
-- else:
-- try:
-- self.exprs = list( exprs )
-- except TypeError:
-- self.exprs = [ exprs ]
-- self.callPreparse = False
--
-- def __getitem__( self, i ):
-- return self.exprs[i]
--
-- def append( self, other ):
-- self.exprs.append( other )
-- self.strRepr = None
-- return self
--
-- def leaveWhitespace( self ):
-- """Extends C{leaveWhitespace} defined in base class, and also
invokes C{leaveWhitespace} on
-- all contained expressions."""
-- self.skipWhitespace = False
-- self.exprs = [ e.copy() for e in self.exprs ]
-- for e in self.exprs:
-- e.leaveWhitespace()
-- return self
--
-- def ignore( self, other ):
-- if isinstance( other, Suppress ):
-- if other not in self.ignoreExprs:
-- super( ParseExpression, self).ignore( other )
-- for e in self.exprs:
-- e.ignore( self.ignoreExprs[-1] )
-- else:
-- super( ParseExpression, self).ignore( other )
-- for e in self.exprs:
-- e.ignore( self.ignoreExprs[-1] )
-- return self
--
-- def __str__( self ):
-- try:
-- return super(ParseExpression,self).__str__()
-- except Exception:
-- pass
--
-- if self.strRepr is None:
-- self.strRepr = "%s:(%s)" % ( self.__class__.__name__,
_ustr(self.exprs) )
-- return self.strRepr
--
-- def streamline( self ):
-- super(ParseExpression,self).streamline()
--
-- for e in self.exprs:
-- e.streamline()
--
-- # collapse nested And's of the form And( And( And( a,b), c), d) to
And( a,b,c,d )
-- # but only if there are no parse actions or resultsNames on the
nested And's
-- # (likewise for Or's and MatchFirst's)
-- if ( len(self.exprs) == 2 ):
-- other = self.exprs[0]
-- if ( isinstance( other, self.__class__ ) and
-- not(other.parseAction) and
-- other.resultsName is None and
-- not other.debug ):
-- self.exprs = other.exprs[:] + [ self.exprs[1] ]
-- self.strRepr = None
-- self.mayReturnEmpty |= other.mayReturnEmpty
-- self.mayIndexError |= other.mayIndexError
--
-- other = self.exprs[-1]
-- if ( isinstance( other, self.__class__ ) and
-- not(other.parseAction) and
-- other.resultsName is None and
-- not other.debug ):
-- self.exprs = self.exprs[:-1] + other.exprs[:]
-- self.strRepr = None
-- self.mayReturnEmpty |= other.mayReturnEmpty
-- self.mayIndexError |= other.mayIndexError
--
-- self.errmsg = "Expected " + _ustr(self)
--
-- return self
--
-- def setResultsName( self, name, listAllMatches=False ):
-- ret =
super(ParseExpression,self).setResultsName(name,listAllMatches)
-- return ret
--
-- def validate( self, validateTrace=[] ):
-- tmp = validateTrace[:]+[self]
-- for e in self.exprs:
-- e.validate(tmp)
-- self.checkRecursion( [] )
--
-- def copy(self):
-- ret = super(ParseExpression,self).copy()
-- ret.exprs = [e.copy() for e in self.exprs]
-- return ret
--
--class And(ParseExpression):
-- """
-- Requires all given C{ParseExpression}s to be found in the given order.
-- Expressions may be separated by whitespace.
-- May be constructed using the C{'+'} operator.
-- May also be constructed using the C{'-'} operator, which will suppress
backtracking.
--
-- Example::
-- integer = Word(nums)
-- name_expr = OneOrMore(Word(alphas))
--
-- expr = And([integer("id"),name_expr("name"),integer("age")])
-- # more easily written as:
-- expr = integer("id") + name_expr("name") + integer("age")
-- """
--
-- class _ErrorStop(Empty):
-- def __init__(self, *args, **kwargs):
-- super(And._ErrorStop,self).__init__(*args, **kwargs)
-- self.name = '-'
-- self.leaveWhitespace()
--
-- def __init__( self, exprs, savelist = True ):
-- super(And,self).__init__(exprs, savelist)
-- self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs)
-- self.setWhitespaceChars( self.exprs[0].whiteChars )
-- self.skipWhitespace = self.exprs[0].skipWhitespace
-- self.callPreparse = True
--
-- def parseImpl( self, instring, loc, doActions=True ):
-- # pass False as last arg to _parse for first element, since we
already
-- # pre-parsed the string as part of our And pre-parsing
-- loc, resultlist = self.exprs[0]._parse( instring, loc, doActions,
callPreParse=False )
-- errorStop = False
-- for e in self.exprs[1:]:
-- if isinstance(e, And._ErrorStop):
-- errorStop = True
-- continue
-- if errorStop:
-- try:
-- loc, exprtokens = e._parse( instring, loc, doActions )
-- except ParseSyntaxException:
-- raise
-- except ParseBaseException as pe:
-- pe.__traceback__ = None
-- raise ParseSyntaxException._from_exception(pe)
-- except IndexError:
-- raise ParseSyntaxException(instring, len(instring),
self.errmsg, self)
-- else:
-- loc, exprtokens = e._parse( instring, loc, doActions )
-- if exprtokens or exprtokens.haskeys():
-- resultlist += exprtokens
-- return loc, resultlist
--
-- def __iadd__(self, other ):
-- if isinstance( other, basestring ):
-- other = ParserElement._literalStringClass( other )
-- return self.append( other ) #And( [ self, other ] )
--
-- def checkRecursion( self, parseElementList ):
-- subRecCheckList = parseElementList[:] + [ self ]
-- for e in self.exprs:
-- e.checkRecursion( subRecCheckList )
-- if not e.mayReturnEmpty:
-- break
--
-- 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
--
--
--class Or(ParseExpression):
-- """
-- Requires that at least one C{ParseExpression} is found.
-- If two expressions match, the expression that matches the longest
string will be used.
-- May be constructed using the C{'^'} operator.
--
-- Example::
-- # construct Or using '^' operator
--
-- number = Word(nums) ^ Combine(Word(nums) + '.' + Word(nums))
-- print(number.searchString("123 3.1416 789"))
-- prints::
-- [['123'], ['3.1416'], ['789']]
-- """
-- def __init__( self, exprs, savelist = False ):
-- super(Or,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
-- matches = []
-- for e in self.exprs:
-- try:
-- loc2 = e.tryParse( instring, loc )
-- except ParseException as err:
-- err.__traceback__ = None
-- 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)
-- 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'], {}))
-- <<leaving remove_duplicate_chars (ret: 'dfjkls')
-- ['dfjkls']
-- """
-- f = _trim_arity(f)
-- def z(*paArgs):
-- thisFunc = f.__name__
-- s,l,t = paArgs[-3:]
-- if len(paArgs)>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( "<<leaving %s (exception: %s)\n" %
(thisFunc,exc) )
-- raise
-- sys.stderr.write( "<<leaving %s (ret: %r)\n" % (thisFunc,ret) )
-- return ret
-- try:
-- z.__name__ = f.__name__
-- except AttributeError:
-- pass
-- return z
--
--#
--# global helpers
--#
--def delimitedList( expr, delim=",", combine=False ):
-- """
-- Helper to define a delimited list of expressions - the delimiter
defaults to ','.
-- By default, the list elements and delimiters can have intervening
whitespace, and
-- comments, but this can be overridden by passing C{combine=True} in the
constructor.
-- If C{combine} is set to C{True}, the matching tokens are returned as a
single token
-- string, with the delimiters included; otherwise, the matching tokens
are returned
-- as a list of tokens, with the delimiters suppressed.
--
-- Example::
-- delimitedList(Word(alphas)).parseString("aa,bb,cc") # -> ['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 <b> bold <i>text</i> </b> normal text "
-- for tag in ("b","i"):
-- opener,closer = makeHTMLTags(tag)
-- patt = originalTextFor(opener + SkipTo(closer) + closer)
-- print(patt.searchString(src)[0])
-- prints::
-- ['<b> bold <i>text</i> </b>']
-- ['<i>text</i>']
-- """
-- 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{<TAB>} 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<ParserElement.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("</") + tagStr + ">")
--
-- openTag = openTag.setResultsName("start"+"".join(resname.replace(":","
").title().split())).setName("<%s>" % resname)
-- closeTag = closeTag.setResultsName("end"+"".join(resname.replace(":","
").title().split())).setName("</%s>" % 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 = '<td>More info at the <a
href="http://pyparsing.wikispaces.com";>pyparsing</a> wiki page</td>'
-- # 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 <A> 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{<TD>} or C{<DIV>}.
--
-- 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 = '''
-- <div>
-- Some text
-- <div type="grid">1 4 0 1 0</div>
-- <div type="graph">1,3 2,3 1,1</div>
-- <div>this has no type</div>
-- </div>
--
-- '''
-- 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 = '''
-- <div>
-- Some text
-- <div class="grid">1 4 0 1 0</div>
-- <div class="graph">1,3 2,3 1,1</div>
-- <div>this &lt;div&gt; has no class</div>
-- </div>
--
-- '''
-- 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<entity>' + '|'.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"<!--[\s\S]*?-->").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<integer>}, L{reals<real>}, L{scientific
notation<sci_real>})
-- - common L{programming identifiers<identifier>}
-- - network addresses (L{MAC<mac_address>}, L{IPv4<ipv4_address>},
L{IPv6<ipv6_address>})
-- - ISO8601 L{dates<iso8601_date>} and L{datetime<iso8601_datetime>}
-- - L{UUID<uuid>}
-- - L{comma-separated list<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<year>\d{4})(?:-(?P<month>\d\d)(?:-(?P<day>\d\d))?)?').setName("ISO8601
date")
-- "ISO8601 date (C{yyyy-mm-dd})"
--
-- iso8601_datetime =
Regex(r'(?P<year>\d{4})-(?P<month>\d\d)-(?P<day>\d\d)[T
](?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d(\.\d*)?)?)?(?P<tz>Z|[+-]\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 = '<td>More info at the <a
href="http://pyparsing.wikispaces.com";>pyparsing</a> wiki page</td>'
-- 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/third_party/python/setuptools/pkg_resources/_vendor/typing_extensions.py
b/third_party/python/setuptools/pkg_resources/_vendor/typing_extensions.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/pkg_resources/_vendor/typing_extensions.py
-@@ -0,0 +1,2209 @@
-+import abc
-+import collections
-+import collections.abc
-+import functools
-+import operator
-+import sys
-+import types as _types
-+import typing
-+
-+
-+__all__ = [
-+ # Super-special typing primitives.
-+ 'Any',
-+ 'ClassVar',
-+ 'Concatenate',
-+ 'Final',
-+ 'LiteralString',
-+ 'ParamSpec',
-+ 'ParamSpecArgs',
-+ 'ParamSpecKwargs',
-+ 'Self',
-+ 'Type',
-+ 'TypeVar',
-+ 'TypeVarTuple',
-+ 'Unpack',
-+
-+ # ABCs (from collections.abc).
-+ 'Awaitable',
-+ 'AsyncIterator',
-+ 'AsyncIterable',
-+ 'Coroutine',
-+ 'AsyncGenerator',
-+ 'AsyncContextManager',
-+ 'ChainMap',
-+
-+ # Concrete collection types.
-+ 'ContextManager',
-+ 'Counter',
-+ 'Deque',
-+ 'DefaultDict',
-+ 'NamedTuple',
-+ 'OrderedDict',
-+ 'TypedDict',
-+
-+ # Structural checks, a.k.a. protocols.
-+ 'SupportsIndex',
-+
-+ # One-off things.
-+ 'Annotated',
-+ 'assert_never',
-+ 'assert_type',
-+ 'clear_overloads',
-+ 'dataclass_transform',
-+ 'get_overloads',
-+ 'final',
-+ 'get_args',
-+ 'get_origin',
-+ 'get_type_hints',
-+ 'IntVar',
-+ 'is_typeddict',
-+ 'Literal',
-+ 'NewType',
-+ 'overload',
-+ 'override',
-+ 'Protocol',
-+ 'reveal_type',
-+ 'runtime',
-+ 'runtime_checkable',
-+ 'Text',
-+ 'TypeAlias',
-+ 'TypeGuard',
-+ 'TYPE_CHECKING',
-+ 'Never',
-+ 'NoReturn',
-+ 'Required',
-+ 'NotRequired',
-+]
-+
-+# for backward compatibility
-+PEP_560 = True
-+GenericMeta = type
-+
-+# The functions below are modified copies of typing internal helpers.
-+# They are needed by _ProtocolMeta and they provide support for PEP 646.
-+
-+_marker = object()
-+
-+
-+def _check_generic(cls, parameters, elen=_marker):
-+ """Check correct count for parameters of a generic cls (internal
helper).
-+ This gives a nice error message in case of count mismatch.
-+ """
-+ if not elen:
-+ raise TypeError(f"{cls} is not a generic class")
-+ if elen is _marker:
-+ if not hasattr(cls, "__parameters__") or not cls.__parameters__:
-+ raise TypeError(f"{cls} is not a generic class")
-+ elen = len(cls.__parameters__)
-+ alen = len(parameters)
-+ if alen != elen:
-+ if hasattr(cls, "__parameters__"):
-+ parameters = [p for p in cls.__parameters__ if not
_is_unpack(p)]
-+ num_tv_tuples = sum(isinstance(p, TypeVarTuple) for p in
parameters)
-+ if (num_tv_tuples > 0) and (alen >= elen - num_tv_tuples):
-+ return
-+ raise TypeError(f"Too {'many' if alen > elen else 'few'} parameters
for {cls};"
-+ f" actual {alen}, expected {elen}")
-+
-+
-+if sys.version_info >= (3, 10):
-+ def _should_collect_from_parameters(t):
-+ return isinstance(
-+ t, (typing._GenericAlias, _types.GenericAlias, _types.UnionType)
-+ )
-+elif sys.version_info >= (3, 9):
-+ def _should_collect_from_parameters(t):
-+ return isinstance(t, (typing._GenericAlias, _types.GenericAlias))
-+else:
-+ def _should_collect_from_parameters(t):
-+ return isinstance(t, typing._GenericAlias) and not t._special
-+
-+
-+def _collect_type_vars(types, typevar_types=None):
-+ """Collect all type variable contained in types in order of
-+ first appearance (lexicographic order). For example::
-+
-+ _collect_type_vars((T, List[S, T])) == (T, S)
-+ """
-+ if typevar_types is None:
-+ typevar_types = typing.TypeVar
-+ tvars = []
-+ for t in types:
-+ if (
-+ isinstance(t, typevar_types) and
-+ t not in tvars and
-+ not _is_unpack(t)
-+ ):
-+ tvars.append(t)
-+ if _should_collect_from_parameters(t):
-+ tvars.extend([t for t in t.__parameters__ if t not in tvars])
-+ return tuple(tvars)
-+
-+
-+NoReturn = typing.NoReturn
-+
-+# Some unconstrained type variables. These are used by the container types.
-+# (These are not for export.)
-+T = typing.TypeVar('T') # Any type.
-+KT = typing.TypeVar('KT') # Key type.
-+VT = typing.TypeVar('VT') # Value type.
-+T_co = typing.TypeVar('T_co', covariant=True) # Any type covariant
containers.
-+T_contra = typing.TypeVar('T_contra', contravariant=True) # Ditto
contravariant.
-+
-+
-+if sys.version_info >= (3, 11):
-+ from typing import Any
-+else:
-+
-+ class _AnyMeta(type):
-+ def __instancecheck__(self, obj):
-+ if self is Any:
-+ raise TypeError("typing_extensions.Any cannot be used with
isinstance()")
-+ return super().__instancecheck__(obj)
-+
-+ def __repr__(self):
-+ if self is Any:
-+ return "typing_extensions.Any"
-+ return super().__repr__()
-+
-+ class Any(metaclass=_AnyMeta):
-+ """Special type indicating an unconstrained type.
-+ - Any is compatible with every type.
-+ - Any assumed to have all methods.
-+ - All values assumed to be instances of Any.
-+ Note that all the above statements are true from the point of view
of
-+ static type checkers. At runtime, Any should not be used with
instance
-+ checks.
-+ """
-+ def __new__(cls, *args, **kwargs):
-+ if cls is Any:
-+ raise TypeError("Any cannot be instantiated")
-+ return super().__new__(cls, *args, **kwargs)
-+
-+
-+ClassVar = typing.ClassVar
-+
-+# On older versions of typing there is an internal class named "Final".
-+# 3.8+
-+if hasattr(typing, 'Final') and sys.version_info[:2] >= (3, 7):
-+ Final = typing.Final
-+# 3.7
-+else:
-+ class _FinalForm(typing._SpecialForm, _root=True):
-+
-+ def __repr__(self):
-+ return 'typing_extensions.' + self._name
-+
-+ def __getitem__(self, parameters):
-+ item = typing._type_check(parameters,
-+ f'{self._name} accepts only a single
type.')
-+ return typing._GenericAlias(self, (item,))
-+
-+ Final = _FinalForm('Final',
-+ doc="""A special typing construct to indicate that a
name
-+ cannot be re-assigned or overridden in a subclass.
-+ For example:
-+
-+ MAX_SIZE: Final = 9000
-+ MAX_SIZE += 1 # Error reported by type checker
-+
-+ class Connection:
-+ TIMEOUT: Final[int] = 10
-+ class FastConnector(Connection):
-+ TIMEOUT = 1 # Error reported by type checker
-+
-+ There is no runtime checking of these properties.""")
-+
-+if sys.version_info >= (3, 11):
-+ final = typing.final
-+else:
-+ # @final exists in 3.8+, but we backport it for all versions
-+ # before 3.11 to keep support for the __final__ attribute.
-+ # See https://bugs.python.org/issue46342
-+ def final(f):
-+ """This decorator can be used to indicate to type checkers that
-+ the decorated method cannot be overridden, and decorated class
-+ cannot be subclassed. For example:
-+
-+ class Base:
-+ @final
-+ def done(self) -> None:
-+ ...
-+ class Sub(Base):
-+ def done(self) -> None: # Error reported by type checker
-+ ...
-+ @final
-+ class Leaf:
-+ ...
-+ class Other(Leaf): # Error reported by type checker
-+ ...
-+
-+ There is no runtime checking of these properties. The decorator
-+ sets the ``__final__`` attribute to ``True`` on the decorated object
-+ to allow runtime introspection.
-+ """
-+ try:
-+ f.__final__ = True
-+ except (AttributeError, TypeError):
-+ # Skip the attribute silently if it is not writable.
-+ # AttributeError happens if the object has __slots__ or a
-+ # read-only property, TypeError if it's a builtin class.
-+ pass
-+ return f
-+
-+
-+def IntVar(name):
-+ return typing.TypeVar(name)
-+
-+
-+# 3.8+:
-+if hasattr(typing, 'Literal'):
-+ Literal = typing.Literal
-+# 3.7:
-+else:
-+ class _LiteralForm(typing._SpecialForm, _root=True):
-+
-+ def __repr__(self):
-+ return 'typing_extensions.' + self._name
-+
-+ def __getitem__(self, parameters):
-+ return typing._GenericAlias(self, parameters)
-+
-+ Literal = _LiteralForm('Literal',
-+ doc="""A type that can be used to indicate to
type checkers
-+ that the corresponding value has a value
literally equivalent
-+ to the provided parameter. For example:
-+
-+ var: Literal[4] = 4
-+
-+ The type checker understands that 'var' is
literally equal to
-+ the value 4 and no other value.
-+
-+ Literal[...] cannot be subclassed. There is no
runtime
-+ checking verifying that the parameter is
actually a value
-+ instead of a type.""")
-+
-+
-+_overload_dummy = typing._overload_dummy # noqa
-+
-+
-+if hasattr(typing, "get_overloads"): # 3.11+
-+ overload = typing.overload
-+ get_overloads = typing.get_overloads
-+ clear_overloads = typing.clear_overloads
-+else:
-+ # {module: {qualname: {firstlineno: func}}}
-+ _overload_registry = collections.defaultdict(
-+ functools.partial(collections.defaultdict, dict)
-+ )
-+
-+ def overload(func):
-+ """Decorator for overloaded functions/methods.
-+
-+ In a stub file, place two or more stub definitions for the same
-+ function in a row, each decorated with @overload. For example:
-+
-+ @overload
-+ def utf8(value: None) -> None: ...
-+ @overload
-+ def utf8(value: bytes) -> bytes: ...
-+ @overload
-+ def utf8(value: str) -> bytes: ...
-+
-+ In a non-stub file (i.e. a regular .py file), do the same but
-+ follow it with an implementation. The implementation should *not*
-+ be decorated with @overload. For example:
-+
-+ @overload
-+ def utf8(value: None) -> None: ...
-+ @overload
-+ def utf8(value: bytes) -> bytes: ...
-+ @overload
-+ def utf8(value: str) -> bytes: ...
-+ def utf8(value):
-+ # implementation goes here
-+
-+ The overloads for a function can be retrieved at runtime using the
-+ get_overloads() function.
-+ """
-+ # classmethod and staticmethod
-+ f = getattr(func, "__func__", func)
-+ try:
-+ _overload_registry[f.__module__][f.__qualname__][
-+ f.__code__.co_firstlineno
-+ ] = func
-+ except AttributeError:
-+ # Not a normal function; ignore.
-+ pass
-+ return _overload_dummy
-+
-+ def get_overloads(func):
-+ """Return all defined overloads for *func* as a sequence."""
-+ # classmethod and staticmethod
-+ f = getattr(func, "__func__", func)
-+ if f.__module__ not in _overload_registry:
-+ return []
-+ mod_dict = _overload_registry[f.__module__]
-+ if f.__qualname__ not in mod_dict:
-+ return []
-+ return list(mod_dict[f.__qualname__].values())
-+
-+ def clear_overloads():
-+ """Clear all overloads in the registry."""
-+ _overload_registry.clear()
-+
-+
-+# This is not a real generic class. Don't use outside annotations.
-+Type = typing.Type
-+
-+# Various ABCs mimicking those in collections.abc.
-+# A few are simply re-exported for completeness.
-+
-+
-+Awaitable = typing.Awaitable
-+Coroutine = typing.Coroutine
-+AsyncIterable = typing.AsyncIterable
-+AsyncIterator = typing.AsyncIterator
-+Deque = typing.Deque
-+ContextManager = typing.ContextManager
-+AsyncContextManager = typing.AsyncContextManager
-+DefaultDict = typing.DefaultDict
-+
-+# 3.7.2+
-+if hasattr(typing, 'OrderedDict'):
-+ OrderedDict = typing.OrderedDict
-+# 3.7.0-3.7.2
-+else:
-+ OrderedDict = typing._alias(collections.OrderedDict, (KT, VT))
-+
-+Counter = typing.Counter
-+ChainMap = typing.ChainMap
-+AsyncGenerator = typing.AsyncGenerator
-+NewType = typing.NewType
-+Text = typing.Text
-+TYPE_CHECKING = typing.TYPE_CHECKING
-+
-+
-+_PROTO_WHITELIST = ['Callable', 'Awaitable',
-+ 'Iterable', 'Iterator', 'AsyncIterable',
'AsyncIterator',
-+ 'Hashable', 'Sized', 'Container', 'Collection',
'Reversible',
-+ 'ContextManager', 'AsyncContextManager']
-+
-+
-+def _get_protocol_attrs(cls):
-+ attrs = set()
-+ for base in cls.__mro__[:-1]: # without object
-+ if base.__name__ in ('Protocol', 'Generic'):
-+ continue
-+ annotations = getattr(base, '__annotations__', {})
-+ for attr in list(base.__dict__.keys()) + list(annotations.keys()):
-+ if (not attr.startswith('_abc_') and attr not in (
-+ '__abstractmethods__', '__annotations__', '__weakref__',
-+ '_is_protocol', '_is_runtime_protocol', '__dict__',
-+ '__args__', '__slots__',
-+ '__next_in_mro__', '__parameters__', '__origin__',
-+ '__orig_bases__', '__extra__', '__tree_hash__',
-+ '__doc__', '__subclasshook__', '__init__', '__new__',
-+ '__module__', '_MutableMapping__marker', '_gorg')):
-+ attrs.add(attr)
-+ return attrs
-+
-+
-+def _is_callable_members_only(cls):
-+ return all(callable(getattr(cls, attr, None)) for attr in
_get_protocol_attrs(cls))
-+
-+
-+def _maybe_adjust_parameters(cls):
-+ """Helper function used in Protocol.__init_subclass__ and
_TypedDictMeta.__new__.
-+
-+ The contents of this function are very similar
-+ to logic found in typing.Generic.__init_subclass__
-+ on the CPython main branch.
-+ """
-+ tvars = []
-+ if '__orig_bases__' in cls.__dict__:
-+ tvars = typing._collect_type_vars(cls.__orig_bases__)
-+ # Look for Generic[T1, ..., Tn] or Protocol[T1, ..., Tn].
-+ # If found, tvars must be a subset of it.
-+ # If not found, tvars is it.
-+ # Also check for and reject plain Generic,
-+ # and reject multiple Generic[...] and/or Protocol[...].
-+ gvars = None
-+ for base in cls.__orig_bases__:
-+ if (isinstance(base, typing._GenericAlias) and
-+ base.__origin__ in (typing.Generic, Protocol)):
-+ # for error messages
-+ the_base = base.__origin__.__name__
-+ if gvars is not None:
-+ raise TypeError(
-+ "Cannot inherit from Generic[...]"
-+ " and/or Protocol[...] multiple types.")
-+ gvars = base.__parameters__
-+ if gvars is None:
-+ gvars = tvars
-+ else:
-+ tvarset = set(tvars)
-+ gvarset = set(gvars)
-+ if not tvarset <= gvarset:
-+ s_vars = ', '.join(str(t) for t in tvars if t not in
gvarset)
-+ s_args = ', '.join(str(g) for g in gvars)
-+ raise TypeError(f"Some type variables ({s_vars}) are"
-+ f" not listed in {the_base}[{s_args}]")
-+ tvars = gvars
-+ cls.__parameters__ = tuple(tvars)
-+
-+
-+# 3.8+
-+if hasattr(typing, 'Protocol'):
-+ Protocol = typing.Protocol
-+# 3.7
-+else:
-+
-+ def _no_init(self, *args, **kwargs):
-+ if type(self)._is_protocol:
-+ raise TypeError('Protocols cannot be instantiated')
-+
-+ class _ProtocolMeta(abc.ABCMeta): # noqa: B024
-+ # This metaclass is a bit unfortunate and exists only because of
the lack
-+ # of __instancehook__.
-+ def __instancecheck__(cls, instance):
-+ # We need this method for situations where attributes are
-+ # assigned in __init__.
-+ if ((not getattr(cls, '_is_protocol', False) or
-+ _is_callable_members_only(cls)) and
-+ issubclass(instance.__class__, cls)):
-+ return True
-+ if cls._is_protocol:
-+ if all(hasattr(instance, attr) and
-+ (not callable(getattr(cls, attr, None)) or
-+ getattr(instance, attr) is not None)
-+ for attr in _get_protocol_attrs(cls)):
-+ return True
-+ return super().__instancecheck__(instance)
-+
-+ class Protocol(metaclass=_ProtocolMeta):
-+ # There is quite a lot of overlapping code with typing.Generic.
-+ # Unfortunately it is hard to avoid this while these live in two
different
-+ # modules. The duplicated code will be removed when Protocol is
moved to typing.
-+ """Base class for protocol classes. Protocol classes are defined
as::
-+
-+ class Proto(Protocol):
-+ def meth(self) -> int:
-+ ...
-+
-+ Such classes are primarily used with static type checkers that
recognize
-+ structural subtyping (static duck-typing), for example::
-+
-+ class C:
-+ def meth(self) -> int:
-+ return 0
-+
-+ def func(x: Proto) -> int:
-+ return x.meth()
-+
-+ func(C()) # Passes static type check
-+
-+ See PEP 544 for details. Protocol classes decorated with
-+ @typing_extensions.runtime act as simple-minded runtime protocol
that checks
-+ only the presence of given attributes, ignoring their type
signatures.
-+
-+ Protocol classes can be generic, they are defined as::
-+
-+ class GenProto(Protocol[T]):
-+ def meth(self) -> T:
-+ ...
-+ """
-+ __slots__ = ()
-+ _is_protocol = True
-+
-+ def __new__(cls, *args, **kwds):
-+ if cls is Protocol:
-+ raise TypeError("Type Protocol cannot be instantiated; "
-+ "it can only be used as a base class")
-+ return super().__new__(cls)
-+
-+ @typing._tp_cache
-+ def __class_getitem__(cls, params):
-+ if not isinstance(params, tuple):
-+ params = (params,)
-+ if not params and cls is not typing.Tuple:
-+ raise TypeError(
-+ f"Parameter list to {cls.__qualname__}[...] cannot be
empty")
-+ msg = "Parameters to generic types must be types."
-+ params = tuple(typing._type_check(p, msg) for p in params) #
noqa
-+ if cls is Protocol:
-+ # Generic can only be subscripted with unique type
variables.
-+ if not all(isinstance(p, typing.TypeVar) for p in params):
-+ i = 0
-+ while isinstance(params[i], typing.TypeVar):
-+ i += 1
-+ raise TypeError(
-+ "Parameters to Protocol[...] must all be type
variables."
-+ f" Parameter {i + 1} is {params[i]}")
-+ if len(set(params)) != len(params):
-+ raise TypeError(
-+ "Parameters to Protocol[...] must all be unique")
-+ else:
-+ # Subscripting a regular Generic subclass.
-+ _check_generic(cls, params, len(cls.__parameters__))
-+ return typing._GenericAlias(cls, params)
-+
-+ def __init_subclass__(cls, *args, **kwargs):
-+ if '__orig_bases__' in cls.__dict__:
-+ error = typing.Generic in cls.__orig_bases__
-+ else:
-+ error = typing.Generic in cls.__bases__
-+ if error:
-+ raise TypeError("Cannot inherit from plain Generic")
-+ _maybe_adjust_parameters(cls)
-+
-+ # Determine if this is a protocol or a concrete subclass.
-+ if not cls.__dict__.get('_is_protocol', None):
-+ cls._is_protocol = any(b is Protocol for b in cls.__bases__)
-+
-+ # Set (or override) the protocol subclass hook.
-+ def _proto_hook(other):
-+ if not cls.__dict__.get('_is_protocol', None):
-+ return NotImplemented
-+ if not getattr(cls, '_is_runtime_protocol', False):
-+ if sys._getframe(2).f_globals['__name__'] in ['abc',
'functools']:
-+ return NotImplemented
-+ raise TypeError("Instance and class checks can only be
used with"
-+ " @runtime protocols")
-+ if not _is_callable_members_only(cls):
-+ if sys._getframe(2).f_globals['__name__'] in ['abc',
'functools']:
-+ return NotImplemented
-+ raise TypeError("Protocols with non-method members"
-+ " don't support issubclass()")
-+ if not isinstance(other, type):
-+ # Same error as for issubclass(1, int)
-+ raise TypeError('issubclass() arg 1 must be a class')
-+ for attr in _get_protocol_attrs(cls):
-+ for base in other.__mro__:
-+ if attr in base.__dict__:
-+ if base.__dict__[attr] is None:
-+ return NotImplemented
-+ break
-+ annotations = getattr(base, '__annotations__', {})
-+ if (isinstance(annotations, typing.Mapping) and
-+ attr in annotations and
-+ isinstance(other, _ProtocolMeta) and
-+ other._is_protocol):
-+ break
-+ else:
-+ return NotImplemented
-+ return True
-+ if '__subclasshook__' not in cls.__dict__:
-+ cls.__subclasshook__ = _proto_hook
-+
-+ # We have nothing more to do for non-protocols.
-+ if not cls._is_protocol:
-+ return
-+
-+ # Check consistency of bases.
-+ for base in cls.__bases__:
-+ if not (base in (object, typing.Generic) or
-+ base.__module__ == 'collections.abc' and
-+ base.__name__ in _PROTO_WHITELIST or
-+ isinstance(base, _ProtocolMeta) and
base._is_protocol):
-+ raise TypeError('Protocols can only inherit from other'
-+ f' protocols, got {repr(base)}')
-+ cls.__init__ = _no_init
-+
-+
-+# 3.8+
-+if hasattr(typing, 'runtime_checkable'):
-+ runtime_checkable = typing.runtime_checkable
-+# 3.7
-+else:
-+ def runtime_checkable(cls):
-+ """Mark a protocol class as a runtime protocol, so that it
-+ can be used with isinstance() and issubclass(). Raise TypeError
-+ if applied to a non-protocol class.
-+
-+ This allows a simple-minded structural check very similar to the
-+ one-offs in collections.abc such as Hashable.
-+ """
-+ if not isinstance(cls, _ProtocolMeta) or not cls._is_protocol:
-+ raise TypeError('@runtime_checkable can be only applied to
protocol classes,'
-+ f' got {cls!r}')
-+ cls._is_runtime_protocol = True
-+ return cls
-+
-+
-+# Exists for backwards compatibility.
-+runtime = runtime_checkable
-+
-+
-+# 3.8+
-+if hasattr(typing, 'SupportsIndex'):
-+ SupportsIndex = typing.SupportsIndex
-+# 3.7
-+else:
-+ @runtime_checkable
-+ class SupportsIndex(Protocol):
-+ __slots__ = ()
-+
-+ @abc.abstractmethod
-+ def __index__(self) -> int:
-+ pass
-+
-+
-+if hasattr(typing, "Required"):
-+ # The standard library TypedDict in Python 3.8 does not store runtime
information
-+ # about which (if any) keys are optional. See
https://bugs.python.org/issue38834
-+ # The standard library TypedDict in Python 3.9.0/1 does not honour the
"total"
-+ # keyword with old-style TypedDict(). See
https://bugs.python.org/issue42059
-+ # The standard library TypedDict below Python 3.11 does not store
runtime
-+ # information about optional and required keys when using Required or
NotRequired.
-+ # Generic TypedDicts are also impossible using typing.TypedDict on
Python <3.11.
-+ TypedDict = typing.TypedDict
-+ _TypedDictMeta = typing._TypedDictMeta
-+ is_typeddict = typing.is_typeddict
-+else:
-+ def _check_fails(cls, other):
-+ try:
-+ if sys._getframe(1).f_globals['__name__'] not in ['abc',
-+ 'functools',
-+ 'typing']:
-+ # Typed dicts are only for static structural subtyping.
-+ raise TypeError('TypedDict does not support instance and
class checks')
-+ except (AttributeError, ValueError):
-+ pass
-+ return False
-+
-+ def _dict_new(*args, **kwargs):
-+ if not args:
-+ raise TypeError('TypedDict.__new__(): not enough arguments')
-+ _, args = args[0], args[1:] # allow the "cls" keyword be passed
-+ return dict(*args, **kwargs)
-+
-+ _dict_new.__text_signature__ = '($cls, _typename, _fields=None, /,
**kwargs)'
-+
-+ def _typeddict_new(*args, total=True, **kwargs):
-+ if not args:
-+ raise TypeError('TypedDict.__new__(): not enough arguments')
-+ _, args = args[0], args[1:] # allow the "cls" keyword be passed
-+ if args:
-+ typename, args = args[0], args[1:] # allow the "_typename"
keyword be passed
-+ elif '_typename' in kwargs:
-+ typename = kwargs.pop('_typename')
-+ import warnings
-+ warnings.warn("Passing '_typename' as keyword argument is
deprecated",
-+ DeprecationWarning, stacklevel=2)
-+ else:
-+ raise TypeError("TypedDict.__new__() missing 1 required
positional "
-+ "argument: '_typename'")
-+ if args:
-+ try:
-+ fields, = args # allow the "_fields" keyword be passed
-+ except ValueError:
-+ raise TypeError('TypedDict.__new__() takes from 2 to 3 '
-+ f'positional arguments but {len(args) + 2} '
-+ 'were given')
-+ elif '_fields' in kwargs and len(kwargs) == 1:
-+ fields = kwargs.pop('_fields')
-+ import warnings
-+ warnings.warn("Passing '_fields' as keyword argument is
deprecated",
-+ DeprecationWarning, stacklevel=2)
-+ else:
-+ fields = None
-+
-+ if fields is None:
-+ fields = kwargs
-+ elif kwargs:
-+ raise TypeError("TypedDict takes either a dict or keyword
arguments,"
-+ " but not both")
-+
-+ ns = {'__annotations__': dict(fields)}
-+ try:
-+ # Setting correct module is necessary to make typed dict
classes pickleable.
-+ ns['__module__'] = sys._getframe(1).f_globals.get('__name__',
'__main__')
-+ except (AttributeError, ValueError):
-+ pass
-+
-+ return _TypedDictMeta(typename, (), ns, total=total)
-+
-+ _typeddict_new.__text_signature__ = ('($cls, _typename, _fields=None,'
-+ ' /, *, total=True, **kwargs)')
-+
-+ class _TypedDictMeta(type):
-+ def __init__(cls, name, bases, ns, total=True):
-+ super().__init__(name, bases, ns)
-+
-+ def __new__(cls, name, bases, ns, total=True):
-+ # Create new typed dict class object.
-+ # This method is called directly when TypedDict is subclassed,
-+ # or via _typeddict_new when TypedDict is instantiated. This way
-+ # TypedDict supports all three syntaxes described in its
docstring.
-+ # Subclasses and instances of TypedDict return actual
dictionaries
-+ # via _dict_new.
-+ ns['__new__'] = _typeddict_new if name == 'TypedDict' else
_dict_new
-+ # Don't insert typing.Generic into __bases__ here,
-+ # or Generic.__init_subclass__ will raise TypeError
-+ # in the super().__new__() call.
-+ # Instead, monkey-patch __bases__ onto the class after it's
been created.
-+ tp_dict = super().__new__(cls, name, (dict,), ns)
-+
-+ if any(issubclass(base, typing.Generic) for base in bases):
-+ tp_dict.__bases__ = (typing.Generic, dict)
-+ _maybe_adjust_parameters(tp_dict)
-+
-+ annotations = {}
-+ own_annotations = ns.get('__annotations__', {})
-+ msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be
a type"
-+ own_annotations = {
-+ n: typing._type_check(tp, msg) for n, tp in
own_annotations.items()
-+ }
-+ required_keys = set()
-+ optional_keys = set()
-+
-+ for base in bases:
-+ annotations.update(base.__dict__.get('__annotations__', {}))
-+ required_keys.update(base.__dict__.get('__required_keys__',
()))
-+ optional_keys.update(base.__dict__.get('__optional_keys__',
()))
-+
-+ annotations.update(own_annotations)
-+ for annotation_key, annotation_type in own_annotations.items():
-+ annotation_origin = get_origin(annotation_type)
-+ if annotation_origin is Annotated:
-+ annotation_args = get_args(annotation_type)
-+ if annotation_args:
-+ annotation_type = annotation_args[0]
-+ annotation_origin = get_origin(annotation_type)
-+
-+ if annotation_origin is Required:
-+ required_keys.add(annotation_key)
-+ elif annotation_origin is NotRequired:
-+ optional_keys.add(annotation_key)
-+ elif total:
-+ required_keys.add(annotation_key)
-+ else:
-+ optional_keys.add(annotation_key)
-+
-+ tp_dict.__annotations__ = annotations
-+ tp_dict.__required_keys__ = frozenset(required_keys)
-+ tp_dict.__optional_keys__ = frozenset(optional_keys)
-+ if not hasattr(tp_dict, '__total__'):
-+ tp_dict.__total__ = total
-+ return tp_dict
-+
-+ __instancecheck__ = __subclasscheck__ = _check_fails
-+
-+ TypedDict = _TypedDictMeta('TypedDict', (dict,), {})
-+ TypedDict.__module__ = __name__
-+ TypedDict.__doc__ = \
-+ """A simple typed name space. At runtime it is equivalent to a
plain dict.
-+
-+ TypedDict creates a dictionary type that expects all of its
-+ instances to have a certain set of keys, with each key
-+ associated with a value of a consistent type. This expectation
-+ is not checked at runtime but is only enforced by type checkers.
-+ Usage::
-+
-+ class Point2D(TypedDict):
-+ x: int
-+ y: int
-+ label: str
-+
-+ a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK
-+ b: Point2D = {'z': 3, 'label': 'bad'} # Fails type
check
-+
-+ assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2,
label='first')
-+
-+ The type info can be accessed via the Point2D.__annotations__ dict,
and
-+ the Point2D.__required_keys__ and Point2D.__optional_keys__
frozensets.
-+ TypedDict supports two additional equivalent forms::
-+
-+ Point2D = TypedDict('Point2D', x=int, y=int, label=str)
-+ Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label':
str})
-+
-+ The class syntax is only supported in Python 3.6+, while two other
-+ syntax forms work for Python 2.7 and 3.2+
-+ """
-+
-+ if hasattr(typing, "_TypedDictMeta"):
-+ _TYPEDDICT_TYPES = (typing._TypedDictMeta, _TypedDictMeta)
-+ else:
-+ _TYPEDDICT_TYPES = (_TypedDictMeta,)
-+
-+ def is_typeddict(tp):
-+ """Check if an annotation is a TypedDict class
-+
-+ For example::
-+ class Film(TypedDict):
-+ title: str
-+ year: int
-+
-+ is_typeddict(Film) # => True
-+ is_typeddict(Union[list, str]) # => False
-+ """
-+ return isinstance(tp, tuple(_TYPEDDICT_TYPES))
-+
-+
-+if hasattr(typing, "assert_type"):
-+ assert_type = typing.assert_type
-+
-+else:
-+ def assert_type(__val, __typ):
-+ """Assert (to the type checker) that the value is of the given type.
-+
-+ When the type checker encounters a call to assert_type(), it
-+ emits an error if the value is not of the specified type::
-+
-+ def greet(name: str) -> None:
-+ assert_type(name, str) # ok
-+ assert_type(name, int) # type checker error
-+
-+ At runtime this returns the first argument unchanged and otherwise
-+ does nothing.
-+ """
-+ return __val
-+
-+
-+if hasattr(typing, "Required"):
-+ get_type_hints = typing.get_type_hints
-+else:
-+ import functools
-+ import types
-+
-+ # replaces _strip_annotations()
-+ def _strip_extras(t):
-+ """Strips Annotated, Required and NotRequired from a given type."""
-+ if isinstance(t, _AnnotatedAlias):
-+ return _strip_extras(t.__origin__)
-+ if hasattr(t, "__origin__") and t.__origin__ in (Required,
NotRequired):
-+ return _strip_extras(t.__args__[0])
-+ if isinstance(t, typing._GenericAlias):
-+ stripped_args = tuple(_strip_extras(a) for a in t.__args__)
-+ if stripped_args == t.__args__:
-+ return t
-+ return t.copy_with(stripped_args)
-+ if hasattr(types, "GenericAlias") and isinstance(t,
types.GenericAlias):
-+ stripped_args = tuple(_strip_extras(a) for a in t.__args__)
-+ if stripped_args == t.__args__:
-+ return t
-+ return types.GenericAlias(t.__origin__, stripped_args)
-+ if hasattr(types, "UnionType") and isinstance(t, types.UnionType):
-+ stripped_args = tuple(_strip_extras(a) for a in t.__args__)
-+ if stripped_args == t.__args__:
-+ return t
-+ return functools.reduce(operator.or_, stripped_args)
-+
-+ return t
-+
-+ def get_type_hints(obj, globalns=None, localns=None,
include_extras=False):
-+ """Return type hints for an object.
-+
-+ This is often the same as obj.__annotations__, but it handles
-+ forward references encoded as string literals, adds Optional[t] if a
-+ default value equal to None is set and recursively replaces all
-+ 'Annotated[T, ...]', 'Required[T]' or 'NotRequired[T]' with 'T'
-+ (unless 'include_extras=True').
-+
-+ The argument may be a module, class, method, or function. The
annotations
-+ are returned as a dictionary. For classes, annotations include also
-+ inherited members.
-+
-+ TypeError is raised if the argument is not of a type that can
contain
-+ annotations, and an empty dictionary is returned if no annotations
are
-+ present.
-+
-+ BEWARE -- the behavior of globalns and localns is counterintuitive
-+ (unless you are familiar with how eval() and exec() work). The
-+ search order is locals first, then globals.
-+
-+ - If no dict arguments are passed, an attempt is made to use the
-+ globals from obj (or the respective module's globals for classes),
-+ and these are also used as the locals. If the object does not
appear
-+ to have globals, an empty dictionary is used.
-+
-+ - If one dict argument is passed, it is used for both globals and
-+ locals.
-+
-+ - If two dict arguments are passed, they specify globals and
-+ locals, respectively.
-+ """
-+ if hasattr(typing, "Annotated"):
-+ hint = typing.get_type_hints(
-+ obj, globalns=globalns, localns=localns, include_extras=True
-+ )
-+ else:
-+ hint = typing.get_type_hints(obj, globalns=globalns,
localns=localns)
-+ if include_extras:
-+ return hint
-+ return {k: _strip_extras(t) for k, t in hint.items()}
-+
-+
-+# Python 3.9+ has PEP 593 (Annotated)
-+if hasattr(typing, 'Annotated'):
-+ Annotated = typing.Annotated
-+ # Not exported and not a public API, but needed for get_origin() and
get_args()
-+ # to work.
-+ _AnnotatedAlias = typing._AnnotatedAlias
-+# 3.7-3.8
-+else:
-+ class _AnnotatedAlias(typing._GenericAlias, _root=True):
-+ """Runtime representation of an annotated type.
-+
-+ At its core 'Annotated[t, dec1, dec2, ...]' is an alias for the
type 't'
-+ with extra annotations. The alias behaves like a normal typing
alias,
-+ instantiating is the same as instantiating the underlying type,
binding
-+ it to types is also the same.
-+ """
-+ def __init__(self, origin, metadata):
-+ if isinstance(origin, _AnnotatedAlias):
-+ metadata = origin.__metadata__ + metadata
-+ origin = origin.__origin__
-+ super().__init__(origin, origin)
-+ self.__metadata__ = metadata
-+
-+ def copy_with(self, params):
-+ assert len(params) == 1
-+ new_type = params[0]
-+ return _AnnotatedAlias(new_type, self.__metadata__)
-+
-+ def __repr__(self):
-+ return
(f"typing_extensions.Annotated[{typing._type_repr(self.__origin__)}, "
-+ f"{', '.join(repr(a) for a in self.__metadata__)}]")
-+
-+ def __reduce__(self):
-+ return operator.getitem, (
-+ Annotated, (self.__origin__,) + self.__metadata__
-+ )
-+
-+ def __eq__(self, other):
-+ if not isinstance(other, _AnnotatedAlias):
-+ return NotImplemented
-+ if self.__origin__ != other.__origin__:
-+ return False
-+ return self.__metadata__ == other.__metadata__
-+
-+ def __hash__(self):
-+ return hash((self.__origin__, self.__metadata__))
-+
-+ class Annotated:
-+ """Add context specific metadata to a type.
-+
-+ Example: Annotated[int, runtime_check.Unsigned] indicates to the
-+ hypothetical runtime_check module that this type is an unsigned int.
-+ Every other consumer of this type can ignore this metadata and treat
-+ this type as int.
-+
-+ The first argument to Annotated must be a valid type (and will be in
-+ the __origin__ field), the remaining arguments are kept as a tuple
in
-+ the __extra__ field.
-+
-+ Details:
-+
-+ - It's an error to call `Annotated` with less than two arguments.
-+ - Nested Annotated are flattened::
-+
-+ Annotated[Annotated[T, Ann1, Ann2], Ann3] == Annotated[T, Ann1,
Ann2, Ann3]
-+
-+ - Instantiating an annotated type is equivalent to instantiating the
-+ underlying type::
-+
-+ Annotated[C, Ann1](5) == C(5)
-+
-+ - Annotated can be used as a generic type alias::
-+
-+ Optimized = Annotated[T, runtime.Optimize()]
-+ Optimized[int] == Annotated[int, runtime.Optimize()]
-+
-+ OptimizedList = Annotated[List[T], runtime.Optimize()]
-+ OptimizedList[int] == Annotated[List[int], runtime.Optimize()]
-+ """
-+
-+ __slots__ = ()
-+
-+ def __new__(cls, *args, **kwargs):
-+ raise TypeError("Type Annotated cannot be instantiated.")
-+
-+ @typing._tp_cache
-+ def __class_getitem__(cls, params):
-+ if not isinstance(params, tuple) or len(params) < 2:
-+ raise TypeError("Annotated[...] should be used "
-+ "with at least two arguments (a type and an
"
-+ "annotation).")
-+ allowed_special_forms = (ClassVar, Final)
-+ if get_origin(params[0]) in allowed_special_forms:
-+ origin = params[0]
-+ else:
-+ msg = "Annotated[t, ...]: t must be a type."
-+ origin = typing._type_check(params[0], msg)
-+ metadata = tuple(params[1:])
-+ return _AnnotatedAlias(origin, metadata)
-+
-+ def __init_subclass__(cls, *args, **kwargs):
-+ raise TypeError(
-+ f"Cannot subclass {cls.__module__}.Annotated"
-+ )
-+
-+# Python 3.8 has get_origin() and get_args() but those implementations
aren't
-+# Annotated-aware, so we can't use those. Python 3.9's versions don't
support
-+# ParamSpecArgs and ParamSpecKwargs, so only Python 3.10's versions will do.
-+if sys.version_info[:2] >= (3, 10):
-+ get_origin = typing.get_origin
-+ get_args = typing.get_args
-+# 3.7-3.9
-+else:
-+ try:
-+ # 3.9+
-+ from typing import _BaseGenericAlias
-+ except ImportError:
-+ _BaseGenericAlias = typing._GenericAlias
-+ try:
-+ # 3.9+
-+ from typing import GenericAlias as _typing_GenericAlias
-+ except ImportError:
-+ _typing_GenericAlias = typing._GenericAlias
-+
-+ def get_origin(tp):
-+ """Get the unsubscripted version of a type.
-+
-+ This supports generic types, Callable, Tuple, Union, Literal,
Final, ClassVar
-+ and Annotated. Return None for unsupported types. Examples::
-+
-+ get_origin(Literal[42]) is Literal
-+ get_origin(int) is None
-+ get_origin(ClassVar[int]) is ClassVar
-+ get_origin(Generic) is Generic
-+ get_origin(Generic[T]) is Generic
-+ get_origin(Union[T, int]) is Union
-+ get_origin(List[Tuple[T, T]][int]) == list
-+ get_origin(P.args) is P
-+ """
-+ if isinstance(tp, _AnnotatedAlias):
-+ return Annotated
-+ if isinstance(tp, (typing._GenericAlias, _typing_GenericAlias,
_BaseGenericAlias,
-+ ParamSpecArgs, ParamSpecKwargs)):
-+ return tp.__origin__
-+ if tp is typing.Generic:
-+ return typing.Generic
-+ return None
-+
-+ def get_args(tp):
-+ """Get type arguments with all substitutions performed.
-+
-+ For unions, basic simplifications used by Union constructor are
performed.
-+ Examples::
-+ get_args(Dict[str, int]) == (str, int)
-+ get_args(int) == ()
-+ get_args(Union[int, Union[T, int], str][int]) == (int, str)
-+ get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str,
int])
-+ get_args(Callable[[], T][int]) == ([], int)
-+ """
-+ if isinstance(tp, _AnnotatedAlias):
-+ return (tp.__origin__,) + tp.__metadata__
-+ if isinstance(tp, (typing._GenericAlias, _typing_GenericAlias)):
-+ if getattr(tp, "_special", False):
-+ return ()
-+ res = tp.__args__
-+ if get_origin(tp) is collections.abc.Callable and res[0] is not
Ellipsis:
-+ res = (list(res[:-1]), res[-1])
-+ return res
-+ return ()
-+
-+
-+# 3.10+
-+if hasattr(typing, 'TypeAlias'):
-+ TypeAlias = typing.TypeAlias
-+# 3.9
-+elif sys.version_info[:2] >= (3, 9):
-+ class _TypeAliasForm(typing._SpecialForm, _root=True):
-+ def __repr__(self):
-+ return 'typing_extensions.' + self._name
-+
-+ @_TypeAliasForm
-+ def TypeAlias(self, parameters):
-+ """Special marker indicating that an assignment should
-+ be recognized as a proper type alias definition by type
-+ checkers.
-+
-+ For example::
-+
-+ Predicate: TypeAlias = Callable[..., bool]
-+
-+ It's invalid when used anywhere except as in the example above.
-+ """
-+ raise TypeError(f"{self} is not subscriptable")
-+# 3.7-3.8
-+else:
-+ class _TypeAliasForm(typing._SpecialForm, _root=True):
-+ def __repr__(self):
-+ return 'typing_extensions.' + self._name
-+
-+ TypeAlias = _TypeAliasForm('TypeAlias',
-+ doc="""Special marker indicating that an
assignment should
-+ be recognized as a proper type alias
definition by type
-+ checkers.
-+
-+ For example::
-+
-+ Predicate: TypeAlias = Callable[...,
bool]
-+
-+ It's invalid when used anywhere except as in
the example
-+ above.""")
-+
-+
-+class _DefaultMixin:
-+ """Mixin for TypeVarLike defaults."""
-+
-+ __slots__ = ()
-+
-+ def __init__(self, default):
-+ if isinstance(default, (tuple, list)):
-+ self.__default__ = tuple((typing._type_check(d, "Default must
be a type")
-+ for d in default))
-+ elif default:
-+ self.__default__ = typing._type_check(default, "Default must be
a type")
-+ else:
-+ self.__default__ = None
-+
-+
-+# Add default and infer_variance parameters from PEP 696 and 695
-+class TypeVar(typing.TypeVar, _DefaultMixin, _root=True):
-+ """Type variable."""
-+
-+ __module__ = 'typing'
-+
-+ def __init__(self, name, *constraints, bound=None,
-+ covariant=False, contravariant=False,
-+ default=None, infer_variance=False):
-+ super().__init__(name, *constraints, bound=bound,
covariant=covariant,
-+ contravariant=contravariant)
-+ _DefaultMixin.__init__(self, default)
-+ self.__infer_variance__ = infer_variance
-+
-+ # for pickling:
-+ try:
-+ def_mod = sys._getframe(1).f_globals.get('__name__', '__main__')
-+ except (AttributeError, ValueError):
-+ def_mod = None
-+ if def_mod != 'typing_extensions':
-+ self.__module__ = def_mod
-+
-+
-+# Python 3.10+ has PEP 612
-+if hasattr(typing, 'ParamSpecArgs'):
-+ ParamSpecArgs = typing.ParamSpecArgs
-+ ParamSpecKwargs = typing.ParamSpecKwargs
-+# 3.7-3.9
-+else:
-+ class _Immutable:
-+ """Mixin to indicate that object should not be copied."""
-+ __slots__ = ()
-+
-+ def __copy__(self):
-+ return self
-+
-+ def __deepcopy__(self, memo):
-+ return self
-+
-+ class ParamSpecArgs(_Immutable):
-+ """The args for a ParamSpec object.
-+
-+ Given a ParamSpec object P, P.args is an instance of ParamSpecArgs.
-+
-+ ParamSpecArgs objects have a reference back to their ParamSpec:
-+
-+ P.args.__origin__ is P
-+
-+ This type is meant for runtime introspection and has no special
meaning to
-+ static type checkers.
-+ """
-+ def __init__(self, origin):
-+ self.__origin__ = origin
-+
-+ def __repr__(self):
-+ return f"{self.__origin__.__name__}.args"
-+
-+ def __eq__(self, other):
-+ if not isinstance(other, ParamSpecArgs):
-+ return NotImplemented
-+ return self.__origin__ == other.__origin__
-+
-+ class ParamSpecKwargs(_Immutable):
-+ """The kwargs for a ParamSpec object.
-+
-+ Given a ParamSpec object P, P.kwargs is an instance of
ParamSpecKwargs.
-+
-+ ParamSpecKwargs objects have a reference back to their ParamSpec:
-+
-+ P.kwargs.__origin__ is P
-+
-+ This type is meant for runtime introspection and has no special
meaning to
-+ static type checkers.
-+ """
-+ def __init__(self, origin):
-+ self.__origin__ = origin
-+
-+ def __repr__(self):
-+ return f"{self.__origin__.__name__}.kwargs"
-+
-+ def __eq__(self, other):
-+ if not isinstance(other, ParamSpecKwargs):
-+ return NotImplemented
-+ return self.__origin__ == other.__origin__
-+
-+# 3.10+
-+if hasattr(typing, 'ParamSpec'):
-+
-+ # Add default Parameter - PEP 696
-+ class ParamSpec(typing.ParamSpec, _DefaultMixin, _root=True):
-+ """Parameter specification variable."""
-+
-+ __module__ = 'typing'
-+
-+ def __init__(self, name, *, bound=None, covariant=False,
contravariant=False,
-+ default=None):
-+ super().__init__(name, bound=bound, covariant=covariant,
-+ contravariant=contravariant)
-+ _DefaultMixin.__init__(self, default)
-+
-+ # for pickling:
-+ try:
-+ def_mod = sys._getframe(1).f_globals.get('__name__',
'__main__')
-+ except (AttributeError, ValueError):
-+ def_mod = None
-+ if def_mod != 'typing_extensions':
-+ self.__module__ = def_mod
-+
-+# 3.7-3.9
-+else:
-+
-+ # Inherits from list as a workaround for Callable checks in Python <
3.9.2.
-+ class ParamSpec(list, _DefaultMixin):
-+ """Parameter specification variable.
-+
-+ Usage::
-+
-+ P = ParamSpec('P')
-+
-+ Parameter specification variables exist primarily for the benefit
of static
-+ type checkers. They are used to forward the parameter types of one
-+ callable to another callable, a pattern commonly found in higher
order
-+ functions and decorators. They are only valid when used in
``Concatenate``,
-+ or s the first argument to ``Callable``. In Python 3.10 and higher,
-+ they are also supported in user-defined Generics at runtime.
-+ See class Generic for more information on generic types. An
-+ example for annotating a decorator::
-+
-+ T = TypeVar('T')
-+ P = ParamSpec('P')
-+
-+ def add_logging(f: Callable[P, T]) -> Callable[P, T]:
-+ '''A type-safe decorator to add logging to a function.'''
-+ def inner(*args: P.args, **kwargs: P.kwargs) -> T:
-+ logging.info(f'{f.__name__} was called')
-+ return f(*args, **kwargs)
-+ return inner
-+
-+ @add_logging
-+ def add_two(x: float, y: float) -> float:
-+ '''Add two numbers together.'''
-+ return x + y
-+
-+ Parameter specification variables defined with covariant=True or
-+ contravariant=True can be used to declare covariant or contravariant
-+ generic types. These keyword arguments are valid, but their actual
semantics
-+ are yet to be decided. See PEP 612 for details.
-+
-+ Parameter specification variables can be introspected. e.g.:
-+
-+ P.__name__ == 'T'
-+ P.__bound__ == None
-+ P.__covariant__ == False
-+ P.__contravariant__ == False
-+
-+ Note that only parameter specification variables defined in global
scope can
-+ be pickled.
-+ """
-+
-+ # Trick Generic __parameters__.
-+ __class__ = typing.TypeVar
-+
-+ @property
-+ def args(self):
-+ return ParamSpecArgs(self)
-+
-+ @property
-+ def kwargs(self):
-+ return ParamSpecKwargs(self)
-+
-+ def __init__(self, name, *, bound=None, covariant=False,
contravariant=False,
-+ default=None):
-+ super().__init__([self])
-+ self.__name__ = name
-+ self.__covariant__ = bool(covariant)
-+ self.__contravariant__ = bool(contravariant)
-+ if bound:
-+ self.__bound__ = typing._type_check(bound, 'Bound must be a
type.')
-+ else:
-+ self.__bound__ = None
-+ _DefaultMixin.__init__(self, default)
-+
-+ # for pickling:
-+ try:
-+ def_mod = sys._getframe(1).f_globals.get('__name__',
'__main__')
-+ except (AttributeError, ValueError):
-+ def_mod = None
-+ if def_mod != 'typing_extensions':
-+ self.__module__ = def_mod
-+
-+ def __repr__(self):
-+ if self.__covariant__:
-+ prefix = '+'
-+ elif self.__contravariant__:
-+ prefix = '-'
-+ else:
-+ prefix = '~'
-+ return prefix + self.__name__
-+
-+ def __hash__(self):
-+ return object.__hash__(self)
-+
-+ def __eq__(self, other):
-+ return self is other
-+
-+ def __reduce__(self):
-+ return self.__name__
-+
-+ # Hack to get typing._type_check to pass.
-+ def __call__(self, *args, **kwargs):
-+ pass
-+
-+
-+# 3.7-3.9
-+if not hasattr(typing, 'Concatenate'):
-+ # Inherits from list as a workaround for Callable checks in Python <
3.9.2.
-+ class _ConcatenateGenericAlias(list):
-+
-+ # Trick Generic into looking into this for __parameters__.
-+ __class__ = typing._GenericAlias
-+
-+ # Flag in 3.8.
-+ _special = False
-+
-+ def __init__(self, origin, args):
-+ super().__init__(args)
-+ self.__origin__ = origin
-+ self.__args__ = args
-+
-+ def __repr__(self):
-+ _type_repr = typing._type_repr
-+ return (f'{_type_repr(self.__origin__)}'
-+ f'[{", ".join(_type_repr(arg) for arg in
self.__args__)}]')
-+
-+ def __hash__(self):
-+ return hash((self.__origin__, self.__args__))
-+
-+ # Hack to get typing._type_check to pass in Generic.
-+ def __call__(self, *args, **kwargs):
-+ pass
-+
-+ @property
-+ def __parameters__(self):
-+ return tuple(
-+ tp for tp in self.__args__ if isinstance(tp,
(typing.TypeVar, ParamSpec))
-+ )
-+
-+
-+# 3.7-3.9
-+@typing._tp_cache
-+def _concatenate_getitem(self, parameters):
-+ if parameters == ():
-+ raise TypeError("Cannot take a Concatenate of no types.")
-+ if not isinstance(parameters, tuple):
-+ parameters = (parameters,)
-+ if not isinstance(parameters[-1], ParamSpec):
-+ raise TypeError("The last parameter to Concatenate should be a "
-+ "ParamSpec variable.")
-+ msg = "Concatenate[arg, ...]: each arg must be a type."
-+ parameters = tuple(typing._type_check(p, msg) for p in parameters)
-+ return _ConcatenateGenericAlias(self, parameters)
-+
-+
-+# 3.10+
-+if hasattr(typing, 'Concatenate'):
-+ Concatenate = typing.Concatenate
-+ _ConcatenateGenericAlias = typing._ConcatenateGenericAlias # noqa
-+# 3.9
-+elif sys.version_info[:2] >= (3, 9):
-+ @_TypeAliasForm
-+ def Concatenate(self, parameters):
-+ """Used in conjunction with ``ParamSpec`` and ``Callable`` to
represent a
-+ higher order function which adds, removes or transforms parameters
of a
-+ callable.
-+
-+ For example::
-+
-+ Callable[Concatenate[int, P], int]
-+
-+ See PEP 612 for detailed information.
-+ """
-+ return _concatenate_getitem(self, parameters)
-+# 3.7-8
-+else:
-+ class _ConcatenateForm(typing._SpecialForm, _root=True):
-+ def __repr__(self):
-+ return 'typing_extensions.' + self._name
-+
-+ def __getitem__(self, parameters):
-+ return _concatenate_getitem(self, parameters)
-+
-+ Concatenate = _ConcatenateForm(
-+ 'Concatenate',
-+ doc="""Used in conjunction with ``ParamSpec`` and ``Callable`` to
represent a
-+ higher order function which adds, removes or transforms parameters
of a
-+ callable.
-+
-+ For example::
-+
-+ Callable[Concatenate[int, P], int]
-+
-+ See PEP 612 for detailed information.
-+ """)
-+
-+# 3.10+
-+if hasattr(typing, 'TypeGuard'):
-+ TypeGuard = typing.TypeGuard
-+# 3.9
-+elif sys.version_info[:2] >= (3, 9):
-+ class _TypeGuardForm(typing._SpecialForm, _root=True):
-+ def __repr__(self):
-+ return 'typing_extensions.' + self._name
-+
-+ @_TypeGuardForm
-+ def TypeGuard(self, parameters):
-+ """Special typing form used to annotate the return type of a
user-defined
-+ type guard function. ``TypeGuard`` only accepts a single type
argument.
-+ At runtime, functions marked this way should return a boolean.
-+
-+ ``TypeGuard`` aims to benefit *type narrowing* -- a technique used
by static
-+ type checkers to determine a more precise type of an expression
within a
-+ program's code flow. Usually type narrowing is done by analyzing
-+ conditional code flow and applying the narrowing to a block of
code. The
-+ conditional expression here is sometimes referred to as a "type
guard".
-+
-+ Sometimes it would be convenient to use a user-defined boolean
function
-+ as a type guard. Such a function should use ``TypeGuard[...]`` as
its
-+ return type to alert static type checkers to this intention.
-+
-+ Using ``-> TypeGuard`` tells the static type checker that for a
given
-+ function:
-+
-+ 1. The return value is a boolean.
-+ 2. If the return value is ``True``, the type of its argument
-+ is the type inside ``TypeGuard``.
-+
-+ For example::
-+
-+ def is_str(val: Union[str, float]):
-+ # "isinstance" type guard
-+ if isinstance(val, str):
-+ # Type of ``val`` is narrowed to ``str``
-+ ...
-+ else:
-+ # Else, type of ``val`` is narrowed to ``float``.
-+ ...
-+
-+ Strict type narrowing is not enforced -- ``TypeB`` need not be a
narrower
-+ form of ``TypeA`` (it can even be a wider form) and this may lead to
-+ type-unsafe results. The main reason is to allow for things like
-+ narrowing ``List[object]`` to ``List[str]`` even though the latter
is not
-+ a subtype of the former, since ``List`` is invariant. The
responsibility of
-+ writing type-safe type guards is left to the user.
-+
-+ ``TypeGuard`` also works with type variables. For more
information, see
-+ PEP 647 (User-Defined Type Guards).
-+ """
-+ item = typing._type_check(parameters, f'{self} accepts only a
single type.')
-+ return typing._GenericAlias(self, (item,))
-+# 3.7-3.8
-+else:
-+ class _TypeGuardForm(typing._SpecialForm, _root=True):
-+
-+ def __repr__(self):
-+ return 'typing_extensions.' + self._name
-+
-+ def __getitem__(self, parameters):
-+ item = typing._type_check(parameters,
-+ f'{self._name} accepts only a single
type')
-+ return typing._GenericAlias(self, (item,))
-+
-+ TypeGuard = _TypeGuardForm(
-+ 'TypeGuard',
-+ doc="""Special typing form used to annotate the return type of a
user-defined
-+ type guard function. ``TypeGuard`` only accepts a single type
argument.
-+ At runtime, functions marked this way should return a boolean.
-+
-+ ``TypeGuard`` aims to benefit *type narrowing* -- a technique used
by static
-+ type checkers to determine a more precise type of an expression
within a
-+ program's code flow. Usually type narrowing is done by analyzing
-+ conditional code flow and applying the narrowing to a block of
code. The
-+ conditional expression here is sometimes referred to as a "type
guard".
-+
-+ Sometimes it would be convenient to use a user-defined boolean
function
-+ as a type guard. Such a function should use ``TypeGuard[...]`` as
its
-+ return type to alert static type checkers to this intention.
-+
-+ Using ``-> TypeGuard`` tells the static type checker that for a
given
-+ function:
-+
-+ 1. The return value is a boolean.
-+ 2. If the return value is ``True``, the type of its argument
-+ is the type inside ``TypeGuard``.
-+
-+ For example::
-+
-+ def is_str(val: Union[str, float]):
-+ # "isinstance" type guard
-+ if isinstance(val, str):
-+ # Type of ``val`` is narrowed to ``str``
-+ ...
-+ else:
-+ # Else, type of ``val`` is narrowed to ``float``.
-+ ...
-+
-+ Strict type narrowing is not enforced -- ``TypeB`` need not be a
narrower
-+ form of ``TypeA`` (it can even be a wider form) and this may lead to
-+ type-unsafe results. The main reason is to allow for things like
-+ narrowing ``List[object]`` to ``List[str]`` even though the latter
is not
-+ a subtype of the former, since ``List`` is invariant. The
responsibility of
-+ writing type-safe type guards is left to the user.
-+
-+ ``TypeGuard`` also works with type variables. For more
information, see
-+ PEP 647 (User-Defined Type Guards).
-+ """)
-+
-+
-+# Vendored from cpython typing._SpecialFrom
-+class _SpecialForm(typing._Final, _root=True):
-+ __slots__ = ('_name', '__doc__', '_getitem')
-+
-+ def __init__(self, getitem):
-+ self._getitem = getitem
-+ self._name = getitem.__name__
-+ self.__doc__ = getitem.__doc__
-+
-+ def __getattr__(self, item):
-+ if item in {'__name__', '__qualname__'}:
-+ return self._name
-+
-+ raise AttributeError(item)
-+
-+ def __mro_entries__(self, bases):
-+ raise TypeError(f"Cannot subclass {self!r}")
-+
-+ def __repr__(self):
-+ return f'typing_extensions.{self._name}'
-+
-+ def __reduce__(self):
-+ return self._name
-+
-+ def __call__(self, *args, **kwds):
-+ raise TypeError(f"Cannot instantiate {self!r}")
-+
-+ def __or__(self, other):
-+ return typing.Union[self, other]
-+
-+ def __ror__(self, other):
-+ return typing.Union[other, self]
-+
-+ def __instancecheck__(self, obj):
-+ raise TypeError(f"{self} cannot be used with isinstance()")
-+
-+ def __subclasscheck__(self, cls):
-+ raise TypeError(f"{self} cannot be used with issubclass()")
-+
-+ @typing._tp_cache
-+ def __getitem__(self, parameters):
-+ return self._getitem(self, parameters)
-+
-+
-+if hasattr(typing, "LiteralString"):
-+ LiteralString = typing.LiteralString
-+else:
-+ @_SpecialForm
-+ def LiteralString(self, params):
-+ """Represents an arbitrary literal string.
-+
-+ Example::
-+
-+ from typing_extensions import LiteralString
-+
-+ def query(sql: LiteralString) -> ...:
-+ ...
-+
-+ query("SELECT * FROM table") # ok
-+ query(f"SELECT * FROM {input()}") # not ok
-+
-+ See PEP 675 for details.
-+
-+ """
-+ raise TypeError(f"{self} is not subscriptable")
-+
-+
-+if hasattr(typing, "Self"):
-+ Self = typing.Self
-+else:
-+ @_SpecialForm
-+ def Self(self, params):
-+ """Used to spell the type of "self" in classes.
-+
-+ Example::
-+
-+ from typing import Self
-+
-+ class ReturnsSelf:
-+ def parse(self, data: bytes) -> Self:
-+ ...
-+ return self
-+
-+ """
-+
-+ raise TypeError(f"{self} is not subscriptable")
-+
-+
-+if hasattr(typing, "Never"):
-+ Never = typing.Never
-+else:
-+ @_SpecialForm
-+ def Never(self, params):
-+ """The bottom type, a type that has no members.
-+
-+ This can be used to define a function that should never be
-+ called, or a function that never returns::
-+
-+ from typing_extensions import Never
-+
-+ def never_call_me(arg: Never) -> None:
-+ pass
-+
-+ def int_or_str(arg: int | str) -> None:
-+ never_call_me(arg) # type checker error
-+ match arg:
-+ case int():
-+ print("It's an int")
-+ case str():
-+ print("It's a str")
-+ case _:
-+ never_call_me(arg) # ok, arg is of type Never
-+
-+ """
-+
-+ raise TypeError(f"{self} is not subscriptable")
-+
-+
-+if hasattr(typing, 'Required'):
-+ Required = typing.Required
-+ NotRequired = typing.NotRequired
-+elif sys.version_info[:2] >= (3, 9):
-+ class _ExtensionsSpecialForm(typing._SpecialForm, _root=True):
-+ def __repr__(self):
-+ return 'typing_extensions.' + self._name
-+
-+ @_ExtensionsSpecialForm
-+ def Required(self, parameters):
-+ """A special typing construct to mark a key of a total=False
TypedDict
-+ as required. For example:
-+
-+ class Movie(TypedDict, total=False):
-+ title: Required[str]
-+ year: int
-+
-+ m = Movie(
-+ title='The Matrix', # typechecker error if key is omitted
-+ year=1999,
-+ )
-+
-+ There is no runtime checking that a required key is actually
provided
-+ when instantiating a related TypedDict.
-+ """
-+ item = typing._type_check(parameters, f'{self._name} accepts only a
single type.')
-+ return typing._GenericAlias(self, (item,))
-+
-+ @_ExtensionsSpecialForm
-+ def NotRequired(self, parameters):
-+ """A special typing construct to mark a key of a TypedDict as
-+ potentially missing. For example:
-+
-+ class Movie(TypedDict):
-+ title: str
-+ year: NotRequired[int]
-+
-+ m = Movie(
-+ title='The Matrix', # typechecker error if key is omitted
-+ year=1999,
-+ )
-+ """
-+ item = typing._type_check(parameters, f'{self._name} accepts only a
single type.')
-+ return typing._GenericAlias(self, (item,))
-+
-+else:
-+ class _RequiredForm(typing._SpecialForm, _root=True):
-+ def __repr__(self):
-+ return 'typing_extensions.' + self._name
-+
-+ def __getitem__(self, parameters):
-+ item = typing._type_check(parameters,
-+ f'{self._name} accepts only a single
type.')
-+ return typing._GenericAlias(self, (item,))
-+
-+ Required = _RequiredForm(
-+ 'Required',
-+ doc="""A special typing construct to mark a key of a total=False
TypedDict
-+ as required. For example:
-+
-+ class Movie(TypedDict, total=False):
-+ title: Required[str]
-+ year: int
-+
-+ m = Movie(
-+ title='The Matrix', # typechecker error if key is omitted
-+ year=1999,
-+ )
-+
-+ There is no runtime checking that a required key is actually
provided
-+ when instantiating a related TypedDict.
-+ """)
-+ NotRequired = _RequiredForm(
-+ 'NotRequired',
-+ doc="""A special typing construct to mark a key of a TypedDict as
-+ potentially missing. For example:
-+
-+ class Movie(TypedDict):
-+ title: str
-+ year: NotRequired[int]
-+
-+ m = Movie(
-+ title='The Matrix', # typechecker error if key is omitted
-+ year=1999,
-+ )
-+ """)
-+
-+
-+if hasattr(typing, "Unpack"): # 3.11+
-+ Unpack = typing.Unpack
-+elif sys.version_info[:2] >= (3, 9):
-+ class _UnpackSpecialForm(typing._SpecialForm, _root=True):
-+ def __repr__(self):
-+ return 'typing_extensions.' + self._name
-+
-+ class _UnpackAlias(typing._GenericAlias, _root=True):
-+ __class__ = typing.TypeVar
-+
-+ @_UnpackSpecialForm
-+ def Unpack(self, parameters):
-+ """A special typing construct to unpack a variadic type. For
example:
-+
-+ Shape = TypeVarTuple('Shape')
-+ Batch = NewType('Batch', int)
-+
-+ def add_batch_axis(
-+ x: Array[Unpack[Shape]]
-+ ) -> Array[Batch, Unpack[Shape]]: ...
-+
-+ """
-+ item = typing._type_check(parameters, f'{self._name} accepts only a
single type.')
-+ return _UnpackAlias(self, (item,))
-+
-+ def _is_unpack(obj):
-+ return isinstance(obj, _UnpackAlias)
-+
-+else:
-+ class _UnpackAlias(typing._GenericAlias, _root=True):
-+ __class__ = typing.TypeVar
-+
-+ class _UnpackForm(typing._SpecialForm, _root=True):
-+ def __repr__(self):
-+ return 'typing_extensions.' + self._name
-+
-+ def __getitem__(self, parameters):
-+ item = typing._type_check(parameters,
-+ f'{self._name} accepts only a single
type.')
-+ return _UnpackAlias(self, (item,))
-+
-+ Unpack = _UnpackForm(
-+ 'Unpack',
-+ doc="""A special typing construct to unpack a variadic type. For
example:
-+
-+ Shape = TypeVarTuple('Shape')
-+ Batch = NewType('Batch', int)
-+
-+ def add_batch_axis(
-+ x: Array[Unpack[Shape]]
-+ ) -> Array[Batch, Unpack[Shape]]: ...
-+
-+ """)
-+
-+ def _is_unpack(obj):
-+ return isinstance(obj, _UnpackAlias)
-+
-+
-+if hasattr(typing, "TypeVarTuple"): # 3.11+
-+
-+ # Add default Parameter - PEP 696
-+ class TypeVarTuple(typing.TypeVarTuple, _DefaultMixin, _root=True):
-+ """Type variable tuple."""
-+
-+ def __init__(self, name, *, default=None):
-+ super().__init__(name)
-+ _DefaultMixin.__init__(self, default)
-+
-+ # for pickling:
-+ try:
-+ def_mod = sys._getframe(1).f_globals.get('__name__',
'__main__')
-+ except (AttributeError, ValueError):
-+ def_mod = None
-+ if def_mod != 'typing_extensions':
-+ self.__module__ = def_mod
-+
-+else:
-+ class TypeVarTuple(_DefaultMixin):
-+ """Type variable tuple.
-+
-+ Usage::
-+
-+ Ts = TypeVarTuple('Ts')
-+
-+ In the same way that a normal type variable is a stand-in for a
single
-+ type such as ``int``, a type variable *tuple* is a stand-in for a
*tuple*
-+ type such as ``Tuple[int, str]``.
-+
-+ Type variable tuples can be used in ``Generic`` declarations.
-+ Consider the following example::
-+
-+ class Array(Generic[*Ts]): ...
-+
-+ The ``Ts`` type variable tuple here behaves like ``tuple[T1, T2]``,
-+ where ``T1`` and ``T2`` are type variables. To use these type
variables
-+ as type parameters of ``Array``, we must *unpack* the type variable
tuple using
-+ the star operator: ``*Ts``. The signature of ``Array`` then behaves
-+ as if we had simply written ``class Array(Generic[T1, T2]): ...``.
-+ In contrast to ``Generic[T1, T2]``, however, ``Generic[*Shape]``
allows
-+ us to parameterise the class with an *arbitrary* number of type
parameters.
-+
-+ Type variable tuples can be used anywhere a normal ``TypeVar`` can.
-+ This includes class definitions, as shown above, as well as function
-+ signatures and variable annotations::
-+
-+ class Array(Generic[*Ts]):
-+
-+ def __init__(self, shape: Tuple[*Ts]):
-+ self._shape: Tuple[*Ts] = shape
-+
-+ def get_shape(self) -> Tuple[*Ts]:
-+ return self._shape
-+
-+ shape = (Height(480), Width(640))
-+ x: Array[Height, Width] = Array(shape)
-+ y = abs(x) # Inferred type is Array[Height, Width]
-+ z = x + x # ... is Array[Height, Width]
-+ x.get_shape() # ... is tuple[Height, Width]
-+
-+ """
-+
-+ # Trick Generic __parameters__.
-+ __class__ = typing.TypeVar
-+
-+ def __iter__(self):
-+ yield self.__unpacked__
-+
-+ def __init__(self, name, *, default=None):
-+ self.__name__ = name
-+ _DefaultMixin.__init__(self, default)
-+
-+ # for pickling:
-+ try:
-+ def_mod = sys._getframe(1).f_globals.get('__name__',
'__main__')
-+ except (AttributeError, ValueError):
-+ def_mod = None
-+ if def_mod != 'typing_extensions':
-+ self.__module__ = def_mod
-+
-+ self.__unpacked__ = Unpack[self]
-+
-+ def __repr__(self):
-+ return self.__name__
-+
-+ def __hash__(self):
-+ return object.__hash__(self)
-+
-+ def __eq__(self, other):
-+ return self is other
-+
-+ def __reduce__(self):
-+ return self.__name__
-+
-+ def __init_subclass__(self, *args, **kwds):
-+ if '_root' not in kwds:
-+ raise TypeError("Cannot subclass special typing classes")
-+
-+
-+if hasattr(typing, "reveal_type"):
-+ reveal_type = typing.reveal_type
-+else:
-+ def reveal_type(__obj: T) -> T:
-+ """Reveal the inferred type of a variable.
-+
-+ When a static type checker encounters a call to ``reveal_type()``,
-+ it will emit the inferred type of the argument::
-+
-+ x: int = 1
-+ reveal_type(x)
-+
-+ Running a static type checker (e.g., ``mypy``) on this example
-+ will produce output similar to 'Revealed type is "builtins.int"'.
-+
-+ At runtime, the function prints the runtime type of the
-+ argument and returns it unchanged.
-+
-+ """
-+ print(f"Runtime type is {type(__obj).__name__!r}", file=sys.stderr)
-+ return __obj
-+
-+
-+if hasattr(typing, "assert_never"):
-+ assert_never = typing.assert_never
-+else:
-+ def assert_never(__arg: Never) -> Never:
-+ """Assert to the type checker that a line of code is unreachable.
-+
-+ Example::
-+
-+ def int_or_str(arg: int | str) -> None:
-+ match arg:
-+ case int():
-+ print("It's an int")
-+ case str():
-+ print("It's a str")
-+ case _:
-+ assert_never(arg)
-+
-+ If a type checker finds that a call to assert_never() is
-+ reachable, it will emit an error.
-+
-+ At runtime, this throws an exception when called.
-+
-+ """
-+ raise AssertionError("Expected code to be unreachable")
-+
-+
-+if hasattr(typing, 'dataclass_transform'):
-+ dataclass_transform = typing.dataclass_transform
-+else:
-+ def dataclass_transform(
-+ *,
-+ eq_default: bool = True,
-+ order_default: bool = False,
-+ kw_only_default: bool = False,
-+ field_specifiers: typing.Tuple[
-+ typing.Union[typing.Type[typing.Any], typing.Callable[...,
typing.Any]],
-+ ...
-+ ] = (),
-+ **kwargs: typing.Any,
-+ ) -> typing.Callable[[T], T]:
-+ """Decorator that marks a function, class, or metaclass as providing
-+ dataclass-like behavior.
-+
-+ Example:
-+
-+ from typing_extensions import dataclass_transform
-+
-+ _T = TypeVar("_T")
-+
-+ # Used on a decorator function
-+ @dataclass_transform()
-+ def create_model(cls: type[_T]) -> type[_T]:
-+ ...
-+ return cls
-+
-+ @create_model
-+ class CustomerModel:
-+ id: int
-+ name: str
-+
-+ # Used on a base class
-+ @dataclass_transform()
-+ class ModelBase: ...
-+
-+ class CustomerModel(ModelBase):
-+ id: int
-+ name: str
-+
-+ # Used on a metaclass
-+ @dataclass_transform()
-+ class ModelMeta(type): ...
-+
-+ class ModelBase(metaclass=ModelMeta): ...
-+
-+ class CustomerModel(ModelBase):
-+ id: int
-+ name: str
-+
-+ Each of the ``CustomerModel`` classes defined in this example will
now
-+ behave similarly to a dataclass created with the
``@dataclasses.dataclass``
-+ decorator. For example, the type checker will synthesize an
``__init__``
-+ method.
-+
-+ The arguments to this decorator can be used to customize this
behavior:
-+ - ``eq_default`` indicates whether the ``eq`` parameter is assumed
to be
-+ True or False if it is omitted by the caller.
-+ - ``order_default`` indicates whether the ``order`` parameter is
-+ assumed to be True or False if it is omitted by the caller.
-+ - ``kw_only_default`` indicates whether the ``kw_only`` parameter is
-+ assumed to be True or False if it is omitted by the caller.
-+ - ``field_specifiers`` specifies a static list of supported classes
-+ or functions that describe fields, similar to
``dataclasses.field()``.
-+
-+ At runtime, this decorator records its arguments in the
-+ ``__dataclass_transform__`` attribute on the decorated object.
-+
-+ See PEP 681 for details.
-+
-+ """
-+ def decorator(cls_or_fn):
-+ cls_or_fn.__dataclass_transform__ = {
-+ "eq_default": eq_default,
-+ "order_default": order_default,
-+ "kw_only_default": kw_only_default,
-+ "field_specifiers": field_specifiers,
-+ "kwargs": kwargs,
-+ }
-+ return cls_or_fn
-+ return decorator
-+
-+
-+if hasattr(typing, "override"):
-+ override = typing.override
-+else:
-+ _F = typing.TypeVar("_F", bound=typing.Callable[..., typing.Any])
-+
-+ def override(__arg: _F) -> _F:
-+ """Indicate that a method is intended to override a method in a
base class.
-+
-+ Usage:
-+
-+ class Base:
-+ def method(self) -> None: ...
-+ pass
-+
-+ class Child(Base):
-+ @override
-+ def method(self) -> None:
-+ super().method()
-+
-+ When this decorator is applied to a method, the type checker will
-+ validate that it overrides a method with the same name on a base
class.
-+ This helps prevent bugs that may occur when a base class is changed
-+ without an equivalent change to a child class.
-+
-+ See PEP 698 for details.
-+
-+ """
-+ return __arg
-+
-+
-+# We have to do some monkey patching to deal with the dual nature of
-+# Unpack/TypeVarTuple:
-+# - We want Unpack to be a kind of TypeVar so it gets accepted in
-+# Generic[Unpack[Ts]]
-+# - We want it to *not* be treated as a TypeVar for the purposes of
-+# counting generic parameters, so that when we subscript a generic,
-+# the runtime doesn't try to substitute the Unpack with the subscripted
type.
-+if not hasattr(typing, "TypeVarTuple"):
-+ typing._collect_type_vars = _collect_type_vars
-+ typing._check_generic = _check_generic
-+
-+
-+# Backport typing.NamedTuple as it exists in Python 3.11.
-+# In 3.11, the ability to define generic `NamedTuple`s was supported.
-+# This was explicitly disallowed in 3.9-3.10, and only half-worked in <=3.8.
-+if sys.version_info >= (3, 11):
-+ NamedTuple = typing.NamedTuple
-+else:
-+ def _caller():
-+ try:
-+ return sys._getframe(2).f_globals.get('__name__', '__main__')
-+ except (AttributeError, ValueError): # For platforms without
_getframe()
-+ return None
-+
-+ def _make_nmtuple(name, types, module, defaults=()):
-+ fields = [n for n, t in types]
-+ annotations = {n: typing._type_check(t, f"field {n} annotation must
be a type")
-+ for n, t in types}
-+ nm_tpl = collections.namedtuple(name, fields,
-+ defaults=defaults, module=module)
-+ nm_tpl.__annotations__ = nm_tpl.__new__.__annotations__ =
annotations
-+ # The `_field_types` attribute was removed in 3.9;
-+ # in earlier versions, it is the same as the `__annotations__`
attribute
-+ if sys.version_info < (3, 9):
-+ nm_tpl._field_types = annotations
-+ return nm_tpl
-+
-+ _prohibited_namedtuple_fields = typing._prohibited
-+ _special_namedtuple_fields = frozenset({'__module__', '__name__',
'__annotations__'})
-+
-+ class _NamedTupleMeta(type):
-+ def __new__(cls, typename, bases, ns):
-+ assert _NamedTuple in bases
-+ for base in bases:
-+ if base is not _NamedTuple and base is not typing.Generic:
-+ raise TypeError(
-+ 'can only inherit from a NamedTuple type and
Generic')
-+ bases = tuple(tuple if base is _NamedTuple else base for base
in bases)
-+ types = ns.get('__annotations__', {})
-+ default_names = []
-+ for field_name in types:
-+ if field_name in ns:
-+ default_names.append(field_name)
-+ elif default_names:
-+ raise TypeError(f"Non-default namedtuple field
{field_name} "
-+ f"cannot follow default field"
-+ f"{'s' if len(default_names) > 1 else
''} "
-+ f"{', '.join(default_names)}")
-+ nm_tpl = _make_nmtuple(
-+ typename, types.items(),
-+ defaults=[ns[n] for n in default_names],
-+ module=ns['__module__']
-+ )
-+ nm_tpl.__bases__ = bases
-+ if typing.Generic in bases:
-+ class_getitem = typing.Generic.__class_getitem__.__func__
-+ nm_tpl.__class_getitem__ = classmethod(class_getitem)
-+ # update from user namespace without overriding special
namedtuple attributes
-+ for key in ns:
-+ if key in _prohibited_namedtuple_fields:
-+ raise AttributeError("Cannot overwrite NamedTuple
attribute " + key)
-+ elif key not in _special_namedtuple_fields and key not in
nm_tpl._fields:
-+ setattr(nm_tpl, key, ns[key])
-+ if typing.Generic in bases:
-+ nm_tpl.__init_subclass__()
-+ return nm_tpl
-+
-+ def NamedTuple(__typename, __fields=None, **kwargs):
-+ if __fields is None:
-+ __fields = kwargs.items()
-+ elif kwargs:
-+ raise TypeError("Either list of fields or keywords"
-+ " can be provided to NamedTuple, not both")
-+ return _make_nmtuple(__typename, __fields, module=_caller())
-+
-+ NamedTuple.__doc__ = typing.NamedTuple.__doc__
-+ _NamedTuple = type.__new__(_NamedTupleMeta, 'NamedTuple', (), {})
-+
-+ # On 3.8+, alter the signature so that it matches typing.NamedTuple.
-+ # The signature of typing.NamedTuple on >=3.8 is invalid syntax in
Python 3.7,
-+ # so just leave the signature as it is on 3.7.
-+ if sys.version_info >= (3, 8):
-+ NamedTuple.__text_signature__ = '(typename, fields=None, /,
**kwargs)'
-+
-+ def _namedtuple_mro_entries(bases):
-+ assert NamedTuple in bases
-+ return (_NamedTuple,)
-+
-+ NamedTuple.__mro_entries__ = _namedtuple_mro_entries
-diff --git a/third_party/python/setuptools/pkg_resources/_vendor/zipp.py
b/third_party/python/setuptools/pkg_resources/_vendor/zipp.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/pkg_resources/_vendor/zipp.py
-@@ -0,0 +1,329 @@
-+import io
-+import posixpath
-+import zipfile
-+import itertools
-+import contextlib
-+import sys
-+import pathlib
-+
-+if sys.version_info < (3, 7):
-+ from collections import OrderedDict
-+else:
-+ OrderedDict = dict
-+
-+
-+__all__ = ['Path']
-+
-+
-+def _parents(path):
-+ """
-+ Given a path with elements separated by
-+ posixpath.sep, generate all parents of that path.
-+
-+ >>> list(_parents('b/d'))
-+ ['b']
-+ >>> list(_parents('/b/d/'))
-+ ['/b']
-+ >>> list(_parents('b/d/f/'))
-+ ['b/d', 'b']
-+ >>> list(_parents('b'))
-+ []
-+ >>> list(_parents(''))
-+ []
-+ """
-+ return itertools.islice(_ancestry(path), 1, None)
-+
-+
-+def _ancestry(path):
-+ """
-+ Given a path with elements separated by
-+ posixpath.sep, generate all elements of that path
-+
-+ >>> list(_ancestry('b/d'))
-+ ['b/d', 'b']
-+ >>> list(_ancestry('/b/d/'))
-+ ['/b/d', '/b']
-+ >>> list(_ancestry('b/d/f/'))
-+ ['b/d/f', 'b/d', 'b']
-+ >>> list(_ancestry('b'))
-+ ['b']
-+ >>> list(_ancestry(''))
-+ []
-+ """
-+ path = path.rstrip(posixpath.sep)
-+ while path and path != posixpath.sep:
-+ yield path
-+ path, tail = posixpath.split(path)
-+
-+
-+_dedupe = OrderedDict.fromkeys
-+"""Deduplicate an iterable in original order"""
-+
-+
-+def _difference(minuend, subtrahend):
-+ """
-+ Return items in minuend not in subtrahend, retaining order
-+ with O(1) lookup.
-+ """
-+ return itertools.filterfalse(set(subtrahend).__contains__, minuend)
-+
-+
-+class CompleteDirs(zipfile.ZipFile):
-+ """
-+ A ZipFile subclass that ensures that implied directories
-+ are always included in the namelist.
-+ """
-+
-+ @staticmethod
-+ def _implied_dirs(names):
-+ parents = itertools.chain.from_iterable(map(_parents, names))
-+ as_dirs = (p + posixpath.sep for p in parents)
-+ return _dedupe(_difference(as_dirs, names))
-+
-+ def namelist(self):
-+ names = super(CompleteDirs, self).namelist()
-+ return names + list(self._implied_dirs(names))
-+
-+ def _name_set(self):
-+ return set(self.namelist())
-+
-+ def resolve_dir(self, name):
-+ """
-+ If the name represents a directory, return that name
-+ as a directory (with the trailing slash).
-+ """
-+ names = self._name_set()
-+ dirname = name + '/'
-+ dir_match = name not in names and dirname in names
-+ return dirname if dir_match else name
-+
-+ @classmethod
-+ def make(cls, source):
-+ """
-+ Given a source (filename or zipfile), return an
-+ appropriate CompleteDirs subclass.
-+ """
-+ if isinstance(source, CompleteDirs):
-+ return source
-+
-+ if not isinstance(source, zipfile.ZipFile):
-+ return cls(_pathlib_compat(source))
-+
-+ # Only allow for FastLookup when supplied zipfile is read-only
-+ if 'r' not in source.mode:
-+ cls = CompleteDirs
-+
-+ source.__class__ = cls
-+ return source
-+
-+
-+class FastLookup(CompleteDirs):
-+ """
-+ ZipFile subclass to ensure implicit
-+ dirs exist and are resolved rapidly.
-+ """
-+
-+ def namelist(self):
-+ with contextlib.suppress(AttributeError):
-+ return self.__names
-+ self.__names = super(FastLookup, self).namelist()
-+ return self.__names
-+
-+ def _name_set(self):
-+ with contextlib.suppress(AttributeError):
-+ return self.__lookup
-+ self.__lookup = super(FastLookup, self)._name_set()
-+ return self.__lookup
-+
-+
-+def _pathlib_compat(path):
-+ """
-+ For path-like objects, convert to a filename for compatibility
-+ on Python 3.6.1 and earlier.
-+ """
-+ try:
-+ return path.__fspath__()
-+ except AttributeError:
-+ return str(path)
-+
-+
-+class Path:
-+ """
-+ A pathlib-compatible interface for zip files.
-+
-+ Consider a zip file with this structure::
-+
-+ .
-+ ├── a.txt
-+ └── b
-+ ├── c.txt
-+ └── d
-+ └── e.txt
-+
-+ >>> data = io.BytesIO()
-+ >>> zf = zipfile.ZipFile(data, 'w')
-+ >>> zf.writestr('a.txt', 'content of a')
-+ >>> zf.writestr('b/c.txt', 'content of c')
-+ >>> zf.writestr('b/d/e.txt', 'content of e')
-+ >>> zf.filename = 'mem/abcde.zip'
-+
-+ Path accepts the zipfile object itself or a filename
-+
-+ >>> root = Path(zf)
-+
-+ From there, several path operations are available.
-+
-+ Directory iteration (including the zip file itself):
-+
-+ >>> a, b = root.iterdir()
-+ >>> a
-+ Path('mem/abcde.zip', 'a.txt')
-+ >>> b
-+ Path('mem/abcde.zip', 'b/')
-+
-+ name property:
-+
-+ >>> b.name
-+ 'b'
-+
-+ join with divide operator:
-+
-+ >>> c = b / 'c.txt'
-+ >>> c
-+ Path('mem/abcde.zip', 'b/c.txt')
-+ >>> c.name
-+ 'c.txt'
-+
-+ Read text:
-+
-+ >>> c.read_text()
-+ 'content of c'
-+
-+ existence:
-+
-+ >>> c.exists()
-+ True
-+ >>> (b / 'missing.txt').exists()
-+ False
-+
-+ Coercion to string:
-+
-+ >>> import os
-+ >>> str(c).replace(os.sep, posixpath.sep)
-+ 'mem/abcde.zip/b/c.txt'
-+
-+ At the root, ``name``, ``filename``, and ``parent``
-+ resolve to the zipfile. Note these attributes are not
-+ valid and will raise a ``ValueError`` if the zipfile
-+ has no filename.
-+
-+ >>> root.name
-+ 'abcde.zip'
-+ >>> str(root.filename).replace(os.sep, posixpath.sep)
-+ 'mem/abcde.zip'
-+ >>> str(root.parent)
-+ 'mem'
-+ """
-+
-+ __repr = "{self.__class__.__name__}({self.root.filename!r},
{self.at!r})"
-+
-+ def __init__(self, root, at=""):
-+ """
-+ Construct a Path from a ZipFile or filename.
-+
-+ Note: When the source is an existing ZipFile object,
-+ its type (__class__) will be mutated to a
-+ specialized type. If the caller wishes to retain the
-+ original type, the caller should either create a
-+ separate ZipFile object or pass a filename.
-+ """
-+ self.root = FastLookup.make(root)
-+ self.at = at
-+
-+ def open(self, mode='r', *args, pwd=None, **kwargs):
-+ """
-+ Open this entry as text or binary following the semantics
-+ of ``pathlib.Path.open()`` by passing arguments through
-+ to io.TextIOWrapper().
-+ """
-+ if self.is_dir():
-+ raise IsADirectoryError(self)
-+ zip_mode = mode[0]
-+ if not self.exists() and zip_mode == 'r':
-+ raise FileNotFoundError(self)
-+ stream = self.root.open(self.at, zip_mode, pwd=pwd)
-+ if 'b' in mode:
-+ if args or kwargs:
-+ raise ValueError("encoding args invalid for binary
operation")
-+ return stream
-+ return io.TextIOWrapper(stream, *args, **kwargs)
-+
-+ @property
-+ def name(self):
-+ return pathlib.Path(self.at).name or self.filename.name
-+
-+ @property
-+ def suffix(self):
-+ return pathlib.Path(self.at).suffix or self.filename.suffix
-+
-+ @property
-+ def suffixes(self):
-+ return pathlib.Path(self.at).suffixes or self.filename.suffixes
-+
-+ @property
-+ def stem(self):
-+ return pathlib.Path(self.at).stem or self.filename.stem
-+
-+ @property
-+ def filename(self):
-+ return pathlib.Path(self.root.filename).joinpath(self.at)
-+
-+ def read_text(self, *args, **kwargs):
-+ with self.open('r', *args, **kwargs) as strm:
-+ return strm.read()
-+
-+ def read_bytes(self):
-+ with self.open('rb') as strm:
-+ return strm.read()
-+
-+ def _is_child(self, path):
-+ return posixpath.dirname(path.at.rstrip("/")) == self.at.rstrip("/")
-+
-+ def _next(self, at):
-+ return self.__class__(self.root, at)
-+
-+ def is_dir(self):
-+ return not self.at or self.at.endswith("/")
-+
-+ def is_file(self):
-+ return self.exists() and not self.is_dir()
-+
-+ def exists(self):
-+ return self.at in self.root._name_set()
-+
-+ def iterdir(self):
-+ if not self.is_dir():
-+ raise ValueError("Can't listdir a file")
-+ subs = map(self._next, self.root.namelist())
-+ return filter(self._is_child, subs)
-+
-+ def __str__(self):
-+ return posixpath.join(self.root.filename, self.at)
-+
-+ def __repr__(self):
-+ return self.__repr.format(self=self)
-+
-+ def joinpath(self, *other):
-+ next = posixpath.join(self.at, *map(_pathlib_compat, other))
-+ return self._next(self.root.resolve_dir(next))
-+
-+ __truediv__ = joinpath
-+
-+ @property
-+ def parent(self):
-+ if not self.at:
-+ return self.filename.parent
-+ parent_at = posixpath.dirname(self.at.rstrip('/'))
-+ if parent_at:
-+ parent_at += '/'
-+ return self._next(parent_at)
-diff --git a/third_party/python/setuptools/pkg_resources/extern/__init__.py
b/third_party/python/setuptools/pkg_resources/extern/__init__.py
---- a/third_party/python/setuptools/pkg_resources/extern/__init__.py
-+++ b/third_party/python/setuptools/pkg_resources/extern/__init__.py
-@@ -1,8 +1,9 @@
-+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.
- """
-@@ -15,27 +16,20 @@ class VendorImporter:
- @property
- def search_path(self):
- """
- Search first the vendor package then as a natural package.
- """
- yield self.vendor_pkg + '.'
- yield ''
-
-- def find_module(self, fullname, path=None):
-- """
-- Return self when fullname starts with root_name and the
-- target module is one vendored through this importer.
-- """
-+ def _module_matches_namespace(self, fullname):
-+ """Figure out if the target module is vendored."""
- root, base, target = fullname.partition(self.root_name + '.')
-- if root:
-- return
-- if not any(map(target.startswith, self.vendored_names)):
-- return
-- return self
-+ 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:
-@@ -49,18 +43,38 @@ class VendorImporter:
- 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'
-+names = (
-+ 'packaging',
-+ 'platformdirs',
-+ 'jaraco',
-+ 'importlib_resources',
-+ 'more_itertools',
-+)
- VendorImporter(__name__, names).install()
-diff --git
a/third_party/python/setuptools/setuptools-51.2.0.dist-info/METADATA
b/third_party/python/setuptools/setuptools-51.2.0.dist-info/METADATA
-deleted file mode 100644
---- a/third_party/python/setuptools/setuptools-51.2.0.dist-info/METADATA
-+++ /dev/null
-@@ -1,110 +0,0 @@
--Metadata-Version: 2.1
--Name: setuptools
--Version: 51.2.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 AT python.org
--License: UNKNOWN
--Project-URL: Documentation, https://setuptools.readthedocs.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
--Provides-Extra: certs
--Requires-Dist: certifi (==2016.9.26) ; extra == 'certs'
--Provides-Extra: docs
--Requires-Dist: sphinx ; extra == 'docs'
--Requires-Dist: jaraco.packaging (>=8.2) ; extra == 'docs'
--Requires-Dist: rst.linker (>=1.9) ; extra == 'docs'
--Requires-Dist: pygments-github-lexers (==0.0.5) ; extra == 'docs'
--Provides-Extra: ssl
--Requires-Dist: wincertstore (==0.2) ; (sys_platform == "win32") and extra
== 'ssl'
--Provides-Extra: testing
--Requires-Dist: pytest (!=3.7.3,>=3.5) ; extra == 'testing'
--Requires-Dist: pytest-checkdocs (>=1.2.3) ; extra == 'testing'
--Requires-Dist: pytest-flake8 ; extra == 'testing'
--Requires-Dist: pytest-cov ; extra == 'testing'
--Requires-Dist: jaraco.test (>=3.2.0) ; extra == 'testing'
--Requires-Dist: mock ; extra == 'testing'
--Requires-Dist: flake8-2020 ; extra == 'testing'
--Requires-Dist: virtualenv (>=13.0.0) ; extra == 'testing'
--Requires-Dist: pytest-virtualenv (>=1.2.7) ; extra == 'testing'
--Requires-Dist: wheel ; extra == 'testing'
--Requires-Dist: paver ; extra == 'testing'
--Requires-Dist: pip (>=19.1) ; extra == 'testing'
--Requires-Dist: jaraco.envs ; 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://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/Automated%20Tests/badge.svg
-- :target:
https://github.com/pypa/setuptools/actions?query=workflow%3A%22Automated+Tests%22
-- :alt: Automated 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.readthedocs.io
--
--.. 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
--<https://packaging.python.org/installing/>`_ 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 <http://mail.python.org/pipermail/distutils-sig/>`_.
--Bug reports and especially tested patches may be
--submitted directly to the `bug tracker
--<https://github.com/pypa/setuptools/issues>`_.
--
--
--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
<https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md>`_.
--
--
--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
<https://tidelift.com/subscription/pkg/pypi-setuptools?utm_source=pypi-setuptools&utm_medium=referral&utm_campaign=github>`_.
--
--
--Security Contact
--================
--
--To report a security vulnerability, please use the
--`Tidelift security contact <https://tidelift.com/security>`_.
--Tidelift will coordinate the fix and disclosure.
--
--
-diff --git
a/third_party/python/setuptools/setuptools-51.2.0.dist-info/RECORD
b/third_party/python/setuptools/setuptools-51.2.0.dist-info/RECORD
-deleted file mode 100644
---- a/third_party/python/setuptools/setuptools-51.2.0.dist-info/RECORD
-+++ /dev/null
-@@ -1,156 +0,0 @@
--distutils-precedence.pth,sha256=fqf_7z_ioRfuEsaO1lU2F_DX_S8FkCV8JcSElZo7c3M,152
--easy_install.py,sha256=MDC9vt5AxDsXX5qcKlBz2TnW6Tpuv_AobnfhCJ9X3PM,126
--_distutils_hack/__init__.py,sha256=wFuARcmlHtkV20HfRBlQaMPY7hQx-TEEtnBpXeysiwI,3552
--_distutils_hack/override.py,sha256=Eu_s-NF6VIZ4Cqd0tbbA5wtWky2IZPNd8et6GLt1mzo,44
--pkg_resources/__init__.py,sha256=lhZRsyFftcjl10dCV_WtPIg9PzS-z4pSckiclF-hYWA,107968
--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=PNMsaZn4UcCHyubgROH1bl6CluduPjI5kFrSp_Zgklo,736
--pkg_resources/_vendor/packaging/__init__.py,sha256=6enbp5XgRfjBjsI9-bn00HjHf5TH21PDMOKkJW8xw-w,562
--pkg_resources/_vendor/packaging/_compat.py,sha256=MXdsGpSE_W-ZrHoC87andI4LV2FAwU7HLL-eHe_CjhU,1128
--pkg_resources/_vendor/packaging/_structures.py,sha256=ozkCX8Q8f2qE1Eic3YiQ4buDVfgz2iYevY9e7R2y3iY,2022
--pkg_resources/_vendor/packaging/_typing.py,sha256=x59EhQ57TMT-kTRyLZV25HZvYGGwbucTo6iKh_O0tMw,1812
--pkg_resources/_vendor/packaging/markers.py,sha256=YSntQkMnKyw1_FG6oRNNnGxLL6bAxcGXOtuFE-YTS3k,9518
--pkg_resources/_vendor/packaging/requirements.py,sha256=R8K4H4xX_iD4LvpGw1U3ouuPbGN-wzsFgD7brhAM71Y,4929
--pkg_resources/_vendor/packaging/specifiers.py,sha256=uYp9l13F0LcknS6d4N60ytiBgFmIhKideOq9AnsxTco,31944
--pkg_resources/_vendor/packaging/tags.py,sha256=NKMS37Zo_nWrZxgsD6zbXsXgc9edn9m160cBiLmHJdE,24067
--pkg_resources/_vendor/packaging/utils.py,sha256=RShlvnjO2CtYSD8uri32frMMFMTmB-3ihsq1-ghzLEw,1811
--pkg_resources/_vendor/packaging/version.py,sha256=Cnbm-OO9D_qd8ZTFxzFcjSavexSYFZmyeaoPvMsjgPc,15470
--pkg_resources/extern/__init__.py,sha256=Gj7bMt5YI5cGt6kcg-M2GNxOL_k2x-bUlLzPaZK8uek,2094
--setuptools/__init__.py,sha256=0c232LRyOLGdL-Ywmgk8uMubx7I21w-ixJWiT0jQK-c,7681
--setuptools/_deprecation_warning.py,sha256=jU9-dtfv6cKmtQJOXN8nP1mm7gONw5kKEtiPtbwnZyI,218
--setuptools/_imp.py,sha256=Qx0LJzEBaWk_6PfICamJtfBN2rh5K9sJq1wXvtZW-mc,2388
--setuptools/archive_util.py,sha256=F1-XrQJTdXHRPRA09kxPWwm9Z2Ms1lE_IQZKG_JZ7rM,6638
--setuptools/build_meta.py,sha256=bm5RnBmygBneOldaLhcuncOiTqH5NPdBZISVeUDeYyc,10096
--setuptools/cli-32.exe,sha256=dfEuovMNnA2HLa3jRfMPVi5tk4R7alCbpTvuxtCyw0Y,65536
--setuptools/cli-64.exe,sha256=KLABu5pyrnokJCv6skjXZ6GsXeyYHGcqOUT3oHI3Xpo,74752
--setuptools/cli.exe,sha256=dfEuovMNnA2HLa3jRfMPVi5tk4R7alCbpTvuxtCyw0Y,65536
--setuptools/config.py,sha256=NUrsVW54pxYCuekfJbV7B6wyKzvzatXM5zT4LqnegWo,21485
--setuptools/dep_util.py,sha256=BDx1BkzNQntvAB4alypHbW5UVBzjqths000PrUL4Zqc,949
--setuptools/depends.py,sha256=iHfZdLdlCu2BllSF9bRg7NU0oqbPWMH8ljm4BuwQDY0,5474
--setuptools/dist.py,sha256=msWjjUNczEXVO5H_X-Xx6Y00_9VM_S0_cIBWehrkVRM,38405
--setuptools/errors.py,sha256=MVOcv381HNSajDgEUWzOQ4J6B5BHCBMSjHfaWcEwA1o,524
--setuptools/extension.py,sha256=NMM46XjNdVelWemc0x8CyVKA5Ks6Zm3xTWSA2SS6xZM,1684
--setuptools/glob.py,sha256=o75cHrOxYsvn854thSxE0x9k8JrKDuhP_rRXlVB00Q4,5084
--setuptools/gui-32.exe,sha256=XBr0bHMA6Hpz2s9s9Bzjl-PwXfa9nH4ie0rFn4V2kWA,65536
--setuptools/gui-64.exe,sha256=aYKMhX1IJLn4ULHgWX0sE0yREUt6B3TEHf_jOw6yNyE,75264
--setuptools/gui.exe,sha256=XBr0bHMA6Hpz2s9s9Bzjl-PwXfa9nH4ie0rFn4V2kWA,65536
--setuptools/installer.py,sha256=G6xBHtc3GYR6fXKLN6xQl4f50KIaAMyMpMsknOLHRt0,5277
--setuptools/launch.py,sha256=TyPT-Ic1T2EnYvGO26gfNRP4ysBlrhpbRjQxWsiO414,812
--setuptools/lib2to3_ex.py,sha256=YKA7CmdIJWwy0-yuZAxUgoNHbXFmT4p53iNadWdBQCk,2335
--setuptools/monkey.py,sha256=0e3HdVKXHL415O7np-AUqhEFXPPuDdJKbI47chQ_DE4,5217
--setuptools/msvc.py,sha256=PHsjWnTwaD12gDJ1nbt9WkcRvwOOfTK0N7w5fmTZTS0,51197
--setuptools/namespaces.py,sha256=PMqGVPXPYQgjUTvEg9bGccRAkIODrQ6NmsDg_fwErwI,3093
--setuptools/package_index.py,sha256=lRLjafYNmRJ3_ZPmgwdU_Evh2BOu63PJoJPZbsXPIrQ,40610
--setuptools/py34compat.py,sha256=KYOd6ybRxjBW8NJmYD8t_UyyVmysppFXqHpFLdslGXU,245
--setuptools/sandbox.py,sha256=IirxmeCHbl1CHT7pEPgQ6tTx9wU854n-d2p80Su8t5c,14151
--setuptools/script
(dev).tmpl,sha256=RUzQzCQUaXtwdLtYHWYbIQmOaES5Brqq1FvUA_tu-5I,218
--setuptools/script.tmpl,sha256=WGTt5piezO27c-Dbx6l5Q4T3Ff20A5z7872hv3aAhYY,138
--setuptools/ssl_support.py,sha256=3yuxmYJxPupL97d4RBihMo1UCMXSou1Upn4VE9jkq48,8511
--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=lpQAphR_7uhWC2fbSEps4Ja9W4YwezN_IX_LJEt3khU,250
--setuptools/_distutils/_msvccompiler.py,sha256=JQcHez50UA3BQKK9fOKANI_GzNFx3_qnZdyHyHNAghA,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=4cqQgq06NbGo0vazGMT2aPZ6K2Z-HcuRn9Pfz_bQUPw,47437
--setuptools/_distutils/cmd.py,sha256=eco6LAGUtobLuPafuhmgKgkwRRL_WY8KJ4YeDCHpcls,18079
--setuptools/_distutils/config.py,sha256=dtHgblx9JhfyrKx1-J7Jlxw_f7s8ZbPFQii2UWMTZpY,4827
--setuptools/_distutils/core.py,sha256=jbdOkpOK09xi-56vhhwvn3fYdhLb5DJO8q3K1fnQz0Q,8876
--setuptools/_distutils/cygwinccompiler.py,sha256=9U4JAusUzlAGJl0Y5nToPkQ3ldzseAtiye434mwJ0ow,16380
--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=8bRxhzp2FsaoHT7TuKD4Qjcuh_B9Ow_xTt_htZJvN2Q,12832
--setuptools/_distutils/log.py,sha256=hWBmdUC2K927QcVv3REMW3HMPclxccPQngxLSuUXQl0,1969
--setuptools/_distutils/msvc9compiler.py,sha256=uv0TAfoWrxEBOQL-Z2uws5g4AXoTPahUEMuq6FLkCYY,30453
--setuptools/_distutils/msvccompiler.py,sha256=ZYsnUgIC4tZT2WkJbTkTUyVSCAc2nFM9DVKIuIfPBU0,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=XBmUqzhxXfay_JE18RkaalHf9kgi7NvXeBPW9BfTqmw,4408
--setuptools/_distutils/sysconfig.py,sha256=5z55MU7gXeceL_G9FK6ex-2OvdeIXJRZJafrtthJcfU,21349
--setuptools/_distutils/text_file.py,sha256=PsuAJeWdKJoLSV_6N6IpB5-0Pa84KzLUucJMFRazw3I,12483
--setuptools/_distutils/unixccompiler.py,sha256=E65edChYLoHY8wi4OxFu_wKt3hJe3GySF6v51G_ZzL0,14696
--setuptools/_distutils/util.py,sha256=Wlz9noChJjzem9mfgOu-KaN8soB4aNhRfe4VGltXd8w,20985
--setuptools/_distutils/version.py,sha256=8NogP6NPPQpp3EUMZcT9czEHia-ehqPo8spo_e7AgUU,12514
--setuptools/_distutils/versionpredicate.py,sha256=ZxpEA-TQv88mUWc6hetUO4qSqA2sa7ipjZ3QEK5evDk,5133
--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=11NyR2UAUzalrkTZ2ph0BAHFWFC2jtSsN7gIaF-NC08,5767
--setuptools/_distutils/command/build_clib.py,sha256=bgVTHh28eLQA2Gkw68amApd_j7qQBX4MTI-zTvAK_J4,8022
--setuptools/_distutils/command/build_ext.py,sha256=Y_SYbd8SHcpgNPfv3ifVniZljYs1cLAFleBSi2_O3CY,31685
--setuptools/_distutils/command/build_py.py,sha256=S_Nlw4hZE8PnIgqX5OFMdmt-GSmOhPQQ4f2jr1uBnoU,17190
--setuptools/_distutils/command/build_scripts.py,sha256=aKycJJPx3LfZ1cvZgSJaxnD2LnvRM5WJ-8xkpdgcLsI,6232
--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=oOM2rD7l_SglARNVDmiZn8u6DAfidXRF_yE5QS328B4,27482
--setuptools/_distutils/command/install_data.py,sha256=YhGOAwh3gJPqF7em5XA0rmpR42z1bLh80ooElzDyUvk,2822
--setuptools/_distutils/command/install_egg_info.py,sha256=0kW0liVMeadkjX0ZcRfMptKFen07Gw6gyw1VHT5KIwc,2603
--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/packaging/__about__.py,sha256=PNMsaZn4UcCHyubgROH1bl6CluduPjI5kFrSp_Zgklo,736
--setuptools/_vendor/packaging/__init__.py,sha256=6enbp5XgRfjBjsI9-bn00HjHf5TH21PDMOKkJW8xw-w,562
--setuptools/_vendor/packaging/_compat.py,sha256=MXdsGpSE_W-ZrHoC87andI4LV2FAwU7HLL-eHe_CjhU,1128
--setuptools/_vendor/packaging/_structures.py,sha256=ozkCX8Q8f2qE1Eic3YiQ4buDVfgz2iYevY9e7R2y3iY,2022
--setuptools/_vendor/packaging/_typing.py,sha256=x59EhQ57TMT-kTRyLZV25HZvYGGwbucTo6iKh_O0tMw,1812
--setuptools/_vendor/packaging/markers.py,sha256=BCCxZbt8xgysH8v5pqbLkdtQnRZHIGkJQqlNBGek4nQ,9509
--setuptools/_vendor/packaging/requirements.py,sha256=VHydZdk8m3qFxReomNwKr71cmpjantEV_xOhkEyyINI,4917
--setuptools/_vendor/packaging/specifiers.py,sha256=uYp9l13F0LcknS6d4N60ytiBgFmIhKideOq9AnsxTco,31944
--setuptools/_vendor/packaging/tags.py,sha256=NKMS37Zo_nWrZxgsD6zbXsXgc9edn9m160cBiLmHJdE,24067
--setuptools/_vendor/packaging/utils.py,sha256=RShlvnjO2CtYSD8uri32frMMFMTmB-3ihsq1-ghzLEw,1811
--setuptools/_vendor/packaging/version.py,sha256=Cnbm-OO9D_qd8ZTFxzFcjSavexSYFZmyeaoPvMsjgPc,15470
--setuptools/command/__init__.py,sha256=QCAuA9whnq8Bnoc0bBaS6Lw_KAUO0DiHYZQXEMNn5hg,568
--setuptools/command/alias.py,sha256=1sLQxZcNh6dDQpDmm4G7UGGTol83nY1NTPmNBbm2siI,2381
--setuptools/command/bdist_egg.py,sha256=9qKKLR9gtYXx84xxU4DQYTtMK__mrsaxwjUAiGQ1foY,18244
--setuptools/command/bdist_rpm.py,sha256=_4Y7tVAzu1zEuDc8tpRdE_sy3zST3h3LPTtzioos5Ck,900
--setuptools/command/bdist_wininst.py,sha256=Tmqa9wW0F8i_72KHWpu9pDdnCN6Er_8uJUs2UmCAwTA,922
--setuptools/command/build_clib.py,sha256=fWHSFGkk10VCddBWCszvNhowbG9Z9CZXVjQ2uSInoOs,4415
--setuptools/command/build_ext.py,sha256=aI_qnK9m8lULZDS6XMv_p2j2pIehVbSarb4PJHDA7dw,13027
--setuptools/command/build_py.py,sha256=10DNYiaM707UGJ-eV6YNcIKRN1pbU7UwXGYUXACrXU8,9473
--setuptools/command/develop.py,sha256=B0p5dh7VrSMdEfhdUE_AJlWk2UxAesOOY14CAV5_DEA,8045
--setuptools/command/dist_info.py,sha256=5t6kOfrdgALT-P3ogss6PF9k-Leyesueycuk3dUyZnI,960
--setuptools/command/easy_install.py,sha256=lMTMBbwsNdf7vYOJjPCZYCUBXVqOzKsd-NCE0V76dnU,86430
--setuptools/command/egg_info.py,sha256=a-fC7SCIprafrOlFEOfAf7oxKSplW92kRm1RZkEuBGo,25495
--setuptools/command/install.py,sha256=8doMxeQEDoK4Eco0mO2WlXXzzp9QnsGJQ7Z7yWkZPG8,4705
--setuptools/command/install_egg_info.py,sha256=bMgeIeRiXzQ4DAGPV1328kcjwQjHjOWU4FngAWLV78Q,2203
--setuptools/command/install_lib.py,sha256=Uz42McsyHZAjrB6cw9E7Bz0xsaTbzxnM1PI9CBhiPtE,3875
--setuptools/command/install_scripts.py,sha256=x7sdEICuyFpaf5LuWXcTp49oYt8EeNbwKkW2Pv-TVXI,2519
--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=R1bt94i_RscMePwKS1iBSvdlpFFRfh5iCPkucsxx0oI,6970
--setuptools/command/setopt.py,sha256=LicqlXockLqBOHYPNv1J032HxoBKD4HOHB11qm_t-Bs,5051
--setuptools/command/test.py,sha256=Y4jwjdX_4DCimImq6fDWoHzBniXDNJVEcD6XxVZIYS0,9469
--setuptools/command/upload.py,sha256=XT3YFVfYPAmA5qhGg0euluU98ftxRUW-PzKcODMLxUs,462
--setuptools/command/upload_docs.py,sha256=482Bd7ksBBfzdokxjNQvnR0A6dg8XOOREg9wB5BwKtU,7151
--setuptools/extern/__init__.py,sha256=59mpPNYGozvXXbAKrxldOmHLYFyBC4aaoYihYHkKnJ0,2121
--setuptools-51.2.0.dist-info/LICENSE,sha256=2z8CRrH5J48VhFuZ_sR4uLUG63ZIeZNyL4xuJUKF-vg,1050
--setuptools-51.2.0.dist-info/METADATA,sha256=CF0akXLXDT596yt3V7w4M0RYB9ICVCo6WkMF115Q2cs,4540
--setuptools-51.2.0.dist-info/WHEEL,sha256=OqRkF0eY5GHssMorFjlbTIq072vpHpF60fIQA6lS9xA,92
--setuptools-51.2.0.dist-info/dependency_links.txt,sha256=HlkCFkoK5TbZ5EMLbLKYhLcY_E31kBWD8TqW2EgmatQ,239
--setuptools-51.2.0.dist-info/entry_points.txt,sha256=ZJ1UygxmvKwag49KPsxSSUYtfwsbIhXiYHZ0szK6CCA,3143
--setuptools-51.2.0.dist-info/top_level.txt,sha256=Xc47-_bn9IXa0F2lP6dE-l-ySe5Xmv_ihIWX-aUjAgg,54
--setuptools-51.2.0.dist-info/RECORD,,
-diff --git
a/third_party/python/setuptools/setuptools-51.2.0.dist-info/dependency_links.txt

b/third_party/python/setuptools/setuptools-51.2.0.dist-info/dependency_links.txt
-deleted file mode 100644
----
a/third_party/python/setuptools/setuptools-51.2.0.dist-info/dependency_links.txt
-+++ /dev/null
-@@ -1,2 +0,0 @@
--https://files.pythonhosted.org/packages/source/c/certifi/certifi-2016.9.26.tar.gz#md5=baa81e951a29958563689d868ef1064d
--https://files.pythonhosted.org/packages/source/w/wincertstore/wincertstore-0.2.zip#md5=ae728f2f007185648d0c7a8679b361e2
-diff --git
a/third_party/python/setuptools/setuptools-51.2.0.dist-info/LICENSE
b/third_party/python/setuptools/setuptools-68.0.0.dist-info/LICENSE
-rename from third_party/python/setuptools/setuptools-51.2.0.dist-info/LICENSE
-rename to third_party/python/setuptools/setuptools-68.0.0.dist-info/LICENSE
---- a/third_party/python/setuptools/setuptools-51.2.0.dist-info/LICENSE
-+++ b/third_party/python/setuptools/setuptools-68.0.0.dist-info/LICENSE
-@@ -1,10 +1,8 @@
--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
-diff --git
a/third_party/python/setuptools/setuptools-68.0.0.dist-info/METADATA
b/third_party/python/setuptools/setuptools-68.0.0.dist-info/METADATA
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools-68.0.0.dist-info/METADATA
-@@ -0,0 +1,140 @@
-+Metadata-Version: 2.1
-+Name: setuptools
-+Version: 68.0.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 AT python.org
-+Project-URL: Documentation, https://setuptools.pypa.io/
-+Project-URL: Changelog, https://setuptools.pypa.io/en/stable/history.html
-+Keywords: CPAN PyPI distutils eggs package management
-+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.7
-+License-File: LICENSE
-+Provides-Extra: certs
-+Provides-Extra: docs
-+Requires-Dist: sphinx (>=3.5) ; extra == 'docs'
-+Requires-Dist: jaraco.packaging (>=9) ; extra == 'docs'
-+Requires-Dist: rst.linker (>=1.9) ; extra == 'docs'
-+Requires-Dist: furo ; extra == 'docs'
-+Requires-Dist: sphinx-lint ; extra == 'docs'
-+Requires-Dist: jaraco.tidelift (>=1.4) ; extra == 'docs'
-+Requires-Dist: pygments-github-lexers (==0.0.5) ; extra == 'docs'
-+Requires-Dist: sphinx-favicon ; extra == 'docs'
-+Requires-Dist: sphinx-inline-tabs ; extra == 'docs'
-+Requires-Dist: sphinx-reredirects ; extra == 'docs'
-+Requires-Dist: sphinxcontrib-towncrier ; extra == 'docs'
-+Requires-Dist: sphinx-notfound-page (==0.8.3) ; extra == 'docs'
-+Requires-Dist: sphinx-hoverxref (<2) ; extra == 'docs'
-+Provides-Extra: ssl
-+Provides-Extra: testing
-+Requires-Dist: pytest (>=6) ; extra == 'testing'
-+Requires-Dist: pytest-checkdocs (>=2.4) ; extra == 'testing'
-+Requires-Dist: pytest-enabler (>=1.3) ; extra == 'testing'
-+Requires-Dist: flake8-2020 ; extra == 'testing'
-+Requires-Dist: virtualenv (>=13.0.0) ; extra == 'testing'
-+Requires-Dist: wheel ; extra == 'testing'
-+Requires-Dist: pip (>=19.1) ; extra == 'testing'
-+Requires-Dist: jaraco.envs (>=2.2) ; extra == 'testing'
-+Requires-Dist: pytest-xdist ; extra == 'testing'
-+Requires-Dist: jaraco.path (>=3.2.0) ; extra == 'testing'
-+Requires-Dist: build[virtualenv] ; extra == 'testing'
-+Requires-Dist: filelock (>=3.4.0) ; extra == 'testing'
-+Requires-Dist: pip-run (>=8.8) ; extra == 'testing'
-+Requires-Dist: ini2toml[lite] (>=0.9) ; extra == 'testing'
-+Requires-Dist: tomli-w (>=1.0.0) ; extra == 'testing'
-+Requires-Dist: pytest-timeout ; extra == 'testing'
-+Requires-Dist: pytest-perf ; extra == 'testing'
-+Provides-Extra: testing-integration
-+Requires-Dist: pytest ; extra == 'testing-integration'
-+Requires-Dist: pytest-xdist ; extra == 'testing-integration'
-+Requires-Dist: pytest-enabler ; extra == 'testing-integration'
-+Requires-Dist: virtualenv (>=13.0.0) ; extra == 'testing-integration'
-+Requires-Dist: tomli ; extra == 'testing-integration'
-+Requires-Dist: wheel ; extra == 'testing-integration'
-+Requires-Dist: jaraco.path (>=3.2.0) ; extra == 'testing-integration'
-+Requires-Dist: jaraco.envs (>=2.2) ; extra == 'testing-integration'
-+Requires-Dist: build[virtualenv] ; extra == 'testing-integration'
-+Requires-Dist: filelock (>=3.4.0) ; extra == 'testing-integration'
-+Requires-Dist: pytest-black (>=0.3.7) ; (platform_python_implementation !=
"PyPy") and extra == 'testing'
-+Requires-Dist: pytest-cov ; (platform_python_implementation != "PyPy") and
extra == 'testing'
-+Requires-Dist: pytest-mypy (>=0.9.1) ; (platform_python_implementation !=
"PyPy") and extra == 'testing'
-+Requires-Dist: pytest-ruff ; (sys_platform != "cygwin") and extra ==
'testing'
-+
-+.. image:: https://img.shields.io/pypi/v/setuptools.svg
-+ :target: https://pypi.org/project/setuptools
-+
-+.. image:: https://img.shields.io/pypi/pyversions/setuptools.svg
-+
-+.. 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/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json
-+ :target: https://github.com/astral-sh/ruff
-+ :alt: Ruff
-+
-+.. 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-2023-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
-+
-+.. image:: https://img.shields.io/discord/803025117553754132
-+ :target:
https://discord.com/channels/803025117553754132/815945031150993468
-+ :alt: Discord
-+
-+See the `Installation Instructions
-+<https://packaging.python.org/installing/>`_ in the Python Packaging
-+User's Guide for instructions on installing, upgrading, and uninstalling
-+Setuptools.
-+
-+Questions and comments should be directed to `GitHub Discussions
-+<https://github.com/pypa/setuptools/discussions>`_.
-+Bug reports and especially tested patches may be
-+submitted directly to the `bug tracker
-+<https://github.com/pypa/setuptools/issues>`_.
-+
-+
-+Code of Conduct
-+===============
-+
-+Everyone interacting in the setuptools project's codebases, issue trackers,
-+chat rooms, and fora is expected to follow the
-+`PSF Code of Conduct
<https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md>`_.
-+
-+
-+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
<https://tidelift.com/subscription/pkg/pypi-setuptools?utm_source=pypi-setuptools&utm_medium=referral&utm_campaign=github>`_.
-+
-+
-+Security Contact
-+================
-+
-+To report a security vulnerability, please use the
-+`Tidelift security contact <https://tidelift.com/security>`_.
-+Tidelift will coordinate the fix and disclosure.
-diff --git
a/third_party/python/setuptools/setuptools-68.0.0.dist-info/RECORD
b/third_party/python/setuptools/setuptools-68.0.0.dist-info/RECORD
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools-68.0.0.dist-info/RECORD
-@@ -0,0 +1,235 @@
-+distutils-precedence.pth,sha256=JjjOniUA5XKl4N5_rtZmHrVp0baW_LoHsN0iPaX10iQ,151
-+_distutils_hack/__init__.py,sha256=RoSaYKfMhRic9rWsYrPxNQBIYs5qllQKgcle9vvE3D4,6299
-+_distutils_hack/override.py,sha256=Eu_s-NF6VIZ4Cqd0tbbA5wtWky2IZPNd8et6GLt1mzo,44
-+pkg_resources/__init__.py,sha256=oZanbvS9AvsPY0oZ9vSZfjDBnNBbJCrPt9Dkaj_92gI,109427
-+pkg_resources/_vendor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-+pkg_resources/_vendor/typing_extensions.py,sha256=ipqWiq5AHzrwczt6c26AP05Llh6a5_GaXRpOBqbogHA,80078
-+pkg_resources/_vendor/zipp.py,sha256=ajztOH-9I7KA_4wqDYygtHa6xUBVZgFpmZ8FE74HHHI,8425
-+pkg_resources/_vendor/importlib_resources/__init__.py,sha256=evPm12kLgYqTm-pbzm60bOuumumT8IpBNWFp0uMyrzE,506
-+pkg_resources/_vendor/importlib_resources/_adapters.py,sha256=o51tP2hpVtohP33gSYyAkGNpLfYDBqxxYsadyiRZi1E,4504
-+pkg_resources/_vendor/importlib_resources/_common.py,sha256=jSC4xfLdcMNbtbWHtpzbFkNa0W7kvf__nsYn14C_AEU,5457
-+pkg_resources/_vendor/importlib_resources/_compat.py,sha256=L8HTWyAC_MIKuxWZuw0zvTq5qmUA0ttrvK941OzDKU8,2925
-+pkg_resources/_vendor/importlib_resources/_itertools.py,sha256=WCdJ1Gs_kNFwKENyIG7TO0Y434IWCu0zjVVSsSbZwU8,884
-+pkg_resources/_vendor/importlib_resources/_legacy.py,sha256=0TKdZixxLWA-xwtAZw4HcpqJmj4Xprx1Zkcty0gTRZY,3481
-+pkg_resources/_vendor/importlib_resources/abc.py,sha256=Icr2IJ2QtH7vvAB9vC5WRJ9KBoaDyJa7KUs8McuROzo,5140
-+pkg_resources/_vendor/importlib_resources/readers.py,sha256=PZsi5qacr2Qn3KHw4qw3Gm1MzrBblPHoTdjqjH7EKWw,3581
-+pkg_resources/_vendor/importlib_resources/simple.py,sha256=0__2TQBTQoqkajYmNPt1HxERcReAT6boVKJA328pr04,2576
-+pkg_resources/_vendor/jaraco/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-+pkg_resources/_vendor/jaraco/context.py,sha256=vlyDzb_PvZ9H7R9bbTr_CMRnveW5Dc56eC7eyd_GfoA,7460
-+pkg_resources/_vendor/jaraco/functools.py,sha256=ggupfjztLyRtNk4pS2JqVrH3lWUX-QbE3wz5PyIKZWE,15056
-+pkg_resources/_vendor/jaraco/text/__init__.py,sha256=cN55bFcceW4wTHG5ruv5IuEDRarP-4hBYX8zl94_c30,15526
-+pkg_resources/_vendor/more_itertools/__init__.py,sha256=mTzXsWGDHiVW5x8zHzcRu1imUMzrEtJnUhfsN-dBrV4,148
-+pkg_resources/_vendor/more_itertools/more.py,sha256=hAluuEi5QOSe0OZfD2_sCwwbfbK5NnAxHg6uvU5AfPU,134976
-+pkg_resources/_vendor/more_itertools/recipes.py,sha256=lgw5bP3UoNfvUPhRaz1VIAfRFkF9pKWN-8UB6H0W5Eo,25416
-+pkg_resources/_vendor/packaging/__init__.py,sha256=kYVZSmXT6CWInT4UJPDtrSQBAZu8fMuFBxpv5GsDTLk,501
-+pkg_resources/_vendor/packaging/_elffile.py,sha256=hbmK8OD6Z7fY6hwinHEUcD1by7czkGiNYu7ShnFEk2k,3266
-+pkg_resources/_vendor/packaging/_manylinux.py,sha256=ESGrDEVmBc8jYTtdZRAWiLk72lOzAKWeezFgoJ_MuBc,8926
-+pkg_resources/_vendor/packaging/_musllinux.py,sha256=mvPk7FNjjILKRLIdMxR7IvJ1uggLgCszo-L9rjfpi0M,2524
-+pkg_resources/_vendor/packaging/_parser.py,sha256=KJQkBh_Xbfb-qsB560YIEItrTpCZaOh4_YMfBtd5XIY,10194
-+pkg_resources/_vendor/packaging/_structures.py,sha256=q3eVNmbWJGG_S0Dit_S3Ao8qQqz_5PYTXFAKBZe5yr4,1431
-+pkg_resources/_vendor/packaging/_tokenizer.py,sha256=alCtbwXhOFAmFGZ6BQ-wCTSFoRAJ2z-ysIf7__MTJ_k,5292
-+pkg_resources/_vendor/packaging/markers.py,sha256=eH-txS2zq1HdNpTd9LcZUcVIwewAiNU0grmq5wjKnOk,8208
-+pkg_resources/_vendor/packaging/metadata.py,sha256=PjELMLxKG_iu3HWjKAOdKhuNrHfWgpdTF2Q4nObsZeM,16397
-+pkg_resources/_vendor/packaging/requirements.py,sha256=hJzvtJyAvENc_VfwfhnOZV1851-VW8JCGh-R96NE4Pc,3287
-+pkg_resources/_vendor/packaging/specifiers.py,sha256=ZOpqL_w_Kj6ZF_OWdliQUzhEyHlDbi6989kr-sF5GHs,39206
-+pkg_resources/_vendor/packaging/tags.py,sha256=_1gLX8h1SgpjAdYCP9XqU37zRjXtU5ZliGy3IM-WcSM,18106
-+pkg_resources/_vendor/packaging/utils.py,sha256=es0cCezKspzriQ-3V88h3yJzxz028euV2sUwM61kE-o,4355
-+pkg_resources/_vendor/packaging/version.py,sha256=2NH3E57hzRhn0BV9boUBvgPsxlTqLJeI0EpYQoNvGi0,16326
-+pkg_resources/_vendor/platformdirs/__init__.py,sha256=edi2JSKpLCapqir0AW_CjpHtinRE3hf6aDk5-VHggLk,12806
-+pkg_resources/_vendor/platformdirs/__main__.py,sha256=VsC0t5m-6f0YVr96PVks93G3EDF8MSNY4KpUMvPahDA,1164
-+pkg_resources/_vendor/platformdirs/android.py,sha256=GKizhyS7ESRiU67u8UnBJLm46goau9937EchXWbPBlk,4068
-+pkg_resources/_vendor/platformdirs/api.py,sha256=MXKHXOL3eh_-trSok-JUTjAR_zjmmKF3rjREVABjP8s,4910
-+pkg_resources/_vendor/platformdirs/macos.py,sha256=-3UXQewbT0yMhMdkzRXfXGAntmLIH7Qt4a9Hlf8I5_Y,2655
-+pkg_resources/_vendor/platformdirs/unix.py,sha256=P-WQjSSieE38DXjMDa1t4XHnKJQ5idEaKT0PyXwm8KQ,6911
-+pkg_resources/_vendor/platformdirs/version.py,sha256=qaN-fw_htIgKUVXoAuAEVgKxQu3tZ9qE2eiKkWIS7LA,160
-+pkg_resources/_vendor/platformdirs/windows.py,sha256=LOrXLgI0CjQldDo2zhOZYGYZ6g4e_cJOCB_pF9aMRWQ,6596
-+pkg_resources/extern/__init__.py,sha256=nDtjbrhEaDu388fp4O6BGSpbihZmHh7PoOz2hhFk-Qg,2442
-+setuptools/__init__.py,sha256=_tPa_a8uV9I9ObTXCtXiIuhBdkvMGR8nHoGGpVjXCGs,9257
-+setuptools/_entry_points.py,sha256=FL1tONMODSygpiA_3rN_46k-HSmKqf3LgoxJdUpvox8,2282
-+setuptools/_imp.py,sha256=gaP2k2rwJVBcZmmToGPP1XHF5V77j1xW12lV_d9hkKM,2395
-+setuptools/_importlib.py,sha256=1Q3l8coa8DcfZZ2XBspzQ7PQBZo0Mksy_znOuD8jj_g,1466
-+setuptools/_itertools.py,sha256=pZAgXNz6tRPUFnHAaKJ90xAgD0gLPemcE1396Zgz73o,675
-+setuptools/_normalization.py,sha256=1--kRIAiru6kzH4i-QoK6V5vW7TEr20Inxb5uG2AL3g,3706
-+setuptools/_path.py,sha256=5xWH5ZZEJVcp_b0JjcAyTuTX2iz1H3F2Yti7fPIxueU,1056
-+setuptools/_reqs.py,sha256=1UTUBFswyoz1BiCQ-ofVlHNBpFYQ1eiNjraQsARoklk,882
-+setuptools/archive_util.py,sha256=6WShpDR_uGZOaORRfzBmJyTYtX9xtrhmXTFPqE8kL8s,7346
-+setuptools/build_meta.py,sha256=Yp3wMo22cgLdez3PwOpO1TU8EmapRYKV1AXtk6WqQhM,19778
-+setuptools/cli-32.exe,sha256=MqzBvFQxFsviz_EMuGd3LfLyVP8mNMhwrvC0bEtpb9s,11776
-+setuptools/cli-64.exe,sha256=u7PeVwdinmpgoMI4zUd7KPB_AGaYL9qVP6b87DkHOko,14336
-+setuptools/cli-arm64.exe,sha256=uafQjaiA36yLz1SOuksG-1m28JsX0zFIoPZhgyiSbGE,13824
-+setuptools/cli.exe,sha256=MqzBvFQxFsviz_EMuGd3LfLyVP8mNMhwrvC0bEtpb9s,11776
-+setuptools/dep_util.py,sha256=BDx1BkzNQntvAB4alypHbW5UVBzjqths000PrUL4Zqc,949
-+setuptools/depends.py,sha256=QYQIadr5DwLxPzkErhNt5hmRhvGhWxoXZMRXCm_jcQ0,5499
-+setuptools/discovery.py,sha256=a6bocT1n3D4MprhAZwEDkQNkmzQta4OspW2F0lY-Fzk,21087
-+setuptools/dist.py,sha256=SBpy_LdidMSYL7pZ9pxvC9H0Qxcdt253mfR-2yhTDTs,46807
-+setuptools/errors.py,sha256=2uToNIRA7dG995pf8ox8a4r7nJtP62-hpLhzsRirnx0,2464
-+setuptools/extension.py,sha256=jpsAdQvCBCkAuvmEXYI90TV4kNGO2Y13NqDr_PrvdhA,5591
-+setuptools/glob.py,sha256=1oZjbfjAHSXbgdhSuR6YGU8jKob9L8NtEmBYqcPTLYk,4873
-+setuptools/gui-32.exe,sha256=hdrh6V13hF8stZvKw9Sv50u-TJGpvMW_SnHNQxBNvnw,11776
-+setuptools/gui-64.exe,sha256=NHG2FA6txkEid9u-_j_vjDRaDxpZd2CGuAo2GMOoPjs,14336
-+setuptools/gui-arm64.exe,sha256=5pT0dDQFyLWSb_RX22_n8aEt7HwWqcOGR4TT9OB64Jc,13824
-+setuptools/gui.exe,sha256=hdrh6V13hF8stZvKw9Sv50u-TJGpvMW_SnHNQxBNvnw,11776
-+setuptools/installer.py,sha256=xU2FppNFwuWGdmZGFRgDVs03qd83X2035-DA05XLgqk,4926
-+setuptools/launch.py,sha256=TyPT-Ic1T2EnYvGO26gfNRP4ysBlrhpbRjQxWsiO414,812
-+setuptools/logging.py,sha256=WT1k7lH5hL-mOxsdVkrBjGV468QSpwAShlQ6pP09H6g,1232
-+setuptools/monkey.py,sha256=isKFUEFh6zcAUTzOeS1l_4TyakYNyGNLT2T91fBbnDw,4697
-+setuptools/msvc.py,sha256=C5V5lpwX84F39b4ZS5un9t2hvMu8QWJ2LkWl4RpvLKA,47345
-+setuptools/namespaces.py,sha256=PMqGVPXPYQgjUTvEg9bGccRAkIODrQ6NmsDg_fwErwI,3093
-+setuptools/package_index.py,sha256=IBb7G_Hiibg5hg9NYALulBWJyj_01A1656hk91KvPr8,38349
-+setuptools/py312compat.py,sha256=6qfRL57v2DWBBQdqv-w_T70KxK0iowZiCLVhESfj36Y,330
-+setuptools/sandbox.py,sha256=Xhj-2948bZhytdV_pJDMXAgV7Vg1lBC_7mcYb4DOwRI,14349
-+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=WJCeUuyq74Aok2TeK9-OexZOu8XrlQy7-y0BEuWNovQ,161
-+setuptools/warnings.py,sha256=durtKqFb2ltBWJ7JZgGWDxk2luMSGhJFLsYm8yWBzU8,3664
-+setuptools/wheel.py,sha256=Ccq__F15z3Jn4xDbCRyJ9wFCf_MhrV4zufGKoc_ejtE,8608
-+setuptools/windows_support.py,sha256=KXrFWrteXjhIou0gGwlfBy0ttAszHP52ETq-2pc0mes,718
-+setuptools/_distutils/__init__.py,sha256=swqU6jm29LbH4slGa3UTxYAaMUCLOzPY1qTMa4tv7PE,359
-+setuptools/_distutils/_collections.py,sha256=2qMJB2M_i53g0LmeYfD5V3SQ9fx3FScCXdFUS03wfiU,5300
-+setuptools/_distutils/_functools.py,sha256=ABZ-Lyw-igKwBFoLF3QYtFmfutwZLiAdWcpRMbcacGU,411
-+setuptools/_distutils/_log.py,sha256=zwFOk2ValRHMQa_kCqDXpHnwaqqZzhxGEwuR4zV-dEs,43
-+setuptools/_distutils/_macos_compat.py,sha256=-v_Z0M1LEH5k-VhSBBbuz_pDp3nSZ4rzU9E7iIskPDc,239
-+setuptools/_distutils/_msvccompiler.py,sha256=sWNC_gUhWzQ0FkCS6bD3Tj2Fvlnk2AwLnP8OvcV_gvQ,19616
-+setuptools/_distutils/archive_util.py,sha256=JtMIta8JuFkCXVTHvZhmneAEdIMnpsdX84nOWKF24rk,8572
-+setuptools/_distutils/bcppcompiler.py,sha256=IAFbt_mF3q3QFBhHGKHA68K1uNfU4MrkhoAJ0zA9S_k,14721
-+setuptools/_distutils/ccompiler.py,sha256=rnLM-1MMQgWm-lMOHz9a7XJ0YARP1xnuCAWkQY0XsDQ,48643
-+setuptools/_distutils/cmd.py,sha256=PcjcZszunlBw0FRICIr63LAAc8lUQoqia9GRLePzqc0,17861
-+setuptools/_distutils/config.py,sha256=NrQjaUO9B88P-JtOfww3BMt9rSn1TirU4G7u0ut5FrM,4911
-+setuptools/_distutils/core.py,sha256=2zrS7rdu7Oe2143xsmCld8H61IbSpwnru9GDeSCQLbY,9397
-+setuptools/_distutils/cygwinccompiler.py,sha256=hBv-OShb_uKvLjo_E2uqtQLEJNBBXTFglvf6mzbUN8o,11924
-+setuptools/_distutils/debug.py,sha256=N6MrTAqK6l9SVk6tWweR108PM8Ol7qNlfyV-nHcLhsY,139
-+setuptools/_distutils/dep_util.py,sha256=9pqhyGw2q2HGGGXAOpbbezj024aAr_47xDfXz5Fas7U,3414
-+setuptools/_distutils/dir_util.py,sha256=Ob0omB4OlZZXfFQtalVoIY6CgIrOkD5YZfATYv2DXZg,8072
-+setuptools/_distutils/dist.py,sha256=YU6OeLdWPDWMg-GRCeykT21fOp7PxAYn1uwnoRpI-uM,50174
-+setuptools/_distutils/errors.py,sha256=ZtBwnhDpQA2bxIazPXNDQ25uNxM4p2omsaSRNpV3rpE,3589
-+setuptools/_distutils/extension.py,sha256=F0TBNjYkMmte_Yg1bhKVHXSNWWNFEPIDUgwhuHdkox8,10270
-+setuptools/_distutils/fancy_getopt.py,sha256=njv20bPVKKusIRbs8Md1YNWlGZQV1mW5fWPNkdYx-QI,17899
-+setuptools/_distutils/file_util.py,sha256=koQCT7uz5wVTVGy-gdsFFPFQO5GfIhc06JUYbIX5V08,8212
-+setuptools/_distutils/filelist.py,sha256=rOKJPBvuLSjElfYuOwju95AzR3Ev5lvJoCJvI_XvZ9g,13715
-+setuptools/_distutils/log.py,sha256=725W7ISJzoSYNtLnEP1FwZe_IMUn1Xq6NEYwFbXg63k,1201
-+setuptools/_distutils/msvc9compiler.py,sha256=X2Xf2g-RMKzb_B4MIihiO3ogyTFjJNV1xRWpZTsbbSA,30188
-+setuptools/_distutils/msvccompiler.py,sha256=Vus9UyDuNCT_PfZjwu253wL0v5PiQ9miiMZmdIro5wM,23577
-+setuptools/_distutils/py38compat.py,sha256=gZ-NQ5c6ufwVEkJ0BwkbrqG9TvWirVJIrVGqhgvaY-Q,217
-+setuptools/_distutils/py39compat.py,sha256=vkxjv22H1bhToalClz3M0UUD8Xr21klbUBTQoVQxx20,639
-+setuptools/_distutils/spawn.py,sha256=E6Il74CIINCRjakXUcWqSWjfC_sdp4Qtod0Bw5y_NNQ,3495
-+setuptools/_distutils/sysconfig.py,sha256=BbXNQAF9_tErImHCfSori3188FwSw2TUFqLBvU1BLdg,18928
-+setuptools/_distutils/text_file.py,sha256=SBgU_IeHYRZMvmmqyE6I8qXAbh1Z-wd60Hf0Yv97Cls,12085
-+setuptools/_distutils/unixccompiler.py,sha256=HYO3TXHm5kLGSsIdf9ytVLYCzUpdLQMt4Jd2NN7duzQ,15601
-+setuptools/_distutils/util.py,sha256=bef-Z_j0XzPU2E1AHJQNvGYNovSxdiJMa3JIbanQm7g,18099
-+setuptools/_distutils/version.py,sha256=9dCa7JcCWXBrfGUsv7Zzvqm-Mrf7yaK6cC5xRzx3iqg,12951
-+setuptools/_distutils/versionpredicate.py,sha256=mkg9LtyF3EWox-KnbBx08gKV8zu0ymIl1izIho2-f7k,5205
-+setuptools/_distutils/command/__init__.py,sha256=fVUps4DJhvShMAod0y7xl02m46bd7r31irEhNofPrrs,430
-+setuptools/_distutils/command/_framework_compat.py,sha256=HW84Z1cWmg4b6aMJvlMI9o6sGZSEH_aWMTlDKstL8lY,1614
-+setuptools/_distutils/command/bdist.py,sha256=EpbYBIrW4QTYrA6G8uUJIKZaLmj8w4S5KWnXzmr6hQo,5408
-+setuptools/_distutils/command/bdist_dumb.py,sha256=FvvNgx_B7ypjf7rMxFNNBOsuF_Dj_OV8L4dmkULhQKM,4665
-+setuptools/_distutils/command/bdist_rpm.py,sha256=QNQku4v38GcOcctHGNbRVoYv5mVMVcexnmCxh9fqpGw,22013
-+setuptools/_distutils/command/build.py,sha256=XDgkAsMp_jLX9mj-6ESdf7GK_8RuX9kwILwXOhN1GaM,5584
-+setuptools/_distutils/command/build_clib.py,sha256=stRzgT6gdXMTmsEi8PyudEO32ZDC7iP--sdUErcMuOs,7684
-+setuptools/_distutils/command/build_ext.py,sha256=2poWttNAhj3Y45ZddgIVMwXjNXAdUcAOO_sc0wh6anQ,31503
-+setuptools/_distutils/command/build_py.py,sha256=LK_l_5gnFv6D02YtyJRBp5kE3SWmHVEC7CbBKe2tjk8,16537
-+setuptools/_distutils/command/build_scripts.py,sha256=cp6WiXTaEd8LWwxizpnFSmbCOSizPLclAHFFsqxRqqs,5604
-+setuptools/_distutils/command/check.py,sha256=f7QOy4LkKUXiRyyti4orzCJX9Z8sY_uOyMYUADADG6g,4872
-+setuptools/_distutils/command/clean.py,sha256=VCRg7BPVdLXgtevEi7t_iChJW6k6fOaO0GyqR_m_MRw,2594
-+setuptools/_distutils/command/config.py,sha256=FU8kAanpAvaaecBbRZTvZ7lcoxxBXq5_nTufwOyZUXg,13077
-+setuptools/_distutils/command/install.py,sha256=5h_6BldPSUPUkYDzdY1t6Jiqaw21yBZZokpkMVaBnyo,30153
-+setuptools/_distutils/command/install_data.py,sha256=NgW_xUoUqcBGjGFr2VHrkYFejVqeAmwsGSu_fGQb384,2762
-+setuptools/_distutils/command/install_egg_info.py,sha256=Cv69kqrFORuwb1I1owe-IxyK0ZANirqGgiLyxcYSnBI,2788
-+setuptools/_distutils/command/install_headers.py,sha256=v-QcVkjaWX5yf0xaup9_KySanVlmd6LhuzEhGpmTiTU,1180
-+setuptools/_distutils/command/install_lib.py,sha256=v3we1bymtqvE-j_7yCSnb4a0Jy32s3z1SLZzF91NpjY,8409
-+setuptools/_distutils/command/install_scripts.py,sha256=oiYYD6IhTx9F4CQMfz5LQeGT1y5hZrndxbKBYSvzTa8,1932
-+setuptools/_distutils/command/py37compat.py,sha256=EoJC8gVYMIv2tA1NpVA2XDyCT1qGp4BEn7aX_5ve1gw,672
-+setuptools/_distutils/command/register.py,sha256=q8kKVA-6IPWbgHPBbc8HvWwRi9DXerjnyiMgMG1fu8A,11817
-+setuptools/_distutils/command/sdist.py,sha256=JkT1SJQUgtlZyjFmyqx0lOL45tDb9I9Dn38iz9ySb-k,19232
-+setuptools/_distutils/command/upload.py,sha256=jsb3Kj3XQtNqwwvtc1WUt_Jk8AEXIehjEXIj3dInv6M,7491
-+setuptools/_vendor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-+setuptools/_vendor/ordered_set.py,sha256=dbaCcs27dyN9gnMWGF5nA_BrVn6Q-NrjKYJpV9_fgBs,15130
-+setuptools/_vendor/typing_extensions.py,sha256=1uqi_RSlI7gos4eJB_NEV3d5wQwzTUQHd3_jrkbTo8Q,87149
-+setuptools/_vendor/zipp.py,sha256=ajztOH-9I7KA_4wqDYygtHa6xUBVZgFpmZ8FE74HHHI,8425
-+setuptools/_vendor/importlib_metadata/__init__.py,sha256=fQEsJb7Gs_9Vq9V0xHICB0EFxNRGyxubr4w4ZFmGcxY,26498
-+setuptools/_vendor/importlib_metadata/_adapters.py,sha256=i8S6Ib1OQjcILA-l4gkzktMZe18TaeUNI49PLRp6OBU,2454
-+setuptools/_vendor/importlib_metadata/_collections.py,sha256=CJ0OTCHIjWA0ZIVS4voORAsn2R4R2cQBEtPsZEJpASY,743
-+setuptools/_vendor/importlib_metadata/_compat.py,sha256=GtdqmFy_ykVSTkz6MdGL2g3V5kxvQKHTWxKZCk5Q59Q,1859
-+setuptools/_vendor/importlib_metadata/_functools.py,sha256=PsY2-4rrKX4RVeRC1oGp1lB1pmC9eKN88_f-bD9uOoA,2895
-+setuptools/_vendor/importlib_metadata/_itertools.py,sha256=cvr_2v8BRbxcIl5x5ldfqdHjhI8Yi8s8yk50G_nm6jQ,2068
-+setuptools/_vendor/importlib_metadata/_meta.py,sha256=v5e1ZDG7yZTH3h7TjbS5bM5p8AGzMPVOu8skDMv4h6k,1165
-+setuptools/_vendor/importlib_metadata/_py39compat.py,sha256=2Tk5twb_VgLCY-1NEAQjdZp_S9OFMC-pUzP2isuaPsQ,1098
-+setuptools/_vendor/importlib_metadata/_text.py,sha256=HCsFksZpJLeTP3NEk_ngrAeXVRRtTrtyh9eOABoRP4A,2166
-+setuptools/_vendor/importlib_resources/__init__.py,sha256=evPm12kLgYqTm-pbzm60bOuumumT8IpBNWFp0uMyrzE,506
-+setuptools/_vendor/importlib_resources/_adapters.py,sha256=o51tP2hpVtohP33gSYyAkGNpLfYDBqxxYsadyiRZi1E,4504
-+setuptools/_vendor/importlib_resources/_common.py,sha256=jSC4xfLdcMNbtbWHtpzbFkNa0W7kvf__nsYn14C_AEU,5457
-+setuptools/_vendor/importlib_resources/_compat.py,sha256=L8HTWyAC_MIKuxWZuw0zvTq5qmUA0ttrvK941OzDKU8,2925
-+setuptools/_vendor/importlib_resources/_itertools.py,sha256=WCdJ1Gs_kNFwKENyIG7TO0Y434IWCu0zjVVSsSbZwU8,884
-+setuptools/_vendor/importlib_resources/_legacy.py,sha256=0TKdZixxLWA-xwtAZw4HcpqJmj4Xprx1Zkcty0gTRZY,3481
-+setuptools/_vendor/importlib_resources/abc.py,sha256=Icr2IJ2QtH7vvAB9vC5WRJ9KBoaDyJa7KUs8McuROzo,5140
-+setuptools/_vendor/importlib_resources/readers.py,sha256=PZsi5qacr2Qn3KHw4qw3Gm1MzrBblPHoTdjqjH7EKWw,3581
-+setuptools/_vendor/importlib_resources/simple.py,sha256=0__2TQBTQoqkajYmNPt1HxERcReAT6boVKJA328pr04,2576
-+setuptools/_vendor/jaraco/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-+setuptools/_vendor/jaraco/context.py,sha256=vlyDzb_PvZ9H7R9bbTr_CMRnveW5Dc56eC7eyd_GfoA,7460
-+setuptools/_vendor/jaraco/functools.py,sha256=0rUJxpJvN1TNlBScfYB2NbFGO1Pv7BeMJwzvqkVqnbY,15053
-+setuptools/_vendor/jaraco/text/__init__.py,sha256=KfFGMerrkN_0V0rgtJVx-9dHt3tW7i_uJypjwEcLtC0,15517
-+setuptools/_vendor/more_itertools/__init__.py,sha256=C7sXffHTXM3P-iaLPPfqfmDoxOflQMJLcM7ed9p3jak,82
-+setuptools/_vendor/more_itertools/more.py,sha256=0rB_mibFR51sq33UlAI_bWfaNdsYNnJr1v6S0CaW7QA,117959
-+setuptools/_vendor/more_itertools/recipes.py,sha256=UkNkrsZyqiwgLHANBTmvMhCvaNSvSNYhyOpz_Jc55DY,16256
-+setuptools/_vendor/packaging/__init__.py,sha256=kYVZSmXT6CWInT4UJPDtrSQBAZu8fMuFBxpv5GsDTLk,501
-+setuptools/_vendor/packaging/_elffile.py,sha256=hbmK8OD6Z7fY6hwinHEUcD1by7czkGiNYu7ShnFEk2k,3266
-+setuptools/_vendor/packaging/_manylinux.py,sha256=ESGrDEVmBc8jYTtdZRAWiLk72lOzAKWeezFgoJ_MuBc,8926
-+setuptools/_vendor/packaging/_musllinux.py,sha256=mvPk7FNjjILKRLIdMxR7IvJ1uggLgCszo-L9rjfpi0M,2524
-+setuptools/_vendor/packaging/_parser.py,sha256=KJQkBh_Xbfb-qsB560YIEItrTpCZaOh4_YMfBtd5XIY,10194
-+setuptools/_vendor/packaging/_structures.py,sha256=q3eVNmbWJGG_S0Dit_S3Ao8qQqz_5PYTXFAKBZe5yr4,1431
-+setuptools/_vendor/packaging/_tokenizer.py,sha256=alCtbwXhOFAmFGZ6BQ-wCTSFoRAJ2z-ysIf7__MTJ_k,5292
-+setuptools/_vendor/packaging/markers.py,sha256=eH-txS2zq1HdNpTd9LcZUcVIwewAiNU0grmq5wjKnOk,8208
-+setuptools/_vendor/packaging/metadata.py,sha256=PjELMLxKG_iu3HWjKAOdKhuNrHfWgpdTF2Q4nObsZeM,16397
-+setuptools/_vendor/packaging/requirements.py,sha256=hJzvtJyAvENc_VfwfhnOZV1851-VW8JCGh-R96NE4Pc,3287
-+setuptools/_vendor/packaging/specifiers.py,sha256=ZOpqL_w_Kj6ZF_OWdliQUzhEyHlDbi6989kr-sF5GHs,39206
-+setuptools/_vendor/packaging/tags.py,sha256=_1gLX8h1SgpjAdYCP9XqU37zRjXtU5ZliGy3IM-WcSM,18106
-+setuptools/_vendor/packaging/utils.py,sha256=es0cCezKspzriQ-3V88h3yJzxz028euV2sUwM61kE-o,4355
-+setuptools/_vendor/packaging/version.py,sha256=2NH3E57hzRhn0BV9boUBvgPsxlTqLJeI0EpYQoNvGi0,16326
-+setuptools/_vendor/tomli/__init__.py,sha256=JhUwV66DB1g4Hvt1UQCVMdfCu-IgAV8FXmvDU9onxd4,396
-+setuptools/_vendor/tomli/_parser.py,sha256=g9-ENaALS-B8dokYpCuzUFalWlog7T-SIYMjLZSWrtM,22633
-+setuptools/_vendor/tomli/_re.py,sha256=dbjg5ChZT23Ka9z9DHOXfdtSpPwUfdgMXnj8NOoly-w,2943
-+setuptools/_vendor/tomli/_types.py,sha256=-GTG2VUqkpxwMqzmVO4F7ybKddIbAnuAHXfmWQcTi3Q,254
-+setuptools/command/__init__.py,sha256=HZlSppOB8Vro73ffvP-xrORuMrh4GnVkOqJspFRG8Pg,396
-+setuptools/command/alias.py,sha256=1sLQxZcNh6dDQpDmm4G7UGGTol83nY1NTPmNBbm2siI,2381
-+setuptools/command/bdist_egg.py,sha256=sHC-ppJdCw-N3Vh4UlcMRISNAqT6V6NkyVVbmaGbFlY,16596
-+setuptools/command/bdist_rpm.py,sha256=98oPQkPdxmda_fzuvZqoeTuO-jbyf_FBz6zxkcI2wGY,1353
-+setuptools/command/build.py,sha256=oRWmv6b8a21B3I5X2Fhbmu79w2hXKxtSZTzmNngyzr8,6784
-+setuptools/command/build_clib.py,sha256=Rq4Q5OoyF19o25XQHF1kzTO4XrA_fS1VJGO7Pw5hztk,4423
-+setuptools/command/build_ext.py,sha256=cYm4OvllPf6I9YE3cWlnjPqqE546Mc7nQTpdJ-yH3jg,15821
-+setuptools/command/build_py.py,sha256=dA6M6rKIXr5K-xya44i8GAwp5bFZDBEygLkG0a6w4Ew,15012
-+setuptools/command/develop.py,sha256=RoTVnRcWXK802UGixySstw4SlcaO2UuQiafxmKLm11M,6744
-+setuptools/command/dist_info.py,sha256=2l6xUznJW-5FzQ4UimGfyAtSJfQ_7tUkQbxV8gr63qE,4195
-+setuptools/command/easy_install.py,sha256=Ver-kPU1Dl_qpivA6pXAonrJoEFwyg7-m-5SoMnibII,86117
-+setuptools/command/editable_wheel.py,sha256=K4iSflXZdYRxj9bLrixWwo_YowtJVukO53YuNxZyGjU,31965
-+setuptools/command/egg_info.py,sha256=bRDmCOiLLNq4CbF7wMezQlNhJgGGTvvc6F1NxjK7lv4,27278
-+setuptools/command/install.py,sha256=MRkbpOZSvn0gwrEliZleMlLlbycFcyNtcsMnV6X5f68,5598
-+setuptools/command/install_egg_info.py,sha256=C4mdexnCBcCzWvsf8Gk1VYV-fGcmmXkLSpT-X08VWuA,2123
-+setuptools/command/install_lib.py,sha256=Uz42McsyHZAjrB6cw9E7Bz0xsaTbzxnM1PI9CBhiPtE,3875
-+setuptools/command/install_scripts.py,sha256=QMwJMIQWNU89B3waJBb2zsQcNWtbTW00GYtMzfRF7uU,2714
-+setuptools/command/launcher
manifest.xml,sha256=xlLbjWrB01tKC0-hlVkOKkiSPbzMml2eOPtJ_ucCnbE,628
-+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=qBqszzyKP4DP1gzAIEVR-87GoazkX-4u9CirkOfcnSE,7007
-+setuptools/command/setopt.py,sha256=okxhqD1NM1nQlbSVDCNv6P7Y7g680sc2r-tUW7wPH1Y,5086
-+setuptools/command/test.py,sha256=ZWoIUdm6u2Zv-WhvSC5If1rPouxm5JmygwsajNA8WWI,8102
-+setuptools/command/upload.py,sha256=XT3YFVfYPAmA5qhGg0euluU98ftxRUW-PzKcODMLxUs,462
-+setuptools/command/upload_docs.py,sha256=GSVG9xe3A-aInmogHAoq2AZi9dMMQh-PGLTU3AAE7Yo,7690
-+setuptools/config/__init__.py,sha256=HVZX0i-bM5lIfhej4Vck0o8ZM6W6w6MEXLqCXcC9lYI,1498
-+setuptools/config/_apply_pyprojecttoml.py,sha256=NebBTKWEyAfUXfOxuYhZFwWafYEYvMDXjOPelrIx6k8,13755
-+setuptools/config/expand.py,sha256=vDaaDM8viv6hz-mGCfHUVxZod7qxx1DVoMlHr6gHcrI,16353
-+setuptools/config/pyprojecttoml.py,sha256=N5e_gzMosTWbh3HcKfplg5m4gZaY0YremwvOy88IJVA,17396
-+setuptools/config/setupcfg.py,sha256=sVN1wSeUaIFXhkM-V8F-fdB5IwzYMtm2-AMAxmx0ZdY,26184
-+setuptools/config/_validate_pyproject/__init__.py,sha256=5YXPW1sabVn5jpZ25sUjeF6ij3_4odJiwUWi4nRD2Dc,1038
-+setuptools/config/_validate_pyproject/error_reporting.py,sha256=vWiDs0hjlCBjZ_g4Xszsh97lIP9M4_JaLQ6MCQ26W9U,11266
-+setuptools/config/_validate_pyproject/extra_validations.py,sha256=wHzrgfdZUMRPBR1ke1lg5mhqRsBSbjEYOMsuFXQH9jY,1153
-+setuptools/config/_validate_pyproject/fastjsonschema_exceptions.py,sha256=w749JgqKi8clBFcObdcbZVqsmF4oJ_QByhZ1SGbUFNw,1612
-+setuptools/config/_validate_pyproject/fastjsonschema_validations.py,sha256=SCElnuZe5jHntpTWswUf7TnXwxlVkTzM-NwlzmSsJB0,274907
-+setuptools/config/_validate_pyproject/formats.py,sha256=5ocLimiqm4i8stjzHyBO89ejcVm-WKnmabDpSuIjE8U,9161
-+setuptools/extern/__init__.py,sha256=K1fgbtXn_E6b-MStbk4U2ULsjdZbkQNZGhd27nB26aU,2527
-+setuptools-68.0.0.dist-info/LICENSE,sha256=htoPAa6uRjSKPD1GUZXcHOzN55956HdppkuNoEsqR0E,1023
-+setuptools-68.0.0.dist-info/METADATA,sha256=TemdbBbUrJXBwToaPC_Cj7yWFyY32HgrGJvnGuW-Hak,6353
-+setuptools-68.0.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
-+setuptools-68.0.0.dist-info/entry_points.txt,sha256=Fe-UZkzgLTUZQOH94hbLTyP4HxM1nxlMuEZ_rS6zNnE,2676
-+setuptools-68.0.0.dist-info/top_level.txt,sha256=d9yL39v_W7qmKDDSH6sT4bE0j_Ls1M3P161OGgdsm4g,41
-+setuptools-68.0.0.dist-info/RECORD,,
-diff --git a/third_party/python/setuptools/setuptools-51.2.0.dist-info/WHEEL
b/third_party/python/setuptools/setuptools-68.0.0.dist-info/WHEEL
-rename from third_party/python/setuptools/setuptools-51.2.0.dist-info/WHEEL
-rename to third_party/python/setuptools/setuptools-68.0.0.dist-info/WHEEL
---- a/third_party/python/setuptools/setuptools-51.2.0.dist-info/WHEEL
-+++ b/third_party/python/setuptools/setuptools-68.0.0.dist-info/WHEEL
-@@ -1,5 +1,5 @@
- Wheel-Version: 1.0
--Generator: bdist_wheel (0.36.2)
-+Generator: bdist_wheel (0.40.0)
- Root-Is-Purelib: true
- Tag: py3-none-any
-
-diff --git
a/third_party/python/setuptools/setuptools-51.2.0.dist-info/entry_points.txt
b/third_party/python/setuptools/setuptools-68.0.0.dist-info/entry_points.txt
-rename from
third_party/python/setuptools/setuptools-51.2.0.dist-info/entry_points.txt
-rename to
third_party/python/setuptools/setuptools-68.0.0.dist-info/entry_points.txt
----
a/third_party/python/setuptools/setuptools-51.2.0.dist-info/entry_points.txt
-+++
b/third_party/python/setuptools/setuptools-68.0.0.dist-info/entry_points.txt
-@@ -1,68 +1,56 @@
--[console_scripts]
--easy_install = setuptools.command.easy_install:main
--easy_install-3.9 = setuptools.command.easy_install:main
--
- [distutils.commands]
- alias = setuptools.command.alias:alias
- bdist_egg = setuptools.command.bdist_egg:bdist_egg
- bdist_rpm = setuptools.command.bdist_rpm:bdist_rpm
--bdist_wininst = setuptools.command.bdist_wininst:bdist_wininst
-+build = setuptools.command.build:build
- 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
-+editable_wheel = setuptools.command.editable_wheel:editable_wheel
- 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]
--convert_2to3_doctests = setuptools.dist:assert_string_list
- 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:assert_bool
--use_2to3_exclude_fixers = setuptools.dist:assert_string_list
--use_2to3_fixers = setuptools.dist:assert_string_list
-+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]
--2to3_doctests = setuptools.dist:Distribution._finalize_2to3_doctests
- keywords = setuptools.dist:Distribution._finalize_setup_keywords
- parent_finalize = setuptools.dist:_Distribution.finalize_options
--
--[setuptools.installation]
--eggsecutable = setuptools.command.easy_install:bootstrap
--
-diff --git
a/third_party/python/setuptools/setuptools-51.2.0.dist-info/top_level.txt
b/third_party/python/setuptools/setuptools-68.0.0.dist-info/top_level.txt
-rename from
third_party/python/setuptools/setuptools-51.2.0.dist-info/top_level.txt
-rename to
third_party/python/setuptools/setuptools-68.0.0.dist-info/top_level.txt
---- a/third_party/python/setuptools/setuptools-51.2.0.dist-info/top_level.txt
-+++ b/third_party/python/setuptools/setuptools-68.0.0.dist-info/top_level.txt
-@@ -1,4 +1,3 @@
- _distutils_hack
--easy_install
- pkg_resources
- setuptools
-diff --git a/third_party/python/setuptools/setuptools/__init__.py
b/third_party/python/setuptools/setuptools/__init__.py
---- a/third_party/python/setuptools/setuptools/__init__.py
-+++ b/third_party/python/setuptools/setuptools/__init__.py
-@@ -1,212 +1,225 @@
- """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 distutils.util import convert_path as _convert_path
-
--from ._deprecation_warning import SetuptoolsDeprecationWarning
-+from .warnings import SetuptoolsDeprecationWarning
-
- import setuptools.version
- from setuptools.extension import Extension
- from setuptools.dist import Distribution
- from setuptools.depends import Require
-+from setuptools.discovery import PackageFinder, PEP420PackageFinder
- from . import monkey
-+from . import logging
-
-
- __all__ = [
-- 'setup', 'Distribution', 'Command', 'Extension', 'Require',
-+ 'setup',
-+ 'Distribution',
-+ 'Command',
-+ 'Extension',
-+ 'Require',
- 'SetuptoolsDeprecationWarning',
-- 'find_packages', 'find_namespace_packages',
-+ 'find_packages',
-+ 'find_namespace_packages',
- ]
-
- __version__ = setuptools.version.__version__
-
- bootstrap_install_from = None
-
--# If we run 2to3 on .py files, should we also convert docstrings?
--# Default: yes; assume that we can detect doctests reliably
--run_2to3_on_doctests = True
--# Standard package names for fixer packages
--lib2to3_fixer_packages = ['lib2to3.fixes']
--
--
--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)
-+ filtered = {k: attrs[k] for k in set(_incl) & set(attrs)}
-+ super().__init__(filtered)
-+ # Prevent accidentally triggering discovery with incomplete set
of attrs
-+ self.set_defaults._disable()
-+
-+ def _get_project_config_files(self, filenames=None):
-+ """Ignore ``pyproject.toml``, they are not related to
setup_requires"""
-+ try:
-+ cfg, toml =
super()._split_standard_project_metadata(filenames)
-+ return cfg, ()
-+ except Exception:
-+ return filenames, ()
-
- 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:
-+ _fetch_build_eggs(dist)
-+
-+
-+def _fetch_build_eggs(dist):
-+ try:
- dist.fetch_build_eggs(dist.setup_requires)
-+ except Exception as ex:
-+ msg = """
-+ It is possible a package already installed in your system
-+ contains an version that is invalid according to PEP 440.
-+ You can try `pip install --use-pep517` as a workaround for this
problem,
-+ or rely on a new virtual environment.
-+
-+ If the problem refers to a package that is not installed yet,
-+ please contact that package's maintainers or distributors.
-+ """
-+ if "InvalidVersion" in ex.__class__.__name__:
-+ if hasattr(ex, "add_note"):
-+ ex.add_note(msg) # PEP 678
-+ else:
-+ dist.announce(f"\n{msg}\n")
-+ raise
-
-
- def setup(**attrs):
- # Make sure we have any requirements needed to interpret 'attrs'.
-+ logging.configure()
- _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__
-+ """
-+ Setuptools internal actions are organized using a *command design
pattern*.
-+ This means that each action (or group of closely related actions)
executed during
-+ the build should be implemented as a ``Command`` subclass.
-+
-+ These commands are abstractions and do not necessarily correspond to a
command that
-+ can (or should) be executed via a terminal, in a CLI fashion (although
historically
-+ they would).
-+
-+ When creating a new command from scratch, custom defined classes
**SHOULD** inherit
-+ from ``setuptools.Command`` and implement a few mandatory methods.
-+ Between these mandatory methods, are listed:
-+
-+ .. method:: initialize_options(self)
-+
-+ Set or (reset) all options/attributes/caches used by the command
-+ to their default values. Note that these values may be overwritten
during
-+ the build.
-+
-+ .. method:: finalize_options(self)
-+
-+ Set final values for all options/attributes used by the command.
-+ Most of the time, each option/attribute/cache should only be set if
it does not
-+ have any value yet (e.g. ``if self.attr is None: self.attr = val``).
-+
-+ .. method:: run(self)
-+
-+ Execute the actions intended by the command.
-+ (Side effects **SHOULD** only take place when ``run`` is executed,
-+ for example, creating new files or writing to the terminal output).
-+
-+ 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 "body" of the
subroutine,
-+ (where it does all the work) is the ``run()`` method.
-+ Between ``initialize_options()`` and ``finalize_options()``,
``setuptools`` may set
-+ the values for options/attributes based on user's input (or
circumstance),
-+ which means that the implementation should be careful to not overwrite
values in
-+ ``finalize_options`` unless necessary.
-+
-+ Please note that other commands (or other parts of setuptools) may also
overwrite
-+ the values of the command's options/attributes multiple times during
the build
-+ process.
-+ Therefore it is important to consistently implement
``initialize_options()`` and
-+ ``finalize_options()``. For example, all derived attributes (or
attributes that
-+ depend on the value of other attributes) **SHOULD** be recomputed in
-+ ``finalize_options``.
-+
-+ When overwriting existing commands, custom defined classes **MUST**
abide by the
-+ same APIs implemented by the original class. They also **SHOULD**
inherit from the
-+ original class.
-+ """
-
- 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)
-+ super().__init__(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))
-+ 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"].
-+
-+ ..
-+ TODO: This method seems to be similar to the one in
``distutils.cmd``
-+ Probably it is just here for backward compatibility with old
Python versions?
-+
-+ :meta private:
- """
- 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))
-+ "'%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):
-@@ -228,14 +241,30 @@ def findall(dir=os.curdir):
- """
- 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)
-
-
-+@functools.wraps(_convert_path)
-+def convert_path(pathname):
-+ SetuptoolsDeprecationWarning.emit(
-+ "Access to implementation detail",
-+ """
-+ The function `convert_path` is not provided by setuptools itself,
-+ and therefore not part of the public API.
-+
-+ Its direct usage by 3rd-party packages is considered improper and
the function
-+ may be removed in the future.
-+ """,
-+ due_date=(2023, 12, 13) # initial deprecation 2022-03-25, see #3201
-+ )
-+ return _convert_path(pathname)
-+
-+
- class sic(str):
- """Treat this string as-is (https://en.wikipedia.org/wiki/Sic)"""
-
-
- # Apply monkey patches
- monkey.patch_all()
-diff --git
a/third_party/python/setuptools/setuptools/_deprecation_warning.py
b/third_party/python/setuptools/setuptools/_deprecation_warning.py
-deleted file mode 100644
---- a/third_party/python/setuptools/setuptools/_deprecation_warning.py
-+++ /dev/null
-@@ -1,7 +0,0 @@
--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/third_party/python/setuptools/setuptools/_distutils/__init__.py
b/third_party/python/setuptools/setuptools/_distutils/__init__.py
---- a/third_party/python/setuptools/setuptools/_distutils/__init__.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/__init__.py
-@@ -1,15 +1,14 @@
--"""distutils
-+import sys
-+import importlib
-
--The main package for the Python Module Distribution Utilities. Normally
--used from a setup script as
-+__version__, _, _ = sys.version.partition(' ')
-
-- from distutils.core import setup
-
-- setup (...)
--"""
--
--import sys
--
--__version__ = sys.version[:sys.version.index(' ')]
--
--local = True
-+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/third_party/python/setuptools/setuptools/_distutils/_collections.py
b/third_party/python/setuptools/setuptools/_distutils/_collections.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/_distutils/_collections.py
-@@ -0,0 +1,194 @@
-+import collections
-+import functools
-+import itertools
-+import operator
-+
-+
-+# from jaraco.collections 3.5.1
-+class DictStack(list, collections.abc.Mapping):
-+ """
-+ A stack of dictionaries that behaves as a view on those dictionaries,
-+ giving preference to the last.
-+
-+ >>> stack = DictStack([dict(a=1, c=2), dict(b=2, a=2)])
-+ >>> stack['a']
-+ 2
-+ >>> stack['b']
-+ 2
-+ >>> stack['c']
-+ 2
-+ >>> len(stack)
-+ 3
-+ >>> stack.push(dict(a=3))
-+ >>> stack['a']
-+ 3
-+ >>> set(stack.keys()) == set(['a', 'b', 'c'])
-+ True
-+ >>> set(stack.items()) == set([('a', 3), ('b', 2), ('c', 2)])
-+ True
-+ >>> dict(**stack) == dict(stack) == dict(a=3, c=2, b=2)
-+ True
-+ >>> d = stack.pop()
-+ >>> stack['a']
-+ 2
-+ >>> d = stack.pop()
-+ >>> stack['a']
-+ 1
-+ >>> stack.get('b', None)
-+ >>> 'c' in stack
-+ True
-+ """
-+
-+ def __iter__(self):
-+ dicts = list.__iter__(self)
-+ return iter(set(itertools.chain.from_iterable(c.keys() for c in
dicts)))
-+
-+ def __getitem__(self, key):
-+ for scope in reversed(tuple(list.__iter__(self))):
-+ if key in scope:
-+ return scope[key]
-+ raise KeyError(key)
-+
-+ push = list.append
-+
-+ def __contains__(self, other):
-+ return collections.abc.Mapping.__contains__(self, other)
-+
-+ def __len__(self):
-+ return len(list(iter(self)))
-+
-+
-+# from jaraco.collections 3.7
-+class RangeMap(dict):
-+ """
-+ A dictionary-like object that uses the keys as bounds for a range.
-+ Inclusion of the value for that range is determined by the
-+ key_match_comparator, which defaults to less-than-or-equal.
-+ A value is returned for a key if it is the first key that matches in
-+ the sorted list of keys.
-+
-+ One may supply keyword parameters to be passed to the sort function used
-+ to sort keys (i.e. key, reverse) as sort_params.
-+
-+ Let's create a map that maps 1-3 -> 'a', 4-6 -> 'b'
-+
-+ >>> r = RangeMap({3: 'a', 6: 'b'}) # boy, that was easy
-+ >>> r[1], r[2], r[3], r[4], r[5], r[6]
-+ ('a', 'a', 'a', 'b', 'b', 'b')
-+
-+ Even float values should work so long as the comparison operator
-+ supports it.
-+
-+ >>> r[4.5]
-+ 'b'
-+
-+ But you'll notice that the way rangemap is defined, it must be
open-ended
-+ on one side.
-+
-+ >>> r[0]
-+ 'a'
-+ >>> r[-1]
-+ 'a'
-+
-+ One can close the open-end of the RangeMap by using undefined_value
-+
-+ >>> r = RangeMap({0: RangeMap.undefined_value, 3: 'a', 6: 'b'})
-+ >>> r[0]
-+ Traceback (most recent call last):
-+ ...
-+ KeyError: 0
-+
-+ One can get the first or last elements in the range by using
RangeMap.Item
-+
-+ >>> last_item = RangeMap.Item(-1)
-+ >>> r[last_item]
-+ 'b'
-+
-+ .last_item is a shortcut for Item(-1)
-+
-+ >>> r[RangeMap.last_item]
-+ 'b'
-+
-+ Sometimes it's useful to find the bounds for a RangeMap
-+
-+ >>> r.bounds()
-+ (0, 6)
-+
-+ RangeMap supports .get(key, default)
-+
-+ >>> r.get(0, 'not found')
-+ 'not found'
-+
-+ >>> r.get(7, 'not found')
-+ 'not found'
-+
-+ One often wishes to define the ranges by their left-most values,
-+ which requires use of sort params and a key_match_comparator.
-+
-+ >>> r = RangeMap({1: 'a', 4: 'b'},
-+ ... sort_params=dict(reverse=True),
-+ ... key_match_comparator=operator.ge)
-+ >>> r[1], r[2], r[3], r[4], r[5], r[6]
-+ ('a', 'a', 'a', 'b', 'b', 'b')
-+
-+ That wasn't nearly as easy as before, so an alternate constructor
-+ is provided:
-+
-+ >>> r = RangeMap.left({1: 'a', 4: 'b', 7: RangeMap.undefined_value})
-+ >>> r[1], r[2], r[3], r[4], r[5], r[6]
-+ ('a', 'a', 'a', 'b', 'b', 'b')
-+
-+ """
-+
-+ def __init__(self, source, sort_params={},
key_match_comparator=operator.le):
-+ dict.__init__(self, source)
-+ self.sort_params = sort_params
-+ self.match = key_match_comparator
-+
-+ @classmethod
-+ def left(cls, source):
-+ return cls(
-+ source, sort_params=dict(reverse=True),
key_match_comparator=operator.ge
-+ )
-+
-+ def __getitem__(self, item):
-+ sorted_keys = sorted(self.keys(), **self.sort_params)
-+ if isinstance(item, RangeMap.Item):
-+ result = self.__getitem__(sorted_keys[item])
-+ else:
-+ key = self._find_first_match_(sorted_keys, item)
-+ result = dict.__getitem__(self, key)
-+ if result is RangeMap.undefined_value:
-+ raise KeyError(key)
-+ return result
-+
-+ def get(self, key, default=None):
-+ """
-+ Return the value for key if key is in the dictionary, else default.
-+ If default is not given, it defaults to None, so that this method
-+ never raises a KeyError.
-+ """
-+ try:
-+ return self[key]
-+ except KeyError:
-+ return default
-+
-+ def _find_first_match_(self, keys, item):
-+ is_match = functools.partial(self.match, item)
-+ matches = list(filter(is_match, keys))
-+ if matches:
-+ return matches[0]
-+ raise KeyError(item)
-+
-+ def bounds(self):
-+ sorted_keys = sorted(self.keys(), **self.sort_params)
-+ return (sorted_keys[RangeMap.first_item],
sorted_keys[RangeMap.last_item])
-+
-+ # some special values for the RangeMap
-+ undefined_value = type('RangeValueUndefined', (), {})()
-+
-+ class Item(int):
-+ "RangeMap Item"
-+
-+ first_item = Item(0)
-+ last_item = Item(-1)
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/_functools.py
b/third_party/python/setuptools/setuptools/_distutils/_functools.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/_distutils/_functools.py
-@@ -0,0 +1,20 @@
-+import functools
-+
-+
-+# from jaraco.functools 3.5
-+def pass_none(func):
-+ """
-+ Wrap func so it's not called if its first param is None
-+
-+ >>> print_text = pass_none(print)
-+ >>> print_text('text')
-+ text
-+ >>> print_text(None)
-+ """
-+
-+ @functools.wraps(func)
-+ def wrapper(param, *args, **kwargs):
-+ if param is not None:
-+ return func(param, *args, **kwargs)
-+
-+ return wrapper
-diff --git a/third_party/python/setuptools/setuptools/_distutils/_log.py
b/third_party/python/setuptools/setuptools/_distutils/_log.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/_distutils/_log.py
-@@ -0,0 +1,4 @@
-+import logging
-+
-+
-+log = logging.getLogger()
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/_macos_compat.py
b/third_party/python/setuptools/setuptools/_distutils/_macos_compat.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/_distutils/_macos_compat.py
-@@ -0,0 +1,12 @@
-+import sys
-+import importlib
-+
-+
-+def bypass_compiler_fixup(cmd, args):
-+ return cmd
-+
-+
-+if sys.platform == 'darwin':
-+ compiler_fixup = importlib.import_module('_osx_support').compiler_fixup
-+else:
-+ compiler_fixup = bypass_compiler_fixup
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/_msvccompiler.py
b/third_party/python/setuptools/setuptools/_distutils/_msvccompiler.py
---- a/third_party/python/setuptools/setuptools/_distutils/_msvccompiler.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/_msvccompiler.py
-@@ -12,34 +12,41 @@ for older versions in distutils.msvc9com
- # 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
-+import unittest.mock as 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 .errors import (
-+ DistutilsExecError,
-+ DistutilsPlatformError,
-+ CompileError,
-+ LibError,
-+ LinkError,
-+)
-+from .ccompiler import CCompiler, gen_lib_options
-+from ._log import log
-+from .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
-+ 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:
-@@ -52,55 +59,67 @@ def _find_vc2015():
- 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()
-+ 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'
-+ '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:
-@@ -109,46 +128,43 @@ def _find_vcvarsall(plat_spec):
-
- 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()
-- }
-+ 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),
-+ f'cmd /u /c "{vcvarsall}" {plat_spec} && set',
- stderr=subprocess.STDOUT,
- ).decode('utf-16le', errors='replace')
- except subprocess.CalledProcessError as exc:
- log.error(exc.output)
-- raise DistutilsPlatformError("Error executing {}"
-- .format(exc.cmd))
-+ raise DistutilsPlatformError(f"Error executing {exc.cmd}")
-
- env = {
- key.lower(): value
-- for key, _, value in
-- (line.partition('=') for line in out.splitlines())
-+ 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'.
-@@ -156,29 +172,31 @@ def _find_exe(exe, paths=None):
- 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'
-+ 'win32': 'x86',
-+ 'win-amd64': 'x86_amd64',
-+ 'win-arm32': 'x86_arm',
-+ 'win-arm64': 'x86_arm64',
- }
-
--class MSVCCompiler(CCompiler) :
-+
-+class MSVCCompiler(CCompiler):
- """Concrete class that implements an interface to Microsoft Visual C++,
-- as defined by the CCompiler abstract class."""
-+ 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.
-@@ -187,91 +205,105 @@ class MSVCCompiler(CCompiler) :
- # 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)
-+ 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)
-+ super().__init__(verbose, dry_run, force)
- # target platform (.plat_name is consistent with 'bdist')
- self.plat_name = None
- self.initialized = False
-
-+ @classmethod
-+ def _configure(cls, vc_env):
-+ """
-+ Set class-level include/lib dirs.
-+ """
-+ cls.include_dirs = cls._parse_path(vc_env.get('include', ''))
-+ cls.library_dirs = cls._parse_path(vc_env.get('lib', ''))
-+
-+ @staticmethod
-+ def _parse_path(val):
-+ return [dir.rstrip(os.sep) for dir in val.split(os.pathsep) if dir]
-+
- 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)))
-+ raise DistutilsPlatformError(
-+ f"--plat-name must be one of {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.")
-+ raise DistutilsPlatformError(
-+ "Unable to find a compatible " "Visual Studio installation."
-+ )
-+ self._configure(vc_env)
-
- 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.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
-
- 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', '/Ox', '/W3', '/GL', '/DNDEBUG', '/MD'
-- ]
-+ self.compile_options = ['/nologo', '/O2', '/W3', '/GL', '/DNDEBUG',
'/MD']
-
- self.compile_options_debug = [
-- '/nologo', '/Od', '/MDd', '/Zi', '/W3', '/D_DEBUG'
-+ '/nologo',
-+ '/Od',
-+ '/MDd',
-+ '/Zi',
-+ '/W3',
-+ '/D_DEBUG',
- ]
-
-- ldflags = [
-- '/nologo', '/INCREMENTAL:NO', '/LTCG'
-- ]
-+ ldflags = ['/nologo', '/INCREMENTAL:NO', '/LTCG']
-
-- ldflags_debug = [
-- '/nologo', '/INCREMENTAL:NO', '/LTCG', '/DEBUG:FULL'
-- ]
-+ 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_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,
-@@ -281,67 +313,51 @@ class MSVCCompiler(CCompiler) :
- (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},
-+ @property
-+ def out_extensions(self):
-+ return {
-+ **super().out_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):
--
-+ def compile( # noqa: C901
-+ 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)
-+ 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:
-@@ -376,123 +392,114 @@ class MSVCCompiler(CCompiler) :
- # 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))
-+ 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))
-+ raise CompileError(f"Don't know how to compile {src} to
{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((input_opt, "/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):
--
-+ 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)
-+ 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?
-+ 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):
--
-+ 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)
-+ 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))
-+ 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)
-+ 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])
-+ 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)
-+ 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)
-@@ -520,31 +527,31 @@ class MSVCCompiler(CCompiler) :
- 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):
-+ warnings.warn("Fallback spawn triggered. Please update distutils
monkeypatch.")
-+ with mock.patch.dict('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")
-+ "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:
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/archive_util.py
b/third_party/python/setuptools/setuptools/_distutils/archive_util.py
---- a/third_party/python/setuptools/setuptools/_distutils/archive_util.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/archive_util.py
-@@ -8,82 +8,91 @@ 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
-+from .errors import DistutilsExecError
-+from .spawn import spawn
-+from .dir_util import mkpath
-+from ._log 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):
-+
-+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'}
-+ 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'")
-+ "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
-@@ -107,29 +116,30 @@ def make_tarball(base_name, base_dir, co
- 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)
-+ warn("'compress' is deprecated.", DeprecationWarning)
- # 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):
-+
-+def make_zipfile(base_name, base_dir, verbose=0, dry_run=0): # noqa: C901
- """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.
- """
-@@ -140,36 +150,39 @@ def make_zipfile(base_name, base_dir, ve
- # '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)
-+ 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)
-+ 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)
-+ 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)
-+ zip = zipfile.ZipFile(
-+ zip_filename, "w", compression=zipfile.ZIP_DEFLATED
-+ )
- except RuntimeError:
-- zip = zipfile.ZipFile(zip_filename, "w",
-- compression=zipfile.ZIP_STORED)
-+ 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:
-@@ -179,37 +192,48 @@ def make_zipfile(base_name, base_dir, ve
- 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")
-- }
-+ '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):
-+
-+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
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/bcppcompiler.py
b/third_party/python/setuptools/setuptools/_distutils/bcppcompiler.py
---- a/third_party/python/setuptools/setuptools/_distutils/bcppcompiler.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/bcppcompiler.py
-@@ -8,26 +8,40 @@ for the Borland C++ compiler.
- # 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
-+import warnings
-
--class BCPPCompiler(CCompiler) :
-+from .errors import (
-+ DistutilsExecError,
-+ CompileError,
-+ LibError,
-+ LinkError,
-+ UnknownFileError,
-+)
-+from .ccompiler import CCompiler, gen_preprocess_options
-+from .file_util import write_file
-+from .dep_util import newer
-+from ._log import log
-+
-+
-+warnings.warn(
-+ "bcppcompiler is deprecated and slated to be removed "
-+ "in the future. Please discontinue use or file an issue "
-+ "with pypa/distutils describing your use case.",
-+ DeprecationWarning,
-+)
-+
-+
-+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,
-@@ -44,23 +58,18 @@ class BCPPCompiler(CCompiler) :
- # 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)
-+ def __init__(self, verbose=0, dry_run=0, force=0):
-+ super().__init__(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"
-@@ -68,57 +77,63 @@ class BCPPCompiler(CCompiler) :
- 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']
--
-+ 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)
-+ def compile( # noqa: C901
-+ 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')
-+ compile_opts.append('-c')
- if debug:
-- compile_opts.extend (self.compile_options_debug)
-+ compile_opts.extend(self.compile_options_debug)
- else:
-- compile_opts.extend (self.compile_options)
-+ 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
-+ 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])
-+ self.spawn(["brcc32", "-fo", obj, src])
- except DistutilsExecError as msg:
- raise CompileError(msg)
-- continue # the 'for' loop
-+ 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
-@@ -127,252 +142,245 @@ class BCPPCompiler(CCompiler) :
- 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])
-+ 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)
-
-- 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):
-+ if self._need_link(objects, output_filename):
- lib_args = [output_filename, '/u'] + objects
- if debug:
-- pass # XXX what goes here?
-+ pass # XXX what goes here?
- try:
-- self.spawn ([self.lib] + 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)
-
- # 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):
--
-+ def link( # noqa: C901
-+ 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)
-+ (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))
-+ log.warning(
-+ "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)
-+ output_filename = os.path.join(output_dir, output_filename)
-
-- if self._need_link (objects, 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)
-+ 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)
-+ for sym in export_symbols or []:
-+ contents.append(' {}=_{}'.format(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
-+ for ell in library_dirs:
-+ ld_args.append("/L%s" % os.path.normpath(ell))
-+ 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])
-+ 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')
-+ ld_args.extend(('import32', 'cw32mt'))
-
- # def file for export symbols
-- ld_args.extend([',',def_file])
-+ 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))
-+ self.mkpath(os.path.dirname(output_filename))
- try:
-- self.spawn ([self.linker] + 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)
-
- # link ()
-
- # -- Miscellaneous methods -----------------------------------------
-
--
-- def find_library_file (self, dirs, lib, debug=0):
-+ 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")
-+ 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 = ''
-+ 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))
-+ (base, ext) = os.path.splitext(os.path.normcase(src_name))
-+ if ext not in (self.src_extensions + ['.rc', '.res']):
-+ raise UnknownFileError(
-+ "unknown file type '{}' (from '{}')".format(ext,
src_name)
-+ )
- if strip_dir:
-- base = os.path.basename (base)
-+ base = os.path.basename(base)
- if ext == '.res':
- # these can go unchanged
-- obj_names.append (os.path.join (output_dir, base + ext))
-+ 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'))
-+ obj_names.append(os.path.join(output_dir, base + '.res'))
- else:
-- obj_names.append (os.path.join (output_dir,
-- base + self.obj_extension))
-+ 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)
-+ 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)
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/ccompiler.py
b/third_party/python/setuptools/setuptools/_distutils/ccompiler.py
---- a/third_party/python/setuptools/setuptools/_distutils/ccompiler.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/ccompiler.py
-@@ -1,21 +1,32 @@
- """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
-+import sys
-+import os
-+import re
-+import warnings
-+
-+from .errors import (
-+ CompileError,
-+ LinkError,
-+ UnknownFileError,
-+ DistutilsPlatformError,
-+ DistutilsModuleError,
-+)
-+from .spawn import spawn
-+from .file_util import move_file
-+from .dir_util import mkpath
-+from .dep_util import newer_group
-+from .util import split_quoted, execute
-+from ._log 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
-@@ -51,42 +62,52 @@ class CCompiler:
- # 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
-+ 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
-+ 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_map = {
-+ ".c": "c",
-+ ".cc": "c++",
-+ ".cpp": "c++",
-+ ".cxx": "c++",
-+ ".m": "objc",
-+ }
- language_order = ["c++", "objc", "c"]
-
-+ include_dirs = []
-+ """
-+ include dirs specific to this compiler class
-+ """
-+
-+ library_dirs = []
-+ """
-+ library dirs specific to this compiler class
-+ """
-+
- 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
-@@ -141,18 +162,20 @@ class CCompiler:
- # 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__))
-+ 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)
-
-@@ -165,54 +188,59 @@ class CCompiler:
- 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)")
--
-+ 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)
-+ 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)
-+ 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
-@@ -296,51 +324,30 @@ class CCompiler:
- 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):
-+ 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")
-+ outdir, macros, incdirs = self._fix_compile_args(outdir, macros,
incdirs)
-
- if extra is None:
- extra = []
-
- # Get the list of expected output (object) files
-- objects = self.object_filenames(sources, strip_dir=0,
-- output_dir=outdir)
-+ 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]
-@@ -377,27 +384,29 @@ class CCompiler:
- 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
-+ include_dirs = list(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")
-+ raise TypeError("'include_dirs' (if supplied) must be a list of
strings")
-+
-+ # add include dirs for class
-+ include_dirs += self.__class__.include_dirs
-
- return output_dir, macros, include_dirs
-
- def _prep_compile(self, sources, output_dir, depends=None):
-- """Decide which souce files must be recompiled.
-+ """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)
-@@ -429,51 +438,54 @@ class CCompiler:
- '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 [])
-+ libraries = list(libraries) + (self.libraries or [])
- else:
-- raise TypeError(
-- "'libraries' (if supplied) must be a list of strings")
-+ 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 [])
-+ library_dirs = list(library_dirs) + (self.library_dirs or [])
- else:
-- raise TypeError(
-- "'library_dirs' (if supplied) must be a list of strings")
-+ raise TypeError("'library_dirs' (if supplied) must be a list of
strings")
-+
-+ # add library dirs for class
-+ library_dirs += self.__class__.library_dirs
-
- 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 []))
-+ 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")
-+ 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')
-+ newer = newer_group(objects, output_file, missing='newer')
- else:
-- newer = newer_group (objects, output_file)
-+ 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]
-@@ -486,36 +498,50 @@ class CCompiler:
- 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):
-+ 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):
-+ 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
-@@ -556,19 +582,19 @@ class CCompiler:
- 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)
-+ 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)
-@@ -577,18 +603,19 @@ class CCompiler:
- 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):
-+ 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
-@@ -603,36 +630,37 @@ class CCompiler:
- '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):
-+ 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).
-@@ -668,76 +696,108 @@ class CCompiler:
- '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_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_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)
--
-+ 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
-@@ -752,61 +812,111 @@ class CCompiler:
- 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.
-+ def has_function( # noqa: C901
-+ self,
-+ funcname,
-+ includes=None,
-+ include_dirs=None,
-+ libraries=None,
-+ library_dirs=None,
-+ ):
-+ """Return a boolean indicating whether funcname is provided as
-+ a symbol on the current platform. The optional arguments can
-+ be used to augment the compilation environment.
-+
-+ The libraries argument is a list of flags to be passed to the
-+ linker to make additional symbol definitions available for
-+ linking.
-+
-+ The includes and include_dirs arguments are deprecated.
-+ Usually, supplying include files with function declarations
-+ will cause function detection to fail even in cases where the
-+ symbol is available for linking.
-+
- """
- # 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 = []
-+ else:
-+ warnings.warn("includes is deprecated", DeprecationWarning)
- if include_dirs is None:
- include_dirs = []
-+ else:
-+ warnings.warn("include_dirs is deprecated", DeprecationWarning)
- 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("""\
-+ if not includes:
-+ # Use "char func(void);" as the prototype to follow
-+ # what autoconf does. This prototype does not match
-+ # any well-known function the compiler might recognize
-+ # as a builtin, so this ends up as a true link test.
-+ # Without a fake prototype, the test would need to
-+ # know the exact argument types, and the has_function
-+ # interface does not provide that level of information.
-+ f.write(
-+ """\
-+#ifdef __cplusplus
-+extern "C"
-+#endif
-+char %s(void);
-+"""
-+ % funcname
-+ )
-+ f.write(
-+ """\
- int main (int argc, char **argv) {
- %s();
- return 0;
- }
--""" % funcname)
-+"""
-+ % 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)
-+ self.link_executable(
-+ objects, "a.out", libraries=libraries,
library_dirs=library_dirs
-+ )
- except (LinkError, TypeError):
- return False
-+ else:
-+ os.remove(
-+ self.executable_filename("a.out",
output_dir=self.output_dir or '')
-+ )
-+ finally:
-+ for fn in objects:
-+ os.remove(fn)
- return True
-
-- def find_library_file (self, dirs, lib, debug=0):
-+ 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
-
-@@ -842,151 +952,177 @@ int main (int argc, char **argv) {
- # (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
-+ return list(
-+ self._make_out_path(output_dir, strip_dir, src_name)
-+ for src_name in source_filenames
-+ )
-+
-+ @property
-+ def out_extensions(self):
-+ return dict.fromkeys(self.src_extensions, self.obj_extension)
-+
-+ def _make_out_path(self, output_dir, strip_dir, src_name):
-+ base, ext = os.path.splitext(src_name)
-+ base = self._make_relative(base)
-+ try:
-+ new_ext = self.out_extensions[ext]
-+ except LookupError:
-+ raise UnknownFileError(
-+ "unknown file type '{}' (from '{}')".format(ext, src_name)
-+ )
-+ if strip_dir:
-+ base = os.path.basename(base)
-+ return os.path.join(output_dir, base + new_ext)
-+
-+ @staticmethod
-+ def _make_relative(base):
-+ """
-+ In order to ensure that a filename always honors the
-+ indicated output_dir, make sure it's relative.
-+ Ref python/cpython#37775.
-+ """
-+ # Chop off the drive
-+ no_drive = os.path.splitdrive(base)[1]
-+ # If abs, chop off leading /
-+ return no_drive[os.path.isabs(no_drive) :]
-
- 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=''):
-+ def library_filename(
-+ self, libname, lib_type='static', strip_dir=0, output_dir='' # or
'shared'
-+ ):
- 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\"")
-+ expected = '"static", "shared", "dylib", "xcode_stub"'
-+ if lib_type not in eval(expected):
-+ raise ValueError(f"'lib_type' must be {expected}")
- 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):
-+ 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.
-+ 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.
-+ 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:
-+ 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"),
-- }
-+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.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'
-@@ -1009,27 +1145,28 @@ def new_compiler(plat=None, compiler=Non
- 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)
-+ __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)
-+ "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))
-+ "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):
-@@ -1052,37 +1189,37 @@ def gen_preprocess_options(macros, inclu
- # '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)
-+ "bad macro definition '%s': "
-+ "each element of 'macros' list must be a 1- or 2-tuple" %
macro
-+ )
-
-- if len(macro) == 1: # undefine this 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
-+ 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):
-+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 = []
-
-@@ -1104,13 +1241,14 @@ def gen_lib_options (compiler, library_d
-
- 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)
-+ compiler.warn(
-+ "no library file corresponding to " "'%s' found
(skipping)" % lib
-+ )
- else:
-- lib_opts.append(compiler.library_option (lib))
-+ lib_opts.append(compiler.library_option(lib))
- return lib_opts
-diff --git a/third_party/python/setuptools/setuptools/_distutils/cmd.py
b/third_party/python/setuptools/setuptools/_distutils/cmd.py
---- a/third_party/python/setuptools/setuptools/_distutils/cmd.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/cmd.py
-@@ -1,18 +1,23 @@
- """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
-+import sys
-+import os
-+import re
-+import logging
-+
-+from .errors import DistutilsOptionError
-+from . import util, dir_util, file_util, archive_util, dep_util
-+from ._log 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
-@@ -36,17 +41,16 @@ class Command:
- # 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.
- """
-@@ -125,77 +129,76 @@ class 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__)
-+ 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__)
--
-+ 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)
-+ self.announce(indent + header, level=logging.INFO)
- indent = indent + " "
-- for (option, _, _) in self.user_options:
-+ 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)
-+ self.announce(indent + "{} = {}".format(option, value),
level=logging.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__)
-+ 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.
-- """
-+ def announce(self, msg, level=logging.DEBUG):
- 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
-@@ -205,18 +208,19 @@ class Command:
- # 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))
-+ raise DistutilsOptionError(
-+ "'{}' must be a {} (got `{}`)".format(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)
-
-@@ -233,37 +237,39 @@ class Command:
- 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))
-+ "'{}' must be a list of strings (got
{!r})".format(option, val)
-+ )
-
-- def _ensure_tested_string(self, option, tester, what, error_fmt,
-- default=None):
-+ 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))
-+ 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")
-+ 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")
--
-+ 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__
-@@ -280,123 +286,149 @@ class Command:
- 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:
-+ 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)
-+ 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:
-+ 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)
-+ log.warning("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):
-+ 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)
-+ 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):
-+ 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)
-+ 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):
-+ 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_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):
-+ 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")
-+ 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))
-+ exec_msg = "generating {} from {}".format(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:
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/command/__init__.py
b/third_party/python/setuptools/setuptools/_distutils/command/__init__.py
---- a/third_party/python/setuptools/setuptools/_distutils/command/__init__.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/command/__init__.py
-@@ -1,31 +1,25 @@
- """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
-- ]
-+__all__ = [ # noqa: F822
-+ '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',
-+ 'check',
-+ 'upload',
-+]
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/command/_framework_compat.py

b/third_party/python/setuptools/setuptools/_distutils/command/_framework_compat.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/_distutils/command/_framework_compat.py
-@@ -0,0 +1,55 @@
-+"""
-+Backward compatibility for homebrew builds on macOS.
-+"""
-+
-+
-+import sys
-+import os
-+import functools
-+import subprocess
-+import sysconfig
-+
-+
-+@functools.lru_cache()
-+def enabled():
-+ """
-+ Only enabled for Python 3.9 framework homebrew builds
-+ except ensurepip and venv.
-+ """
-+ PY39 = (3, 9) < sys.version_info < (3, 10)
-+ framework = sys.platform == 'darwin' and sys._framework
-+ homebrew = "Cellar" in sysconfig.get_config_var('projectbase')
-+ venv = sys.prefix != sys.base_prefix
-+ ensurepip = os.environ.get("ENSUREPIP_OPTIONS")
-+ return PY39 and framework and homebrew and not venv and not ensurepip
-+
-+
-+schemes = dict(
-+ osx_framework_library=dict(
-+ stdlib='{installed_base}/{platlibdir}/python{py_version_short}',
-+ platstdlib='{platbase}/{platlibdir}/python{py_version_short}',
-+
purelib='{homebrew_prefix}/lib/python{py_version_short}/site-packages',
-+
platlib='{homebrew_prefix}/{platlibdir}/python{py_version_short}/site-packages',
-+
include='{installed_base}/include/python{py_version_short}{abiflags}',
-+
platinclude='{installed_platbase}/include/python{py_version_short}{abiflags}',
-+ scripts='{homebrew_prefix}/bin',
-+ data='{homebrew_prefix}',
-+ )
-+)
-+
-+
-+@functools.lru_cache()
-+def vars():
-+ if not enabled():
-+ return {}
-+ homebrew_prefix = subprocess.check_output(['brew', '--prefix'],
text=True).strip()
-+ return locals()
-+
-+
-+def scheme(name):
-+ """
-+ Override the selected scheme for posix_prefix.
-+ """
-+ if not enabled() or not name.endswith('_prefix'):
-+ return name
-+ return 'osx_framework_library'
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/command/bdist.py
b/third_party/python/setuptools/setuptools/_distutils/command/bdist.py
---- a/third_party/python/setuptools/setuptools/_distutils/command/bdist.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/command/bdist.py
-@@ -1,87 +1,100 @@
- """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
-+import warnings
-+
-+from ..core import Command
-+from ..errors import DistutilsPlatformError, DistutilsOptionError
-+from ..util import get_platform
-
-
- def show_formats():
-- """Print list of available formats (arguments to "--format" option).
-- """
-- from distutils.fancy_getopt import FancyGetopt
-+ """Print list of available formats (arguments to "--format" option)."""
-+ from ..fancy_getopt import FancyGetopt
-+
- formats = []
- for format in bdist.format_commands:
-- formats.append(("formats=" + format, None,
-- bdist.format_command[format][1]))
-+ formats.append(("formats=" + format, None,
bdist.format_commands[format][1]))
- pretty_printer = FancyGetopt(formats)
- pretty_printer.print_help("List of available distribution formats:")
-
-
-+class ListCompat(dict):
-+ # adapter to allow for Setuptools compatibility in format_commands
-+ def append(self, item):
-+ warnings.warn(
-+ """format_commands is now a dict. append is deprecated.""",
-+ DeprecationWarning,
-+ stacklevel=2,
-+ )
-+
-+
- 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]"),
-- ]
-+ 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),
-- ]
-+ ('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']
-+ default_format = {'posix': 'gztar', 'nt': 'zip'}
-
-- # 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")
-- }
-+ # Define commands in preferred order for the --help-formats option
-+ format_commands = ListCompat(
-+ {
-+ '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"),
-+ 'zip': ('bdist_dumb', "ZIP file"),
-+ }
-+ )
-
-+ # for compatibility until consumers only reference format_commands
-+ format_command = format_commands
-
- 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
-@@ -95,37 +108,37 @@ class bdist(Command):
- 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.<plat>/dumb", "build/bdist.<plat>/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.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)
-+ "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])
-+ commands.append(self.format_commands[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:
-@@ -133,11 +146,11 @@ class bdist(Command):
-
- # 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:]:
-+ if cmd_name in commands[i + 1 :]:
- sub_cmd.keep_temp = 1
- self.run_command(cmd_name)
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/command/bdist_dumb.py
b/third_party/python/setuptools/setuptools/_distutils/command/bdist_dumb.py
----
a/third_party/python/setuptools/setuptools/_distutils/command/bdist_dumb.py
-+++
b/third_party/python/setuptools/setuptools/_distutils/command/bdist_dumb.py
-@@ -1,56 +1,67 @@
- """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
-+from ..core import Command
-+from ..util import get_platform
-+from ..dir_util import remove_tree, ensure_relative
-+from ..errors import DistutilsPlatformError
-+from ..sysconfig import get_python_version
-+from distutils._log 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]"),
-- ]
-+ 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' }
-+ 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
-@@ -63,61 +74,70 @@ class bdist_dumb(Command):
- 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)
-+ "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'))
-+ 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)
-+ archive_basename = "{}.{}".format(
-+ 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)):
-+ 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)))
-+ "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))
-+ 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)
-+ 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))
-+ 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/third_party/python/setuptools/setuptools/_distutils/command/bdist_msi.py
b/third_party/python/setuptools/setuptools/_distutils/command/bdist_msi.py
-deleted file mode 100644
----
a/third_party/python/setuptools/setuptools/_distutils/command/bdist_msi.py
-+++ /dev/null
-@@ -1,749 +0,0 @@
--# 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
-- # """
-- # <actual script>
-- 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/third_party/python/setuptools/setuptools/_distutils/command/bdist_rpm.py
b/third_party/python/setuptools/setuptools/_distutils/command/bdist_rpm.py
----
a/third_party/python/setuptools/setuptools/_distutils/command/bdist_rpm.py
-+++
b/third_party/python/setuptools/setuptools/_distutils/command/bdist_rpm.py
-@@ -1,141 +1,159 @@
- """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
-+import subprocess
-+import sys
-+import os
-+
-+from ..core import Command
-+from ..debug import DEBUG
-+from ..file_util import write_file
-+from ..errors import (
-+ DistutilsOptionError,
-+ DistutilsPlatformError,
-+ DistutilsFileError,
-+ DistutilsExecError,
-+)
-+from ..sysconfig import get_python_version
-+from distutils._log 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"),
--
-+ ('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 <joe AT example.com>\") "
-- "[default: maintainer or author from setup script]"),
-- ('packager=', None,
-- "RPM packager (eg. \"Jane Doe <jane AT example.net>\") "
-- "[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"),
--
-+ (
-+ '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 <joe AT example.com>\") "
-+ "[default: maintainer or author from setup script]",
-+ ),
-+ (
-+ 'packager=',
-+ None,
-+ "RPM packager (eg. \"Jane Doe <jane AT example.net>\") "
"[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"),
--
-+ ('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"),
-+ ('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"),
-+ ]
-
-- ('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"),
-+ boolean_options = [
-+ 'keep-temp',
-+ 'use-rpm-opt-flags',
-+ 'rpm3-mode',
-+ 'no-autoreq',
-+ 'quiet',
-+ ]
-
-- # 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'}
--
-+ 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
-@@ -176,62 +194,66 @@ class bdist_rpm(Command):
-
- 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")
-+ 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")
-+ "--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)
-+ 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'")
-+ "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(
-+ '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('serial') # should it be an int?
-
- self.ensure_string('distribution_name')
-
- self.ensure_string('changelog')
-- # Format changelog correctly
-+ # 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')
-@@ -248,17 +270,17 @@ class bdist_rpm(Command):
- 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):
-+ def run(self): # noqa: C901
- 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
-@@ -269,24 +291,22 @@ class bdist_rpm(Command):
- 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.<plat> 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)
-+ 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
-+ 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']
-@@ -298,64 +318,65 @@ class bdist_rpm(Command):
- 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)
-+ 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?
-+ 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)])
-+ 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)
-+ q_cmd = r"rpm -q --qf '{} {}\n' --specfile '{}'".format(
-+ 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])
-+ ell = line.strip().split()
-+ assert len(ell) == 2
-+ binary_rpms.append(ell[1])
- # The source rpm is named after the first entry in the spec
file
- if source_rpm is None:
-- source_rpm = l[0]
-+ source_rpm = ell[0]
-
- status = out.close()
- if status:
- raise DistutilsExecError("Failed to execute: %s" %
repr(q_cmd))
-
- finally:
- out.close()
-
-@@ -364,207 +385,221 @@ class bdist_rpm(Command):
- 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))
-+ 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))
-+ 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))
-+ filename = os.path.join(self.dist_dir,
os.path.basename(rpm))
- self.distribution.dist_files.append(
-- ('bdist_rpm', pyversion, filename))
-+ ('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):
-+ def _make_spec_file(self): # noqa: C901
- """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 version ' +
self.distribution.get_version().replace('-', '_'),
- '%define unmangled_version ' + self.distribution.get_version(),
-- '%define release ' + self.release.replace('-','_'),
-+ '%define release ' + self.release.replace('-', '_'),
- '',
-- 'Summary: ' + self.distribution.get_description(),
-- ]
-+ 'Summary: ' + (self.distribution.get_description() or
"UNKNOWN"),
-+ ]
-
- # 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()])
-+ 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():
-+ # 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}',])
-+ 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}', ])
-+ spec_file.extend(
-+ [
-+ 'License: ' + (self.distribution.get_license() or
"UNKNOWN"),
-+ '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 )
-+ spec_file.append('BuildArch: %s' % self.force_arch)
-
-- for field in ('Vendor',
-- 'Packager',
-- 'Provides',
-- 'Requires',
-- 'Conflicts',
-- 'Obsoletes',
-- ):
-+ 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)))
-+ spec_file.append('{}: {}'.format(field, ' '.join(val)))
- elif val is not None:
-- spec_file.append('%s: %s' % (field, val))
-+ spec_file.append('{}: {}'.format(field, val))
-
--
-- if self.distribution.get_url() != 'UNKNOWN':
-+ if self.distribution.get_url():
- 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))
-+ 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()
-- ])
-+ spec_file.extend(
-+ [
-+ '',
-+ '%description',
-+ self.distribution.get_long_description() or "",
-+ ]
-+ )
-
- # put locale descriptions into spec file
- # XXX again, suppressed because config file syntax doesn't
- # easily support this ;-(
-- #for locale in self.descriptions.keys():
-+ # 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_setup_call = "{} {}".format(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
-+ 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:
-+ 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,])
-+ 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)',
-- ])
-+ 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(
-+ [
-+ '',
-+ '%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
-- """
-+ """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] == '-':
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/command/bdist_wininst.py

b/third_party/python/setuptools/setuptools/_distutils/command/bdist_wininst.py
-deleted file mode 100644
----
a/third_party/python/setuptools/setuptools/_distutils/command/bdist_wininst.py
-+++ /dev/null
-@@ -1,377 +0,0 @@
--"""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("<iii",
-- 0x1234567B, # tag
-- len(cfgdata), # length
-- bitmaplen, # number of bytes in
bitmap
-- )
-- file.write(header)
-- with open(arcname, "rb") as f:
-- file.write(f.read())
--
-- def get_installer_filename(self, fullname):
-- # Factored out to allow overriding in subclasses
-- if self.target_version:
-- # if we create an installer for a specific python version,
-- # it's better to include this in the name
-- installer_name = os.path.join(self.dist_dir,
-- "%s.%s-py%s.exe" %
-- (fullname, self.plat_name,
self.target_version))
-- else:
-- installer_name = os.path.join(self.dist_dir,
-- "%s.%s.exe" % (fullname,
self.plat_name))
-- return installer_name
--
-- def get_exe_bytes(self):
-- # If a target-version other than the current version has been
-- # specified, then using the MSVC version from *this* build is no
good.
-- # Without actually finding and executing the target version and
parsing
-- # its sys.version, we just hard-code our knowledge of old versions.
-- # NOTE: Possible alternative is to allow "--target-version" to
-- # specify a Python executable rather than a simple version string.
-- # We can then execute this program to obtain any info we need, such
-- # as the real sys.version string for the build.
-- cur_version = get_python_version()
--
-- # If the target version is *later* than us, then we assume they
-- # use what we use
-- # string compares seem wrong, but are what sysconfig.py itself uses
-- if self.target_version and self.target_version < cur_version:
-- if self.target_version < "2.4":
-- bv = '6.0'
-- elif self.target_version == "2.4":
-- bv = '7.1'
-- elif self.target_version == "2.5":
-- bv = '8.0'
-- elif self.target_version <= "3.2":
-- bv = '9.0'
-- elif self.target_version <= "3.4":
-- bv = '10.0'
-- else:
-- bv = '14.0'
-- else:
-- # for current version - use authoritative check.
-- try:
-- from msvcrt import CRT_ASSEMBLY_VERSION
-- except ImportError:
-- # cross-building, so assume the latest version
-- bv = '14.0'
-- else:
-- # as far as we know, CRT is binary compatible based on
-- # the first field, so assume 'x.0' until proven otherwise
-- major = CRT_ASSEMBLY_VERSION.partition('.')[0]
-- bv = major + '.0'
--
--
-- # wininst-x.y.exe is in the same directory as this file
-- directory = os.path.dirname(__file__)
-- # we must use a wininst-x.y.exe built with the same C compiler
-- # used for python. XXX What about mingw, borland, and so on?
--
-- # if plat_name starts with "win" but is not "win32"
-- # we want to strip "win" and leave the rest (e.g. -amd64)
-- # for all other cases, we don't want any suffix
-- if self.plat_name != 'win32' and self.plat_name[:3] == 'win':
-- sfix = self.plat_name[3:]
-- else:
-- sfix = ''
--
-- filename = os.path.join(directory, "wininst-%s%s.exe" % (bv, sfix))
-- f = open(filename, "rb")
-- try:
-- return f.read()
-- finally:
-- f.close()
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/command/build.py
b/third_party/python/setuptools/setuptools/_distutils/command/build.py
---- a/third_party/python/setuptools/setuptools/_distutils/command/build.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/command/build.py
-@@ -1,62 +1,58 @@
- """distutils.command.build
-
- Implements the Distutils 'build' command."""
-
--import sys, os
--from distutils.core import Command
--from distutils.errors import DistutilsOptionError
--from distutils.util import get_platform
-+import sys
-+import os
-+from ..core import Command
-+from ..errors import DistutilsOptionError
-+from ..util import get_platform
-
-
- def show_compilers():
-- from distutils.ccompiler import show_compilers
-+ from ..ccompiler import show_compilers
-+
- show_compilers()
-
-
- class build(Command):
--
- description = "build everything needed to install"
-
- user_options = [
-- ('build-base=', 'b',
-- "base directory for build library"),
-- ('build-purelib=', None,
-- "build directory for platform-neutral distributions"),
-- ('build-platlib=', None,
-- "build directory for platform-specific distributions"),
-- ('build-lib=', None,
-- "build directory for all distribution (defaults to either " +
-- "build-purelib or build-platlib"),
-- ('build-scripts=', None,
-- "build directory for scripts"),
-- ('build-temp=', 't',
-- "temporary build directory"),
-- ('plat-name=', 'p',
-- "platform name to build for, if supported "
-- "(default: %s)" % get_platform()),
-- ('compiler=', 'c',
-- "specify the compiler type"),
-- ('parallel=', 'j',
-- "number of parallel build jobs"),
-- ('debug', 'g',
-- "compile extensions and libraries with debugging information"),
-- ('force', 'f',
-- "forcibly build everything (ignore file timestamps)"),
-- ('executable=', 'e',
-- "specify final destination interpreter path (build.py)"),
-- ]
-+ ('build-base=', 'b', "base directory for build library"),
-+ ('build-purelib=', None, "build directory for platform-neutral
distributions"),
-+ ('build-platlib=', None, "build directory for platform-specific
distributions"),
-+ (
-+ 'build-lib=',
-+ None,
-+ "build directory for all distribution (defaults to either "
-+ + "build-purelib or build-platlib",
-+ ),
-+ ('build-scripts=', None, "build directory for scripts"),
-+ ('build-temp=', 't', "temporary build directory"),
-+ (
-+ 'plat-name=',
-+ 'p',
-+ "platform name to build for, if supported "
-+ "(default: %s)" % get_platform(),
-+ ),
-+ ('compiler=', 'c', "specify the compiler type"),
-+ ('parallel=', 'j', "number of parallel build jobs"),
-+ ('debug', 'g', "compile extensions and libraries with debugging
information"),
-+ ('force', 'f', "forcibly build everything (ignore file
timestamps)"),
-+ ('executable=', 'e', "specify final destination interpreter path
(build.py)"),
-+ ]
-
- boolean_options = ['debug', 'force']
-
- help_options = [
-- ('help-compiler', None,
-- "list available compilers", show_compilers),
-- ]
-+ ('help-compiler', None, "list available compilers", show_compilers),
-+ ]
-
- def initialize_options(self):
- self.build_base = 'build'
- # these are decided only after 'build_base' has its final value
- # (unless overridden by the user or client)
- self.build_purelib = None
- self.build_platlib = None
- self.build_lib = None
-@@ -64,62 +60,62 @@ class build(Command):
- self.build_scripts = None
- self.compiler = None
- self.plat_name = None
- self.debug = None
- self.force = 0
- self.executable = None
- self.parallel = None
-
-- def finalize_options(self):
-+ def finalize_options(self): # noqa: C901
- if self.plat_name is None:
- self.plat_name = get_platform()
- else:
- # plat-name only supported for windows (other platforms are
- # supported via ./configure flags, if at all). Avoid misleading
- # other platforms.
- if os.name != 'nt':
- raise DistutilsOptionError(
-- "--plat-name only supported on Windows (try "
-- "using './configure --help' on your platform)")
-+ "--plat-name only supported on Windows (try "
-+ "using './configure --help' on your platform)"
-+ )
-
-- plat_specifier = ".%s-%d.%d" % (self.plat_name,
*sys.version_info[:2])
-+ plat_specifier = ".{}-{}".format(self.plat_name,
sys.implementation.cache_tag)
-
- # Make it so Python 2.x and Python 2.x with --with-pydebug don't
- # share the same build directories. Doing so confuses the build
- # process for C modules
- if hasattr(sys, 'gettotalrefcount'):
- plat_specifier += '-pydebug'
-
- # 'build_purelib' and 'build_platlib' just default to 'lib' and
- # 'lib.<plat>' 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)
-+ 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.ext_modules:
-+ 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.<plat>"
- if self.build_temp is None:
-- self.build_temp = os.path.join(self.build_base,
-- 'temp' + plat_specifier)
-+ 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])
-+ 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:
-@@ -129,29 +125,28 @@ class build(Command):
- # 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),
-- ]
-+ sub_commands = [
-+ ('build_py', has_pure_modules),
-+ ('build_clib', has_c_libraries),
-+ ('build_ext', has_ext_modules),
-+ ('build_scripts', has_scripts),
-+ ]
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/command/build_clib.py
b/third_party/python/setuptools/setuptools/_distutils/command/build_clib.py
----
a/third_party/python/setuptools/setuptools/_distutils/command/build_clib.py
-+++
b/third_party/python/setuptools/setuptools/_distutils/command/build_clib.py
-@@ -10,200 +10,198 @@ module."""
- # 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
-+from ..core import Command
-+from ..errors import DistutilsSetupError
-+from ..sysconfig import customize_compiler
-+from distutils._log import log
-+
-
- def show_compilers():
-- from distutils.ccompiler import show_compilers
-+ from ..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"),
-- ]
-+ ('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),
-- ]
-+ ('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.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)
-+ from ..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:
-+ 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")
-+ 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")
-+ 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)")
-+ "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])
-+ 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)")
--
-+ "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:
-+ 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:
-+ 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)
-+ "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:
-+ 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)
-+ "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)
-+ 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)
-+ self.compiler.create_static_lib(
-+ objects, lib_name, output_dir=self.build_clib,
debug=self.debug
-+ )
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/command/build_ext.py
b/third_party/python/setuptools/setuptools/_distutils/command/build_ext.py
----
a/third_party/python/setuptools/setuptools/_distutils/command/build_ext.py
-+++
b/third_party/python/setuptools/setuptools/_distutils/command/build_ext.py
-@@ -3,41 +3,47 @@
- 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 ..core import Command
-+from ..errors import (
-+ DistutilsOptionError,
-+ DistutilsSetupError,
-+ CCompilerError,
-+ DistutilsError,
-+ CompileError,
-+ DistutilsPlatformError,
-+)
-+from ..sysconfig import customize_compiler, get_python_version
-+from ..sysconfig import get_config_h_filename
-+from ..dep_util import newer_group
-+from ..extension import Extension
-+from ..util import get_platform
-+from distutils._log 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]*)*$')
-+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
-+def show_compilers():
-+ from ..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
-@@ -50,64 +56,60 @@ class build_ext(Command):
- # - 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")
-- ]
-+ ('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),
-- ]
-+ ('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
-@@ -123,28 +125,29 @@ class build_ext(Command):
- 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):
-+ def finalize_options(self): # noqa: C901
- 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'),
-- )
-+ 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.
-@@ -159,18 +162,17 @@ class build_ext(Command):
- # 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.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 = []
-@@ -197,47 +199,47 @@ class build_ext(Command):
- 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()))
-- _sys_home = getattr(sys, '_home', None)
-- if _sys_home:
-- self.library_dirs.append(_sys_home)
-+ 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 sys.executable.startswith(os.path.join(sys.exec_prefix,
"bin")):
-+ if not sysconfig.python_build:
- # building third party extensions
-- self.library_dirs.append(os.path.join(sys.prefix, "lib",
-- "python" +
get_python_version(),
-- "config"))
-+ 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 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
-@@ -271,18 +273,18 @@ class build_ext(Command):
- 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
-+ def run(self): # noqa: C901
-+ from ..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)
-@@ -299,123 +301,138 @@ class build_ext(Command):
- # 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)
-+ 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:
-+ 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):
-+ def check_extensions_list(self, extensions): # noqa: C901
- """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")
-+ "'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)
-+ 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")
-+ "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)
-+ log.warning(
-+ "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)):
-+ 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)")
-+ "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)")
-+ "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'):
-+ 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")
-+ log.warning(
-+ "'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")
-+ "'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):
-@@ -458,44 +475,45 @@ class build_ext(Command):
- 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]
-+ 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))
-+ self.warn('building extension "{}" failed: {}'.format(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)
-+ "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
-@@ -522,48 +540,52 @@ class build_ext(Command):
- # 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)
-+ 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,
-+ 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)
-+ 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 = []
-@@ -571,27 +593,30 @@ class build_ext(Command):
- 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++")
-+ log.warning("--swig-cpp is deprecated - use --swig-opts=-c++")
-
-- if self.swig_cpp or ('-c++' in self.swig_opts) or \
-- ('-c++' in extension.swig_opts):
-+ 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
-+ 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
-@@ -628,18 +653,19 @@ class build_ext(Command):
- 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)
-+ "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).
-@@ -647,17 +673,17 @@ class build_ext(Command):
- 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])
-+ 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))
-
-@@ -674,71 +700,78 @@ class build_ext(Command):
- 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
-+ from ..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.
- """
-- suffix = '_' + ext.name.split('.')[-1]
-+ 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
-- suffix.encode('ascii')
-+ # https://peps.python.org/pep-0489/#export-hook-name
-+ name.encode('ascii')
- except UnicodeEncodeError:
-- suffix = 'U' + suffix.encode('punycode').replace(b'-',
b'_').decode('ascii')
-+ 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):
-+ def get_libraries(self, ext): # noqa: C901
- """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
-+ from .._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))
-+ 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
-+ from ..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:
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/command/build_py.py
b/third_party/python/setuptools/setuptools/_distutils/command/build_py.py
---- a/third_party/python/setuptools/setuptools/_distutils/command/build_py.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/command/build_py.py
-@@ -2,52 +2,55 @@
-
- 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, Mixin2to3
--from distutils import log
-+from ..core import Command
-+from ..errors import DistutilsOptionError, DistutilsFileError
-+from ..util import convert_path
-+from distutils._log import log
-
--class build_py (Command):
-
-+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]"),
-+ (
-+ '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'}
-+ 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'))
-+ 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:
-@@ -104,52 +107,52 @@ class build_py (Command):
- 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
-+ plen = len(src_dir) + 1
-
- # Strip directory from globbed filenames
-- filenames = [
-- file[plen:] for file in self.find_data_files(package,
src_dir)
-- ]
-+ 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, []))
-+ 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)))
-+ 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)])
-+ 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)
-+ 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)."""
-+ 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:
-@@ -183,38 +186,37 @@ class build_py (Command):
- 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)
-+ "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)
-+ "supposed package directory '%s' exists, "
-+ "but is not a directory" % package_dir
-+ )
-
-- # Require __init__.py for all but the "root package"
-+ # Directories without __init__.py are namespace packages (PEP 420).
- 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)
-+ log.warning("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 = []
-@@ -302,54 +304,59 @@ class build_py (Command):
-
- 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:
-+ 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=''))
-+ 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.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")
-+ "'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:
-+ 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:
-@@ -362,55 +369,38 @@ class build_py (Command):
- # 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:
-+ 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
-+ from ..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)
-+ 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)
--
--class build_py_2to3(build_py, Mixin2to3):
-- def run(self):
-- self.updated_files = []
--
-- # Base class code
-- if self.py_modules:
-- self.build_modules()
-- if self.packages:
-- self.build_packages()
-- self.build_package_data()
--
-- # 2to3
-- self.run_2to3(self.updated_files)
--
-- # Remaining base class code
-- self.byte_compile(self.get_outputs(include_bytecode=0))
--
-- def build_module(self, module, module_file, package):
-- res = build_py.build_module(self, module, module_file, package)
-- if res[1]:
-- # file was copied
-- self.updated_files.append(res[0])
-- return res
-+ byte_compile(
-+ files,
-+ optimize=self.optimize,
-+ force=self.force,
-+ prefix=prefix,
-+ dry_run=self.dry_run,
-+ )
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/command/build_scripts.py

b/third_party/python/setuptools/setuptools/_distutils/command/build_scripts.py
----
a/third_party/python/setuptools/setuptools/_distutils/command/build_scripts.py
-+++
b/third_party/python/setuptools/setuptools/_distutils/command/build_scripts.py
-@@ -1,160 +1,172 @@
- """distutils.command.build_scripts
-
- Implements the Distutils 'build_scripts' command."""
-
--import os, re
-+import os
-+import 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, Mixin2to3
--from distutils import log
-+from ..core import Command
-+from ..dep_util import newer
-+from ..util import convert_path
-+from distutils._log 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].*)?$')
-+shebang_pattern = re.compile('^#!.*python[0-9.]*([ \t].*)?$')
-+"""
-+Pattern matching a Python interpreter indicated in first line of a script.
-+"""
-+
-+# for Setuptools compatibility
-+first_line_re = shebang_pattern
-+
-
- 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.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):
-+ """
-+ Copy each script listed in ``self.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.
-+ If a script is marked as a Python script (first line matches
-+ 'shebang_pattern', i.e. starts with ``#!`` and contains
-+ "python"), then adjust in the copy the first line to refer to
-+ the current Python interpreter.
- """
- 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''
-+ self._copy_script(script, outfiles, updated_files)
-
-- 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)
-+ self._change_modes(outfiles)
-
-- 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
-
--class build_scripts_2to3(build_scripts, Mixin2to3):
-+ def _copy_script(self, script, outfiles, updated_files): # noqa: C901
-+ shebang_match = None
-+ 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)
-+ return
-+
-+ # Always open the file, but ignore failures in dry-run mode
-+ # in order to attempt to copy directly.
-+ try:
-+ f = tokenize.open(script)
-+ except OSError:
-+ if not self.dry_run:
-+ raise
-+ f = None
-+ else:
-+ first_line = f.readline()
-+ if not first_line:
-+ self.warn("%s is an empty file (skipping)" % script)
-+ return
-+
-+ shebang_match = shebang_pattern.match(first_line)
-
-- def copy_scripts(self):
-- outfiles, updated_files = build_scripts.copy_scripts(self)
-- if not self.dry_run:
-- self.run_2to3(updated_files)
-- return outfiles, updated_files
-+ updated_files.append(outfile)
-+ if shebang_match:
-+ log.info("copying and adjusting %s -> %s", script,
self.build_dir)
-+ 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"),
-+ ),
-+ )
-+ post_interp = shebang_match.group(1) or ''
-+ shebang = "#!" + executable + post_interp + "\n"
-+ self._validate_shebang(shebang, f.encoding)
-+ with open(outfile, "w", encoding=f.encoding) as outf:
-+ outf.write(shebang)
-+ outf.writelines(f.readlines())
-+ if f:
-+ f.close()
-+ else:
-+ if f:
-+ f.close()
-+ self.copy_file(script, outfile)
-+
-+ def _change_modes(self, outfiles):
-+ if os.name != 'posix':
-+ return
-+
-+ for file in outfiles:
-+ self._change_mode(file)
-+
-+ def _change_mode(self, file):
-+ if self.dry_run:
-+ log.info("changing mode of %s", file)
-+ return
-+
-+ 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)
-+
-+ @staticmethod
-+ def _validate_shebang(shebang, encoding):
-+ # 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 encodable to
-+ # UTF-8.
-+ try:
-+ shebang.encode('utf-8')
-+ except UnicodeEncodeError:
-+ raise ValueError(
-+ "The shebang ({!r}) is not encodable " "to
utf-8".format(shebang)
-+ )
-+
-+ # If the script is encoded to a custom encoding (use a
-+ # #coding:xxx cookie), the shebang has to be encodable to
-+ # the script encoding too.
-+ try:
-+ shebang.encode(encoding)
-+ except UnicodeEncodeError:
-+ raise ValueError(
-+ "The shebang ({!r}) is not encodable "
-+ "to the script encoding ({})".format(shebang, encoding)
-+ )
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/command/check.py
b/third_party/python/setuptools/setuptools/_distutils/command/check.py
---- a/third_party/python/setuptools/setuptools/_distutils/command/check.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/command/check.py
-@@ -1,52 +1,62 @@
- """distutils.command.check
-
- Implements the Distutils 'check' command.
- """
--from distutils.core import Command
--from distutils.errors import DistutilsSetupError
-+import contextlib
-+
-+from ..core import Command
-+from ..errors import DistutilsSetupError
-+
-+with contextlib.suppress(ImportError):
-+ import docutils.utils
-+ import docutils.parsers.rst
-+ import docutils.frontend
-+ import docutils.nodes
-
--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'):
-+ class SilentReporter(docutils.utils.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)
-+ super().__init__(
-+ 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)
-+ return docutils.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')]
-+ """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
-@@ -61,88 +71,81 @@ class check(Command):
- 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()
-+ if 'docutils' in globals():
-+ try:
-+ self.check_restructuredtext()
-+ except TypeError as exc:
-+ raise DistutilsSetupError(str(exc))
- 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))
-+ name, version
-
- 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)):
-+ for attr in 'name', 'version':
-+ if not getattr(metadata, attr, None):
- 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")
-+ self.warn("missing required meta-data: %s" % ', '.join(missing))
-
- 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)
-+ warning = '{} (line {})'.format(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()
-+ parser = docutils.parsers.rst.Parser()
-+ settings = docutils.frontend.OptionParser(
-+ components=(docutils.parsers.rst.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)
-+ 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 = docutils.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, '', {}))
-+ (-1, 'Could not finish the parsing: %s.' % e, '', {})
-+ )
-
- return reporter.messages
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/command/clean.py
b/third_party/python/setuptools/setuptools/_distutils/command/clean.py
---- a/third_party/python/setuptools/setuptools/_distutils/command/clean.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/command/clean.py
-@@ -1,75 +1,74 @@
- """distutils.command.clean
-
- Implements the Distutils 'clean' command."""
-
- # contributed by Bastian Kleineidam <calvin AT cs.uni-sb.de>, added 2000-03-18
-
- import os
--from distutils.core import Command
--from distutils.dir_util import remove_tree
--from distutils import log
-+from ..core import Command
-+from ..dir_util import remove_tree
-+from distutils._log 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")
-+ ('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'))
-+ 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.<plat> 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)
-+ 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):
-+ 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)
-+ log.warning("'%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:
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/command/config.py
b/third_party/python/setuptools/setuptools/_distutils/command/config.py
---- a/third_party/python/setuptools/setuptools/_distutils/command/config.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/command/config.py
-@@ -4,51 +4,45 @@ Implements the Distutils 'config' comman
- 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
-+import os
-+import re
-
--from distutils.core import Command
--from distutils.errors import DistutilsExecError
--from distutils.sysconfig import customize_compiler
--from distutils import log
-+from ..core import Command
-+from ..errors import DistutilsExecError
-+from ..sysconfig import customize_compiler
-+from distutils._log 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"),
-- ]
--
-+ ('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
-@@ -87,20 +81,22 @@ class config(Command):
- # 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
-+ from ..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)
-+ 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)
-
-@@ -127,24 +123,26 @@ class config(Command):
- 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):
-+ 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)
-+ 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):
-@@ -153,17 +151,16 @@ class config(Command):
- 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.
-
-@@ -171,29 +168,29 @@ class config(Command):
-
- 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
-+ from ..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"):
-+ 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()
-@@ -214,74 +211,98 @@ class config(Command):
-
- 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
-+ from ..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"):
-+ 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
-+ from ..ccompiler import CompileError, LinkError
-+
- self._check_compiler()
- try:
-- self._link(body, headers, include_dirs,
-- libraries, library_dirs, lang)
-+ 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"):
-+ 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
-+ from ..ccompiler import CompileError, LinkError
-+
- self._check_compiler()
- try:
-- src, obj, exe = self._link(body, headers, include_dirs,
-- libraries, library_dirs, lang)
-+ 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):
-+ 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
-@@ -297,41 +318,52 @@ class config(Command):
- 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)
-+ 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=[]):
-+ 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)
-+ 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"):
-+ 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)
-+ 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)
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/command/install.py
b/third_party/python/setuptools/setuptools/_distutils/command/install.py
---- a/third_party/python/setuptools/setuptools/_distutils/command/install.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/command/install.py
-@@ -1,166 +1,261 @@
- """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 distutils._log import log
-+from ..core import Command
-+from ..debug import DEBUG
-+from ..sysconfig import get_config_vars
-+from ..file_util import write_file
-+from ..util import convert_path, subst_vars, change_root
-+from ..util import get_platform
-+from ..errors import DistutilsOptionError, DistutilsPlatformError
-+from . import _framework_compat as fw
-+from .. import _collections
-
- 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',
-+ 'purelib': '{base}/Lib/site-packages',
-+ 'platlib': '{base}/Lib/site-packages',
-+ 'headers': '{base}/Include/{dist_name}',
-+ 'scripts': '{base}/Scripts',
-+ 'data': '{base}',
- }
-
- INSTALL_SCHEMES = {
-- 'unix_prefix': {
-- 'purelib': '$base/lib/python$py_version_short/site-packages',
-- 'platlib':
'$platbase/$platlibdir/python$py_version_short/site-packages',
-- 'headers':
'$base/include/python$py_version_short$abiflags/$dist_name',
-- 'scripts': '$base/bin',
-- 'data' : '$base',
-- },
-- 'unix_home': {
-- 'purelib': '$base/lib/python',
-- 'platlib': '$base/$platlibdir/python',
-- 'headers': '$base/include/python/$dist_name',
-- 'scripts': '$base/bin',
-- 'data' : '$base',
-- },
-+ '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',
-- },
-+ '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',
-- },
-- }
-+ '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/Python$py_version_nodot/Include/$dist_name',
-- 'scripts': '$userbase/Python$py_version_nodot/Scripts',
-- 'data' : '$userbase',
-- }
-+ 'purelib': '{usersite}',
-+ 'platlib': '{usersite}',
-+ 'headers': '{userbase}/{implementation}{py_version_nodot_plat}'
-+ '/Include/{dist_name}',
-+ 'scripts':
'{userbase}/{implementation}{py_version_nodot_plat}/Scripts',
-+ 'data': '{userbase}',
-+ }
-
-- INSTALL_SCHEMES['unix_user'] = {
-- 'purelib': '$usersite',
-- 'platlib': '$usersite',
-- 'headers':
-- '$userbase/include/python$py_version_short$abiflags/$dist_name',
-- 'scripts': '$userbase/bin',
-- '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}',
-+ }
-+
-+
-+INSTALL_SCHEMES.update(fw.schemes)
-+
-
- # 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'
-+
-+
-+def _select_scheme(ob, name):
-+ scheme = _inject_headers(name, _load_scheme(_resolve_scheme(name)))
-+ vars(ob).update(_remove_set(ob, _scheme_attrs(scheme)))
-+
-+
-+def _remove_set(ob, attrs):
-+ """
-+ Include only attrs that are None in ob.
-+ """
-+ return {key: value for key, value in attrs.items() if getattr(ob, key)
is None}
-+
-+
-+def _resolve_scheme(name):
-+ os_name, sep, key = name.partition('_')
-+ try:
-+ resolved = sysconfig.get_preferred_scheme(key)
-+ except Exception:
-+ resolved = fw.scheme(_pypy_hack(name))
-+ return resolved
-+
-+
-+def _load_scheme(name):
-+ return _load_schemes()[name]
-+
-+
-+def _inject_headers(name, scheme):
-+ """
-+ Given a scheme name and the resolved scheme,
-+ if the scheme does not include headers, resolve
-+ the fallback scheme for the name and use headers
-+ from it. pypa/distutils#88
-+ """
-+ # Bypass the preferred scheme, which may not
-+ # have defined headers.
-+ fallback = _load_scheme(_pypy_hack(name))
-+ scheme.setdefault('headers', fallback['headers'])
-+ return scheme
-+
-+
-+def _scheme_attrs(scheme):
-+ """Resolve install directories by applying the install schemes."""
-+ return {f'install_{key}': scheme[key] for key in SCHEME_KEYS}
-+
-+
-+def _pypy_hack(name):
-+ PY37 = sys.version_info < (3, 8)
-+ old_pypy = hasattr(sys, 'pypy_version_info') and PY37
-+ prefix = not name.endswith(('_user', '_home'))
-+ pypy_name = 'pypy' + '_nt' * (os.name == 'nt')
-+ return pypy_name if old_pypy and prefix else name
-+
-+
- 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"),
--
-+ ('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"),
--
-+ (
-+ '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"),
--
-+ (
-+ '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]"),
--
-+ (
-+ '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)"),
--
-+ ('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"),
-- ]
-+ # ('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))
-+ user_options.append(
-+ ('user', None, "install in user site-package '%s'" % USER_SITE)
-+ )
- boolean_options.append('user')
-
-- negative_opt = {'no-compile' : 'compile'}
--
-+ 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
-@@ -172,20 +267,20 @@ class install(Command):
- 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_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
-
-@@ -217,73 +312,82 @@ class install(Command):
- # 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.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):
-+ def finalize_options(self): # noqa: C901
- """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)):
-+ 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")
-+ "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")
-+ "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")
-+ 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_SCHEME dictionary above. Phew!
-+ # install schemes. Phew!
-
- self.dump_dirs("pre-finalize_{unix,other}")
-
- if os.name == 'posix':
- self.finalize_unix()
- else:
- self.finalize_other()
-
-@@ -296,105 +400,127 @@ class install(Command):
-
- 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'),
-- }
-+ local_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(),
-+ }
-+
-+ # vars for compatibility on older Pythons
-+ compat_vars = dict(
-+ # Python 3.9 and earlier
-+ py_version_nodot_plat=getattr(sys, 'winver', '').replace('.',
''),
-+ )
-
- if HAS_USER_SITE:
-- self.config_vars['userbase'] = self.install_userbase
-- self.config_vars['usersite'] = self.install_usersite
-+ local_vars['userbase'] = self.install_userbase
-+ local_vars['usersite'] = self.install_usersite
-+
-+ self.config_vars = _collections.DictStack(
-+ [fw.vars(), compat_vars, sysconfig.get_config_vars(),
local_vars]
-+ )
-
- 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
-+ local_vars['base'] = self.install_base
-+ local_vars['platbase'] = self.install_platbase
-
- if DEBUG:
- from pprint import pprint
-+
- print("config vars:")
-- pprint(self.config_vars)
-+ pprint(dict(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.ext_modules: # has extensions: non-pure
-+ 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')
-+ 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_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.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'))
-+ 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
-+ from ..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)
-@@ -402,137 +528,142 @@ class install(Command):
- 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):
-+ incomplete_scheme = (
-+ (
-+ 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
-+ )
-+ if incomplete_scheme:
- raise DistutilsOptionError(
-- "install-base or install-platbase supplied, but "
-- "installation scheme is incomplete")
-+ "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")
-+ raise DistutilsPlatformError("User base directory is not
specified")
- self.install_base = self.install_platbase =
self.install_userbase
-- self.select_scheme("unix_user")
-+ self.select_scheme("posix_user")
- elif self.home is not None:
- self.install_base = self.install_platbase = self.home
-- self.select_scheme("unix_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")
-+ "must not supply exec-prefix without prefix"
-+ )
-
-- self.prefix = os.path.normpath(sys.prefix)
-- self.exec_prefix = os.path.normpath(sys.exec_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("unix_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")
-+ 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("unix_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)
-+ "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
-- not name.endswith(('_user', '_home'))):
-- if os.name == 'nt':
-- name = 'pypy_nt'
-- else:
-- name = 'pypy'
-- scheme = INSTALL_SCHEMES[name]
-- for key in SCHEME_KEYS:
-- attrname = 'install_' + key
-- if getattr(self, attrname) is None:
-- setattr(self, attrname, scheme[key])
-+ _select_scheme(self, 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':
-+ if os.name in ('posix', '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',])
-+ 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(
-+ log.warning(
- "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")
-+ "'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 = ''
-
-@@ -548,94 +679,97 @@ class install(Command):
- 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):
-+ if str(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")
-+ 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
-+ 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)
-+ 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)
-+ 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")
-+ 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)
-+ 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"))
-+ 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():
-@@ -644,18 +778,19 @@ class install(Command):
-
- 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())
-+ 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.
-@@ -664,14 +799,15 @@ class install(Command):
-
- 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),
-- ]
-+ 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/third_party/python/setuptools/setuptools/_distutils/command/install_data.py
b/third_party/python/setuptools/setuptools/_distutils/command/install_data.py
----
a/third_party/python/setuptools/setuptools/_distutils/command/install_data.py
-+++
b/third_party/python/setuptools/setuptools/_distutils/command/install_data.py
-@@ -1,59 +1,63 @@
- """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
-+from ..core import Command
-+from ..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"),
-+ (
-+ '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'),
-- )
-+ 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))
-+ 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:
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/command/install_egg_info.py

b/third_party/python/setuptools/setuptools/_distutils/command/install_egg_info.py
----
a/third_party/python/setuptools/setuptools/_distutils/command/install_egg_info.py
-+++
b/third_party/python/setuptools/setuptools/_distutils/command/install_egg_info.py
-@@ -1,77 +1,92 @@
--"""distutils.command.install_egg_info
-+"""
-+distutils.command.install_egg_info
-
- Implements the Distutils 'install_egg_info' command, for installing
--a package's PKG-INFO metadata."""
--
-+a package's PKG-INFO metadata.
-+"""
-
--from distutils.cmd import Command
--from distutils import log, dir_util
--import os, sys, re
-+import os
-+import sys
-+import re
-+
-+from ..cmd import Command
-+from .. import dir_util
-+from .._log import log
-+
-
- 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
-
-- def finalize_options(self):
--
self.set_undefined_options('install_lib',('install_dir','install_dir'))
-- basename = "%s-%s-py%d.%d.egg-info" % (
-+ @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]
-+ *sys.version_info[:2],
- )
-- self.target = os.path.join(self.install_dir, basename)
-+
-+ 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)
-+ 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)
-+ 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(' ','.')
-+ 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('-','_')
-+ return name.replace('-', '_')
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/command/install_headers.py

b/third_party/python/setuptools/setuptools/_distutils/command/install_headers.py
----
a/third_party/python/setuptools/setuptools/_distutils/command/install_headers.py
-+++
b/third_party/python/setuptools/setuptools/_distutils/command/install_headers.py
-@@ -1,39 +1,36 @@
- """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
-+from ..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)"),
-- ]
-+ 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'))
--
-+ 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:
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/command/install_lib.py
b/third_party/python/setuptools/setuptools/_distutils/command/install_lib.py
----
a/third_party/python/setuptools/setuptools/_distutils/command/install_lib.py
-+++
b/third_party/python/setuptools/setuptools/_distutils/command/install_lib.py
-@@ -2,25 +2,25 @@
-
- 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
-+from ..core import Command
-+from ..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)
-@@ -30,50 +30,54 @@ class install_lib(Command):
- # 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)"),
-+ ('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]"),
-+ (
-+ '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'}
-+ 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'),
-- )
-+ 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:
-@@ -105,43 +109,52 @@ class install_lib(Command):
- 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)
-+ 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
-+ from ..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)
-+ 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)
--
-+ 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)
-@@ -160,46 +173,53 @@ class install_lib(Command):
- 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=''))
-+ 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))
-+ 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)
-+ 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)
-+ 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()'.
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/command/install_scripts.py

b/third_party/python/setuptools/setuptools/_distutils/command/install_scripts.py
----
a/third_party/python/setuptools/setuptools/_distutils/command/install_scripts.py
-+++
b/third_party/python/setuptools/setuptools/_distutils/command/install_scripts.py
-@@ -1,47 +1,47 @@
- """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 ..core import Command
-+from distutils._log 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)"),
-+ ('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'),
-- )
-+ 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.
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/command/py37compat.py
b/third_party/python/setuptools/setuptools/_distutils/command/py37compat.py
----
a/third_party/python/setuptools/setuptools/_distutils/command/py37compat.py
-+++
b/third_party/python/setuptools/setuptools/_distutils/command/py37compat.py
-@@ -2,22 +2,23 @@ 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,
-+ (sys.hexversion >> 16) & 0xFF,
- sysconfig.get_config_var('ABIFLAGS'),
- )
-
-
- def compose(f1, f2):
- return lambda *args, **kwargs: f1(f2(*args, **kwargs))
-
-
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/command/register.py
b/third_party/python/setuptools/setuptools/_distutils/command/register.py
---- a/third_party/python/setuptools/setuptools/_distutils/command/register.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/command/register.py
-@@ -2,47 +2,55 @@
-
- Implements the Distutils 'register' command (register with the repository).
- """
-
- # created 2002/10/21, Richard Jones
-
- import getpass
- import io
--import urllib.parse, urllib.request
-+import logging
-+import urllib.parse
-+import urllib.request
- from warnings import warn
-
--from distutils.core import PyPIRCCommand
--from distutils.errors import *
--from distutils import log
-+from ..core import PyPIRCCommand
-+from distutils._log import log
-+
-
- class register(PyPIRCCommand):
--
-- description = ("register the distribution with the Python package
index")
-+ 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')
-- ]
-+ ('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']
-+ '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)}
-+ 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():
-@@ -52,103 +60,106 @@ class register(PyPIRCCommand):
- 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)
-+ warn(
-+ "distutils.command.register.check_metadata is deprecated; "
-+ "use the check command instead",
-+ DeprecationWarning,
-+ )
- 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.
-- '''
-+ '''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'
-+ '''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 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.
-+ def send_metadata(self): # noqa: C901
-+ '''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.
-+ 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:
-+ 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
-+ [distutils]
-+ index-servers =
-+ pypi
-
-- [pypi]
-- username: fred
-- password: sekrit
-+ [pypi]
-+ username: fred
-+ password: sekrit
-
-- Otherwise, to figure who the user is, we offer the user three
-- choices:
-+ 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.
-+ 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('''\
-+ 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)
-+Your selection [default 1]: ''',
-+ logging.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
-@@ -157,32 +168,37 @@ Your selection [default 1]: ''', log.INF
- 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)
-+ code, result =
self.post_to_server(self.build_post_data('submit'), auth)
-+ self.announce('Server response ({}): {}'.format(code, result),
logging.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)
-+ self.announce(
-+ (
-+ 'I can store your PyPI login so future '
-+ 'submissions will be faster.'
-+ ),
-+ logging.INFO,
-+ )
-+ self.announce(
-+ '(the login will be stored in %s)' %
self._get_rc_file(),
-+ logging.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)
-
-@@ -203,33 +219,32 @@ Your selection [default 1]: ''', log.INF
- 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.'))
-+ 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',
-+ '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(),
-@@ -241,48 +256,49 @@ Your selection [default 1]: ''', log.INF
- '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.
-- '''
-+ def post_to_server(self, data, auth=None): # noqa: C901
-+ '''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)
-+ self.announce(
-+ 'Registering {} to {}'.format(data['name'],
self.repository),
-+ logging.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( () )):
-+ 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('\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))
-+ '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 = ''
-@@ -295,10 +311,10 @@ Your selection [default 1]: ''', log.INF
- 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)
-+ self.announce(msg, logging.INFO)
- return result
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/command/sdist.py
b/third_party/python/setuptools/setuptools/_distutils/command/sdist.py
---- a/third_party/python/setuptools/setuptools/_distutils/command/sdist.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/command/sdist.py
-@@ -2,102 +2,122 @@
-
- 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 ..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
-+from ..text_file import TextFile
-+from ..filelist import FileList
-+from distutils._log import log
-+from ..util import convert_path
-+from ..errors import DistutilsOptionError, DistutilsTemplateError
-
-
- 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
-+ from ..fancy_getopt import FancyGetopt
-+ from ..archive_util import ARCHIVE_FORMATS
-+
- formats = []
- for format in ARCHIVE_FORMATS.keys():
-- formats.append(("formats=" + format, None,
-- ARCHIVE_FORMATS[format][2]))
-+ formats.append(("formats=" + format, None,
ARCHIVE_FORMATS[format][2]))
- formats.sort()
-- FancyGetopt(formats).print_help(
-- "List of available source distribution formats:")
-+ 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]"),
-- ]
-+ ('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']
-+ boolean_options = [
-+ 'use-defaults',
-+ 'prune',
-+ 'manifest-only',
-+ 'force-manifest',
-+ 'keep-temp',
-+ 'metadata-check',
-+ ]
-
- help_options = [
-- ('help-formats', None,
-- "list available distribution formats", show_formats),
-- ]
-+ ('help-formats', None, "list available distribution formats",
show_formats),
-+ ]
-
-- negative_opt = {'no-defaults': 'use-defaults',
-- 'no-prune': 'prune' }
-+ 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.
-@@ -126,18 +146,17 @@ class sdist(Command):
- 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)
-+ 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()
-@@ -156,18 +175,21 @@ class sdist(Command):
- 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)
-+ 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
-@@ -184,19 +206,20 @@ class sdist(Command):
- 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.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()
-
-@@ -206,17 +229,17 @@ class sdist(Command):
- 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
-+ - tests/test*.py and 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.
-@@ -254,26 +277,27 @@ class sdist(Command):
- 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))
-+ 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']
-+ optional = ['tests/test*.py', '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
-@@ -323,35 +347,42 @@ class sdist(Command):
-
- 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)
-+ 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
-+ 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))
-+ 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"
-@@ -364,35 +395,39 @@ class sdist(Command):
- 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)
-+ vcs_dirs = ['RCS', 'CVS', r'\.svn', r'\.hg', r'\.git', r'\.bzr',
'_darcs']
-+ vcs_ptrn = r'(^|{})({})({}).*'.format(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)
-+ 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)
-+ 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:
-@@ -432,30 +467,30 @@ class sdist(Command):
-
- # 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
-+ 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
-+ 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?")
-+ log.warning("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)
-+ log.warning("'%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
-@@ -466,24 +501,25 @@ class sdist(Command):
- 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
-+ 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)
-+ 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)
-
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/command/upload.py
b/third_party/python/setuptools/setuptools/_distutils/command/upload.py
---- a/third_party/python/setuptools/setuptools/_distutils/command/upload.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/command/upload.py
-@@ -3,155 +3,147 @@ distutils.command.upload
-
- Implements the Distutils 'upload' subcommand (upload package to a package
- index).
- """
-
- import os
- import io
- import hashlib
-+import logging
- 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
-+from ..errors import DistutilsError, DistutilsOptionError
-+from ..core import PyPIRCCommand
-+from ..spawn import spawn
-
-
- # 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'),
-+ ('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"
-- )
-+ 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)")
-+ 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):
-+ def upload_file(self, command, pyversion, filename): # noqa: C901
- # Makes sure the repository URL is compliant
-- schema, netloc, url, params, query, fragments = \
-- urlparse(self.repository)
-+ 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)
-+ 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')
-+ 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),
-+ '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())
-+ 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
-@@ -172,43 +164,43 @@ class upload(PyPIRCCommand):
- 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)
-+ msg = "Submitting {} to {}".format(filename, self.repository)
-+ self.announce(msg, logging.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)
-+ 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)
-+ self.announce(str(e), logging.ERROR)
- raise
-
- if status == 200:
-- self.announce('Server response (%s): %s' % (status, reason),
-- log.INFO)
-+ self.announce(
-+ 'Server response ({}): {}'.format(status, reason),
logging.INFO
-+ )
- if self.show_response:
- text = self._read_pypi_response(result)
- msg = '\n'.join(('-' * 75, text, '-' * 75))
-- self.announce(msg, log.INFO)
-+ self.announce(msg, logging.INFO)
- else:
-- msg = 'Upload failed (%s): %s' % (status, reason)
-- self.announce(msg, log.ERROR)
-+ msg = 'Upload failed ({}): {}'.format(status, reason)
-+ self.announce(msg, logging.ERROR)
- raise DistutilsError(msg)
-diff --git a/third_party/python/setuptools/setuptools/_distutils/config.py
b/third_party/python/setuptools/setuptools/_distutils/config.py
---- a/third_party/python/setuptools/setuptools/_distutils/config.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/config.py
-@@ -1,122 +1,131 @@
- """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
-+from .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
-- """
-+ """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')]
-+ ('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):
-+ def _read_pypirc(self): # noqa: C901
- """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() != '']
-+ _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)):
-+ 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')):
-+ 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):
-+ 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 {
-+ '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
-diff --git a/third_party/python/setuptools/setuptools/_distutils/core.py
b/third_party/python/setuptools/setuptools/_distutils/core.py
---- a/third_party/python/setuptools/setuptools/_distutils/core.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/core.py
-@@ -3,63 +3,102 @@
- 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 *
-+from .debug import DEBUG
-+from .errors import (
-+ DistutilsSetupError,
-+ DistutilsError,
-+ CCompilerError,
-+ DistutilsArgError,
-+)
-
- # 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
-+from .dist import Distribution
-+from .cmd import Command
-+from .config import PyPIRCCommand
-+from .extension import Extension
-+
-+
-+__all__ = ['Distribution', 'Command', 'PyPIRCCommand', 'Extension', 'setup']
-
- # 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):
-+
-+def gen_usage(script_name):
- script = os.path.basename(script_name)
-- return USAGE % vars()
-+ return USAGE % locals()
-
-
- # 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',
-- )
-+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')
-+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):
-+
-+def setup(**attrs): # noqa: C901
- """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
-@@ -88,35 +127,34 @@ def setup (**attrs):
- """
-
- 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']
-+ attrs.pop('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:
-+ 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))
-+ raise SystemExit("error in {} setup command:
{}".format(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()
-
-@@ -139,40 +177,52 @@ def setup (**attrs):
- 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:
-- 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 run_commands(dist)
-
- return dist
-
-+
- # setup ()
-
-
--def run_setup (script_name, script_args=None, stop_after="run"):
-+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: {}\n".format(exc))
-+ raise
-+ else:
-+ raise SystemExit("error: {}".format(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
-@@ -194,41 +244,48 @@ def run_setup (script_name, script_args=
- 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,))
-+ raise ValueError("invalid value for 'stop_after':
{!r}".format(stop_after))
-
- global _setup_stop_after, _setup_distribution
- _setup_stop_after = stop_after
-
- save_argv = sys.argv.copy()
-- g = {'__file__': script_name}
-+ g = {'__file__': script_name, '__name__': '__main__'}
- try:
- try:
- sys.argv[0] = script_name
- if script_args is not None:
- sys.argv[1:] = script_args
-- with open(script_name, 'rb') as f:
-- exec(f.read(), g)
-+ # 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)
-+ 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
-+ # print "_setup_distribution:", _setup_distribution
- return _setup_distribution
-
-+
- # run_setup ()
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/cygwinccompiler.py
b/third_party/python/setuptools/setuptools/_distutils/cygwinccompiler.py
---- a/third_party/python/setuptools/setuptools/_distutils/cygwinccompiler.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/cygwinccompiler.py
-@@ -1,333 +1,306 @@
- """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)
--
- import os
-+import re
- import sys
- import copy
--from subprocess import Popen, PIPE, check_output
--import re
-+import shlex
-+import warnings
-+from subprocess import check_output
-+
-+from .unixccompiler import UnixCCompiler
-+from .file_util import write_file
-+from .errors import (
-+ DistutilsExecError,
-+ DistutilsPlatformError,
-+ CCompilerError,
-+ CompileError,
-+)
-+from .version import LooseVersion, suppress_known_deprecation
-+from ._collections import RangeMap
-+
-
--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
-+_msvcr_lookup = RangeMap.left(
-+ {
-+ # MSVC 7.0
-+ 1300: ['msvcr70'],
-+ # MSVC 7.1
-+ 1310: ['msvcr71'],
-+ # VS2005 / MSVC 8.0
-+ 1400: ['msvcr80'],
-+ # VS2008 / MSVC 9.0
-+ 1500: ['msvcr90'],
-+ # VS2010 / MSVC 10.0
-+ 1600: ['msvcr100'],
-+ # VS2012 / MSVC 11.0
-+ 1700: ['msvcr110'],
-+ # VS2013 / MSVC 12.0
-+ 1800: ['msvcr120'],
-+ # VS2015 / MSVC 14.0
-+ 1900: ['vcruntime140'],
-+ 2000: RangeMap.undefined_value,
-+ },
-+)
-+
-
- 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']
-- else:
-- raise ValueError("Unknown MS Compiler version %s " % msc_ver)
-+ match = re.search(r'MSC v\.(\d{4})', sys.version)
-+ try:
-+ msc_ver = int(match.group(1))
-+ except AttributeError:
-+ return
-+ try:
-+ return _msvcr_lookup[msc_ver]
-+ except KeyError:
-+ raise ValueError("Unknown MS Compiler version %s " % msc_ver)
-+
-+
-+_runtime_library_dirs_msg = (
-+ "Unable to set runtime library search path on Windows, "
-+ "usually indicated by `runtime_library_dirs` parameter to Extension"
-+)
-
-
- class CygwinCCompiler(UnixCCompiler):
-- """ Handles the Cygwin port of the GNU C compiler to Windows.
-- """
-+ """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"
-+ shared_lib_extension = ".dll.a"
-+ dylib_lib_extension = ".dll"
- static_lib_format = "lib%s%s"
-- shared_lib_format = "%s%s"
-+ shared_lib_format = "lib%s%s"
-+ dylib_lib_format = "cyg%s%s"
- exe_extension = ".exe"
-
- def __init__(self, verbose=0, dry_run=0, force=0):
--
-- UnixCCompiler.__init__(self, verbose, dry_run, force)
-+ super().__init__(verbose, dry_run, force)
-
- status, details = check_config_h()
-- self.debug_print("Python's GCC status: %s (details: %s)" %
-- (status, details))
-+ self.debug_print(
-+ "Python's GCC status: {} (details: {})".format(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)
-+ "Compiling may fail because of undefined preprocessor
macros." % details
-+ )
-
-- 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) )
-+ self.cc = os.environ.get('CC', 'gcc')
-+ self.cxx = os.environ.get('CXX', 'g++')
-
-- # 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 = "gcc"
-- else:
-- self.linker_dll = "dllwrap"
-+ 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=('{} -mcygwin {}'.format(self.linker_dll,
shared_option)),
-+ )
-
-- # 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"
-+ # Include the appropriate MSVC runtime library if Python was built
-+ # with MSVC 7.0 or later.
-+ self.dll_libraries = get_msvcr()
-
-- # Hard-code GCC because that's what this is all about.
-- # XXX optimization, warnings etc. should be customizable.
-- self.set_executables(compiler='gcc -mcygwin -O -Wall',
-- compiler_so='gcc -mcygwin -mdll -O -Wall',
-- compiler_cxx='g++ -mcygwin -O -Wall',
-- linker_exe='gcc -mcygwin',
-- linker_so=('%s -mcygwin %s' %
-- (self.linker_dll, shared_option)))
--
-- # cygwin and mingw32 need different sets of libraries
-- if 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()
-+ @property
-+ def gcc_version(self):
-+ # Older numpy depended on this existing to check for ancient
-+ # gcc versions. This doesn't make much sense with clang etc so
-+ # just hardcode to something recent.
-+ # https://github.com/numpy/numpy/pull/20333
-+ warnings.warn(
-+ "gcc_version attribute of CygwinCCompiler is deprecated. "
-+ "Instead of returning actual gcc version a fixed value 11.2.0
is returned.",
-+ DeprecationWarning,
-+ stacklevel=2,
-+ )
-+ with suppress_known_deprecation():
-+ return LooseVersion("11.2.0")
-
- 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':
-+ if ext in ('.rc', '.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
-+ else: # for other files use the C-compiler
- try:
-- self.spawn(self.compiler_so + cc_args + [src, '-o', obj] +
-- extra_postargs)
-+ 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):
-+ 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 [])
-
-+ if runtime_library_dirs:
-+ self.warn(_runtime_library_dirs_msg)
-+
- # 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")):
-+ 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))
-+ 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"]
-+ 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)
-+ self.execute(write_file, (def_file, contents), "writing %s" %
def_file)
-
-- # next add options for def-file and to creating import libraries
-+ # next add options for def-file
-
-- # 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)
-+ # for gcc/ld the def-file is specified as any object files
-+ objects.append(def_file)
-
-- #end: if ((export_symbols is not None) and
-+ # 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
-+ # otherwise we let 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)
-+ 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,
-+ )
-+
-+ def runtime_library_dir_option(self, dir):
-+ # cygwin doesn't support rpath. While in theory we could error
-+ # out like MSVC does, code might expect it to work like on Unix, so
-+ # just warn and hope for the best.
-+ self.warn(_runtime_library_dirs_msg)
-+ return []
-
- # -- 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
-+ def _make_out_path(self, output_dir, strip_dir, src_name):
-+ # use normcase to make sure '.rc' is really '.rc' and not '.RC'
-+ norm_src_name = os.path.normcase(src_name)
-+ return super()._make_out_path(output_dir, strip_dir, norm_src_name)
-+
-+ @property
-+ def out_extensions(self):
-+ """
-+ Add support for rc and res files.
-+ """
-+ return {
-+ **super().out_extensions,
-+ **{ext: ext + self.obj_extension for ext in ('.res', '.rc')},
-+ }
-+
-
- # the same as cygwin plus some additional parameters
- class Mingw32CCompiler(CygwinCCompiler):
-- """ Handles the Mingw32 port of the GNU C compiler to Windows.
-- """
-+ """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)
-+ super().__init__(verbose, dry_run, force)
-
-- # 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"
-+ 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 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')
-
-- if is_cygwingcc():
-- 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='{} {}'.format(self.linker_dll, shared_option),
-+ )
-
-- self.set_executables(compiler='gcc -O -Wall',
-- compiler_so='gcc -mdll -O -Wall',
-- compiler_cxx='g++ -O -Wall',
-- linker_exe='gcc',
-- 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')
-+ def runtime_library_dir_option(self, dir):
-+ raise DistutilsPlatformError(_runtime_library_dirs_msg)
-
-- # 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
-@@ -346,58 +319,38 @@ def check_config_h():
-
- 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))
-+ return (CONFIG_H_UNCERTAIN, "couldn't read '{}': {}".format(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
-- # so we need to decode our bytes
-- return LooseVersion(result.group(1).decode())
-+def is_cygwincc(cc):
-+ '''Try to determine if the compiler that would be used is from
cygwin.'''
-+ out_string = check_output(shlex.split(cc) + ['-dumpmachine'])
-+ return out_string.strip().endswith(b'cygwin')
-+
-
--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_cygwingcc():
-- '''Try to determine if the gcc that would be used is from cygwin.'''
-- out_string = check_output(['gcc', '-dumpmachine'])
-- return out_string.strip().endswith(b'cygwin')
-+get_versions = None
-+"""
-+A stand-in for the previous get_versions() function to prevent failures
-+when monkeypatched. See pypa/setuptools#2969.
-+"""
-diff --git a/third_party/python/setuptools/setuptools/_distutils/dep_util.py
b/third_party/python/setuptools/setuptools/_distutils/dep_util.py
---- a/third_party/python/setuptools/setuptools/_distutils/dep_util.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/dep_util.py
-@@ -1,40 +1,41 @@
- """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
-+from .errors import DistutilsFileError
-
-
--def newer (source, target):
-+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))
-+ 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):
-+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")
-
-@@ -43,20 +44,21 @@ def newer_pairwise (sources, targets):
- 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'):
-+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
-@@ -68,25 +70,27 @@ def newer_group (sources, target, missin
- 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
-+ 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
-+ 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/third_party/python/setuptools/setuptools/_distutils/dir_util.py
b/third_party/python/setuptools/setuptools/_distutils/dir_util.py
---- a/third_party/python/setuptools/setuptools/_distutils/dir_util.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/dir_util.py
-@@ -1,87 +1,94 @@
- """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
-+from .errors import DistutilsInternalError, DistutilsFileError
-+from ._log 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):
-+
-+def mkpath(name, mode=0o777, verbose=1, dry_run=0): # noqa: C901
- """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.
-+
-+ os.makedirs is not used because:
-+
-+ a) It's new to Python 1.5.2, and
-+ b) it blows up if the directory already exists (in which case it should
-+ silently succeed).
- """
-
- 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,))
-+ "mkpath: 'name' must be a string (got {!r})".format(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
-+ 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
-+ 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),
-+ # 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]))
-+ "could not create '{}': {}".format(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
-@@ -91,18 +98,27 @@ def create_tree(base_dir, files, mode=0o
- 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):
-+
-+def copy_tree( # noqa: C901
-+ 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
-@@ -115,26 +131,26 @@ def copy_tree(src, dst, preserve_mode=1,
- 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)
-+ 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))
-+ "error listing files in '{}': {}".format(src, e.strerror)
-+ )
-
- if not dry_run:
- mkpath(dst, verbose=verbose)
-
- outputs = []
-
- for n in names:
- src_name = os.path.join(src, n)
-@@ -149,37 +165,53 @@ def copy_tree(src, dst, preserve_mode=1,
- 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))
-+ 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)
-+ 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)
-+ 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
-
-@@ -190,19 +222,20 @@ def remove_tree(directory, verbose=1, dr
- 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]
-+ _path_created.pop(abspath)
- except OSError as exc:
-- log.warn("error removing %s: %s", directory, exc)
-+ log.warning("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:
-diff --git a/third_party/python/setuptools/setuptools/_distutils/dist.py
b/third_party/python/setuptools/setuptools/_distutils/dist.py
---- a/third_party/python/setuptools/setuptools/_distutils/dist.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/dist.py
-@@ -2,28 +2,36 @@
-
- Provides the Distribution class, which represents the module distribution
- being built/installed/distributed.
- """
-
- import sys
- import os
- import re
-+import pathlib
-+import contextlib
-+import logging
- 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
-+from .errors import (
-+ DistutilsOptionError,
-+ DistutilsModuleError,
-+ DistutilsArgError,
-+ DistutilsClassError,
-+)
-+from .fancy_getopt import FancyGetopt, translate_longopt
-+from .util import check_environ, strtobool, rfc822_escape
-+from ._log import log
-+from .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_]*)$')
-
-
-@@ -32,17 +40,17 @@ def _ensure_list(value, fieldname):
- # 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)
-+ log.warning(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.
-@@ -64,82 +72,64 @@ class Distribution:
- # 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'),
-+ ('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 <package name>-<version>"),
-- ('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")
-- ]
-+ ('help-commands', None, "list all available commands"),
-+ ('name', None, "print package name"),
-+ ('version', 'V', "print package version"),
-+ ('fullname', None, "print <package name>-<version>"),
-+ ('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):
-+ def __init__(self, attrs=None): # noqa: C901
- """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()'.
-@@ -242,33 +232,33 @@ Common commands: (see '--help-commands'
- 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():
-+ for command, cmd_options in options.items():
- opt_dict = self.get_option_dict(command)
-- for (opt, val) in cmd_options.items():
-+ 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():
-+ 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)
-@@ -301,99 +291,100 @@ Common commands: (see '--help-commands'
- 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
-+ 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)
-+ self.announce(indent + "no option dict for '%s' command" %
cmd_name)
- else:
-- self.announce(indent +
-- "option dict for '%s' command:" % cmd_name)
-+ 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.
-+ There are multiple possible config files:
-+ - distutils.cfg in the Distutils installation directory (i.e.
-+ 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; may be disabled
-+ with the ``--no-user-cfg`` option
-+ - setup.cfg in the current directory
-+ - a file named by an environment variable
- """
-- 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)
-+ files = [str(path) for path in self._gen_paths() if
os.path.isfile(path)]
-
- if DEBUG:
- self.announce("using config files: %s" % ', '.join(files))
-
- return files
-
-- def parse_config_files(self, filenames=None):
-+ def _gen_paths(self):
-+ # The system-wide Distutils config file
-+ sys_dir = pathlib.Path(sys.modules['distutils'].__file__).parent
-+ yield sys_dir / "distutils.cfg"
-+
-+ # The per-user config file
-+ prefix = '.' * (os.name == 'posix')
-+ filename = prefix + 'pydistutils.cfg'
-+ if self.want_user_cfg:
-+ yield pathlib.Path('~').expanduser() / filename
-+
-+ # All platforms support local setup.cfg
-+ yield pathlib.Path('setup.cfg')
-+
-+ # Additional config indicated in the environment
-+ with contextlib.suppress(TypeError):
-+ yield pathlib.Path(os.getenv("DIST_EXTRA_CONFIG"))
-+
-+ def parse_config_files(self, filenames=None): # noqa: C901
- 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']
-+ '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()
-
-@@ -406,34 +397,34 @@ Common commands: (see '--help-commands'
- 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)
-+ 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():
-+ 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!
-+ 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 ----------------------------------
-
-@@ -470,57 +461,60 @@ Common commands: (see '--help-commands'
- # 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)
-+ logging.getLogger().setLevel(logging.WARN - 10 * 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)
-+ 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)
-+ 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"),
-- ]
-+ (
-+ "command-packages=",
-+ None,
-+ "list of packages that provide distutils commands",
-+ ),
-+ ]
-
-- def _parse_command_opts(self, parser, args):
-+ def _parse_command_opts(self, parser, args): # noqa: C901
- """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.
- """
-@@ -540,73 +534,81 @@ Common commands: (see '--help-commands'
- 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)
-+ "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)")
-+ 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)):
-+ 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_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(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
-+ 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))
-+ % (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():
-+ 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.
-@@ -614,18 +616,17 @@ Common commands: (see '--help-commands'
- 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=[]):
-+ 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
-@@ -644,29 +645,30 @@ Common commands: (see '--help-commands'
- 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)")
-+ "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))
-+ 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):
-@@ -689,24 +691,23 @@ Common commands: (see '--help-commands'
- # 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:
-+ 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']:
-+ value = getattr(self.metadata, "get_" + opt)()
-+ if opt in ('keywords', 'platforms'):
- print(','.join(value))
-- elif opt in ('classifiers', 'provides', 'requires',
-- 'obsoletes'):
-+ 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):
-@@ -730,62 +731,60 @@ Common commands: (see '--help-commands'
- """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):
-+ 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)
-+ self.print_command_list(std_commands, "Standard commands",
max_length)
- if extra_commands:
- print()
-- self.print_command_list(extra_commands,
-- "Extra commands",
-- max_length)
-+ 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):
-+ 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))
-@@ -817,86 +816,87 @@ Common commands: (see '--help-commands'
- 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)
-+ module_name = "{}.{}".format(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))
-+ % (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)
-+ 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):
-+ 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').
- """
- 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():
-+ for option, (source, value) in option_dict.items():
- if DEBUG:
-- self.announce(" %s = %s (from %s)" % (option, value,
-- source))
-+ self.announce(" {} = {} (from {})".format(option, value,
source))
- try:
-- bool_opts = [translate_longopt(o)
-- for o in command_obj.boolean_options]
-+ 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:
-@@ -905,17 +905,18 @@ Common commands: (see '--help-commands'
- 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))
-+ % (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
-@@ -929,16 +930,17 @@ Common commands: (see '--help-commands'
- 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
-@@ -950,17 +952,17 @@ Common commands: (see '--help-commands'
- 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):
-+ def announce(self, msg, level=logging.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:
-@@ -1005,40 +1007,58 @@ Common commands: (see '--help-commands'
-
- 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())
-+ 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",
-- )
-+ _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
-@@ -1059,19 +1079,18 @@ class DistributionMetadata:
- 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
-+ if value and value != "UNKNOWN":
-+ return value
-
- def _read_list(name):
- values = msg.get_all(name, None)
- if values == []:
- return None
- return values
-
- metadata_version = msg['metadata-version']
-@@ -1106,152 +1125,163 @@ class DistributionMetadata:
- 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:
-+ """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.
-- """
-+ """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):
-+ if (
-+ self.provides
-+ or self.requires
-+ or self.obsoletes
-+ or self.classifiers
-+ or self.download_url
-+ ):
- version = '1.1'
-
-+ # required fields
- 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)
-+
-+ def maybe_write(header, val):
-+ if val:
-+ file.write(f"{header}: {val}\n")
-
-- 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)
-+ # optional fields
-+ maybe_write("Summary", self.get_description())
-+ maybe_write("Home-page", self.get_url())
-+ maybe_write("Author", self.get_contact())
-+ maybe_write("Author-email", self.get_contact_email())
-+ maybe_write("License", self.get_license())
-+ maybe_write("Download-URL", self.download_url)
-+ maybe_write("Description",
rfc822_escape(self.get_long_description() or ""))
-+ maybe_write("Keywords", ",".join(self.get_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):
-+ values = values or []
- for value in values:
-- file.write('%s: %s\n' % (name, value))
-+ file.write('{}: {}\n'.format(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())
-+ return "{}-{}".format(self.get_name(), self.get_version())
-
- def get_author(self):
-- return self.author or "UNKNOWN"
-+ return self.author
-
- def get_author_email(self):
-- return self.author_email or "UNKNOWN"
-+ return self.author_email
-
- def get_maintainer(self):
-- return self.maintainer or "UNKNOWN"
-+ return self.maintainer
-
- def get_maintainer_email(self):
-- return self.maintainer_email or "UNKNOWN"
-+ return self.maintainer_email
-
- def get_contact(self):
-- return self.maintainer or self.author or "UNKNOWN"
-+ return self.maintainer or self.author
-
- def get_contact_email(self):
-- return self.maintainer_email or self.author_email or "UNKNOWN"
-+ return self.maintainer_email or self.author_email
-
- def get_url(self):
-- return self.url or "UNKNOWN"
-+ return self.url
-
- def get_license(self):
-- return self.license or "UNKNOWN"
-+ return self.license
-+
- get_licence = get_license
-
- def get_description(self):
-- return self.description or "UNKNOWN"
-+ return self.description
-
- def get_long_description(self):
-- return self.long_description or "UNKNOWN"
-+ return self.long_description
-
- 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"]
-+ return self.platforms
-
- 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"
-+ return self.download_url
-
- # 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/third_party/python/setuptools/setuptools/_distutils/errors.py
b/third_party/python/setuptools/setuptools/_distutils/errors.py
---- a/third_party/python/setuptools/setuptools/_distutils/errors.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/errors.py
-@@ -3,95 +3,125 @@
- 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):
-+
-+class DistutilsError(Exception):
- """The root of all Distutils evil."""
-+
- pass
-
--class DistutilsModuleError (DistutilsError):
-+
-+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):
-+
-+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):
-+
-+class DistutilsGetoptError(DistutilsError):
- """The option table provided to 'fancy_getopt()' is bogus."""
-+
- pass
-
--class DistutilsArgError (DistutilsError):
-+
-+class DistutilsArgError(DistutilsError):
- """Raised by fancy_getopt in response to getopt.error -- ie. an
- error in the command line usage."""
-+
- pass
-
--class DistutilsFileError (DistutilsError):
-+
-+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):
-+
-+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):
-+
-+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):
-+
-+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):
-+
-+class DistutilsExecError(DistutilsError):
- """Any problems executing an external program (such as the C
- compiler, when compiling C files)."""
-+
- pass
-
--class DistutilsInternalError (DistutilsError):
-+
-+class DistutilsInternalError(DistutilsError):
- """Internal inconsistencies or impossibilities (obviously, this
- should never be seen if the code is working!)."""
-+
- pass
-
--class DistutilsTemplateError (DistutilsError):
-+
-+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):
-+class CCompilerError(Exception):
- """Some compile/link operation failed."""
-
--class PreprocessError (CCompilerError):
-+
-+class PreprocessError(CCompilerError):
- """Failure to preprocess one or more C/C++ files."""
-
--class CompileError (CCompilerError):
-+
-+class CompileError(CCompilerError):
- """Failure to compile one or more C/C++ source files."""
-
--class LibError (CCompilerError):
-+
-+class LibError(CCompilerError):
- """Failure to create a static library from one or more C/C++ object
- files."""
-
--class LinkError (CCompilerError):
-+
-+class LinkError(CCompilerError):
- """Failure to link one or more C/C++ object files into an executable
- or shared library file."""
-
--class UnknownFileError (CCompilerError):
-+
-+class UnknownFileError(CCompilerError):
- """Attempt to process an unknown file type."""
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/extension.py
b/third_party/python/setuptools/setuptools/_distutils/extension.py
---- a/third_party/python/setuptools/setuptools/_distutils/extension.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/extension.py
-@@ -11,16 +11,17 @@ import warnings
- # 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.
-@@ -78,37 +79,39 @@ class Extension:
- 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
-- ):
-+ 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)):
-+ 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 []
-@@ -126,47 +129,52 @@ class Extension:
- # 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>' % (
-+ return '<{}.{}({!r}) at {:#x}>'.format(
- self.__class__.__module__,
- self.__class__.__qualname__,
- self.name,
-- id(self))
-+ id(self),
-+ )
-
-
--def read_setup_file(filename):
-+def read_setup_file(filename): # noqa: C901
- """Reads a Setup file and returns Extension instances."""
-- from distutils.sysconfig import (parse_makefile, expand_makefile_vars,
-- _variable_rx)
-+ 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
- # <module> ... [<sourcefile> ...] [<cpparg> ...] [<library> ...]
-- file = TextFile(filename,
-- strip_comments=1, skip_blanks=1, join_lines=1,
-- lstrip_ws=1, rstrip_ws=1)
-+ 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
-+ if line is None: # eof
- break
-- if _variable_rx.match(line): # VAR=VALUE, handled in first
pass
-+ 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)
-@@ -183,35 +191,35 @@ def read_setup_file(filename):
-
- 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:]
-+ 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
-+ 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:]))
-+ 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!
-+ 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":
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/fancy_getopt.py
b/third_party/python/setuptools/setuptools/_distutils/fancy_getopt.py
---- a/third_party/python/setuptools/setuptools/_distutils/fancy_getopt.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/fancy_getopt.py
-@@ -3,34 +3,37 @@
- 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 sys
-+import string
-+import re
- import getopt
--from distutils.errors import *
-+from .errors import DistutilsGetoptError, DistutilsArgError
-
- # 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))
-+neg_alias_re = re.compile("^({})=!({})$".format(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
-@@ -85,17 +88,18 @@ class FancyGetopt:
-
- 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)
-+ "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'."""
-@@ -104,38 +108,42 @@ class FancyGetopt:
- 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():
-+ 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))
-+ 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))
-+ 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):
-+ def _grok_option_table(self): # noqa: C901
- """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 = {}
-@@ -144,75 +152,80 @@ class FancyGetopt:
- 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,))
-+ raise ValueError("invalid option tuple:
{!r}".format(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)
-+ 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)
-+ 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 + ':'
-+ 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))
-+ "invalid negative alias '%s': "
-+ "aliased option '%s' takes a value" % (long,
alias_to)
-+ )
-
-- self.long_opts[-1] = long # XXX redundant?!
-+ 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))
-+ "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)
-+ "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):
-+ def getopt(self, args=None, object=None): # noqa: C901
- """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
-@@ -230,27 +243,27 @@ class FancyGetopt:
-
- 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
-+ 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?
-+ 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
-
-@@ -273,37 +286,37 @@ class FancyGetopt:
- 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):
-+ def generate_help(self, header=None): # noqa: C901
- """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)
-+ ell = len(long)
- if long[-1] == '=':
-- l = l - 1
-+ ell = ell - 1
- if short is not None:
-- l = l + 5 # " (-x)" where short == 'x'
-- if l > max_opt:
-- max_opt = l
-+ ell = ell + 5 # " (-x)" where short == 'x'
-+ if ell > max_opt:
-+ max_opt = ell
-
-- opt_width = max_opt + 2 + 2 + 2 # room for indent + dashes +
gutter
-+ 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)
-@@ -341,76 +354,76 @@ class FancyGetopt:
- 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)
-+ opt_names = "{} (-{})".format(long, short)
- if text:
-- lines.append(" --%-*s %s" %
-- (max_opt, opt_names, text[0]))
-+ 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)
-+ for ell in text[1:]:
-+ lines.append(big_indent + ell)
- 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}
-+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
-+ 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
-+ 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
-+ ell = len(chunks[0])
-+ if cur_len + ell <= 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
-+ cur_len = cur_len + ell
-+ 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 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
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/file_util.py
b/third_party/python/setuptools/setuptools/_distutils/file_util.py
---- a/third_party/python/setuptools/setuptools/_distutils/file_util.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/file_util.py
-@@ -1,76 +1,87 @@
- """distutils.file_util
-
- Utility functions for operating on single files.
- """
-
- import os
--from distutils.errors import DistutilsFileError
--from distutils import log
-+from .errors import DistutilsFileError
-+from ._log import log
-
- # for generating verbose output in 'copy_file()'
--_copy_action = { None: 'copying',
-- 'hard': 'hard linking',
-- 'sym': 'symbolically linking' }
-+_copy_action = {None: 'copying', 'hard': 'hard linking', 'sym':
'symbolically linking'}
-
-
--def _copy_file_contents(src, dst, buffer_size=16*1024):
-+def _copy_file_contents(src, dst, buffer_size=16 * 1024): # noqa: C901
- """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))
-+ raise DistutilsFileError("could not open '{}': {}".format(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))
-+ "could not delete '{}': {}".format(dst, e.strerror)
-+ )
-
- try:
- fdst = open(dst, 'wb')
- except OSError as e:
- raise DistutilsFileError(
-- "could not create '%s': %s" % (dst, e.strerror))
-+ "could not create '{}': {}".format(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))
-+ "could not read from '{}': {}".format(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))
-+ "could not write to '{}': {}".format(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):
-+
-+def copy_file( # noqa: C901
-+ 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
-@@ -97,17 +108,18 @@ def copy_file(src, dst, preserve_mode=1,
- # 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)
-+ "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):
-@@ -158,20 +170,17 @@ def copy_file(src, dst, preserve_mode=1,
- 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):
--
-+def move_file(src, dst, verbose=1, dry_run=0): # noqa: C901
- """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
-@@ -185,53 +194,54 @@ def move_file (src, 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))
-+ "can't move '{}': destination '{}' already exists".format(src,
dst)
-+ )
-
- if not isdir(dirname(dst)):
- raise DistutilsFileError(
-- "can't move '%s': destination '%s' not a valid path" %
-- (src, dst))
-+ "can't move '{}': destination '{}' not a valid
path".format(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))
-+ "couldn't move '{}' to '{}': {}".format(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))
-+ "couldn't move '%s' to '%s' by copy/delete: "
-+ "delete '%s' failed: %s" % (src, dst, src, msg)
-+ )
- return dst
-
-
--def write_file (filename, contents):
-+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:
-diff --git a/third_party/python/setuptools/setuptools/_distutils/filelist.py
b/third_party/python/setuptools/setuptools/_distutils/filelist.py
---- a/third_party/python/setuptools/setuptools/_distutils/filelist.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/filelist.py
-@@ -1,20 +1,23 @@
- """distutils.filelist
-
- Provides the FileList class, used for poking about the filesystem
- and building lists of files.
- """
-
--import os, re
-+import os
-+import re
- import fnmatch
- import functools
--from distutils.util import convert_path
--from distutils.errors import DistutilsTemplateError, DistutilsInternalError
--from distutils import log
-+
-+from .util import convert_path
-+from .errors import DistutilsTemplateError, DistutilsInternalError
-+from ._log 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
-@@ -38,149 +41,168 @@ class FileList:
- 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)
-
-- # -- List-like methods ---------------------------------------------
-+ # 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 ---------------------------
-+ # 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 ---------------------------------------
-+ # "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 action in ('include', 'exclude', 'global-include',
'global-exclude'):
- if len(words) < 2:
- raise DistutilsTemplateError(
-- "'%s' expects <pattern1> <pattern2> ..." % action)
-+ "'%s' expects <pattern1> <pattern2> ..." % 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 <dir> <pattern1> <pattern2> ..." %
action)
-+ "'%s' expects <dir> <pattern1> <pattern2> ..." % 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 <dir_pattern>" % action)
-+ "'%s' expects a single <dir_pattern>" % 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):
-+ def process_template_line(self, line): # noqa: C901
- # 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)
-+ log.warning("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)
-+ log.warning(
-+ (
-+ "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)
-+ log.warning(
-+ (
-+ "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)
-+ log.warning(
-+ (
-+ "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)))
-+ self.debug_print("recursive-include {} {}".format(dir, '
'.join(patterns)))
- for pattern in patterns:
- if not self.include_pattern(pattern, prefix=dir):
-- log.warn(("warning: no files found matching '%s' "
-- "under directory '%s'"),
-- pattern, dir)
-+ msg = (
-+ "warning: no files found matching '%s' " "under
directory '%s'"
-+ )
-+ log.warning(msg, pattern, dir)
-
- elif action == 'recursive-exclude':
-- self.debug_print("recursive-exclude %s %s" %
-- (dir, ' '.join(patterns)))
-+ self.debug_print("recursive-exclude {} {}".format(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)
-+ log.warning(
-+ (
-+ "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)
-+ log.warning("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)
-+ log.warning(
-+ ("no previously-included directories found " "matching
'%s'"),
-+ dir_pattern,
-+ )
- else:
- raise DistutilsInternalError(
-- "this cannot happen: invalid action '%s'" % action)
-+ "this cannot happen: invalid action '%s'" % action
-+ )
-
--
-- # -- Filtering/selection methods -----------------------------------
-+ # 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.
-@@ -200,66 +222,88 @@ class FileList:
-
- 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)
-+ 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):
-+ 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):
-+ 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 os.walk(path, followlinks=True)
-- for file in files
-+ 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)
-@@ -309,19 +353,19 @@ def translate_pattern(pattern, anchor=1,
- pattern_re = glob_to_re(pattern)
- assert pattern_re.startswith(start) and pattern_re.endswith(end)
- else:
- pattern_re = ''
-
- if prefix is not None:
- prefix_re = glob_to_re(prefix)
- assert prefix_re.startswith(start) and prefix_re.endswith(end)
-- prefix_re = prefix_re[len(start): len(prefix_re) - len(end)]
-+ prefix_re = prefix_re[len(start) : len(prefix_re) - len(end)]
- sep = os.sep
- if os.sep == '\\':
- sep = r'\\'
-- pattern_re = pattern_re[len(start): len(pattern_re) - len(end)]
-- pattern_re = r'%s\A%s%s.*%s%s' % (start, prefix_re, sep,
pattern_re, end)
-- else: # no prefix -- respect anchor flag
-+ pattern_re = pattern_re[len(start) : len(pattern_re) - len(end)]
-+ pattern_re = r'{}\A{}{}.*{}{}'.format(start, prefix_re, sep,
pattern_re, end)
-+ else: # no prefix -- respect anchor flag
- if anchor:
-- pattern_re = r'%s\A%s' % (start, pattern_re[len(start):])
-+ pattern_re = r'{}\A{}'.format(start, pattern_re[len(start) :])
-
- return re.compile(pattern_re)
-diff --git a/third_party/python/setuptools/setuptools/_distutils/log.py
b/third_party/python/setuptools/setuptools/_distutils/log.py
---- a/third_party/python/setuptools/setuptools/_distutils/log.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/log.py
-@@ -1,77 +1,57 @@
--"""A simple log mechanism styled after PEP 282."""
--
--# The class here is styled after PEP 282 so that it could later be
--# replaced with a standard Python logging implementation.
--
--DEBUG = 1
--INFO = 2
--WARN = 3
--ERROR = 4
--FATAL = 5
-+"""
-+A simple log mechanism styled after PEP 282.
-
--import sys
--
--class Log:
-+Retained for compatibility and should not be used.
-+"""
-
-- def __init__(self, threshold=WARN):
-- self.threshold = threshold
--
-- def _log(self, level, msg, args):
-- if level not in (DEBUG, INFO, WARN, ERROR, FATAL):
-- raise ValueError('%s wrong log level' % str(level))
-+import logging
-+import warnings
-
-- if level >= 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()
-+from ._log import log as _global_log
-+
-
-- 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)
-+DEBUG = logging.DEBUG
-+INFO = logging.INFO
-+WARN = logging.WARN
-+ERROR = logging.ERROR
-+FATAL = logging.FATAL
-
-- 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
-+warn = _global_log.warning
- 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
-+ orig = _global_log.level
-+ _global_log.setLevel(level)
-+ return orig
-+
-
- def set_verbosity(v):
- if v <= 0:
-- set_threshold(WARN)
-+ set_threshold(logging.WARN)
- elif v == 1:
-- set_threshold(INFO)
-+ set_threshold(logging.INFO)
- elif v >= 2:
-- set_threshold(DEBUG)
-+ set_threshold(logging.DEBUG)
-+
-+
-+class Log(logging.Logger):
-+ """distutils.log.Log is deprecated, please use an alternative from
`logging`."""
-+
-+ def __init__(self, threshold=WARN):
-+ warnings.warn(Log.__doc__) # avoid DeprecationWarning to ensure
warn is shown
-+ super().__init__(__name__, level=threshold)
-+
-+ @property
-+ def threshold(self):
-+ return self.level
-+
-+ @threshold.setter
-+ def threshold(self, level):
-+ self.setLevel(level)
-+
-+ warn = logging.Logger.warning
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/msvc9compiler.py
b/third_party/python/setuptools/setuptools/_distutils/msvc9compiler.py
---- a/third_party/python/setuptools/setuptools/_distutils/msvc9compiler.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/msvc9compiler.py
-@@ -11,66 +11,82 @@ for older versions of VS in distutils.ms
- # 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
-+import warnings
-
--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 .errors import (
-+ DistutilsExecError,
-+ DistutilsPlatformError,
-+ CompileError,
-+ LibError,
-+ LinkError,
-+)
-+from .ccompiler import CCompiler, gen_lib_options
-+from ._log import log
-+from .util import get_platform
-
- import winreg
-
-+warnings.warn(
-+ "msvc9compiler is deprecated and slated to be removed "
-+ "in the future. Please discontinue use or file an issue "
-+ "with pypa/distutils describing your use case.",
-+ DeprecationWarning,
-+)
-+
- 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)
-+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)
-+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',
-+ 'win32': 'x86',
-+ 'win-amd64': 'amd64',
- }
-
-+
- class Reg:
-- """Helper class to read values from the registry
-- """
-+ """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
-@@ -79,16 +95,17 @@ class Reg:
- 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:
-@@ -101,74 +118,77 @@ class Reg:
- 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")
-+ self.set_macro("FrameworkSDKDir", NET_BASE,
"sdkinstallrootv2.0")
- else:
- raise KeyError("sdkinstallrootv2.0")
- except KeyError:
- raise DistutilsPlatformError(
-- """Python was built with Visual Studio 2008;
-+ """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.""")
-+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))
-+ d = Reg.get_value(base, r"{}\{}".format(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)
-@@ -184,51 +204,52 @@ def get_build_version():
- # 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.
-- """
-+ """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")
-+ 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)
-
-@@ -244,29 +265,31 @@ def find_vcvarsall(version):
- 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
-- """
-+ """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)
-+ popen = subprocess.Popen(
-+ '"{}" {} & set'.format(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)
-@@ -284,25 +307,25 @@ def query_vcvarsall(version, arch="x86")
- 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) :
-+
-+class MSVCCompiler(CCompiler):
- """Concrete class that implements an interface to Microsoft Visual C++,
-- as defined by the CCompiler abstract class."""
-+ 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.
-@@ -311,164 +334,185 @@ class MSVCCompiler(CCompiler) :
- # 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)
-+ 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)
-+ super().__init__(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.__arch = None # deprecated name
- self.initialized = False
-
-- def initialize(self, plat_name=None):
-+ def initialize(self, plat_name=None): # noqa: C901
- # multi-init means we would need to check platform same each time...
- assert not self.initialized, "don't init multiple times"
-+ if self.__version < 8.0:
-+ raise DistutilsPlatformError(
-+ "VC %0.1f is not supported by this module" % self.__version
-+ )
- 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,))
-+ raise DistutilsPlatformError(
-+ "--plat-name must be one of {}".format(ok_plats)
-+ )
-
-- if "DISTUTILS_USE_SDK" in os.environ and "MSSdk" in os.environ and
self.find_exe("cl.exe"):
-+ 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':
-+ if plat_name in (get_platform(), '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]
-+ 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)
-+ 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')
-+ 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', '/Ox', '/MD', '/W3',
-- '/DNDEBUG']
-- self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3',
-- '/Z7', '/D_DEBUG']
-+ 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', '/Ox', '/MD', '/W3', '/GS-'
,
-- '/DNDEBUG']
-- self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3',
'/GS-',
-- '/Z7', '/D_DEBUG']
-+ 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.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=''):
-+ 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 = ''
-+ 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 /
-+ (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)
-+ raise CompileError("Don't know how to compile %s" %
src_name)
- if strip_dir:
-- base = os.path.basename (base)
-+ base = os.path.basename(base)
- if ext in self._rc_extensions:
-- obj_names.append (os.path.join (output_dir,
-- base + self.res_extension))
-+ 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))
-+ 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))
-+ 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):
--
-+ def compile( # noqa: C901
-+ 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)
-+ 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')
-+ 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]
-@@ -484,18 +528,17 @@ class MSVCCompiler(CCompiler) :
- 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])
-+ 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
-@@ -505,132 +548,128 @@ class MSVCCompiler(CCompiler) :
- # 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')
-+ 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])
-+ 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))
-+ raise CompileError(
-+ "Don't know how to compile {} to {}".format(src, obj)
-+ )
-
- output_opt = "/Fo" + obj
- try:
-- self.spawn([self.cc] + compile_opts + pp_opts +
-- [input_opt, output_opt] +
-- extra_postargs)
-+ 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):
--
-+ 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)
-+ 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?
-+ 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):
--
-+ def link( # noqa: C901
-+ 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)
-+ 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))
-+ 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)
-+ 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 []):
-+ for sym in export_symbols or []:
- export_opts.append("/EXPORT:" + sym)
-
-- ld_args = (ldflags + lib_opts + export_opts +
-- objects + ['/OUT:' + output_filename])
-+ 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)
-+ 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)
-
-@@ -643,35 +682,34 @@ class MSVCCompiler(CCompiler) :
- # 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)
-+ out_arg = '-outputresource:{};{}'.format(output_filename,
mfid)
- try:
-- self.spawn(['mt.exe', '-nologo', '-manifest',
-- mffilename, out_arg])
-+ 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")
-+ 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:
-@@ -704,27 +742,30 @@ class MSVCCompiler(CCompiler) :
- # 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"""<assemblyIdentity.*?name=("|')Microsoft\."""\
-+ r"""<assemblyIdentity.*?name=("|')Microsoft\."""
- r"""VC\d{2}\.CRT("|').*?(/>|</assemblyIdentity>)""",
-- re.DOTALL)
-+ re.DOTALL,
-+ )
- manifest_buf = re.sub(pattern, "", manifest_buf)
- pattern = r"<dependentAssembly>\s*</dependentAssembly>"
- 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"""<assemblyIdentity.*?name=(?:"|')(.+?)(?:"|')"""
-- r""".*?(?:/>|</assemblyIdentity>)""", re.DOTALL)
-+ r""".*?(?:/>|</assemblyIdentity>)""",
-+ 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:
-@@ -736,32 +777,32 @@ class MSVCCompiler(CCompiler) :
- # 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++")
-+ "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))
-+ 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
-
-@@ -776,13 +817,13 @@ class MSVCCompiler(CCompiler) :
- """
- 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)
-+ fn = os.path.join(os.path.abspath(p), exe)
- if os.path.isfile(fn):
- return fn
-
- return exe
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/msvccompiler.py
b/third_party/python/setuptools/setuptools/_distutils/msvccompiler.py
---- a/third_party/python/setuptools/setuptools/_distutils/msvccompiler.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/msvccompiler.py
-@@ -3,23 +3,28 @@
- 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
-+import sys
-+import os
-+import warnings
-+from .errors import (
-+ DistutilsExecError,
-+ DistutilsPlatformError,
-+ CompileError,
-+ LibError,
-+ LinkError,
-+)
-+from .ccompiler import CCompiler, gen_lib_options
-+from ._log import log
-
- _can_read_reg = False
- try:
- import winreg
-
- _can_read_reg = True
- hkey_mod = winreg
-
-@@ -27,35 +32,49 @@ try:
- 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.")
-+ 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)
-+ HKEYS = (
-+ hkey_mod.HKEY_USERS,
-+ hkey_mod.HKEY_CURRENT_USER,
-+ hkey_mod.HKEY_LOCAL_MACHINE,
-+ hkey_mod.HKEY_CLASSES_ROOT,
-+ )
-+
-+
-+warnings.warn(
-+ "msvccompiler is deprecated and slated to be removed "
-+ "in the future. Please discontinue use or file an issue "
-+ "with pypa/distutils describing your use case.",
-+ DeprecationWarning,
-+)
-+
-
- def read_keys(base, key):
- """Return list of registry keys."""
- try:
- handle = RegOpenKeyEx(base, key)
- except RegError:
- return None
- L = []
-@@ -64,16 +83,17 @@ def read_keys(base, key):
- 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:
-@@ -85,25 +105,27 @@ def read_values(base, key):
- 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)
-@@ -117,38 +139,40 @@ class MacroExpander:
- 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: #
-+ except KeyError:
- raise DistutilsPlatformError(
-- """Python was built with Visual Studio 2003;
-+ """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.""")
-+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))
-+ d = read_values(base, r"{}\{}".format(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)
-@@ -164,47 +188,49 @@ def get_build_version():
- # 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]
-+ 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) :
-+class MSVCCompiler(CCompiler):
- """Concrete class that implements an interface to Microsoft Visual C++,
-- as defined by the CCompiler abstract class."""
-+ 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.
-@@ -213,27 +239,26 @@ class MSVCCompiler(CCompiler) :
- # 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)
-+ 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)
-+ super().__init__(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:
-@@ -242,121 +267,142 @@ class MSVCCompiler(CCompiler) :
- 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"):
-+ 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)
-+ 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.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', '/Ox', '/MD', '/W3', '/GX' ,
-- '/DNDEBUG']
-- self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3',
'/GX',
-- '/Z7', '/D_DEBUG']
-+ 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', '/Ox', '/MD', '/W3', '/GS-'
,
-- '/DNDEBUG']
-- self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3',
'/GS-',
-- '/Z7', '/D_DEBUG']
-+ 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_shared_debug = ['/DLL', '/nologo',
'/INCREMENTAL:no', '/DEBUG']
- else:
- self.ldflags_shared_debug = [
-- '/DLL', '/nologo', '/INCREMENTAL:no', '/pdb:None', '/DEBUG'
-- ]
-- self.ldflags_static = [ '/nologo']
-+ '/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=''):
-+ 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 = ''
-+ 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 /
-+ (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)
-+ raise CompileError("Don't know how to compile %s" %
src_name)
- if strip_dir:
-- base = os.path.basename (base)
-+ base = os.path.basename(base)
- if ext in self._rc_extensions:
-- obj_names.append (os.path.join (output_dir,
-- base + self.res_extension))
-+ 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))
-+ 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))
-+ 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):
--
-+ def compile( # noqa: C901
-+ 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)
-+ 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')
-+ 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]
-@@ -372,18 +418,17 @@ class MSVCCompiler(CCompiler) :
- 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])
-+ 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
-@@ -393,172 +438,169 @@ class MSVCCompiler(CCompiler) :
- # 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')
-+ 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])
-+ 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))
-+ raise CompileError(
-+ "Don't know how to compile {} to {}".format(src, obj)
-+ )
-
- output_opt = "/Fo" + obj
- try:
-- self.spawn([self.cc] + compile_opts + pp_opts +
-- [input_opt, output_opt] +
-- extra_postargs)
-+ 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):
--
-+ 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)
-+ 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?
-+ 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):
--
-+ def link( # noqa: C901
-+ 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)
-+ 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))
-+ 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)
-+ 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 []):
-+ for sym in export_symbols or []:
- export_opts.append("/EXPORT:" + sym)
-
-- ld_args = (ldflags + lib_opts + export_opts +
-- objects + ['/OUT:' + output_filename])
-+ 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))
-+ 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)
-+ 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++")
-+ "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))
-+ 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
-
-@@ -573,55 +615,61 @@ class MSVCCompiler(CCompiler) :
- """
- 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)
-+ 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))
-+ key =
r"{}\{:0.1f}\VC\VC_OBJECTS_PLATFORM_INFO\Win32\Directories".format(
-+ self.__root,
-+ self.__version,
-+ )
- else:
-- key = (r"%s\6.0\Build System\Components\Platforms"
-- r"\Win32 (%s)\Directories" % (self.__root, platform))
-+ 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,
"
-+ 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.")
-+ "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.
-@@ -634,10 +682,11 @@ class MSVCCompiler(CCompiler) :
- 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
-+ from distutils.msvc9compiler import MacroExpander # noqa: F811
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/py35compat.py
b/third_party/python/setuptools/setuptools/_distutils/py35compat.py
-deleted file mode 100644
---- a/third_party/python/setuptools/setuptools/_distutils/py35compat.py
-+++ /dev/null
-@@ -1,19 +0,0 @@
--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/third_party/python/setuptools/setuptools/_distutils/py38compat.py
b/third_party/python/setuptools/setuptools/_distutils/py38compat.py
---- a/third_party/python/setuptools/setuptools/_distutils/py38compat.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/py38compat.py
-@@ -1,7 +1,8 @@
- 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)
-+ return "{}-{}.{}".format(osname, version, release)
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/py39compat.py
b/third_party/python/setuptools/setuptools/_distutils/py39compat.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/_distutils/py39compat.py
-@@ -0,0 +1,22 @@
-+import sys
-+import platform
-+
-+
-+def add_ext_suffix_39(vars):
-+ """
-+ Ensure vars contains 'EXT_SUFFIX'. pypa/distutils#130
-+ """
-+ import _imp
-+
-+ ext_suffix = _imp.extension_suffixes()[0]
-+ vars.update(
-+ EXT_SUFFIX=ext_suffix,
-+ # sysconfig sets SO to match EXT_SUFFIX, so maintain
-+ # that expectation.
-+ #
https://github.com/python/cpython/blob/785cc6770588de087d09e89a69110af2542be208/Lib/sysconfig.py#L671-L673
-+ SO=ext_suffix,
-+ )
-+
-+
-+needs_ext_suffix = sys.version_info < (3, 10) and platform.system() ==
'Windows'
-+add_ext_suffix = add_ext_suffix_39 if needs_ext_suffix else lambda vars:
None
-diff --git a/third_party/python/setuptools/setuptools/_distutils/spawn.py
b/third_party/python/setuptools/setuptools/_distutils/spawn.py
---- a/third_party/python/setuptools/setuptools/_distutils/spawn.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/spawn.py
-@@ -5,27 +5,22 @@ specific functions for launching another
- 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
-+from .errors import DistutilsExecError
-+from .debug import DEBUG
-+from ._log import log
-
-
--if sys.platform == 'darwin':
-- _cfg_target = None
-- _cfg_target_split = None
--
--
--def spawn(cmd, search_path=1, verbose=0, dry_run=0, env=None):
-+def spawn(cmd, search_path=1, verbose=0, dry_run=0, env=None): # noqa: C901
- """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
-@@ -35,62 +30,51 @@ def spawn(cmd, search_path=1, verbose=0,
-
- 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(' '.join(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':
-- global _cfg_target, _cfg_target_split
-- if _cfg_target is None:
-- from distutils import sysconfig
-- _cfg_target = sysconfig.get_config_var(
-- 'MACOSX_DEPLOYMENT_TARGET') or ''
-- if _cfg_target:
-- _cfg_target_split = [int(x) for x in _cfg_target.split('.')]
-- if _cfg_target:
-- # ensure that the deployment target of build process is not less
-- # than that used when the interpreter was built. This ensures
-- # extension modules are built with correct compatibility values
-- cur_target = os.environ.get('MACOSX_DEPLOYMENT_TARGET',
_cfg_target)
-- if _cfg_target_split > [int(x) for x in cur_target.split('.')]:
-- my_msg = ('$MACOSX_DEPLOYMENT_TARGET mismatch: '
-- 'now "%s" but "%s" during configure'
-- % (cur_target, _cfg_target))
-- raise DistutilsPlatformError(my_msg)
-- env.update(MACOSX_DEPLOYMENT_TARGET=cur_target)
-+ 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
-+ "command {!r} failed: {}".format(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))
-+ "command {!r} failed with exit code {}".format(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.
- """
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/sysconfig.py
b/third_party/python/setuptools/setuptools/_distutils/sysconfig.py
---- a/third_party/python/setuptools/setuptools/_distutils/sysconfig.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/sysconfig.py
-@@ -4,22 +4,25 @@ configuration. The values may be retrie
- 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: <fdrake AT acm.org>
- """
-
--import _imp
- import os
- import re
- import sys
-+import sysconfig
-+import pathlib
-
- from .errors import DistutilsPlatformError
-+from . import py39compat
-+from ._functools import pass_none
-
- 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)
-@@ -33,56 +36,74 @@ 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
-+ """
-+ Return True if the target directory appears to point to an
-+ un-installed Python.
-+ """
-+ modules = pathlib.Path(d).joinpath('Modules')
-+ return any(modules.joinpath(fn).is_file() for fn in ('Setup',
'Setup.local'))
-+
-
- _sys_home = getattr(sys, '_home', None)
-
-+
-+def _is_parent(dir_a, dir_b):
-+ """
-+ Return True if a is a parent of b.
-+ """
-+ return os.path.normcase(dir_a).startswith(os.path.normcase(dir_b))
-+
-+
- if os.name == 'nt':
-+
-+ @pass_none
- def _fix_pcbuild(d):
-- if d and os.path.normcase(d).startswith(
-- os.path.normcase(os.path.join(PREFIX, "PCbuild"))):
-- return PREFIX
-- return d
-+ # In a venv, sys._home will be inside BASE_PREFIX rather than
PREFIX.
-+ prefixes = PREFIX, BASE_PREFIX
-+ matched = (
-+ prefix
-+ for prefix in prefixes
-+ if _is_parent(d, os.path.join(prefix, "PCbuild"))
-+ )
-+ return next(matched, 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]
-
-
-@@ -92,102 +113,166 @@ def get_python_inc(plat_specific=0, pref
- 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 IS_PYPY:
-- return os.path.join(prefix, 'include')
-- elif os.name == "posix":
-- 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)
-- python_dir = 'python' + 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:
-+ default_prefix = BASE_EXEC_PREFIX if plat_specific else BASE_PREFIX
-+ resolved_prefix = prefix if prefix is not None else default_prefix
-+ try:
-+ getter = globals()[f'_get_python_inc_{os.name}']
-+ except KeyError:
- raise DistutilsPlatformError(
- "I don't know where Python installs its C header files "
-- "on platform '%s'" % os.name)
-+ "on platform '%s'" % os.name
-+ )
-+ return getter(resolved_prefix, prefix, plat_specific)
-+
-+
-+@pass_none
-+def _extant(path):
-+ """
-+ Replace path with None if it doesn't exist.
-+ """
-+ return path if os.path.exists(path) else None
-+
-+
-+def _get_python_inc_posix(prefix, spec_prefix, plat_specific):
-+ if IS_PYPY and sys.version_info < (3, 8):
-+ return os.path.join(prefix, 'include')
-+ return (
-+ _get_python_inc_posix_python(plat_specific)
-+ or _extant(_get_python_inc_from_config(plat_specific, spec_prefix))
-+ or _get_python_inc_posix_prefix(prefix)
-+ )
-+
-+
-+def _get_python_inc_posix_python(plat_specific):
-+ """
-+ 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,
-+ use "srcdir" from the makefile to find the "Include"
-+ directory.
-+ """
-+ if not python_build:
-+ return
-+ if plat_specific:
-+ return _sys_home or project_base
-+ incdir = os.path.join(get_config_var('srcdir'), 'Include')
-+ return os.path.normpath(incdir)
-+
-+
-+def _get_python_inc_from_config(plat_specific, spec_prefix):
-+ """
-+ If no prefix was explicitly specified, provide the include
-+ directory from the config vars. Useful when
-+ cross-compiling, since the config vars may come from
-+ the host
-+ platform Python installation, while the current Python
-+ executable is from the build platform installation.
-+
-+ >>> monkeypatch = getfixture('monkeypatch')
-+ >>> gpifc = _get_python_inc_from_config
-+ >>> monkeypatch.setitem(gpifc.__globals__, 'get_config_var', str.lower)
-+ >>> gpifc(False, '/usr/bin/')
-+ >>> gpifc(False, '')
-+ >>> gpifc(False, None)
-+ 'includepy'
-+ >>> gpifc(True, None)
-+ 'confincludepy'
-+ """
-+ if spec_prefix is None:
-+ return get_config_var('CONF' * plat_specific + 'INCLUDEPY')
-+
-+
-+def _get_python_inc_posix_prefix(prefix):
-+ implementation = 'pypy' if IS_PYPY else 'python'
-+ python_dir = implementation + get_python_version() + build_flags
-+ return os.path.join(prefix, "include", python_dir)
-+
-+
-+def _get_python_inc_nt(prefix, spec_prefix, plat_specific):
-+ 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")
-+
-+
-+# 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:
-+
-+ 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"
-- libpython = os.path.join(prefix, libdir,
-- "python" + get_python_version())
-- if standard_lib:
-- return libpython
-- else:
-- return os.path.join(libpython, "site-packages")
-+ 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)
-+ "on platform '%s'" % os.name
-+ )
-
-
--
--def customize_compiler(compiler):
-+def customize_compiler(compiler): # noqa: C901
- """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
-@@ -197,40 +282,55 @@ def customize_compiler(compiler):
- # 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')
-+ (
-+ 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 (sys.platform == 'darwin'
-- and 'LDSHARED' not in os.environ
-- and ldshared.startswith(cc)):
-- # On OS X, if CC is overridden, use that as the default
-+ 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):]
-+ 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
-+ 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']
-@@ -245,98 +345,80 @@ def customize_compiler(compiler):
- 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)
-+ 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
-+ return os.path.join(inc_dir, 'pyconfig.h')
- else:
-- inc_dir = get_python_inc(plat_specific=1)
--
-- return os.path.join(inc_dir, 'pyconfig.h')
-+ return sysconfig.get_config_h_filename()
-
-
- 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)
-- config_file = 'config-{}{}'.format(get_python_version(), build_flags)
-- if hasattr(sys.implementation, '_multiarch'):
-- config_file += '-%s' % sys.implementation._multiarch
-- return os.path.join(lib_dir, config_file, 'Makefile')
-+ return sysconfig.get_makefile_filename()
-
-
- 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
-+ return sysconfig.parse_config_h(fp, vars=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):
-+
-+def parse_makefile(fn, g=None): # noqa: C901
- """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")
-+
-+ 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
-+ 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('$$', '')
-
-@@ -381,31 +463,30 @@ def parse_makefile(fn, g=None):
- 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
-+ after = value[m.end() :]
-+ value = value[: m.start()] + item + after
- if "$" in after:
- notdone[name] = value
- else:
-- try: value = int(value)
-+ 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:
--
-+ 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()
-@@ -442,132 +523,37 @@ def expand_makefile_vars(s, vars):
- s = s[0:beg] + vars.get(m.group(1)) + s[end:]
- else:
- break
- return s
-
-
- _config_vars = None
-
--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',
-- '_sysconfigdata_{abi}_{platform}_{multiarch}'.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
-+ _config_vars = sysconfig.get_config_vars().copy()
-+ py39compat.add_ext_suffix(_config_vars)
-
-- # 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))
-+ return [_config_vars.get(name) for name in args] if args else
_config_vars
-
-- # 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/third_party/python/setuptools/setuptools/_distutils/text_file.py
b/third_party/python/setuptools/setuptools/_distutils/text_file.py
---- a/third_party/python/setuptools/setuptools/_distutils/text_file.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/text_file.py
-@@ -1,92 +1,95 @@
- """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
-+import sys
-
-
- 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.
-+ 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.
-+ 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:
-+ Constructor is called as:
-
-- TextFile (filename=None, file=None, **options)
-+ 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()'.
-+ 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
-+ 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."""
-+ 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',
-- }
-+ 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()'."""
-+ (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'")
-+ 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])
-@@ -96,33 +99,33 @@ class TextFile:
- 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!
-+ 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."""
-+ 'filename' and 'file' arguments to the constructor."""
- self.filename = filename
-- self.file = io.open(self.filename, 'r', errors=self.errors)
-+ self.file = open(self.filename, 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)."""
-+ (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 = []
-@@ -136,34 +139,34 @@ class TextFile:
- 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."""
-+ 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):
-+ def readline(self): # noqa: C901
- """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."""
-+ 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
-@@ -172,32 +175,31 @@ class TextFile:
-
- 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
-+ 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] != "\\":
-+ 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
-@@ -206,40 +208,38 @@ class TextFile:
- # *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 "#"
-+ 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")
-+ 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]
-+ self.current_line = [self.current_line,
self.current_line + 1]
- # just an ordinary line, read it as usual
- else:
-- if line is None: # eof
-+ 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
-
-@@ -249,38 +249,38 @@ class TextFile:
- 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:
-+ if line in ('', '\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."""
-+ 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."""
-+ checked by future 'readline()' calls. Handy for implementing
-+ a parser with line-at-a-time lookahead."""
- self.linebuf.append(line)
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/unixccompiler.py
b/third_party/python/setuptools/setuptools/_distutils/unixccompiler.py
---- a/third_party/python/setuptools/setuptools/_distutils/unixccompiler.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/unixccompiler.py
-@@ -8,28 +8,28 @@ the "typical" Unix-style command-line C
- * 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
-+import os
-+import sys
-+import re
-+import shlex
-+import itertools
-
--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
-+from . import sysconfig
-+from .dep_util import newer
-+from .ccompiler import CCompiler, gen_preprocess_options, gen_lib_options
-+from .errors import DistutilsExecError, CompileError, LibError, LinkError
-+from ._log import log
-+from ._macos_compat import compiler_fixup
-
- # 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
-@@ -37,292 +37,364 @@ if sys.platform == 'darwin':
- # 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.
-
-
-+def _split_env(cmd):
-+ """
-+ For macOS, split command into 'env' portion (if any)
-+ and the rest of the linker command.
-+
-+ >>> _split_env(['a', 'b', 'c'])
-+ ([], ['a', 'b', 'c'])
-+ >>> _split_env(['/usr/bin/env', 'A=3', 'gcc'])
-+ (['/usr/bin/env', 'A=3'], ['gcc'])
-+ """
-+ pivot = 0
-+ if os.path.basename(cmd[0]) == "env":
-+ pivot = 1
-+ while '=' in cmd[pivot]:
-+ pivot += 1
-+ return cmd[:pivot], cmd[pivot:]
-+
-+
-+def _split_aix(cmd):
-+ """
-+ AIX platforms prefix the compiler with the ld_so_aix
-+ script, so split that from the linker command.
-+
-+ >>> _split_aix(['a', 'b', 'c'])
-+ ([], ['a', 'b', 'c'])
-+ >>> _split_aix(['/bin/foo/ld_so_aix', 'gcc'])
-+ (['/bin/foo/ld_so_aix'], ['gcc'])
-+ """
-+ pivot = os.path.basename(cmd[0]) == 'ld_so_aix'
-+ return cmd[:pivot], cmd[pivot:]
-+
-+
-+def _linker_params(linker_cmd, compiler_cmd):
-+ """
-+ The linker command usually begins with the compiler
-+ command (possibly multiple elements), followed by zero or more
-+ params for shared library building.
-+
-+ If the LDSHARED env variable overrides the linker command,
-+ however, the commands may not match.
-+
-+ Return the best guess of the linker parameters by stripping
-+ the linker command. If the compiler command does not
-+ match the linker command, assume the linker command is
-+ just the first element.
-+
-+ >>> _linker_params('gcc foo bar'.split(), ['gcc'])
-+ ['foo', 'bar']
-+ >>> _linker_params('gcc foo bar'.split(), ['other'])
-+ ['foo', 'bar']
-+ >>> _linker_params('ccache gcc foo bar'.split(), 'ccache gcc'.split())
-+ ['foo', 'bar']
-+ >>> _linker_params(['gcc'], ['gcc'])
-+ []
-+ """
-+ c_len = len(compiler_cmd)
-+ pivot = c_len if linker_cmd[:c_len] == compiler_cmd else 1
-+ return linker_cmd[pivot:]
-+
-+
- 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,
-- }
-+ 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"]
-+ 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):
-+ 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)
-+ # reasons to preprocess:
-+ # - force is indicated
-+ # - output is directed to stdout
-+ # - source file is newer than the target
-+ preprocess = self.force or output_file is None or newer(source,
output_file)
-+ if not preprocess:
-+ return
-
-- 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)
-+ if output_file:
-+ self.mkpath(os.path.dirname(output_file))
-+
- try:
-- self.spawn(compiler_so + cc_args + [src, '-o', obj] +
-- extra_postargs)
-+ self.spawn(pp_args)
- except DistutilsExecError as msg:
- raise CompileError(msg)
-
-- def create_static_lib(self, objects, output_libname,
-- output_dir=None, debug=0, target_lang=None):
-+ def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts):
-+ compiler_so = compiler_fixup(self.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)
-+ 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)
-+ 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):
-+ 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)
-+ 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)
-+ 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])
-+ 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[:]
-+ # Select a linker based on context: linker_exe when
-+ # building an executable or linker_so (with shared options)
-+ # when building a shared library.
-+ building_exe = target_desc == CCompiler.EXECUTABLE
-+ linker = (self.linker_exe if building_exe else
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
-+ env, linker_ne = _split_env(linker)
-+ aix, linker_na = _split_aix(linker_ne)
-+ _, compiler_cxx_ne = _split_env(self.compiler_cxx)
-+ _, linker_exe_ne = _split_env(self.linker_exe)
-
-- 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
-+ params = _linker_params(linker_na, linker_exe_ne)
-+ linker = env + aix + compiler_cxx_ne + params
-
-- linker[i+offset] = self.compiler_cxx[i]
--
-- if sys.platform == 'darwin':
-- linker = _osx_support.compiler_fixup(linker, ld_args)
-+ linker = 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 _is_gcc(self):
-+ cc_var = sysconfig.get_config_var("CC")
-+ compiler = os.path.basename(shlex.split(cc_var)[0])
-+ return "gcc" in compiler or "g++" in compiler
-
- 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(sysconfig.get_config_var("CC"))
- if sys.platform[:6] == "darwin":
-- # MacOSX's linker doesn't understand the -R flag at all
-- return "-L" + dir
-+ 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]
-+ return [
-+ "-Wl,+s" if self._is_gcc() else "+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:
-- if self._is_gcc(compiler):
-- # gcc on non-GNU systems does not need -Wl, but can
-- # use it anyway. Since distutils has always passed in
-- # -Wl whenever gcc was used in the past it is probably
-- # safest to keep doing so.
-- 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
-- else:
-- # No idea how --enable-new-dtags would be passed on to
-- # ld if this system was using GNU ld. Don't know if a
-- # system like this even exists.
-- return "-R" + dir
-+ 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')
-+ @staticmethod
-+ def _library_root(dir):
-+ """
-+ macOS users can specify an alternate SDK using'-isysroot'.
-+ Calculate the SDK root if it is specified.
-
-- 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)
-+ 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')
-+ match = re.search(r'-isysroot\s*(\S+)', cflags)
-+
-+ apply_root = (
-+ sys.platform == 'darwin'
-+ and match
-+ and (
-+ dir.startswith('/System/')
-+ or (dir.startswith('/usr/') and not
dir.startswith('/usr/local/'))
-+ )
-+ )
-+
-+ return os.path.join(match.group(1), dir[1:]) if apply_root else dir
-
--
--
-- 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/'))):
-+ def find_library_file(self, dirs, lib, debug=0):
-+ r"""
-+ Second-guess the linker with not much hard
-+ data to go on: GCC seems to prefer the shared library, so
-+ assume that *all* Unix C compilers do,
-+ ignoring even GCC's "-static" option.
-
-- 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)
-+ >>> compiler = UnixCCompiler()
-+ >>> compiler._library_root = lambda dir: dir
-+ >>> monkeypatch = getfixture('monkeypatch')
-+ >>> monkeypatch.setattr(os.path, 'exists', lambda d: 'existing' in
d)
-+ >>> dirs = ('/foo/bar/missing', '/foo/bar/existing')
-+ >>> compiler.find_library_file(dirs, 'abc').replace('\\', '/')
-+ '/foo/bar/existing/libabc.dylib'
-+ >>> compiler.find_library_file(reversed(dirs), 'abc').replace('\\',
'/')
-+ '/foo/bar/existing/libabc.dylib'
-+ >>> monkeypatch.setattr(os.path, 'exists',
-+ ... lambda d: 'existing' in d and '.a' in d)
-+ >>> compiler.find_library_file(dirs, 'abc').replace('\\', '/')
-+ '/foo/bar/existing/libabc.a'
-+ >>> compiler.find_library_file(reversed(dirs), 'abc').replace('\\',
'/')
-+ '/foo/bar/existing/libabc.a'
-+ """
-+ lib_names = (
-+ self.library_filename(lib, lib_type=type)
-+ for type in 'dylib xcode_stub shared static'.split()
-+ )
-
-- # 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
-+ roots = map(self._library_root, dirs)
-
-- # Oops, didn't find it in *any* of 'dirs'
-- return None
-+ searched = (
-+ os.path.join(root, lib_name)
-+ for root, lib_name in itertools.product(roots, lib_names)
-+ )
-+
-+ found = filter(os.path.exists, searched)
-+
-+ # Return None if it could not be found in any dir.
-+ return next(found, None)
-diff --git a/third_party/python/setuptools/setuptools/_distutils/util.py
b/third_party/python/setuptools/setuptools/_distutils/util.py
---- a/third_party/python/setuptools/setuptools/_distutils/util.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/util.py
-@@ -1,119 +1,133 @@
- """distutils.util
-
- Miscellaneous utility functions -- anything that doesn't fit into
- one of the other *util.py modules.
- """
-
-+import importlib.util
- import os
- import re
--import importlib.util
- import string
-+import subprocess
- 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
-+import sysconfig
-+import functools
-+
-+from .errors import DistutilsPlatformError, DistutilsByteCompileError
-+from .dep_util import newer
-+from .spawn import spawn
-+from ._log import log
-
-
- 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'.
--
-+ """
-+ Return a string that identifies the current platform. Use this
-+ function to distinguish platform-specific build directories and
-+ platform-specific built distributions.
- """
-- 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
-+ # This function initially exposed platforms as defined in Python 3.9
-+ # even with older Python versions when distutils was split out.
-+ # Now it delegates to stdlib sysconfig, but maintains compatibility.
-
-- (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 sys.version_info < (3, 8):
-+ if os.name == 'nt':
-+ if '(arm)' in sys.version.lower():
-+ return 'win-arm32'
-+ if '(arm64)' in sys.version.lower():
-+ return 'win-arm64'
-
-- 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)
-+ if sys.version_info < (3, 9):
-+ if os.name == "posix" and hasattr(os, 'uname'):
-+ osname, host, release, version, machine = os.uname()
-+ if osname[:3] == "aix":
-+ from .py38compat import aix_platform
-
-- return "%s-%s-%s" % (osname, release, machine)
-+ return aix_platform(osname, version, release)
-+
-+ return sysconfig.get_platform()
-+
-
- def get_platform():
- if os.name == 'nt':
- TARGET_TO_PLAT = {
-- 'x86' : 'win32',
-- 'x64' : 'win-amd64',
-- 'arm' : 'win-arm32',
-+ '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()
-+ target = os.environ.get('VSCMD_ARG_TGT_ARCH')
-+ return TARGET_TO_PLAT.get(target) or get_host_platform()
-+ 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.
-
--def convert_path (pathname):
-+ 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.
- """
-@@ -128,20 +142,21 @@ def convert_path (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):
-+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)
-@@ -149,189 +164,210 @@ def change_root (new_root, pathname):
- 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)
-+ raise DistutilsPlatformError(f"nothing known about platform
'{os.name}'")
-
-
--_environ_checked = 0
--def check_environ ():
-+@functools.lru_cache()
-+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 shell/Perl-style variable substitution on 'string'. Every
-- occurrence of '$' followed by a name is considered a variable, and
-- variable is substituted by the value found in the 'local_vars'
-- dictionary, or in 'os.environ' if it's not in 'local_vars'.
-+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()
-- def _subst (match, local_vars=local_vars):
-- var_name = match.group(1)
-- if var_name in local_vars:
-- return str(local_vars[var_name])
-- else:
-- return os.environ[var_name]
--
-+ lookup = dict(os.environ)
-+ lookup.update((name, str(value)) for name, value in local_vars.items())
- try:
-- return re.sub(r'\$([a-zA-Z_][a-zA-Z_0-9]*)', _subst, s)
-+ return _subst_compat(s).format_map(lookup)
- except KeyError as var:
-- raise ValueError("invalid variable '$%s'" % var)
--
--# subst_vars ()
-+ raise ValueError(f"invalid variable {var}")
-
-
--def grok_environment_error (exc, prefix="error: "):
-+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 substitutions 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):
-+
-+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()
-+ 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
-+ if s[end] in string.whitespace:
-+ # unescaped, unquoted whitespace: now
-+ # we definitely have a word delimiter
-+ words.append(s[:end])
- 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
-+ 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
-+ if s[end] == "'": # slurp singly-quoted string
- m = _squote_re.match(s, end)
-- elif s[end] == '"': # slurp doubly-quoted string
-+ 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:]
-+ 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):
-+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 = "{}{!r}".format(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):
-+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,))
-+ raise ValueError("invalid truth value {!r}".format(val))
-
-
--def byte_compile (py_files,
-- optimize=0, force=0,
-- prefix=None, base_dir=None,
-- verbose=1, dry_run=0,
-- direct=None):
-+def byte_compile( # noqa: C901
-+ 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
-@@ -351,87 +387,85 @@ def byte_compile (py_files,
- 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)
-+ 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("""\
-+ 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("""
-+ 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))
-+"""
-+ % (optimize, force, prefix, base_dir, verbose)
-+ )
-
- cmd = [sys.executable]
-- cmd.extend(_optim_args_from_interpreter_flags())
-+ cmd.extend(subprocess._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)
-+ 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
-
-@@ -441,121 +475,39 @@ byte_compile(files, optimize=%r, force=%
- # 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)
-+ 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 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)
-+ log.debug("skipping byte-compilation of %s to %s",
file, cfile_base)
-
--# byte_compile ()
-
--def rfc822_escape (header):
-+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)
--
--# 2to3 support
--
--def run_2to3(files, fixer_names=None, options=None, explicit=None):
-- """Invoke 2to3 on a list of Python files.
-- The files should all come from the build area, as the
-- modification is done in-place. To reduce the build time,
-- only files modified since the last invocation of this
-- function should be passed in the files argument."""
--
-- if not files:
-- return
--
-- # Make this class local, to delay import of 2to3
-- from lib2to3.refactor import RefactoringTool, get_fixers_from_package
-- class DistutilsRefactoringTool(RefactoringTool):
-- def log_error(self, msg, *args, **kw):
-- log.error(msg, *args)
--
-- def log_message(self, msg, *args):
-- log.info(msg, *args)
--
-- def log_debug(self, msg, *args):
-- log.debug(msg, *args)
--
-- if fixer_names is None:
-- fixer_names = get_fixers_from_package('lib2to3.fixes')
-- r = DistutilsRefactoringTool(fixer_names, options=options)
-- r.refactor(files, write=True)
--
--def copydir_run_2to3(src, dest, template=None, fixer_names=None,
-- options=None, explicit=None):
-- """Recursively copy a directory, only copying new and changed files,
-- running run_2to3 over all newly copied Python modules afterward.
--
-- If you give a template string, it's parsed like a MANIFEST.in.
-- """
-- from distutils.dir_util import mkpath
-- from distutils.file_util import copy_file
-- from distutils.filelist import FileList
-- filelist = FileList()
-- curdir = os.getcwd()
-- os.chdir(src)
-- try:
-- filelist.findall()
-- finally:
-- os.chdir(curdir)
-- filelist.files[:] = filelist.allfiles
-- if template:
-- for line in template.splitlines():
-- line = line.strip()
-- if not line: continue
-- filelist.process_template_line(line)
-- copied = []
-- for filename in filelist.files:
-- outname = os.path.join(dest, filename)
-- mkpath(os.path.dirname(outname))
-- res = copy_file(os.path.join(src, filename), outname, update=1)
-- if res[1]: copied.append(outname)
-- run_2to3([fn for fn in copied if fn.lower().endswith('.py')],
-- fixer_names=fixer_names, options=options, explicit=explicit)
-- return copied
--
--class Mixin2to3:
-- '''Mixin class for commands that run 2to3.
-- To configure 2to3, setup scripts may either change
-- the class variables, or inherit from individual commands
-- to override how 2to3 is invoked.'''
--
-- # provide list of fixers to run;
-- # defaults to all from lib2to3.fixers
-- fixer_names = None
--
-- # options dictionary
-- options = None
--
-- # list of fixers to invoke even though they are marked as explicit
-- explicit = None
--
-- def run_2to3(self, files):
-- return run_2to3(files, self.fixer_names, self.options,
self.explicit)
-diff --git a/third_party/python/setuptools/setuptools/_distutils/version.py
b/third_party/python/setuptools/setuptools/_distutils/version.py
---- a/third_party/python/setuptools/setuptools/_distutils/version.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/version.py
-@@ -22,30 +22,50 @@ Every version number class implements th
- 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):
-+ def __init__(self, vstring=None):
- if vstring:
- self.parse(vstring)
-+ warnings.warn(
-+ "distutils Version classes are deprecated. "
-+ "Use packaging.version instead.",
-+ DeprecationWarning,
-+ stacklevel=2,
-+ )
-
-- def __repr__ (self):
-- return "%s ('%s')" % (self.__class__.__name__, str(self))
-+ def __repr__(self):
-+ return "{} ('{}')".format(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):
-@@ -85,17 +105,17 @@ class Version:
- # (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):
-+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
-@@ -122,55 +142,52 @@ class StrictVersion (Version):
- 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)
-+ version_re = re.compile(
-+ r'^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$', re.VERBOSE | re.ASCII
-+ )
-
--
-- def parse (self, vstring):
-+ 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)
-+ (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):
--
-+ 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):
-+ def _cmp(self, other): # noqa: C901
- if isinstance(other, str):
-- other = StrictVersion(other)
-+ 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
-@@ -178,32 +195,33 @@ class StrictVersion (Version):
- 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):
-+ if not self.prerelease and not other.prerelease:
- return 0
-- elif (self.prerelease and not other.prerelease):
-+ elif self.prerelease and not other.prerelease:
- return -1
-- elif (not self.prerelease and other.prerelease):
-+ elif not self.prerelease and other.prerelease:
- return 1
-- elif (self.prerelease and other.prerelease):
-+ 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
-@@ -261,17 +279,18 @@ class StrictVersion (Version):
- # ../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):
-+
-+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:
-@@ -296,46 +315,37 @@ class LooseVersion (Version):
- 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 __init__ (self, vstring=None):
-- if vstring:
-- self.parse(vstring)
--
--
-- def parse (self, vstring):
-+ 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 != '.']
-+ 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):
-+ def __str__(self):
- return self.vstring
-
--
-- def __repr__ (self):
-+ def __repr__(self):
- return "LooseVersion ('%s')" % str(self)
-
--
-- def _cmp (self, other):
-+ 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:
-diff --git
a/third_party/python/setuptools/setuptools/_distutils/versionpredicate.py
b/third_party/python/setuptools/setuptools/_distutils/versionpredicate.py
---- a/third_party/python/setuptools/setuptools/_distutils/versionpredicate.py
-+++ b/third_party/python/setuptools/setuptools/_distutils/versionpredicate.py
-@@ -1,37 +1,46 @@
- """Module for parsing and testing package version predicate strings.
- """
- import re
--import distutils.version
-+from . import version
- import operator
-
-
--re_validPackage = re.compile(r"(?i)^\s*([a-z_]\w*(?:\.[a-z_]\w*)*)(.*)",
-- re.ASCII)
-+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_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()
-- return (comp, distutils.version.StrictVersion(verStr))
-+ with version.suppress_known_deprecation():
-+ other = version.StrictVersion(verStr)
-+ return (comp, other)
-+
-
--compmap = {"<": operator.lt, "<=": operator.le, "==": operator.eq,
-- ">": operator.gt, ">=": operator.ge, "!=": operator.ne}
-+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::
-
-@@ -89,18 +98,17 @@ class VersionPredicate:
- >>> 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.
-- """
-+ """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)
-@@ -110,18 +118,17 @@ class VersionPredicate:
- 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)
-+ 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:
-@@ -135,32 +142,34 @@ class VersionPredicate:
- 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)
-+
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:
-- ver = distutils.version.StrictVersion(ver)
-+ with version.suppress_known_deprecation():
-+ ver = version.StrictVersion(ver)
- return m.group(1), ver
-diff --git a/third_party/python/setuptools/setuptools/_entry_points.py
b/third_party/python/setuptools/setuptools/_entry_points.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/_entry_points.py
-@@ -0,0 +1,94 @@
-+import functools
-+import operator
-+import itertools
-+
-+from .errors import OptionError
-+from .extern.jaraco.text import yield_lines
-+from .extern.jaraco.functools import pass_none
-+from ._importlib import metadata
-+from ._itertools import ensure_unique
-+from .extern.more_itertools import consume
-+
-+
-+def ensure_valid(ep):
-+ """
-+ Exercise one of the dynamic properties to trigger
-+ the pattern match.
-+ """
-+ try:
-+ ep.extras
-+ except AttributeError as ex:
-+ msg = (
-+ f"Problems to parse {ep}.\nPlease ensure entry-point follows
the spec: "
-+
"https://packaging.python.org/en/latest/specifications/entry-points/";
-+ )
-+ raise OptionError(msg) from ex
-+
-+
-+def load_group(value, group):
-+ """
-+ Given a value of an entry point or series of entry points,
-+ return each as an EntryPoint.
-+ """
-+ # normalize to a single sequence of lines
-+ lines = yield_lines(value)
-+ text = f'[{group}]\n' + '\n'.join(lines)
-+ return metadata.EntryPoints._from_text(text)
-+
-+
-+def by_group_and_name(ep):
-+ return ep.group, ep.name
-+
-+
-+def validate(eps: metadata.EntryPoints):
-+ """
-+ Ensure entry points are unique by group and name and validate each.
-+ """
-+ consume(map(ensure_valid, ensure_unique(eps, key=by_group_and_name)))
-+ return eps
-+
-+
-+@functools.singledispatch
-+def load(eps):
-+ """
-+ Given a Distribution.entry_points, produce EntryPoints.
-+ """
-+ groups = itertools.chain.from_iterable(
-+ load_group(value, group)
-+ for group, value in eps.items())
-+ return validate(metadata.EntryPoints(groups))
-+
-+
-+@load.register(str)
-+def _(eps):
-+ r"""
-+ >>> ep, = load('[console_scripts]\nfoo=bar')
-+ >>> ep.group
-+ 'console_scripts'
-+ >>> ep.name
-+ 'foo'
-+ >>> ep.value
-+ 'bar'
-+ """
-+ return
validate(metadata.EntryPoints(metadata.EntryPoints._from_text(eps)))
-+
-+
-+load.register(type(None), lambda x: x)
-+
-+
-+@pass_none
-+def render(eps: metadata.EntryPoints):
-+ by_group = operator.attrgetter('group')
-+ groups = itertools.groupby(sorted(eps, key=by_group), by_group)
-+
-+ return '\n'.join(
-+ f'[{group}]\n{render_items(items)}\n'
-+ for group, items in groups
-+ )
-+
-+
-+def render_items(eps):
-+ return '\n'.join(
-+ f'{ep.name} = {ep.value}'
-+ for ep in sorted(eps)
-+ )
-diff --git a/third_party/python/setuptools/setuptools/_imp.py
b/third_party/python/setuptools/setuptools/_imp.py
---- a/third_party/python/setuptools/setuptools/_imp.py
-+++ b/third_party/python/setuptools/setuptools/_imp.py
-@@ -2,17 +2,17 @@
- 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
-+from importlib.util import module_from_spec
-
-
- PY_SOURCE = 1
- PY_COMPILED = 2
- C_EXTENSION = 3
- C_BUILTIN = 6
- PY_FROZEN = 7
-
-@@ -36,22 +36,22 @@ def find_module(module, paths=None):
-
- 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 compability
-+ 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 compability
-+ 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:
-diff --git a/third_party/python/setuptools/setuptools/_importlib.py
b/third_party/python/setuptools/setuptools/_importlib.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/_importlib.py
-@@ -0,0 +1,50 @@
-+import sys
-+
-+
-+def disable_importlib_metadata_finder(metadata):
-+ """
-+ Ensure importlib_metadata doesn't provide older, incompatible
-+ Distributions.
-+
-+ Workaround for #3102.
-+ """
-+ try:
-+ import importlib_metadata
-+ except ImportError:
-+ return
-+ except AttributeError:
-+ from .warnings import SetuptoolsWarning
-+
-+ SetuptoolsWarning.emit(
-+ "Incompatibility problem.",
-+ """
-+ `importlib-metadata` version is incompatible with `setuptools`.
-+ This problem is likely to be solved by installing an updated
version of
-+ `importlib-metadata`.
-+ """,
-+
see_url="https://github.com/python/importlib_metadata/issues/396";
-+ ) # Ensure a descriptive message is shown.
-+ raise # This exception can be suppressed by _distutils_hack
-+
-+ if importlib_metadata is metadata:
-+ return
-+ to_remove = [
-+ ob
-+ for ob in sys.meta_path
-+ if isinstance(ob, importlib_metadata.MetadataPathFinder)
-+ ]
-+ for item in to_remove:
-+ sys.meta_path.remove(item)
-+
-+
-+if sys.version_info < (3, 10):
-+ from setuptools.extern import importlib_metadata as metadata
-+ disable_importlib_metadata_finder(metadata)
-+else:
-+ import importlib.metadata as metadata # noqa: F401
-+
-+
-+if sys.version_info < (3, 9):
-+ from setuptools.extern import importlib_resources as resources
-+else:
-+ import importlib.resources as resources # noqa: F401
-diff --git a/third_party/python/setuptools/setuptools/_itertools.py
b/third_party/python/setuptools/setuptools/_itertools.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/_itertools.py
-@@ -0,0 +1,23 @@
-+from setuptools.extern.more_itertools import consume # noqa: F401
-+
-+
-+# copied from jaraco.itertools 6.1
-+def ensure_unique(iterable, key=lambda x: x):
-+ """
-+ Wrap an iterable to raise a ValueError if non-unique values are
encountered.
-+
-+ >>> list(ensure_unique('abc'))
-+ ['a', 'b', 'c']
-+ >>> consume(ensure_unique('abca'))
-+ Traceback (most recent call last):
-+ ...
-+ ValueError: Duplicate element 'a' encountered.
-+ """
-+ seen = set()
-+ seen_add = seen.add
-+ for element in iterable:
-+ k = key(element)
-+ if k in seen:
-+ raise ValueError(f"Duplicate element {element!r} encountered.")
-+ seen_add(k)
-+ yield element
-diff --git a/third_party/python/setuptools/setuptools/_normalization.py
b/third_party/python/setuptools/setuptools/_normalization.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/_normalization.py
-@@ -0,0 +1,114 @@
-+"""
-+Helpers for normalization as expected in wheel/sdist/module file names
-+and core metadata
-+"""
-+import re
-+from pathlib import Path
-+from typing import Union
-+
-+from .extern import packaging
-+from .warnings import SetuptoolsDeprecationWarning
-+
-+_Path = Union[str, Path]
-+
-+# https://packaging.python.org/en/latest/specifications/core-metadata/#name
-+_VALID_NAME = re.compile(r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.I)
-+_UNSAFE_NAME_CHARS = re.compile(r"[^A-Z0-9.]+", re.I)
-+
-+
-+def safe_identifier(name: str) -> str:
-+ """Make a string safe to be used as Python identifier.
-+ >>> safe_identifier("12abc")
-+ '_12abc'
-+ >>> safe_identifier("__editable__.myns.pkg-78.9.3_local")
-+ '__editable___myns_pkg_78_9_3_local'
-+ """
-+ safe = re.sub(r'\W|^(?=\d)', '_', name)
-+ assert safe.isidentifier()
-+ return safe
-+
-+
-+def safe_name(component: str) -> str:
-+ """Escape a component used as a project name according to Core Metadata.
-+ >>> safe_name("hello world")
-+ 'hello-world'
-+ >>> safe_name("hello?world")
-+ 'hello-world'
-+ """
-+ # See pkg_resources.safe_name
-+ return _UNSAFE_NAME_CHARS.sub("-", component)
-+
-+
-+def safe_version(version: str) -> str:
-+ """Convert an arbitrary string into a valid version string.
-+ >>> safe_version("1988 12 25")
-+ '1988.12.25'
-+ >>> safe_version("v0.2.1")
-+ '0.2.1'
-+ >>> safe_version("v0.2?beta")
-+ '0.2b0'
-+ >>> safe_version("v0.2 beta")
-+ '0.2b0'
-+ >>> safe_version("ubuntu lts")
-+ Traceback (most recent call last):
-+ ...
-+ setuptools.extern.packaging.version.InvalidVersion: Invalid version:
'ubuntu.lts'
-+ """
-+ v = version.replace(' ', '.')
-+ try:
-+ return str(packaging.version.Version(v))
-+ except packaging.version.InvalidVersion:
-+ attempt = _UNSAFE_NAME_CHARS.sub("-", v)
-+ return str(packaging.version.Version(attempt))
-+
-+
-+def best_effort_version(version: str) -> str:
-+ """Convert an arbitrary string into a version-like string.
-+ >>> best_effort_version("v0.2 beta")
-+ '0.2b0'
-+
-+ >>> import warnings
-+ >>> warnings.simplefilter("ignore",
category=SetuptoolsDeprecationWarning)
-+ >>> best_effort_version("ubuntu lts")
-+ 'ubuntu.lts'
-+ """
-+ # See pkg_resources.safe_version
-+ try:
-+ return safe_version(version)
-+ except packaging.version.InvalidVersion:
-+ SetuptoolsDeprecationWarning.emit(
-+ f"Invalid version: {version!r}.",
-+ f"""
-+ Version {version!r} is not valid according to PEP 440.
-+
-+ Please make sure to specify a valid version for your package.
-+ Also note that future releases of setuptools may halt the build
process
-+ if an invalid version is given.
-+ """,
-+ see_url="https://peps.python.org/pep-0440/";,
-+ due_date=(2023, 9, 26), # See setuptools/dist _validate_version
-+ )
-+ v = version.replace(' ', '.')
-+ return safe_name(v)
-+
-+
-+def filename_component(value: str) -> str:
-+ """Normalize each component of a filename (e.g. distribution/version
part of wheel)
-+ Note: ``value`` needs to be already normalized.
-+ >>> filename_component("my-pkg")
-+ 'my_pkg'
-+ """
-+ return value.replace("-", "_").strip("_")
-+
-+
-+def safer_name(value: str) -> str:
-+ """Like ``safe_name`` but can be used as filename component for wheel"""
-+ # See bdist_wheel.safer_name
-+ return filename_component(safe_name(value))
-+
-+
-+def safer_best_effort_version(value: str) -> str:
-+ """Like ``best_effort_version`` but can be used as filename component
for wheel"""
-+ # See bdist_wheel.safer_verion
-+ # TODO: Replace with only safe_version in the future (no need for best
effort)
-+ return filename_component(best_effort_version(value))
-diff --git a/third_party/python/setuptools/setuptools/_path.py
b/third_party/python/setuptools/setuptools/_path.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/_path.py
-@@ -0,0 +1,37 @@
-+import os
-+import sys
-+from typing import Union
-+
-+_Path = Union[str, os.PathLike]
-+
-+
-+def ensure_directory(path):
-+ """Ensure that the parent directory of `path` exists"""
-+ dirname = os.path.dirname(path)
-+ os.makedirs(dirname, exist_ok=True)
-+
-+
-+def same_path(p1: _Path, p2: _Path) -> bool:
-+ """Differs from os.path.samefile because it does not require paths to
exist.
-+ Purely string based (no comparison between i-nodes).
-+ >>> same_path("a/b", "./a/b")
-+ True
-+ >>> same_path("a/b", "a/./b")
-+ True
-+ >>> same_path("a/b", "././a/b")
-+ True
-+ >>> same_path("a/b", "./a/b/c/..")
-+ True
-+ >>> same_path("a/b", "../a/b/c")
-+ False
-+ >>> same_path("a", "a/b")
-+ False
-+ """
-+ return normpath(p1) == normpath(p2)
-+
-+
-+def normpath(filename: _Path) -> str:
-+ """Normalize a file/dir name for comparison purposes."""
-+ # See pkg_resources.normalize_path for notes about cygwin
-+ file = os.path.abspath(filename) if sys.platform == 'cygwin' else
filename
-+ return os.path.normcase(os.path.realpath(os.path.normpath(file)))
-diff --git a/third_party/python/setuptools/setuptools/_reqs.py
b/third_party/python/setuptools/setuptools/_reqs.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/_reqs.py
-@@ -0,0 +1,33 @@
-+from typing import Callable, Iterable, Iterator, TypeVar, Union, overload
-+
-+import setuptools.extern.jaraco.text as text
-+from setuptools.extern.packaging.requirements import Requirement
-+
-+_T = TypeVar("_T")
-+_StrOrIter = Union[str, Iterable[str]]
-+
-+
-+def parse_strings(strs: _StrOrIter) -> Iterator[str]:
-+ """
-+ Yield requirement strings for each specification in `strs`.
-+
-+ `strs` must be a string, or a (possibly-nested) iterable thereof.
-+ """
-+ return text.join_continuation(map(text.drop_comment,
text.yield_lines(strs)))
-+
-+
-+@overload
-+def parse(strs: _StrOrIter) -> Iterator[Requirement]:
-+ ...
-+
-+
-+@overload
-+def parse(strs: _StrOrIter, parser: Callable[[str], _T]) -> Iterator[_T]:
-+ ...
-+
-+
-+def parse(strs, parser=Requirement):
-+ """
-+ Replacement for ``pkg_resources.parse_requirements`` that uses
``packaging``.
-+ """
-+ return map(parser, parse_strings(strs))
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/importlib_metadata/__init__.py

b/third_party/python/setuptools/setuptools/_vendor/importlib_metadata/__init__.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/_vendor/importlib_metadata/__init__.py
-@@ -0,0 +1,904 @@
-+import os
-+import re
-+import abc
-+import csv
-+import sys
-+from .. import zipp
-+import email
-+import pathlib
-+import operator
-+import textwrap
-+import warnings
-+import functools
-+import itertools
-+import posixpath
-+import collections
-+
-+from . import _adapters, _meta, _py39compat
-+from ._collections import FreezableDefaultDict, Pair
-+from ._compat import (
-+ NullFinder,
-+ install,
-+ pypy_partial,
-+)
-+from ._functools import method_cache, pass_none
-+from ._itertools import always_iterable, unique_everseen
-+from ._meta import PackageMetadata, SimplePath
-+
-+from contextlib import suppress
-+from importlib import import_module
-+from importlib.abc import MetaPathFinder
-+from itertools import starmap
-+from typing import List, Mapping, Optional
-+
-+
-+__all__ = [
-+ 'Distribution',
-+ 'DistributionFinder',
-+ 'PackageMetadata',
-+ 'PackageNotFoundError',
-+ 'distribution',
-+ 'distributions',
-+ 'entry_points',
-+ 'files',
-+ 'metadata',
-+ 'packages_distributions',
-+ 'requires',
-+ 'version',
-+]
-+
-+
-+class PackageNotFoundError(ModuleNotFoundError):
-+ """The package was not found."""
-+
-+ def __str__(self):
-+ return f"No package metadata was found for {self.name}"
-+
-+ @property
-+ def name(self):
-+ (name,) = self.args
-+ return name
-+
-+
-+class Sectioned:
-+ """
-+ A simple entry point config parser for performance
-+
-+ >>> for item in Sectioned.read(Sectioned._sample):
-+ ... print(item)
-+ Pair(name='sec1', value='# comments ignored')
-+ Pair(name='sec1', value='a = 1')
-+ Pair(name='sec1', value='b = 2')
-+ Pair(name='sec2', value='a = 2')
-+
-+ >>> res = Sectioned.section_pairs(Sectioned._sample)
-+ >>> item = next(res)
-+ >>> item.name
-+ 'sec1'
-+ >>> item.value
-+ Pair(name='a', value='1')
-+ >>> item = next(res)
-+ >>> item.value
-+ Pair(name='b', value='2')
-+ >>> item = next(res)
-+ >>> item.name
-+ 'sec2'
-+ >>> item.value
-+ Pair(name='a', value='2')
-+ >>> list(res)
-+ []
-+ """
-+
-+ _sample = textwrap.dedent(
-+ """
-+ [sec1]
-+ # comments ignored
-+ a = 1
-+ b = 2
-+
-+ [sec2]
-+ a = 2
-+ """
-+ ).lstrip()
-+
-+ @classmethod
-+ def section_pairs(cls, text):
-+ return (
-+ section._replace(value=Pair.parse(section.value))
-+ for section in cls.read(text, filter_=cls.valid)
-+ if section.name is not None
-+ )
-+
-+ @staticmethod
-+ def read(text, filter_=None):
-+ lines = filter(filter_, map(str.strip, text.splitlines()))
-+ name = None
-+ for value in lines:
-+ section_match = value.startswith('[') and value.endswith(']')
-+ if section_match:
-+ name = value.strip('[]')
-+ continue
-+ yield Pair(name, value)
-+
-+ @staticmethod
-+ def valid(line):
-+ return line and not line.startswith('#')
-+
-+
-+class DeprecatedTuple:
-+ """
-+ Provide subscript item access for backward compatibility.
-+
-+ >>> recwarn = getfixture('recwarn')
-+ >>> ep = EntryPoint(name='name', value='value', group='group')
-+ >>> ep[:]
-+ ('name', 'value', 'group')
-+ >>> ep[0]
-+ 'name'
-+ >>> len(recwarn)
-+ 1
-+ """
-+
-+ # Do not remove prior to 2023-05-01 or Python 3.13
-+ _warn = functools.partial(
-+ warnings.warn,
-+ "EntryPoint tuple interface is deprecated. Access members by name.",
-+ DeprecationWarning,
-+ stacklevel=pypy_partial(2),
-+ )
-+
-+ def __getitem__(self, item):
-+ self._warn()
-+ return self._key()[item]
-+
-+
-+class EntryPoint(DeprecatedTuple):
-+ """An entry point as defined by Python packaging conventions.
-+
-+ See `the packaging docs on entry points
-+ <https://packaging.python.org/specifications/entry-points/>`_
-+ for more information.
-+
-+ >>> ep = EntryPoint(
-+ ... name=None, group=None, value='package.module:attr [extra1,
extra2]')
-+ >>> ep.module
-+ 'package.module'
-+ >>> ep.attr
-+ 'attr'
-+ >>> ep.extras
-+ ['extra1', 'extra2']
-+ """
-+
-+ pattern = re.compile(
-+ r'(?P<module>[\w.]+)\s*'
-+ r'(:\s*(?P<attr>[\w.]+)\s*)?'
-+ r'((?P<extras>\[.*\])\s*)?$'
-+ )
-+ """
-+ A regular expression describing the syntax for an entry point,
-+ which might look like:
-+
-+ - module
-+ - package.module
-+ - package.module:attribute
-+ - package.module:object.attribute
-+ - package.module:attr [extra1, extra2]
-+
-+ Other combinations are possible as well.
-+
-+ The expression is lenient about whitespace around the ':',
-+ following the attr, and following any extras.
-+ """
-+
-+ name: str
-+ value: str
-+ group: str
-+
-+ dist: Optional['Distribution'] = None
-+
-+ def __init__(self, name, value, group):
-+ vars(self).update(name=name, value=value, group=group)
-+
-+ def load(self):
-+ """Load the entry point from its definition. If only a module
-+ is indicated by the value, return that module. Otherwise,
-+ return the named object.
-+ """
-+ match = self.pattern.match(self.value)
-+ module = import_module(match.group('module'))
-+ attrs = filter(None, (match.group('attr') or '').split('.'))
-+ return functools.reduce(getattr, attrs, module)
-+
-+ @property
-+ def module(self):
-+ match = self.pattern.match(self.value)
-+ return match.group('module')
-+
-+ @property
-+ def attr(self):
-+ match = self.pattern.match(self.value)
-+ return match.group('attr')
-+
-+ @property
-+ def extras(self):
-+ match = self.pattern.match(self.value)
-+ return re.findall(r'\w+', match.group('extras') or '')
-+
-+ def _for(self, dist):
-+ vars(self).update(dist=dist)
-+ return self
-+
-+ def matches(self, **params):
-+ """
-+ EntryPoint matches the given parameters.
-+
-+ >>> ep = EntryPoint(group='foo', name='bar', value='bing:bong
[extra1, extra2]')
-+ >>> ep.matches(group='foo')
-+ True
-+ >>> ep.matches(name='bar', value='bing:bong [extra1, extra2]')
-+ True
-+ >>> ep.matches(group='foo', name='other')
-+ False
-+ >>> ep.matches()
-+ True
-+ >>> ep.matches(extras=['extra1', 'extra2'])
-+ True
-+ >>> ep.matches(module='bing')
-+ True
-+ >>> ep.matches(attr='bong')
-+ True
-+ """
-+ attrs = (getattr(self, param) for param in params)
-+ return all(map(operator.eq, params.values(), attrs))
-+
-+ def _key(self):
-+ return self.name, self.value, self.group
-+
-+ def __lt__(self, other):
-+ return self._key() < other._key()
-+
-+ def __eq__(self, other):
-+ return self._key() == other._key()
-+
-+ def __setattr__(self, name, value):
-+ raise AttributeError("EntryPoint objects are immutable.")
-+
-+ def __repr__(self):
-+ return (
-+ f'EntryPoint(name={self.name!r}, value={self.value!r}, '
-+ f'group={self.group!r})'
-+ )
-+
-+ def __hash__(self):
-+ return hash(self._key())
-+
-+
-+class EntryPoints(tuple):
-+ """
-+ An immutable collection of selectable EntryPoint objects.
-+ """
-+
-+ __slots__ = ()
-+
-+ def __getitem__(self, name): # -> EntryPoint:
-+ """
-+ Get the EntryPoint in self matching name.
-+ """
-+ try:
-+ return next(iter(self.select(name=name)))
-+ except StopIteration:
-+ raise KeyError(name)
-+
-+ def select(self, **params):
-+ """
-+ Select entry points from self that match the
-+ given parameters (typically group and/or name).
-+ """
-+ return EntryPoints(ep for ep in self if _py39compat.ep_matches(ep,
**params))
-+
-+ @property
-+ def names(self):
-+ """
-+ Return the set of all names of all entry points.
-+ """
-+ return {ep.name for ep in self}
-+
-+ @property
-+ def groups(self):
-+ """
-+ Return the set of all groups of all entry points.
-+ """
-+ return {ep.group for ep in self}
-+
-+ @classmethod
-+ def _from_text_for(cls, text, dist):
-+ return cls(ep._for(dist) for ep in cls._from_text(text))
-+
-+ @staticmethod
-+ def _from_text(text):
-+ return (
-+ EntryPoint(name=item.value.name, value=item.value.value,
group=item.name)
-+ for item in Sectioned.section_pairs(text or '')
-+ )
-+
-+
-+class PackagePath(pathlib.PurePosixPath):
-+ """A reference to a path in a package"""
-+
-+ def read_text(self, encoding='utf-8'):
-+ with self.locate().open(encoding=encoding) as stream:
-+ return stream.read()
-+
-+ def read_binary(self):
-+ with self.locate().open('rb') as stream:
-+ return stream.read()
-+
-+ def locate(self):
-+ """Return a path-like object for this path"""
-+ return self.dist.locate_file(self)
-+
-+
-+class FileHash:
-+ def __init__(self, spec):
-+ self.mode, _, self.value = spec.partition('=')
-+
-+ def __repr__(self):
-+ return f'<FileHash mode: {self.mode} value: {self.value}>'
-+
-+
-+class Distribution(metaclass=abc.ABCMeta):
-+ """A Python distribution package."""
-+
-+ @abc.abstractmethod
-+ def read_text(self, filename):
-+ """Attempt to load metadata file given by the name.
-+
-+ :param filename: The name of the file in the distribution info.
-+ :return: The text if found, otherwise None.
-+ """
-+
-+ @abc.abstractmethod
-+ def locate_file(self, path):
-+ """
-+ Given a path to a file in this distribution, return a path
-+ to it.
-+ """
-+
-+ @classmethod
-+ def from_name(cls, name: str):
-+ """Return the Distribution for the given package name.
-+
-+ :param name: The name of the distribution package to search for.
-+ :return: The Distribution instance (or subclass thereof) for the
named
-+ package, if found.
-+ :raises PackageNotFoundError: When the named package's distribution
-+ metadata cannot be found.
-+ :raises ValueError: When an invalid value is supplied for name.
-+ """
-+ if not name:
-+ raise ValueError("A distribution name is required.")
-+ try:
-+ return next(cls.discover(name=name))
-+ except StopIteration:
-+ raise PackageNotFoundError(name)
-+
-+ @classmethod
-+ def discover(cls, **kwargs):
-+ """Return an iterable of Distribution objects for all packages.
-+
-+ Pass a ``context`` or pass keyword arguments for constructing
-+ a context.
-+
-+ :context: A ``DistributionFinder.Context`` object.
-+ :return: Iterable of Distribution objects for all packages.
-+ """
-+ context = kwargs.pop('context', None)
-+ if context and kwargs:
-+ raise ValueError("cannot accept context and kwargs")
-+ context = context or DistributionFinder.Context(**kwargs)
-+ return itertools.chain.from_iterable(
-+ resolver(context) for resolver in cls._discover_resolvers()
-+ )
-+
-+ @staticmethod
-+ def at(path):
-+ """Return a Distribution for the indicated metadata path
-+
-+ :param path: a string or path-like object
-+ :return: a concrete Distribution instance for the path
-+ """
-+ return PathDistribution(pathlib.Path(path))
-+
-+ @staticmethod
-+ def _discover_resolvers():
-+ """Search the meta_path for resolvers."""
-+ declared = (
-+ getattr(finder, 'find_distributions', None) for finder in
sys.meta_path
-+ )
-+ return filter(None, declared)
-+
-+ @property
-+ def metadata(self) -> _meta.PackageMetadata:
-+ """Return the parsed metadata for this Distribution.
-+
-+ The returned object will have keys that name the various bits of
-+ metadata. See PEP 566 for details.
-+ """
-+ text = (
-+ self.read_text('METADATA')
-+ or self.read_text('PKG-INFO')
-+ # This last clause is here to support old egg-info files. Its
-+ # effect is to just end up using the PathDistribution's
self._path
-+ # (which points to the egg-info file) attribute unchanged.
-+ or self.read_text('')
-+ )
-+ return _adapters.Message(email.message_from_string(text))
-+
-+ @property
-+ def name(self):
-+ """Return the 'Name' metadata for the distribution package."""
-+ return self.metadata['Name']
-+
-+ @property
-+ def _normalized_name(self):
-+ """Return a normalized version of the name."""
-+ return Prepared.normalize(self.name)
-+
-+ @property
-+ def version(self):
-+ """Return the 'Version' metadata for the distribution package."""
-+ return self.metadata['Version']
-+
-+ @property
-+ def entry_points(self):
-+ return
EntryPoints._from_text_for(self.read_text('entry_points.txt'), self)
-+
-+ @property
-+ def files(self):
-+ """Files in this distribution.
-+
-+ :return: List of PackagePath for this distribution or None
-+
-+ Result is `None` if the metadata file that enumerates files
-+ (i.e. RECORD for dist-info or SOURCES.txt for egg-info) is
-+ missing.
-+ Result may be empty if the metadata exists but is empty.
-+ """
-+
-+ def make_file(name, hash=None, size_str=None):
-+ result = PackagePath(name)
-+ result.hash = FileHash(hash) if hash else None
-+ result.size = int(size_str) if size_str else None
-+ result.dist = self
-+ return result
-+
-+ @pass_none
-+ def make_files(lines):
-+ return list(starmap(make_file, csv.reader(lines)))
-+
-+ return make_files(self._read_files_distinfo() or
self._read_files_egginfo())
-+
-+ def _read_files_distinfo(self):
-+ """
-+ Read the lines of RECORD
-+ """
-+ text = self.read_text('RECORD')
-+ return text and text.splitlines()
-+
-+ def _read_files_egginfo(self):
-+ """
-+ SOURCES.txt might contain literal commas, so wrap each line
-+ in quotes.
-+ """
-+ text = self.read_text('SOURCES.txt')
-+ return text and map('"{}"'.format, text.splitlines())
-+
-+ @property
-+ def requires(self):
-+ """Generated requirements specified for this Distribution"""
-+ reqs = self._read_dist_info_reqs() or self._read_egg_info_reqs()
-+ return reqs and list(reqs)
-+
-+ def _read_dist_info_reqs(self):
-+ return self.metadata.get_all('Requires-Dist')
-+
-+ def _read_egg_info_reqs(self):
-+ source = self.read_text('requires.txt')
-+ return pass_none(self._deps_from_requires_text)(source)
-+
-+ @classmethod
-+ def _deps_from_requires_text(cls, source):
-+ return
cls._convert_egg_info_reqs_to_simple_reqs(Sectioned.read(source))
-+
-+ @staticmethod
-+ def _convert_egg_info_reqs_to_simple_reqs(sections):
-+ """
-+ Historically, setuptools would solicit and store 'extra'
-+ requirements, including those with environment markers,
-+ in separate sections. More modern tools expect each
-+ dependency to be defined separately, with any relevant
-+ extras and environment markers attached directly to that
-+ requirement. This method converts the former to the
-+ latter. See _test_deps_from_requires_text for an example.
-+ """
-+
-+ def make_condition(name):
-+ return name and f'extra == "{name}"'
-+
-+ def quoted_marker(section):
-+ section = section or ''
-+ extra, sep, markers = section.partition(':')
-+ if extra and markers:
-+ markers = f'({markers})'
-+ conditions = list(filter(None, [markers,
make_condition(extra)]))
-+ return '; ' + ' and '.join(conditions) if conditions else ''
-+
-+ def url_req_space(req):
-+ """
-+ PEP 508 requires a space between the url_spec and the
quoted_marker.
-+ Ref python/importlib_metadata#357.
-+ """
-+ # '@' is uniquely indicative of a url_req.
-+ return ' ' * ('@' in req)
-+
-+ for section in sections:
-+ space = url_req_space(section.value)
-+ yield section.value + space + quoted_marker(section.name)
-+
-+
-+class DistributionFinder(MetaPathFinder):
-+ """
-+ A MetaPathFinder capable of discovering installed distributions.
-+ """
-+
-+ class Context:
-+ """
-+ Keyword arguments presented by the caller to
-+ ``distributions()`` or ``Distribution.discover()``
-+ to narrow the scope of a search for distributions
-+ in all DistributionFinders.
-+
-+ Each DistributionFinder may expect any parameters
-+ and should attempt to honor the canonical
-+ parameters defined below when appropriate.
-+ """
-+
-+ name = None
-+ """
-+ Specific name for which a distribution finder should match.
-+ A name of ``None`` matches all distributions.
-+ """
-+
-+ def __init__(self, **kwargs):
-+ vars(self).update(kwargs)
-+
-+ @property
-+ def path(self):
-+ """
-+ The sequence of directory path that a distribution finder
-+ should search.
-+
-+ Typically refers to Python installed package paths such as
-+ "site-packages" directories and defaults to ``sys.path``.
-+ """
-+ return vars(self).get('path', sys.path)
-+
-+ @abc.abstractmethod
-+ def find_distributions(self, context=Context()):
-+ """
-+ Find distributions.
-+
-+ Return an iterable of all Distribution instances capable of
-+ loading the metadata for packages matching the ``context``,
-+ a DistributionFinder.Context instance.
-+ """
-+
-+
-+class FastPath:
-+ """
-+ Micro-optimized class for searching a path for
-+ children.
-+
-+ >>> FastPath('').children()
-+ ['...']
-+ """
-+
-+ @functools.lru_cache() # type: ignore
-+ def __new__(cls, root):
-+ return super().__new__(cls)
-+
-+ def __init__(self, root):
-+ self.root = root
-+
-+ def joinpath(self, child):
-+ return pathlib.Path(self.root, child)
-+
-+ def children(self):
-+ with suppress(Exception):
-+ return os.listdir(self.root or '.')
-+ with suppress(Exception):
-+ return self.zip_children()
-+ return []
-+
-+ def zip_children(self):
-+ zip_path = zipp.Path(self.root)
-+ names = zip_path.root.namelist()
-+ self.joinpath = zip_path.joinpath
-+
-+ return dict.fromkeys(child.split(posixpath.sep, 1)[0] for child in
names)
-+
-+ def search(self, name):
-+ return self.lookup(self.mtime).search(name)
-+
-+ @property
-+ def mtime(self):
-+ with suppress(OSError):
-+ return os.stat(self.root).st_mtime
-+ self.lookup.cache_clear()
-+
-+ @method_cache
-+ def lookup(self, mtime):
-+ return Lookup(self)
-+
-+
-+class Lookup:
-+ def __init__(self, path: FastPath):
-+ base = os.path.basename(path.root).lower()
-+ base_is_egg = base.endswith(".egg")
-+ self.infos = FreezableDefaultDict(list)
-+ self.eggs = FreezableDefaultDict(list)
-+
-+ for child in path.children():
-+ low = child.lower()
-+ if low.endswith((".dist-info", ".egg-info")):
-+ # rpartition is faster than splitext and suitable for this
purpose.
-+ name = low.rpartition(".")[0].partition("-")[0]
-+ normalized = Prepared.normalize(name)
-+ self.infos[normalized].append(path.joinpath(child))
-+ elif base_is_egg and low == "egg-info":
-+ name = base.rpartition(".")[0].partition("-")[0]
-+ legacy_normalized = Prepared.legacy_normalize(name)
-+ self.eggs[legacy_normalized].append(path.joinpath(child))
-+
-+ self.infos.freeze()
-+ self.eggs.freeze()
-+
-+ def search(self, prepared):
-+ infos = (
-+ self.infos[prepared.normalized]
-+ if prepared
-+ else itertools.chain.from_iterable(self.infos.values())
-+ )
-+ eggs = (
-+ self.eggs[prepared.legacy_normalized]
-+ if prepared
-+ else itertools.chain.from_iterable(self.eggs.values())
-+ )
-+ return itertools.chain(infos, eggs)
-+
-+
-+class Prepared:
-+ """
-+ A prepared search for metadata on a possibly-named package.
-+ """
-+
-+ normalized = None
-+ legacy_normalized = None
-+
-+ def __init__(self, name):
-+ self.name = name
-+ if name is None:
-+ return
-+ self.normalized = self.normalize(name)
-+ self.legacy_normalized = self.legacy_normalize(name)
-+
-+ @staticmethod
-+ def normalize(name):
-+ """
-+ PEP 503 normalization plus dashes as underscores.
-+ """
-+ return re.sub(r"[-_.]+", "-", name).lower().replace('-', '_')
-+
-+ @staticmethod
-+ def legacy_normalize(name):
-+ """
-+ Normalize the package name as found in the convention in
-+ older packaging tools versions and specs.
-+ """
-+ return name.lower().replace('-', '_')
-+
-+ def __bool__(self):
-+ return bool(self.name)
-+
-+
-+@install
-+class MetadataPathFinder(NullFinder, DistributionFinder):
-+ """A degenerate finder for distribution packages on the file system.
-+
-+ This finder supplies only a find_distributions() method for versions
-+ of Python that do not have a PathFinder find_distributions().
-+ """
-+
-+ def find_distributions(self, context=DistributionFinder.Context()):
-+ """
-+ Find distributions.
-+
-+ Return an iterable of all Distribution instances capable of
-+ loading the metadata for packages matching ``context.name``
-+ (or all names if ``None`` indicated) along the paths in the list
-+ of directories ``context.path``.
-+ """
-+ found = self._search_paths(context.name, context.path)
-+ return map(PathDistribution, found)
-+
-+ @classmethod
-+ def _search_paths(cls, name, paths):
-+ """Find metadata directories in paths heuristically."""
-+ prepared = Prepared(name)
-+ return itertools.chain.from_iterable(
-+ path.search(prepared) for path in map(FastPath, paths)
-+ )
-+
-+ def invalidate_caches(cls):
-+ FastPath.__new__.cache_clear()
-+
-+
-+class PathDistribution(Distribution):
-+ def __init__(self, path: SimplePath):
-+ """Construct a distribution.
-+
-+ :param path: SimplePath indicating the metadata directory.
-+ """
-+ self._path = path
-+
-+ def read_text(self, filename):
-+ with suppress(
-+ FileNotFoundError,
-+ IsADirectoryError,
-+ KeyError,
-+ NotADirectoryError,
-+ PermissionError,
-+ ):
-+ return self._path.joinpath(filename).read_text(encoding='utf-8')
-+
-+ read_text.__doc__ = Distribution.read_text.__doc__
-+
-+ def locate_file(self, path):
-+ return self._path.parent / path
-+
-+ @property
-+ def _normalized_name(self):
-+ """
-+ Performance optimization: where possible, resolve the
-+ normalized name from the file system path.
-+ """
-+ stem = os.path.basename(str(self._path))
-+ return (
-+ pass_none(Prepared.normalize)(self._name_from_stem(stem))
-+ or super()._normalized_name
-+ )
-+
-+ @staticmethod
-+ def _name_from_stem(stem):
-+ """
-+ >>> PathDistribution._name_from_stem('foo-3.0.egg-info')
-+ 'foo'
-+ >>> PathDistribution._name_from_stem('CherryPy-3.0.dist-info')
-+ 'CherryPy'
-+ >>> PathDistribution._name_from_stem('face.egg-info')
-+ 'face'
-+ >>> PathDistribution._name_from_stem('foo.bar')
-+ """
-+ filename, ext = os.path.splitext(stem)
-+ if ext not in ('.dist-info', '.egg-info'):
-+ return
-+ name, sep, rest = filename.partition('-')
-+ return name
-+
-+
-+def distribution(distribution_name):
-+ """Get the ``Distribution`` instance for the named package.
-+
-+ :param distribution_name: The name of the distribution package as a
string.
-+ :return: A ``Distribution`` instance (or subclass thereof).
-+ """
-+ return Distribution.from_name(distribution_name)
-+
-+
-+def distributions(**kwargs):
-+ """Get all ``Distribution`` instances in the current environment.
-+
-+ :return: An iterable of ``Distribution`` instances.
-+ """
-+ return Distribution.discover(**kwargs)
-+
-+
-+def metadata(distribution_name) -> _meta.PackageMetadata:
-+ """Get the metadata for the named package.
-+
-+ :param distribution_name: The name of the distribution package to query.
-+ :return: A PackageMetadata containing the parsed metadata.
-+ """
-+ return Distribution.from_name(distribution_name).metadata
-+
-+
-+def version(distribution_name):
-+ """Get the version string for the named package.
-+
-+ :param distribution_name: The name of the distribution package to query.
-+ :return: The version string for the package as defined in the package's
-+ "Version" metadata key.
-+ """
-+ return distribution(distribution_name).version
-+
-+
-+_unique = functools.partial(
-+ unique_everseen,
-+ key=_py39compat.normalized_name,
-+)
-+"""
-+Wrapper for ``distributions`` to return unique distributions by name.
-+"""
-+
-+
-+def entry_points(**params) -> EntryPoints:
-+ """Return EntryPoint objects for all installed packages.
-+
-+ Pass selection parameters (group or name) to filter the
-+ result to entry points matching those properties (see
-+ EntryPoints.select()).
-+
-+ :return: EntryPoints for all installed packages.
-+ """
-+ eps = itertools.chain.from_iterable(
-+ dist.entry_points for dist in _unique(distributions())
-+ )
-+ return EntryPoints(eps).select(**params)
-+
-+
-+def files(distribution_name):
-+ """Return a list of files for the named package.
-+
-+ :param distribution_name: The name of the distribution package to query.
-+ :return: List of files composing the distribution.
-+ """
-+ return distribution(distribution_name).files
-+
-+
-+def requires(distribution_name):
-+ """
-+ Return a list of requirements for the named package.
-+
-+ :return: An iterator of requirements, suitable for
-+ packaging.requirement.Requirement.
-+ """
-+ return distribution(distribution_name).requires
-+
-+
-+def packages_distributions() -> Mapping[str, List[str]]:
-+ """
-+ Return a mapping of top-level packages to their
-+ distributions.
-+
-+ >>> import collections.abc
-+ >>> pkgs = packages_distributions()
-+ >>> all(isinstance(dist, collections.abc.Sequence) for dist in
pkgs.values())
-+ True
-+ """
-+ pkg_to_dist = collections.defaultdict(list)
-+ for dist in distributions():
-+ for pkg in _top_level_declared(dist) or _top_level_inferred(dist):
-+ pkg_to_dist[pkg].append(dist.metadata['Name'])
-+ return dict(pkg_to_dist)
-+
-+
-+def _top_level_declared(dist):
-+ return (dist.read_text('top_level.txt') or '').split()
-+
-+
-+def _top_level_inferred(dist):
-+ return {
-+ f.parts[0] if len(f.parts) > 1 else f.with_suffix('').name
-+ for f in always_iterable(dist.files)
-+ if f.suffix == ".py"
-+ }
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/importlib_metadata/_adapters.py

b/third_party/python/setuptools/setuptools/_vendor/importlib_metadata/_adapters.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/_vendor/importlib_metadata/_adapters.py
-@@ -0,0 +1,90 @@
-+import functools
-+import warnings
-+import re
-+import textwrap
-+import email.message
-+
-+from ._text import FoldedCase
-+from ._compat import pypy_partial
-+
-+
-+# Do not remove prior to 2024-01-01 or Python 3.14
-+_warn = functools.partial(
-+ warnings.warn,
-+ "Implicit None on return values is deprecated and will raise
KeyErrors.",
-+ DeprecationWarning,
-+ stacklevel=pypy_partial(2),
-+)
-+
-+
-+class Message(email.message.Message):
-+ multiple_use_keys = set(
-+ map(
-+ FoldedCase,
-+ [
-+ 'Classifier',
-+ 'Obsoletes-Dist',
-+ 'Platform',
-+ 'Project-URL',
-+ 'Provides-Dist',
-+ 'Provides-Extra',
-+ 'Requires-Dist',
-+ 'Requires-External',
-+ 'Supported-Platform',
-+ 'Dynamic',
-+ ],
-+ )
-+ )
-+ """
-+ Keys that may be indicated multiple times per PEP 566.
-+ """
-+
-+ def __new__(cls, orig: email.message.Message):
-+ res = super().__new__(cls)
-+ vars(res).update(vars(orig))
-+ return res
-+
-+ def __init__(self, *args, **kwargs):
-+ self._headers = self._repair_headers()
-+
-+ # suppress spurious error from mypy
-+ def __iter__(self):
-+ return super().__iter__()
-+
-+ def __getitem__(self, item):
-+ """
-+ Warn users that a ``KeyError`` can be expected when a
-+ mising key is supplied. Ref python/importlib_metadata#371.
-+ """
-+ res = super().__getitem__(item)
-+ if res is None:
-+ _warn()
-+ return res
-+
-+ def _repair_headers(self):
-+ def redent(value):
-+ "Correct for RFC822 indentation"
-+ if not value or '\n' not in value:
-+ return value
-+ return textwrap.dedent(' ' * 8 + value)
-+
-+ headers = [(key, redent(value)) for key, value in
vars(self)['_headers']]
-+ if self._payload:
-+ headers.append(('Description', self.get_payload()))
-+ return headers
-+
-+ @property
-+ def json(self):
-+ """
-+ Convert PackageMetadata to a JSON-compatible format
-+ per PEP 0566.
-+ """
-+
-+ def transform(key):
-+ value = self.get_all(key) if key in self.multiple_use_keys else
self[key]
-+ if key == 'Keywords':
-+ value = re.split(r'\s+', value)
-+ tk = key.lower().replace('-', '_')
-+ return tk, value
-+
-+ return dict(map(transform, map(FoldedCase, self)))
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/importlib_metadata/_collections.py

b/third_party/python/setuptools/setuptools/_vendor/importlib_metadata/_collections.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/_vendor/importlib_metadata/_collections.py
-@@ -0,0 +1,30 @@
-+import collections
-+
-+
-+# from jaraco.collections 3.3
-+class FreezableDefaultDict(collections.defaultdict):
-+ """
-+ Often it is desirable to prevent the mutation of
-+ a default dict after its initial construction, such
-+ as to prevent mutation during iteration.
-+
-+ >>> dd = FreezableDefaultDict(list)
-+ >>> dd[0].append('1')
-+ >>> dd.freeze()
-+ >>> dd[1]
-+ []
-+ >>> len(dd)
-+ 1
-+ """
-+
-+ def __missing__(self, key):
-+ return getattr(self, '_frozen', super().__missing__)(key)
-+
-+ def freeze(self):
-+ self._frozen = lambda key: self.default_factory()
-+
-+
-+class Pair(collections.namedtuple('Pair', 'name value')):
-+ @classmethod
-+ def parse(cls, text):
-+ return cls(*map(str.strip, text.split("=", 1)))
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/importlib_metadata/_compat.py

b/third_party/python/setuptools/setuptools/_vendor/importlib_metadata/_compat.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/_vendor/importlib_metadata/_compat.py
-@@ -0,0 +1,72 @@
-+import sys
-+import platform
-+
-+
-+__all__ = ['install', 'NullFinder', 'Protocol']
-+
-+
-+try:
-+ from typing import Protocol
-+except ImportError: # pragma: no cover
-+ # Python 3.7 compatibility
-+ from ..typing_extensions import Protocol # type: ignore
-+
-+
-+def install(cls):
-+ """
-+ Class decorator for installation on sys.meta_path.
-+
-+ Adds the backport DistributionFinder to sys.meta_path and
-+ attempts to disable the finder functionality of the stdlib
-+ DistributionFinder.
-+ """
-+ sys.meta_path.append(cls())
-+ disable_stdlib_finder()
-+ return cls
-+
-+
-+def disable_stdlib_finder():
-+ """
-+ Give the backport primacy for discovering path-based distributions
-+ by monkey-patching the stdlib O_O.
-+
-+ See #91 for more background for rationale on this sketchy
-+ behavior.
-+ """
-+
-+ def matches(finder):
-+ return getattr(
-+ finder, '__module__', None
-+ ) == '_frozen_importlib_external' and hasattr(finder,
'find_distributions')
-+
-+ for finder in filter(matches, sys.meta_path): # pragma: nocover
-+ del finder.find_distributions
-+
-+
-+class NullFinder:
-+ """
-+ A "Finder" (aka "MetaClassFinder") that never finds any modules,
-+ but may find distributions.
-+ """
-+
-+ @staticmethod
-+ def find_spec(*args, **kwargs):
-+ return None
-+
-+ # In Python 2, the import system requires finders
-+ # to have a find_module() method, but this usage
-+ # is deprecated in Python 3 in favor of find_spec().
-+ # For the purposes of this finder (i.e. being present
-+ # on sys.meta_path but having no other import
-+ # system functionality), the two methods are identical.
-+ find_module = find_spec
-+
-+
-+def pypy_partial(val):
-+ """
-+ Adjust for variable stacklevel on partial under PyPy.
-+
-+ Workaround for #327.
-+ """
-+ is_pypy = platform.python_implementation() == 'PyPy'
-+ return val + is_pypy
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/importlib_metadata/_functools.py

b/third_party/python/setuptools/setuptools/_vendor/importlib_metadata/_functools.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/_vendor/importlib_metadata/_functools.py
-@@ -0,0 +1,104 @@
-+import types
-+import functools
-+
-+
-+# from jaraco.functools 3.3
-+def method_cache(method, cache_wrapper=None):
-+ """
-+ Wrap lru_cache to support storing the cache data in the object
instances.
-+
-+ Abstracts the common paradigm where the method explicitly saves an
-+ underscore-prefixed protected property on first call and returns that
-+ subsequently.
-+
-+ >>> class MyClass:
-+ ... calls = 0
-+ ...
-+ ... @method_cache
-+ ... def method(self, value):
-+ ... self.calls += 1
-+ ... return value
-+
-+ >>> a = MyClass()
-+ >>> a.method(3)
-+ 3
-+ >>> for x in range(75):
-+ ... res = a.method(x)
-+ >>> a.calls
-+ 75
-+
-+ Note that the apparent behavior will be exactly like that of lru_cache
-+ except that the cache is stored on each instance, so values in one
-+ instance will not flush values from another, and when an instance is
-+ deleted, so are the cached values for that instance.
-+
-+ >>> b = MyClass()
-+ >>> for x in range(35):
-+ ... res = b.method(x)
-+ >>> b.calls
-+ 35
-+ >>> a.method(0)
-+ 0
-+ >>> a.calls
-+ 75
-+
-+ Note that if method had been decorated with ``functools.lru_cache()``,
-+ a.calls would have been 76 (due to the cached value of 0 having been
-+ flushed by the 'b' instance).
-+
-+ Clear the cache with ``.cache_clear()``
-+
-+ >>> a.method.cache_clear()
-+
-+ Same for a method that hasn't yet been called.
-+
-+ >>> c = MyClass()
-+ >>> c.method.cache_clear()
-+
-+ Another cache wrapper may be supplied:
-+
-+ >>> cache = functools.lru_cache(maxsize=2)
-+ >>> MyClass.method2 = method_cache(lambda self: 3, cache_wrapper=cache)
-+ >>> a = MyClass()
-+ >>> a.method2()
-+ 3
-+
-+ Caution - do not subsequently wrap the method with another decorator,
such
-+ as ``@property``, which changes the semantics of the function.
-+
-+ See also
-+
http://code.activestate.com/recipes/577452-a-memoize-decorator-for-instance-methods/
-+ for another implementation and additional justification.
-+ """
-+ cache_wrapper = cache_wrapper or functools.lru_cache()
-+
-+ def wrapper(self, *args, **kwargs):
-+ # it's the first call, replace the method with a cached, bound
method
-+ bound_method = types.MethodType(method, self)
-+ cached_method = cache_wrapper(bound_method)
-+ setattr(self, method.__name__, cached_method)
-+ return cached_method(*args, **kwargs)
-+
-+ # Support cache clear even before cache has been created.
-+ wrapper.cache_clear = lambda: None
-+
-+ return wrapper
-+
-+
-+# From jaraco.functools 3.3
-+def pass_none(func):
-+ """
-+ Wrap func so it's not called if its first param is None
-+
-+ >>> print_text = pass_none(print)
-+ >>> print_text('text')
-+ text
-+ >>> print_text(None)
-+ """
-+
-+ @functools.wraps(func)
-+ def wrapper(param, *args, **kwargs):
-+ if param is not None:
-+ return func(param, *args, **kwargs)
-+
-+ return wrapper
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/importlib_metadata/_itertools.py

b/third_party/python/setuptools/setuptools/_vendor/importlib_metadata/_itertools.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/_vendor/importlib_metadata/_itertools.py
-@@ -0,0 +1,73 @@
-+from itertools import filterfalse
-+
-+
-+def unique_everseen(iterable, key=None):
-+ "List unique elements, preserving order. Remember all elements ever
seen."
-+ # unique_everseen('AAAABBBCCDAABBB') --> A B C D
-+ # unique_everseen('ABBCcAD', str.lower) --> A B C D
-+ seen = set()
-+ seen_add = seen.add
-+ if key is None:
-+ for element in filterfalse(seen.__contains__, iterable):
-+ seen_add(element)
-+ yield element
-+ else:
-+ for element in iterable:
-+ k = key(element)
-+ if k not in seen:
-+ seen_add(k)
-+ yield element
-+
-+
-+# copied from more_itertools 8.8
-+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,))
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/importlib_metadata/_meta.py

b/third_party/python/setuptools/setuptools/_vendor/importlib_metadata/_meta.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/_vendor/importlib_metadata/_meta.py
-@@ -0,0 +1,49 @@
-+from ._compat import Protocol
-+from typing import Any, Dict, Iterator, List, TypeVar, Union
-+
-+
-+_T = TypeVar("_T")
-+
-+
-+class PackageMetadata(Protocol):
-+ def __len__(self) -> int:
-+ ... # pragma: no cover
-+
-+ def __contains__(self, item: str) -> bool:
-+ ... # pragma: no cover
-+
-+ def __getitem__(self, key: str) -> str:
-+ ... # pragma: no cover
-+
-+ def __iter__(self) -> Iterator[str]:
-+ ... # pragma: no cover
-+
-+ def get_all(self, name: str, failobj: _T = ...) -> Union[List[Any], _T]:
-+ """
-+ Return all values associated with a possibly multi-valued key.
-+ """
-+
-+ @property
-+ def json(self) -> Dict[str, Union[str, List[str]]]:
-+ """
-+ A JSON-compatible form of the metadata.
-+ """
-+
-+
-+class SimplePath(Protocol[_T]):
-+ """
-+ A minimal subset of pathlib.Path required by PathDistribution.
-+ """
-+
-+ def joinpath(self) -> _T:
-+ ... # pragma: no cover
-+
-+ def __truediv__(self, other: Union[str, _T]) -> _T:
-+ ... # pragma: no cover
-+
-+ @property
-+ def parent(self) -> _T:
-+ ... # pragma: no cover
-+
-+ def read_text(self) -> str:
-+ ... # pragma: no cover
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/importlib_metadata/_py39compat.py

b/third_party/python/setuptools/setuptools/_vendor/importlib_metadata/_py39compat.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/_vendor/importlib_metadata/_py39compat.py
-@@ -0,0 +1,35 @@
-+"""
-+Compatibility layer with Python 3.8/3.9
-+"""
-+from typing import TYPE_CHECKING, Any, Optional
-+
-+if TYPE_CHECKING: # pragma: no cover
-+ # Prevent circular imports on runtime.
-+ from . import Distribution, EntryPoint
-+else:
-+ Distribution = EntryPoint = Any
-+
-+
-+def normalized_name(dist: Distribution) -> Optional[str]:
-+ """
-+ Honor name normalization for distributions that don't provide
``_normalized_name``.
-+ """
-+ try:
-+ return dist._normalized_name
-+ except AttributeError:
-+ from . import Prepared # -> delay to prevent circular imports.
-+
-+ return Prepared.normalize(getattr(dist, "name", None) or
dist.metadata['Name'])
-+
-+
-+def ep_matches(ep: EntryPoint, **params) -> bool:
-+ """
-+ Workaround for ``EntryPoint`` objects without the ``matches`` method.
-+ """
-+ try:
-+ return ep.matches(**params)
-+ except AttributeError:
-+ from . import EntryPoint # -> delay to prevent circular imports.
-+
-+ # Reconstruct the EntryPoint object to make sure it is compatible.
-+ return EntryPoint(ep.name, ep.value, ep.group).matches(**params)
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/importlib_metadata/_text.py

b/third_party/python/setuptools/setuptools/_vendor/importlib_metadata/_text.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/_vendor/importlib_metadata/_text.py
-@@ -0,0 +1,99 @@
-+import re
-+
-+from ._functools import method_cache
-+
-+
-+# from jaraco.text 3.5
-+class FoldedCase(str):
-+ """
-+ A case insensitive string class; behaves just like str
-+ except compares equal when the only variation is case.
-+
-+ >>> s = FoldedCase('hello world')
-+
-+ >>> s == 'Hello World'
-+ True
-+
-+ >>> 'Hello World' == s
-+ True
-+
-+ >>> s != 'Hello World'
-+ False
-+
-+ >>> s.index('O')
-+ 4
-+
-+ >>> s.split('O')
-+ ['hell', ' w', 'rld']
-+
-+ >>> sorted(map(FoldedCase, ['GAMMA', 'alpha', 'Beta']))
-+ ['alpha', 'Beta', 'GAMMA']
-+
-+ Sequence membership is straightforward.
-+
-+ >>> "Hello World" in [s]
-+ True
-+ >>> s in ["Hello World"]
-+ True
-+
-+ You may test for set inclusion, but candidate and elements
-+ must both be folded.
-+
-+ >>> FoldedCase("Hello World") in {s}
-+ True
-+ >>> s in {FoldedCase("Hello World")}
-+ True
-+
-+ String inclusion works as long as the FoldedCase object
-+ is on the right.
-+
-+ >>> "hello" in FoldedCase("Hello World")
-+ True
-+
-+ But not if the FoldedCase object is on the left:
-+
-+ >>> FoldedCase('hello') in 'Hello World'
-+ False
-+
-+ In that case, use in_:
-+
-+ >>> FoldedCase('hello').in_('Hello World')
-+ True
-+
-+ >>> FoldedCase('hello') > FoldedCase('Hello')
-+ False
-+ """
-+
-+ def __lt__(self, other):
-+ return self.lower() < other.lower()
-+
-+ def __gt__(self, other):
-+ return self.lower() > other.lower()
-+
-+ def __eq__(self, other):
-+ return self.lower() == other.lower()
-+
-+ def __ne__(self, other):
-+ return self.lower() != other.lower()
-+
-+ def __hash__(self):
-+ return hash(self.lower())
-+
-+ def __contains__(self, other):
-+ return super().lower().__contains__(other.lower())
-+
-+ def in_(self, other):
-+ "Does self appear in other?"
-+ return self in FoldedCase(other)
-+
-+ # cache lower since it's likely to be called frequently.
-+ @method_cache
-+ def lower(self):
-+ return super().lower()
-+
-+ def index(self, sub):
-+ return self.lower().index(sub.lower())
-+
-+ def split(self, splitter=' ', maxsplit=0):
-+ pattern = re.compile(re.escape(splitter), re.I)
-+ return pattern.split(self, maxsplit)
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/importlib_resources/__init__.py

b/third_party/python/setuptools/setuptools/_vendor/importlib_resources/__init__.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/_vendor/importlib_resources/__init__.py
-@@ -0,0 +1,36 @@
-+"""Read resources contained within a package."""
-+
-+from ._common import (
-+ as_file,
-+ files,
-+ Package,
-+)
-+
-+from ._legacy import (
-+ contents,
-+ open_binary,
-+ read_binary,
-+ open_text,
-+ read_text,
-+ is_resource,
-+ path,
-+ Resource,
-+)
-+
-+from .abc import ResourceReader
-+
-+
-+__all__ = [
-+ 'Package',
-+ 'Resource',
-+ 'ResourceReader',
-+ 'as_file',
-+ 'contents',
-+ 'files',
-+ 'is_resource',
-+ 'open_binary',
-+ 'open_text',
-+ 'path',
-+ 'read_binary',
-+ 'read_text',
-+]
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/importlib_resources/_adapters.py

b/third_party/python/setuptools/setuptools/_vendor/importlib_resources/_adapters.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/_vendor/importlib_resources/_adapters.py
-@@ -0,0 +1,170 @@
-+from contextlib import suppress
-+from io import TextIOWrapper
-+
-+from . import abc
-+
-+
-+class SpecLoaderAdapter:
-+ """
-+ Adapt a package spec to adapt the underlying loader.
-+ """
-+
-+ def __init__(self, spec, adapter=lambda spec: spec.loader):
-+ self.spec = spec
-+ self.loader = adapter(spec)
-+
-+ def __getattr__(self, name):
-+ return getattr(self.spec, name)
-+
-+
-+class TraversableResourcesLoader:
-+ """
-+ Adapt a loader to provide TraversableResources.
-+ """
-+
-+ def __init__(self, spec):
-+ self.spec = spec
-+
-+ def get_resource_reader(self, name):
-+ return CompatibilityFiles(self.spec)._native()
-+
-+
-+def _io_wrapper(file, mode='r', *args, **kwargs):
-+ if mode == 'r':
-+ return TextIOWrapper(file, *args, **kwargs)
-+ elif mode == 'rb':
-+ return file
-+ raise ValueError(
-+ "Invalid mode value '{}', only 'r' and 'rb' are
supported".format(mode)
-+ )
-+
-+
-+class CompatibilityFiles:
-+ """
-+ Adapter for an existing or non-existent resource reader
-+ to provide a compatibility .files().
-+ """
-+
-+ class SpecPath(abc.Traversable):
-+ """
-+ Path tied to a module spec.
-+ Can be read and exposes the resource reader children.
-+ """
-+
-+ def __init__(self, spec, reader):
-+ self._spec = spec
-+ self._reader = reader
-+
-+ def iterdir(self):
-+ if not self._reader:
-+ return iter(())
-+ return iter(
-+ CompatibilityFiles.ChildPath(self._reader, path)
-+ for path in self._reader.contents()
-+ )
-+
-+ def is_file(self):
-+ return False
-+
-+ is_dir = is_file
-+
-+ def joinpath(self, other):
-+ if not self._reader:
-+ return CompatibilityFiles.OrphanPath(other)
-+ return CompatibilityFiles.ChildPath(self._reader, other)
-+
-+ @property
-+ def name(self):
-+ return self._spec.name
-+
-+ def open(self, mode='r', *args, **kwargs):
-+ return _io_wrapper(self._reader.open_resource(None), mode,
*args, **kwargs)
-+
-+ class ChildPath(abc.Traversable):
-+ """
-+ Path tied to a resource reader child.
-+ Can be read but doesn't expose any meaningful children.
-+ """
-+
-+ def __init__(self, reader, name):
-+ self._reader = reader
-+ self._name = name
-+
-+ def iterdir(self):
-+ return iter(())
-+
-+ def is_file(self):
-+ return self._reader.is_resource(self.name)
-+
-+ def is_dir(self):
-+ return not self.is_file()
-+
-+ def joinpath(self, other):
-+ return CompatibilityFiles.OrphanPath(self.name, other)
-+
-+ @property
-+ def name(self):
-+ return self._name
-+
-+ def open(self, mode='r', *args, **kwargs):
-+ return _io_wrapper(
-+ self._reader.open_resource(self.name), mode, *args, **kwargs
-+ )
-+
-+ class OrphanPath(abc.Traversable):
-+ """
-+ Orphan path, not tied to a module spec or resource reader.
-+ Can't be read and doesn't expose any meaningful children.
-+ """
-+
-+ def __init__(self, *path_parts):
-+ if len(path_parts) < 1:
-+ raise ValueError('Need at least one path part to construct
a path')
-+ self._path = path_parts
-+
-+ def iterdir(self):
-+ return iter(())
-+
-+ def is_file(self):
-+ return False
-+
-+ is_dir = is_file
-+
-+ def joinpath(self, other):
-+ return CompatibilityFiles.OrphanPath(*self._path, other)
-+
-+ @property
-+ def name(self):
-+ return self._path[-1]
-+
-+ def open(self, mode='r', *args, **kwargs):
-+ raise FileNotFoundError("Can't open orphan path")
-+
-+ def __init__(self, spec):
-+ self.spec = spec
-+
-+ @property
-+ def _reader(self):
-+ with suppress(AttributeError):
-+ return self.spec.loader.get_resource_reader(self.spec.name)
-+
-+ def _native(self):
-+ """
-+ Return the native reader if it supports files().
-+ """
-+ reader = self._reader
-+ return reader if hasattr(reader, 'files') else self
-+
-+ def __getattr__(self, attr):
-+ return getattr(self._reader, attr)
-+
-+ def files(self):
-+ return CompatibilityFiles.SpecPath(self.spec, self._reader)
-+
-+
-+def wrap_spec(package):
-+ """
-+ Construct a package spec with traversable compatibility
-+ on the spec/loader/reader.
-+ """
-+ return SpecLoaderAdapter(package.__spec__, TraversableResourcesLoader)
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/importlib_resources/_common.py

b/third_party/python/setuptools/setuptools/_vendor/importlib_resources/_common.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/_vendor/importlib_resources/_common.py
-@@ -0,0 +1,207 @@
-+import os
-+import pathlib
-+import tempfile
-+import functools
-+import contextlib
-+import types
-+import importlib
-+import inspect
-+import warnings
-+import itertools
-+
-+from typing import Union, Optional, cast
-+from .abc import ResourceReader, Traversable
-+
-+from ._compat import wrap_spec
-+
-+Package = Union[types.ModuleType, str]
-+Anchor = Package
-+
-+
-+def package_to_anchor(func):
-+ """
-+ Replace 'package' parameter as 'anchor' and warn about the change.
-+
-+ Other errors should fall through.
-+
-+ >>> files('a', 'b')
-+ Traceback (most recent call last):
-+ TypeError: files() takes from 0 to 1 positional arguments but 2 were
given
-+ """
-+ undefined = object()
-+
-+ @functools.wraps(func)
-+ def wrapper(anchor=undefined, package=undefined):
-+ if package is not undefined:
-+ if anchor is not undefined:
-+ return func(anchor, package)
-+ warnings.warn(
-+ "First parameter to files is renamed to 'anchor'",
-+ DeprecationWarning,
-+ stacklevel=2,
-+ )
-+ return func(package)
-+ elif anchor is undefined:
-+ return func()
-+ return func(anchor)
-+
-+ return wrapper
-+
-+
-+@package_to_anchor
-+def files(anchor: Optional[Anchor] = None) -> Traversable:
-+ """
-+ Get a Traversable resource for an anchor.
-+ """
-+ return from_package(resolve(anchor))
-+
-+
-+def get_resource_reader(package: types.ModuleType) ->
Optional[ResourceReader]:
-+ """
-+ Return the package's loader if it's a ResourceReader.
-+ """
-+ # We can't use
-+ # a issubclass() check here because apparently abc.'s
__subclasscheck__()
-+ # hook wants to create a weak reference to the object, but
-+ # zipimport.zipimporter does not support weak references, resulting in a
-+ # TypeError. That seems terrible.
-+ spec = package.__spec__
-+ reader = getattr(spec.loader, 'get_resource_reader', None) # type:
ignore
-+ if reader is None:
-+ return None
-+ return reader(spec.name) # type: ignore
-+
-+
-+@functools.singledispatch
-+def resolve(cand: Optional[Anchor]) -> types.ModuleType:
-+ return cast(types.ModuleType, cand)
-+
-+
-+@resolve.register
-+def _(cand: str) -> types.ModuleType:
-+ return importlib.import_module(cand)
-+
-+
-+@resolve.register
-+def _(cand: None) -> types.ModuleType:
-+ return resolve(_infer_caller().f_globals['__name__'])
-+
-+
-+def _infer_caller():
-+ """
-+ Walk the stack and find the frame of the first caller not in this
module.
-+ """
-+
-+ def is_this_file(frame_info):
-+ return frame_info.filename == __file__
-+
-+ def is_wrapper(frame_info):
-+ return frame_info.function == 'wrapper'
-+
-+ not_this_file = itertools.filterfalse(is_this_file, inspect.stack())
-+ # also exclude 'wrapper' due to singledispatch in the call stack
-+ callers = itertools.filterfalse(is_wrapper, not_this_file)
-+ return next(callers).frame
-+
-+
-+def from_package(package: types.ModuleType):
-+ """
-+ Return a Traversable object for the given package.
-+
-+ """
-+ spec = wrap_spec(package)
-+ reader = spec.loader.get_resource_reader(spec.name)
-+ return reader.files()
-+
-+
-+@contextlib.contextmanager
-+def _tempfile(
-+ reader,
-+ suffix='',
-+ # gh-93353: Keep a reference to call os.remove() in late Python
-+ # finalization.
-+ *,
-+ _os_remove=os.remove,
-+):
-+ # Not using tempfile.NamedTemporaryFile as it leads to deeper 'try'
-+ # blocks due to the need to close the temporary file to work on Windows
-+ # properly.
-+ fd, raw_path = tempfile.mkstemp(suffix=suffix)
-+ try:
-+ try:
-+ os.write(fd, reader())
-+ finally:
-+ os.close(fd)
-+ del reader
-+ yield pathlib.Path(raw_path)
-+ finally:
-+ try:
-+ _os_remove(raw_path)
-+ except FileNotFoundError:
-+ pass
-+
-+
-+def _temp_file(path):
-+ return _tempfile(path.read_bytes, suffix=path.name)
-+
-+
-+def _is_present_dir(path: Traversable) -> bool:
-+ """
-+ Some Traversables implement ``is_dir()`` to raise an
-+ exception (i.e. ``FileNotFoundError``) when the
-+ directory doesn't exist. This function wraps that call
-+ to always return a boolean and only return True
-+ if there's a dir and it exists.
-+ """
-+ with contextlib.suppress(FileNotFoundError):
-+ return path.is_dir()
-+ return False
-+
-+
-+@functools.singledispatch
-+def as_file(path):
-+ """
-+ Given a Traversable object, return that object as a
-+ path on the local file system in a context manager.
-+ """
-+ return _temp_dir(path) if _is_present_dir(path) else _temp_file(path)
-+
-+
-+@as_file.register(pathlib.Path)
-+@contextlib.contextmanager
-+def _(path):
-+ """
-+ Degenerate behavior for pathlib.Path objects.
-+ """
-+ yield path
-+
-+
-+@contextlib.contextmanager
-+def _temp_path(dir: tempfile.TemporaryDirectory):
-+ """
-+ Wrap tempfile.TemporyDirectory to return a pathlib object.
-+ """
-+ with dir as result:
-+ yield pathlib.Path(result)
-+
-+
-+@contextlib.contextmanager
-+def _temp_dir(path):
-+ """
-+ Given a traversable dir, recursively replicate the whole tree
-+ to the file system in a context manager.
-+ """
-+ assert path.is_dir()
-+ with _temp_path(tempfile.TemporaryDirectory()) as temp_dir:
-+ yield _write_contents(temp_dir, path)
-+
-+
-+def _write_contents(target, source):
-+ child = target.joinpath(source.name)
-+ if source.is_dir():
-+ child.mkdir()
-+ for item in source.iterdir():
-+ _write_contents(child, item)
-+ else:
-+ child.write_bytes(source.read_bytes())
-+ return child
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/importlib_resources/_compat.py

b/third_party/python/setuptools/setuptools/_vendor/importlib_resources/_compat.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/_vendor/importlib_resources/_compat.py
-@@ -0,0 +1,108 @@
-+# flake8: noqa
-+
-+import abc
-+import os
-+import sys
-+import pathlib
-+from contextlib import suppress
-+from typing import Union
-+
-+
-+if sys.version_info >= (3, 10):
-+ from zipfile import Path as ZipPath # type: ignore
-+else:
-+ from ..zipp import Path as ZipPath # type: ignore
-+
-+
-+try:
-+ from typing import runtime_checkable # type: ignore
-+except ImportError:
-+
-+ def runtime_checkable(cls): # type: ignore
-+ return cls
-+
-+
-+try:
-+ from typing import Protocol # type: ignore
-+except ImportError:
-+ Protocol = abc.ABC # type: ignore
-+
-+
-+class TraversableResourcesLoader:
-+ """
-+ Adapt loaders to provide TraversableResources and other
-+ compatibility.
-+
-+ Used primarily for Python 3.9 and earlier where the native
-+ loaders do not yet implement TraversableResources.
-+ """
-+
-+ def __init__(self, spec):
-+ self.spec = spec
-+
-+ @property
-+ def path(self):
-+ return self.spec.origin
-+
-+ def get_resource_reader(self, name):
-+ from . import readers, _adapters
-+
-+ def _zip_reader(spec):
-+ with suppress(AttributeError):
-+ return readers.ZipReader(spec.loader, spec.name)
-+
-+ def _namespace_reader(spec):
-+ with suppress(AttributeError, ValueError):
-+ return
readers.NamespaceReader(spec.submodule_search_locations)
-+
-+ def _available_reader(spec):
-+ with suppress(AttributeError):
-+ return spec.loader.get_resource_reader(spec.name)
-+
-+ def _native_reader(spec):
-+ reader = _available_reader(spec)
-+ return reader if hasattr(reader, 'files') else None
-+
-+ def _file_reader(spec):
-+ try:
-+ path = pathlib.Path(self.path)
-+ except TypeError:
-+ return None
-+ if path.exists():
-+ return readers.FileReader(self)
-+
-+ return (
-+ # native reader if it supplies 'files'
-+ _native_reader(self.spec)
-+ or
-+ # local ZipReader if a zip module
-+ _zip_reader(self.spec)
-+ or
-+ # local NamespaceReader if a namespace module
-+ _namespace_reader(self.spec)
-+ or
-+ # local FileReader
-+ _file_reader(self.spec)
-+ # fallback - adapt the spec ResourceReader to TraversableReader
-+ or _adapters.CompatibilityFiles(self.spec)
-+ )
-+
-+
-+def wrap_spec(package):
-+ """
-+ Construct a package spec with traversable compatibility
-+ on the spec/loader/reader.
-+
-+ Supersedes _adapters.wrap_spec to use TraversableResourcesLoader
-+ from above for older Python compatibility (<3.10).
-+ """
-+ from . import _adapters
-+
-+ return _adapters.SpecLoaderAdapter(package.__spec__,
TraversableResourcesLoader)
-+
-+
-+if sys.version_info >= (3, 9):
-+ StrPath = Union[str, os.PathLike[str]]
-+else:
-+ # PathLike is only subscriptable at runtime in 3.9+
-+ StrPath = Union[str, "os.PathLike[str]"]
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/importlib_resources/_itertools.py

b/third_party/python/setuptools/setuptools/_vendor/importlib_resources/_itertools.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/_vendor/importlib_resources/_itertools.py
-@@ -0,0 +1,35 @@
-+from itertools import filterfalse
-+
-+from typing import (
-+ Callable,
-+ Iterable,
-+ Iterator,
-+ Optional,
-+ Set,
-+ TypeVar,
-+ Union,
-+)
-+
-+# Type and type variable definitions
-+_T = TypeVar('_T')
-+_U = TypeVar('_U')
-+
-+
-+def unique_everseen(
-+ iterable: Iterable[_T], key: Optional[Callable[[_T], _U]] = None
-+) -> Iterator[_T]:
-+ "List unique elements, preserving order. Remember all elements ever
seen."
-+ # unique_everseen('AAAABBBCCDAABBB') --> A B C D
-+ # unique_everseen('ABBCcAD', str.lower) --> A B C D
-+ seen: Set[Union[_T, _U]] = set()
-+ seen_add = seen.add
-+ if key is None:
-+ for element in filterfalse(seen.__contains__, iterable):
-+ seen_add(element)
-+ yield element
-+ else:
-+ for element in iterable:
-+ k = key(element)
-+ if k not in seen:
-+ seen_add(k)
-+ yield element
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/importlib_resources/_legacy.py

b/third_party/python/setuptools/setuptools/_vendor/importlib_resources/_legacy.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/_vendor/importlib_resources/_legacy.py
-@@ -0,0 +1,120 @@
-+import functools
-+import os
-+import pathlib
-+import types
-+import warnings
-+
-+from typing import Union, Iterable, ContextManager, BinaryIO, TextIO, Any
-+
-+from . import _common
-+
-+Package = Union[types.ModuleType, str]
-+Resource = str
-+
-+
-+def deprecated(func):
-+ @functools.wraps(func)
-+ def wrapper(*args, **kwargs):
-+ warnings.warn(
-+ f"{func.__name__} is deprecated. Use files() instead. "
-+ "Refer to https://importlib-resources.readthedocs.io";
-+ "/en/latest/using.html#migrating-from-legacy for migration
advice.",
-+ DeprecationWarning,
-+ stacklevel=2,
-+ )
-+ return func(*args, **kwargs)
-+
-+ return wrapper
-+
-+
-+def normalize_path(path: Any) -> str:
-+ """Normalize a path by ensuring it is a string.
-+
-+ If the resulting string contains path separators, an exception is
raised.
-+ """
-+ str_path = str(path)
-+ parent, file_name = os.path.split(str_path)
-+ if parent:
-+ raise ValueError(f'{path!r} must be only a file name')
-+ return file_name
-+
-+
-+@deprecated
-+def open_binary(package: Package, resource: Resource) -> BinaryIO:
-+ """Return a file-like object opened for binary reading of the
resource."""
-+ return (_common.files(package) / normalize_path(resource)).open('rb')
-+
-+
-+@deprecated
-+def read_binary(package: Package, resource: Resource) -> bytes:
-+ """Return the binary contents of the resource."""
-+ return (_common.files(package) / normalize_path(resource)).read_bytes()
-+
-+
-+@deprecated
-+def open_text(
-+ package: Package,
-+ resource: Resource,
-+ encoding: str = 'utf-8',
-+ errors: str = 'strict',
-+) -> TextIO:
-+ """Return a file-like object opened for text reading of the resource."""
-+ return (_common.files(package) / normalize_path(resource)).open(
-+ 'r', encoding=encoding, errors=errors
-+ )
-+
-+
-+@deprecated
-+def read_text(
-+ package: Package,
-+ resource: Resource,
-+ encoding: str = 'utf-8',
-+ errors: str = 'strict',
-+) -> str:
-+ """Return the decoded string of the resource.
-+
-+ The decoding-related arguments have the same semantics as those of
-+ bytes.decode().
-+ """
-+ with open_text(package, resource, encoding, errors) as fp:
-+ return fp.read()
-+
-+
-+@deprecated
-+def contents(package: Package) -> Iterable[str]:
-+ """Return an iterable of entries in `package`.
-+
-+ Note that not all entries are resources. Specifically, directories are
-+ not considered resources. Use `is_resource()` on each entry returned
here
-+ to check if it is a resource or not.
-+ """
-+ return [path.name for path in _common.files(package).iterdir()]
-+
-+
-+@deprecated
-+def is_resource(package: Package, name: str) -> bool:
-+ """True if `name` is a resource inside `package`.
-+
-+ Directories are *not* resources.
-+ """
-+ resource = normalize_path(name)
-+ return any(
-+ traversable.name == resource and traversable.is_file()
-+ for traversable in _common.files(package).iterdir()
-+ )
-+
-+
-+@deprecated
-+def path(
-+ package: Package,
-+ resource: Resource,
-+) -> ContextManager[pathlib.Path]:
-+ """A context manager providing a file path object to the resource.
-+
-+ If the resource does not already exist on its own on the file system,
-+ a temporary file will be created. If the file was created, the file
-+ will be deleted upon exiting the context manager (no exception is
-+ raised if the file was deleted prior to the context manager
-+ exiting).
-+ """
-+ return _common.as_file(_common.files(package) /
normalize_path(resource))
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/importlib_resources/abc.py
b/third_party/python/setuptools/setuptools/_vendor/importlib_resources/abc.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/_vendor/importlib_resources/abc.py
-@@ -0,0 +1,170 @@
-+import abc
-+import io
-+import itertools
-+import pathlib
-+from typing import Any, BinaryIO, Iterable, Iterator, NoReturn, Text,
Optional
-+
-+from ._compat import runtime_checkable, Protocol, StrPath
-+
-+
-+__all__ = ["ResourceReader", "Traversable", "TraversableResources"]
-+
-+
-+class ResourceReader(metaclass=abc.ABCMeta):
-+ """Abstract base class for loaders to provide resource reading
support."""
-+
-+ @abc.abstractmethod
-+ def open_resource(self, resource: Text) -> BinaryIO:
-+ """Return an opened, file-like object for binary reading.
-+
-+ The 'resource' argument is expected to represent only a file name.
-+ If the resource cannot be found, FileNotFoundError is raised.
-+ """
-+ # This deliberately raises FileNotFoundError instead of
-+ # NotImplementedError so that if this method is accidentally called,
-+ # it'll still do the right thing.
-+ raise FileNotFoundError
-+
-+ @abc.abstractmethod
-+ def resource_path(self, resource: Text) -> Text:
-+ """Return the file system path to the specified resource.
-+
-+ The 'resource' argument is expected to represent only a file name.
-+ If the resource does not exist on the file system, raise
-+ FileNotFoundError.
-+ """
-+ # This deliberately raises FileNotFoundError instead of
-+ # NotImplementedError so that if this method is accidentally called,
-+ # it'll still do the right thing.
-+ raise FileNotFoundError
-+
-+ @abc.abstractmethod
-+ def is_resource(self, path: Text) -> bool:
-+ """Return True if the named 'path' is a resource.
-+
-+ Files are resources, directories are not.
-+ """
-+ raise FileNotFoundError
-+
-+ @abc.abstractmethod
-+ def contents(self) -> Iterable[str]:
-+ """Return an iterable of entries in `package`."""
-+ raise FileNotFoundError
-+
-+
-+class TraversalError(Exception):
-+ pass
-+
-+
-+@runtime_checkable
-+class Traversable(Protocol):
-+ """
-+ An object with a subset of pathlib.Path methods suitable for
-+ traversing directories and opening files.
-+
-+ Any exceptions that occur when accessing the backing resource
-+ may propagate unaltered.
-+ """
-+
-+ @abc.abstractmethod
-+ def iterdir(self) -> Iterator["Traversable"]:
-+ """
-+ Yield Traversable objects in self
-+ """
-+
-+ def read_bytes(self) -> bytes:
-+ """
-+ Read contents of self as bytes
-+ """
-+ with self.open('rb') as strm:
-+ return strm.read()
-+
-+ def read_text(self, encoding: Optional[str] = None) -> str:
-+ """
-+ Read contents of self as text
-+ """
-+ with self.open(encoding=encoding) as strm:
-+ return strm.read()
-+
-+ @abc.abstractmethod
-+ def is_dir(self) -> bool:
-+ """
-+ Return True if self is a directory
-+ """
-+
-+ @abc.abstractmethod
-+ def is_file(self) -> bool:
-+ """
-+ Return True if self is a file
-+ """
-+
-+ def joinpath(self, *descendants: StrPath) -> "Traversable":
-+ """
-+ Return Traversable resolved with any descendants applied.
-+
-+ Each descendant should be a path segment relative to self
-+ and each may contain multiple levels separated by
-+ ``posixpath.sep`` (``/``).
-+ """
-+ if not descendants:
-+ return self
-+ names = itertools.chain.from_iterable(
-+ path.parts for path in map(pathlib.PurePosixPath, descendants)
-+ )
-+ target = next(names)
-+ matches = (
-+ traversable for traversable in self.iterdir() if
traversable.name == target
-+ )
-+ try:
-+ match = next(matches)
-+ except StopIteration:
-+ raise TraversalError(
-+ "Target not found during traversal.", target, list(names)
-+ )
-+ return match.joinpath(*names)
-+
-+ def __truediv__(self, child: StrPath) -> "Traversable":
-+ """
-+ Return Traversable child in self
-+ """
-+ return self.joinpath(child)
-+
-+ @abc.abstractmethod
-+ def open(self, mode='r', *args, **kwargs):
-+ """
-+ mode may be 'r' or 'rb' to open as text or binary. Return a handle
-+ suitable for reading (same as pathlib.Path.open).
-+
-+ When opening as text, accepts encoding parameters such as those
-+ accepted by io.TextIOWrapper.
-+ """
-+
-+ @property
-+ @abc.abstractmethod
-+ def name(self) -> str:
-+ """
-+ The base name of this object without any parent references.
-+ """
-+
-+
-+class TraversableResources(ResourceReader):
-+ """
-+ The required interface for providing traversable
-+ resources.
-+ """
-+
-+ @abc.abstractmethod
-+ def files(self) -> "Traversable":
-+ """Return a Traversable object for the loaded package."""
-+
-+ def open_resource(self, resource: StrPath) -> io.BufferedReader:
-+ return self.files().joinpath(resource).open('rb')
-+
-+ def resource_path(self, resource: Any) -> NoReturn:
-+ raise FileNotFoundError(resource)
-+
-+ def is_resource(self, path: StrPath) -> bool:
-+ return self.files().joinpath(path).is_file()
-+
-+ def contents(self) -> Iterator[str]:
-+ return (item.name for item in self.files().iterdir())
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/importlib_resources/readers.py

b/third_party/python/setuptools/setuptools/_vendor/importlib_resources/readers.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/_vendor/importlib_resources/readers.py
-@@ -0,0 +1,120 @@
-+import collections
-+import pathlib
-+import operator
-+
-+from . import abc
-+
-+from ._itertools import unique_everseen
-+from ._compat import ZipPath
-+
-+
-+def remove_duplicates(items):
-+ return iter(collections.OrderedDict.fromkeys(items))
-+
-+
-+class FileReader(abc.TraversableResources):
-+ def __init__(self, loader):
-+ self.path = pathlib.Path(loader.path).parent
-+
-+ def resource_path(self, resource):
-+ """
-+ Return the file system path to prevent
-+ `resources.path()` from creating a temporary
-+ copy.
-+ """
-+ return str(self.path.joinpath(resource))
-+
-+ def files(self):
-+ return self.path
-+
-+
-+class ZipReader(abc.TraversableResources):
-+ def __init__(self, loader, module):
-+ _, _, name = module.rpartition('.')
-+ self.prefix = loader.prefix.replace('\\', '/') + name + '/'
-+ self.archive = loader.archive
-+
-+ def open_resource(self, resource):
-+ try:
-+ return super().open_resource(resource)
-+ except KeyError as exc:
-+ raise FileNotFoundError(exc.args[0])
-+
-+ def is_resource(self, path):
-+ # workaround for `zipfile.Path.is_file` returning true
-+ # for non-existent paths.
-+ target = self.files().joinpath(path)
-+ return target.is_file() and target.exists()
-+
-+ def files(self):
-+ return ZipPath(self.archive, self.prefix)
-+
-+
-+class MultiplexedPath(abc.Traversable):
-+ """
-+ Given a series of Traversable objects, implement a merged
-+ version of the interface across all objects. Useful for
-+ namespace packages which may be multihomed at a single
-+ name.
-+ """
-+
-+ def __init__(self, *paths):
-+ self._paths = list(map(pathlib.Path, remove_duplicates(paths)))
-+ if not self._paths:
-+ message = 'MultiplexedPath must contain at least one path'
-+ raise FileNotFoundError(message)
-+ if not all(path.is_dir() for path in self._paths):
-+ raise NotADirectoryError('MultiplexedPath only supports
directories')
-+
-+ def iterdir(self):
-+ files = (file for path in self._paths for file in path.iterdir())
-+ return unique_everseen(files, key=operator.attrgetter('name'))
-+
-+ def read_bytes(self):
-+ raise FileNotFoundError(f'{self} is not a file')
-+
-+ def read_text(self, *args, **kwargs):
-+ raise FileNotFoundError(f'{self} is not a file')
-+
-+ def is_dir(self):
-+ return True
-+
-+ def is_file(self):
-+ return False
-+
-+ def joinpath(self, *descendants):
-+ try:
-+ return super().joinpath(*descendants)
-+ except abc.TraversalError:
-+ # One of the paths did not resolve (a directory does not exist).
-+ # Just return something that will not exist.
-+ return self._paths[0].joinpath(*descendants)
-+
-+ def open(self, *args, **kwargs):
-+ raise FileNotFoundError(f'{self} is not a file')
-+
-+ @property
-+ def name(self):
-+ return self._paths[0].name
-+
-+ def __repr__(self):
-+ paths = ', '.join(f"'{path}'" for path in self._paths)
-+ return f'MultiplexedPath({paths})'
-+
-+
-+class NamespaceReader(abc.TraversableResources):
-+ def __init__(self, namespace_path):
-+ if 'NamespacePath' not in str(namespace_path):
-+ raise ValueError('Invalid path')
-+ self.path = MultiplexedPath(*list(namespace_path))
-+
-+ def resource_path(self, resource):
-+ """
-+ Return the file system path to prevent
-+ `resources.path()` from creating a temporary
-+ copy.
-+ """
-+ return str(self.path.joinpath(resource))
-+
-+ def files(self):
-+ return self.path
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/importlib_resources/simple.py

b/third_party/python/setuptools/setuptools/_vendor/importlib_resources/simple.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/_vendor/importlib_resources/simple.py
-@@ -0,0 +1,106 @@
-+"""
-+Interface adapters for low-level readers.
-+"""
-+
-+import abc
-+import io
-+import itertools
-+from typing import BinaryIO, List
-+
-+from .abc import Traversable, TraversableResources
-+
-+
-+class SimpleReader(abc.ABC):
-+ """
-+ The minimum, low-level interface required from a resource
-+ provider.
-+ """
-+
-+ @property
-+ @abc.abstractmethod
-+ def package(self) -> str:
-+ """
-+ The name of the package for which this reader loads resources.
-+ """
-+
-+ @abc.abstractmethod
-+ def children(self) -> List['SimpleReader']:
-+ """
-+ Obtain an iterable of SimpleReader for available
-+ child containers (e.g. directories).
-+ """
-+
-+ @abc.abstractmethod
-+ def resources(self) -> List[str]:
-+ """
-+ Obtain available named resources for this virtual package.
-+ """
-+
-+ @abc.abstractmethod
-+ def open_binary(self, resource: str) -> BinaryIO:
-+ """
-+ Obtain a File-like for a named resource.
-+ """
-+
-+ @property
-+ def name(self):
-+ return self.package.split('.')[-1]
-+
-+
-+class ResourceContainer(Traversable):
-+ """
-+ Traversable container for a package's resources via its reader.
-+ """
-+
-+ def __init__(self, reader: SimpleReader):
-+ self.reader = reader
-+
-+ def is_dir(self):
-+ return True
-+
-+ def is_file(self):
-+ return False
-+
-+ def iterdir(self):
-+ files = (ResourceHandle(self, name) for name in
self.reader.resources)
-+ dirs = map(ResourceContainer, self.reader.children())
-+ return itertools.chain(files, dirs)
-+
-+ def open(self, *args, **kwargs):
-+ raise IsADirectoryError()
-+
-+
-+class ResourceHandle(Traversable):
-+ """
-+ Handle to a named resource in a ResourceReader.
-+ """
-+
-+ def __init__(self, parent: ResourceContainer, name: str):
-+ self.parent = parent
-+ self.name = name # type: ignore
-+
-+ def is_file(self):
-+ return True
-+
-+ def is_dir(self):
-+ return False
-+
-+ def open(self, mode='r', *args, **kwargs):
-+ stream = self.parent.reader.open_binary(self.name)
-+ if 'b' not in mode:
-+ stream = io.TextIOWrapper(*args, **kwargs)
-+ return stream
-+
-+ def joinpath(self, name):
-+ raise RuntimeError("Cannot traverse into a resource")
-+
-+
-+class TraversableReader(TraversableResources, SimpleReader):
-+ """
-+ A TraversableResources based on SimpleReader. Resource providers
-+ may derive from this class to provide the TraversableResources
-+ interface by supplying the SimpleReader interface.
-+ """
-+
-+ def files(self):
-+ return ResourceContainer(self)
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/jaraco/__init__.py
b/third_party/python/setuptools/setuptools/_vendor/jaraco/__init__.py
-new file mode 100644
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/jaraco/context.py
b/third_party/python/setuptools/setuptools/_vendor/jaraco/context.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/_vendor/jaraco/context.py
-@@ -0,0 +1,288 @@
-+import os
-+import subprocess
-+import contextlib
-+import functools
-+import tempfile
-+import shutil
-+import operator
-+import warnings
-+
-+
-+@contextlib.contextmanager
-+def pushd(dir):
-+ """
-+ >>> tmp_path = getfixture('tmp_path')
-+ >>> with pushd(tmp_path):
-+ ... assert os.getcwd() == os.fspath(tmp_path)
-+ >>> assert os.getcwd() != os.fspath(tmp_path)
-+ """
-+
-+ orig = os.getcwd()
-+ os.chdir(dir)
-+ try:
-+ yield dir
-+ finally:
-+ os.chdir(orig)
-+
-+
-+@contextlib.contextmanager
-+def tarball_context(url, target_dir=None, runner=None, pushd=pushd):
-+ """
-+ Get a tarball, extract it, change to that directory, yield, then
-+ clean up.
-+ `runner` is the function to invoke commands.
-+ `pushd` is a context manager for changing the directory.
-+ """
-+ if target_dir is None:
-+ target_dir = os.path.basename(url).replace('.tar.gz',
'').replace('.tgz', '')
-+ if runner is None:
-+ runner = functools.partial(subprocess.check_call, shell=True)
-+ else:
-+ warnings.warn("runner parameter is deprecated", DeprecationWarning)
-+ # In the tar command, use --strip-components=1 to strip the first path
and
-+ # then
-+ # use -C to cause the files to be extracted to {target_dir}. This
ensures
-+ # that we always know where the files were extracted.
-+ runner('mkdir {target_dir}'.format(**vars()))
-+ try:
-+ getter = 'wget {url} -O -'
-+ extract = 'tar x{compression} --strip-components=1 -C {target_dir}'
-+ cmd = ' | '.join((getter, extract))
-+ runner(cmd.format(compression=infer_compression(url), **vars()))
-+ with pushd(target_dir):
-+ yield target_dir
-+ finally:
-+ runner('rm -Rf {target_dir}'.format(**vars()))
-+
-+
-+def infer_compression(url):
-+ """
-+ Given a URL or filename, infer the compression code for tar.
-+
-+ >>> infer_compression('http://foo/bar.tar.gz')
-+ 'z'
-+ >>> infer_compression('http://foo/bar.tgz')
-+ 'z'
-+ >>> infer_compression('file.bz')
-+ 'j'
-+ >>> infer_compression('file.xz')
-+ 'J'
-+ """
-+ # cheat and just assume it's the last two characters
-+ compression_indicator = url[-2:]
-+ mapping = dict(gz='z', bz='j', xz='J')
-+ # Assume 'z' (gzip) if no match
-+ return mapping.get(compression_indicator, 'z')
-+
-+
-+@contextlib.contextmanager
-+def temp_dir(remover=shutil.rmtree):
-+ """
-+ Create a temporary directory context. Pass a custom remover
-+ to override the removal behavior.
-+
-+ >>> import pathlib
-+ >>> with temp_dir() as the_dir:
-+ ... assert os.path.isdir(the_dir)
-+ ... _ =
pathlib.Path(the_dir).joinpath('somefile').write_text('contents')
-+ >>> assert not os.path.exists(the_dir)
-+ """
-+ temp_dir = tempfile.mkdtemp()
-+ try:
-+ yield temp_dir
-+ finally:
-+ remover(temp_dir)
-+
-+
-+@contextlib.contextmanager
-+def repo_context(url, branch=None, quiet=True, dest_ctx=temp_dir):
-+ """
-+ Check out the repo indicated by url.
-+
-+ If dest_ctx is supplied, it should be a context manager
-+ to yield the target directory for the check out.
-+ """
-+ exe = 'git' if 'git' in url else 'hg'
-+ with dest_ctx() as repo_dir:
-+ cmd = [exe, 'clone', url, repo_dir]
-+ if branch:
-+ cmd.extend(['--branch', branch])
-+ devnull = open(os.path.devnull, 'w')
-+ stdout = devnull if quiet else None
-+ subprocess.check_call(cmd, stdout=stdout)
-+ yield repo_dir
-+
-+
-+@contextlib.contextmanager
-+def null():
-+ """
-+ A null context suitable to stand in for a meaningful context.
-+
-+ >>> with null() as value:
-+ ... assert value is None
-+ """
-+ yield
-+
-+
-+class ExceptionTrap:
-+ """
-+ A context manager that will catch certain exceptions and provide an
-+ indication they occurred.
-+
-+ >>> with ExceptionTrap() as trap:
-+ ... raise Exception()
-+ >>> bool(trap)
-+ True
-+
-+ >>> with ExceptionTrap() as trap:
-+ ... pass
-+ >>> bool(trap)
-+ False
-+
-+ >>> with ExceptionTrap(ValueError) as trap:
-+ ... raise ValueError("1 + 1 is not 3")
-+ >>> bool(trap)
-+ True
-+ >>> trap.value
-+ ValueError('1 + 1 is not 3')
-+ >>> trap.tb
-+ <traceback object at ...>
-+
-+ >>> with ExceptionTrap(ValueError) as trap:
-+ ... raise Exception()
-+ Traceback (most recent call last):
-+ ...
-+ Exception
-+
-+ >>> bool(trap)
-+ False
-+ """
-+
-+ exc_info = None, None, None
-+
-+ def __init__(self, exceptions=(Exception,)):
-+ self.exceptions = exceptions
-+
-+ def __enter__(self):
-+ return self
-+
-+ @property
-+ def type(self):
-+ return self.exc_info[0]
-+
-+ @property
-+ def value(self):
-+ return self.exc_info[1]
-+
-+ @property
-+ def tb(self):
-+ return self.exc_info[2]
-+
-+ def __exit__(self, *exc_info):
-+ type = exc_info[0]
-+ matches = type and issubclass(type, self.exceptions)
-+ if matches:
-+ self.exc_info = exc_info
-+ return matches
-+
-+ def __bool__(self):
-+ return bool(self.type)
-+
-+ def raises(self, func, *, _test=bool):
-+ """
-+ Wrap func and replace the result with the truth
-+ value of the trap (True if an exception occurred).
-+
-+ First, give the decorator an alias to support Python 3.8
-+ Syntax.
-+
-+ >>> raises = ExceptionTrap(ValueError).raises
-+
-+ Now decorate a function that always fails.
-+
-+ >>> @raises
-+ ... def fail():
-+ ... raise ValueError('failed')
-+ >>> fail()
-+ True
-+ """
-+
-+ @functools.wraps(func)
-+ def wrapper(*args, **kwargs):
-+ with ExceptionTrap(self.exceptions) as trap:
-+ func(*args, **kwargs)
-+ return _test(trap)
-+
-+ return wrapper
-+
-+ def passes(self, func):
-+ """
-+ Wrap func and replace the result with the truth
-+ value of the trap (True if no exception).
-+
-+ First, give the decorator an alias to support Python 3.8
-+ Syntax.
-+
-+ >>> passes = ExceptionTrap(ValueError).passes
-+
-+ Now decorate a function that always fails.
-+
-+ >>> @passes
-+ ... def fail():
-+ ... raise ValueError('failed')
-+
-+ >>> fail()
-+ False
-+ """
-+ return self.raises(func, _test=operator.not_)
-+
-+
-+class suppress(contextlib.suppress, contextlib.ContextDecorator):
-+ """
-+ A version of contextlib.suppress with decorator support.
-+
-+ >>> @suppress(KeyError)
-+ ... def key_error():
-+ ... {}['']
-+ >>> key_error()
-+ """
-+
-+
-+class on_interrupt(contextlib.ContextDecorator):
-+ """
-+ Replace a KeyboardInterrupt with SystemExit(1)
-+
-+ >>> def do_interrupt():
-+ ... raise KeyboardInterrupt()
-+ >>> on_interrupt('error')(do_interrupt)()
-+ Traceback (most recent call last):
-+ ...
-+ SystemExit: 1
-+ >>> on_interrupt('error', code=255)(do_interrupt)()
-+ Traceback (most recent call last):
-+ ...
-+ SystemExit: 255
-+ >>> on_interrupt('suppress')(do_interrupt)()
-+ >>> with __import__('pytest').raises(KeyboardInterrupt):
-+ ... on_interrupt('ignore')(do_interrupt)()
-+ """
-+
-+ def __init__(
-+ self,
-+ action='error',
-+ # py3.7 compat
-+ # /,
-+ code=1,
-+ ):
-+ self.action = action
-+ self.code = code
-+
-+ def __enter__(self):
-+ return self
-+
-+ def __exit__(self, exctype, excinst, exctb):
-+ if exctype is not KeyboardInterrupt or self.action == 'ignore':
-+ return
-+ elif self.action == 'error':
-+ raise SystemExit(self.code) from excinst
-+ return self.action == 'suppress'
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/jaraco/functools.py
b/third_party/python/setuptools/setuptools/_vendor/jaraco/functools.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/_vendor/jaraco/functools.py
-@@ -0,0 +1,556 @@
-+import functools
-+import time
-+import inspect
-+import collections
-+import types
-+import itertools
-+import warnings
-+
-+import setuptools.extern.more_itertools
-+
-+from typing import Callable, TypeVar
-+
-+
-+CallableT = TypeVar("CallableT", bound=Callable[..., object])
-+
-+
-+def compose(*funcs):
-+ """
-+ Compose any number of unary functions into a single unary function.
-+
-+ >>> import textwrap
-+ >>> expected = str.strip(textwrap.dedent(compose.__doc__))
-+ >>> strip_and_dedent = compose(str.strip, textwrap.dedent)
-+ >>> strip_and_dedent(compose.__doc__) == expected
-+ True
-+
-+ Compose also allows the innermost function to take arbitrary arguments.
-+
-+ >>> round_three = lambda x: round(x, ndigits=3)
-+ >>> f = compose(round_three, int.__truediv__)
-+ >>> [f(3*x, x+1) for x in range(1,10)]
-+ [1.5, 2.0, 2.25, 2.4, 2.5, 2.571, 2.625, 2.667, 2.7]
-+ """
-+
-+ def compose_two(f1, f2):
-+ return lambda *args, **kwargs: f1(f2(*args, **kwargs))
-+
-+ return functools.reduce(compose_two, funcs)
-+
-+
-+def method_caller(method_name, *args, **kwargs):
-+ """
-+ Return a function that will call a named method on the
-+ target object with optional positional and keyword
-+ arguments.
-+
-+ >>> lower = method_caller('lower')
-+ >>> lower('MyString')
-+ 'mystring'
-+ """
-+
-+ def call_method(target):
-+ func = getattr(target, method_name)
-+ return func(*args, **kwargs)
-+
-+ return call_method
-+
-+
-+def once(func):
-+ """
-+ Decorate func so it's only ever called the first time.
-+
-+ This decorator can ensure that an expensive or non-idempotent function
-+ will not be expensive on subsequent calls and is idempotent.
-+
-+ >>> add_three = once(lambda a: a+3)
-+ >>> add_three(3)
-+ 6
-+ >>> add_three(9)
-+ 6
-+ >>> add_three('12')
-+ 6
-+
-+ To reset the stored value, simply clear the property ``saved_result``.
-+
-+ >>> del add_three.saved_result
-+ >>> add_three(9)
-+ 12
-+ >>> add_three(8)
-+ 12
-+
-+ Or invoke 'reset()' on it.
-+
-+ >>> add_three.reset()
-+ >>> add_three(-3)
-+ 0
-+ >>> add_three(0)
-+ 0
-+ """
-+
-+ @functools.wraps(func)
-+ def wrapper(*args, **kwargs):
-+ if not hasattr(wrapper, 'saved_result'):
-+ wrapper.saved_result = func(*args, **kwargs)
-+ return wrapper.saved_result
-+
-+ wrapper.reset = lambda: vars(wrapper).__delitem__('saved_result')
-+ return wrapper
-+
-+
-+def method_cache(
-+ method: CallableT,
-+ cache_wrapper: Callable[
-+ [CallableT], CallableT
-+ ] = functools.lru_cache(), # type: ignore[assignment]
-+) -> CallableT:
-+ """
-+ Wrap lru_cache to support storing the cache data in the object
instances.
-+
-+ Abstracts the common paradigm where the method explicitly saves an
-+ underscore-prefixed protected property on first call and returns that
-+ subsequently.
-+
-+ >>> class MyClass:
-+ ... calls = 0
-+ ...
-+ ... @method_cache
-+ ... def method(self, value):
-+ ... self.calls += 1
-+ ... return value
-+
-+ >>> a = MyClass()
-+ >>> a.method(3)
-+ 3
-+ >>> for x in range(75):
-+ ... res = a.method(x)
-+ >>> a.calls
-+ 75
-+
-+ Note that the apparent behavior will be exactly like that of lru_cache
-+ except that the cache is stored on each instance, so values in one
-+ instance will not flush values from another, and when an instance is
-+ deleted, so are the cached values for that instance.
-+
-+ >>> b = MyClass()
-+ >>> for x in range(35):
-+ ... res = b.method(x)
-+ >>> b.calls
-+ 35
-+ >>> a.method(0)
-+ 0
-+ >>> a.calls
-+ 75
-+
-+ Note that if method had been decorated with ``functools.lru_cache()``,
-+ a.calls would have been 76 (due to the cached value of 0 having been
-+ flushed by the 'b' instance).
-+
-+ Clear the cache with ``.cache_clear()``
-+
-+ >>> a.method.cache_clear()
-+
-+ Same for a method that hasn't yet been called.
-+
-+ >>> c = MyClass()
-+ >>> c.method.cache_clear()
-+
-+ Another cache wrapper may be supplied:
-+
-+ >>> cache = functools.lru_cache(maxsize=2)
-+ >>> MyClass.method2 = method_cache(lambda self: 3, cache_wrapper=cache)
-+ >>> a = MyClass()
-+ >>> a.method2()
-+ 3
-+
-+ Caution - do not subsequently wrap the method with another decorator,
such
-+ as ``@property``, which changes the semantics of the function.
-+
-+ See also
-+
http://code.activestate.com/recipes/577452-a-memoize-decorator-for-instance-methods/
-+ for another implementation and additional justification.
-+ """
-+
-+ def wrapper(self: object, *args: object, **kwargs: object) -> object:
-+ # it's the first call, replace the method with a cached, bound
method
-+ bound_method: CallableT = types.MethodType( # type:
ignore[assignment]
-+ method, self
-+ )
-+ cached_method = cache_wrapper(bound_method)
-+ setattr(self, method.__name__, cached_method)
-+ return cached_method(*args, **kwargs)
-+
-+ # Support cache clear even before cache has been created.
-+ wrapper.cache_clear = lambda: None # type: ignore[attr-defined]
-+
-+ return ( # type: ignore[return-value]
-+ _special_method_cache(method, cache_wrapper) or wrapper
-+ )
-+
-+
-+def _special_method_cache(method, cache_wrapper):
-+ """
-+ Because Python treats special methods differently, it's not
-+ possible to use instance attributes to implement the cached
-+ methods.
-+
-+ Instead, install the wrapper method under a different name
-+ and return a simple proxy to that wrapper.
-+
-+ https://github.com/jaraco/jaraco.functools/issues/5
-+ """
-+ name = method.__name__
-+ special_names = '__getattr__', '__getitem__'
-+ if name not in special_names:
-+ return
-+
-+ wrapper_name = '__cached' + name
-+
-+ def proxy(self, *args, **kwargs):
-+ if wrapper_name not in vars(self):
-+ bound = types.MethodType(method, self)
-+ cache = cache_wrapper(bound)
-+ setattr(self, wrapper_name, cache)
-+ else:
-+ cache = getattr(self, wrapper_name)
-+ return cache(*args, **kwargs)
-+
-+ return proxy
-+
-+
-+def apply(transform):
-+ """
-+ Decorate a function with a transform function that is
-+ invoked on results returned from the decorated function.
-+
-+ >>> @apply(reversed)
-+ ... def get_numbers(start):
-+ ... "doc for get_numbers"
-+ ... return range(start, start+3)
-+ >>> list(get_numbers(4))
-+ [6, 5, 4]
-+ >>> get_numbers.__doc__
-+ 'doc for get_numbers'
-+ """
-+
-+ def wrap(func):
-+ return functools.wraps(func)(compose(transform, func))
-+
-+ return wrap
-+
-+
-+def result_invoke(action):
-+ r"""
-+ Decorate a function with an action function that is
-+ invoked on the results returned from the decorated
-+ function (for its side-effect), then return the original
-+ result.
-+
-+ >>> @result_invoke(print)
-+ ... def add_two(a, b):
-+ ... return a + b
-+ >>> x = add_two(2, 3)
-+ 5
-+ >>> x
-+ 5
-+ """
-+
-+ def wrap(func):
-+ @functools.wraps(func)
-+ def wrapper(*args, **kwargs):
-+ result = func(*args, **kwargs)
-+ action(result)
-+ return result
-+
-+ return wrapper
-+
-+ return wrap
-+
-+
-+def invoke(f, *args, **kwargs):
-+ """
-+ Call a function for its side effect after initialization.
-+
-+ The benefit of using the decorator instead of simply invoking a function
-+ after defining it is that it makes explicit the author's intent for the
-+ function to be called immediately. Whereas if one simply calls the
-+ function immediately, it's less obvious if that was intentional or
-+ incidental. It also avoids repeating the name - the two actions,
defining
-+ the function and calling it immediately are modeled separately, but
linked
-+ by the decorator construct.
-+
-+ The benefit of having a function construct (opposed to just invoking
some
-+ behavior inline) is to serve as a scope in which the behavior occurs. It
-+ avoids polluting the global namespace with local variables, provides an
-+ anchor on which to attach documentation (docstring), keeps the behavior
-+ logically separated (instead of conceptually separated or not separated
at
-+ all), and provides potential to re-use the behavior for testing or other
-+ purposes.
-+
-+ This function is named as a pithy way to communicate, "call this
function
-+ primarily for its side effect", or "while defining this function, also
-+ take it aside and call it". It exists because there's no Python
construct
-+ for "define and call" (nor should there be, as decorators serve this
need
-+ just fine). The behavior happens immediately and synchronously.
-+
-+ >>> @invoke
-+ ... def func(): print("called")
-+ called
-+ >>> func()
-+ called
-+
-+ Use functools.partial to pass parameters to the initial call
-+
-+ >>> @functools.partial(invoke, name='bingo')
-+ ... def func(name): print("called with", name)
-+ called with bingo
-+ """
-+ f(*args, **kwargs)
-+ return f
-+
-+
-+def call_aside(*args, **kwargs):
-+ """
-+ Deprecated name for invoke.
-+ """
-+ warnings.warn("call_aside is deprecated, use invoke",
DeprecationWarning)
-+ return invoke(*args, **kwargs)
-+
-+
-+class Throttler:
-+ """
-+ Rate-limit a function (or other callable)
-+ """
-+
-+ def __init__(self, func, max_rate=float('Inf')):
-+ if isinstance(func, Throttler):
-+ func = func.func
-+ self.func = func
-+ self.max_rate = max_rate
-+ self.reset()
-+
-+ def reset(self):
-+ self.last_called = 0
-+
-+ def __call__(self, *args, **kwargs):
-+ self._wait()
-+ return self.func(*args, **kwargs)
-+
-+ def _wait(self):
-+ "ensure at least 1/max_rate seconds from last call"
-+ elapsed = time.time() - self.last_called
-+ must_wait = 1 / self.max_rate - elapsed
-+ time.sleep(max(0, must_wait))
-+ self.last_called = time.time()
-+
-+ def __get__(self, obj, type=None):
-+ return first_invoke(self._wait, functools.partial(self.func, obj))
-+
-+
-+def first_invoke(func1, func2):
-+ """
-+ Return a function that when invoked will invoke func1 without
-+ any parameters (for its side-effect) and then invoke func2
-+ with whatever parameters were passed, returning its result.
-+ """
-+
-+ def wrapper(*args, **kwargs):
-+ func1()
-+ return func2(*args, **kwargs)
-+
-+ return wrapper
-+
-+
-+def retry_call(func, cleanup=lambda: None, retries=0, trap=()):
-+ """
-+ Given a callable func, trap the indicated exceptions
-+ for up to 'retries' times, invoking cleanup on the
-+ exception. On the final attempt, allow any exceptions
-+ to propagate.
-+ """
-+ attempts = itertools.count() if retries == float('inf') else
range(retries)
-+ for attempt in attempts:
-+ try:
-+ return func()
-+ except trap:
-+ cleanup()
-+
-+ return func()
-+
-+
-+def retry(*r_args, **r_kwargs):
-+ """
-+ Decorator wrapper for retry_call. Accepts arguments to retry_call
-+ except func and then returns a decorator for the decorated function.
-+
-+ Ex:
-+
-+ >>> @retry(retries=3)
-+ ... def my_func(a, b):
-+ ... "this is my funk"
-+ ... print(a, b)
-+ >>> my_func.__doc__
-+ 'this is my funk'
-+ """
-+
-+ def decorate(func):
-+ @functools.wraps(func)
-+ def wrapper(*f_args, **f_kwargs):
-+ bound = functools.partial(func, *f_args, **f_kwargs)
-+ return retry_call(bound, *r_args, **r_kwargs)
-+
-+ return wrapper
-+
-+ return decorate
-+
-+
-+def print_yielded(func):
-+ """
-+ Convert a generator into a function that prints all yielded elements
-+
-+ >>> @print_yielded
-+ ... def x():
-+ ... yield 3; yield None
-+ >>> x()
-+ 3
-+ None
-+ """
-+ print_all = functools.partial(map, print)
-+ print_results = compose(more_itertools.consume, print_all, func)
-+ return functools.wraps(func)(print_results)
-+
-+
-+def pass_none(func):
-+ """
-+ Wrap func so it's not called if its first param is None
-+
-+ >>> print_text = pass_none(print)
-+ >>> print_text('text')
-+ text
-+ >>> print_text(None)
-+ """
-+
-+ @functools.wraps(func)
-+ def wrapper(param, *args, **kwargs):
-+ if param is not None:
-+ return func(param, *args, **kwargs)
-+
-+ return wrapper
-+
-+
-+def assign_params(func, namespace):
-+ """
-+ Assign parameters from namespace where func solicits.
-+
-+ >>> def func(x, y=3):
-+ ... print(x, y)
-+ >>> assigned = assign_params(func, dict(x=2, z=4))
-+ >>> assigned()
-+ 2 3
-+
-+ The usual errors are raised if a function doesn't receive
-+ its required parameters:
-+
-+ >>> assigned = assign_params(func, dict(y=3, z=4))
-+ >>> assigned()
-+ Traceback (most recent call last):
-+ TypeError: func() ...argument...
-+
-+ It even works on methods:
-+
-+ >>> class Handler:
-+ ... def meth(self, arg):
-+ ... print(arg)
-+ >>> assign_params(Handler().meth, dict(arg='crystal', foo='clear'))()
-+ crystal
-+ """
-+ sig = inspect.signature(func)
-+ params = sig.parameters.keys()
-+ call_ns = {k: namespace[k] for k in params if k in namespace}
-+ return functools.partial(func, **call_ns)
-+
-+
-+def save_method_args(method):
-+ """
-+ Wrap a method such that when it is called, the args and kwargs are
-+ saved on the method.
-+
-+ >>> class MyClass:
-+ ... @save_method_args
-+ ... def method(self, a, b):
-+ ... print(a, b)
-+ >>> my_ob = MyClass()
-+ >>> my_ob.method(1, 2)
-+ 1 2
-+ >>> my_ob._saved_method.args
-+ (1, 2)
-+ >>> my_ob._saved_method.kwargs
-+ {}
-+ >>> my_ob.method(a=3, b='foo')
-+ 3 foo
-+ >>> my_ob._saved_method.args
-+ ()
-+ >>> my_ob._saved_method.kwargs == dict(a=3, b='foo')
-+ True
-+
-+ The arguments are stored on the instance, allowing for
-+ different instance to save different args.
-+
-+ >>> your_ob = MyClass()
-+ >>> your_ob.method({str('x'): 3}, b=[4])
-+ {'x': 3} [4]
-+ >>> your_ob._saved_method.args
-+ ({'x': 3},)
-+ >>> my_ob._saved_method.args
-+ ()
-+ """
-+ args_and_kwargs = collections.namedtuple('args_and_kwargs', 'args
kwargs')
-+
-+ @functools.wraps(method)
-+ def wrapper(self, *args, **kwargs):
-+ attr_name = '_saved_' + method.__name__
-+ attr = args_and_kwargs(args, kwargs)
-+ setattr(self, attr_name, attr)
-+ return method(self, *args, **kwargs)
-+
-+ return wrapper
-+
-+
-+def except_(*exceptions, replace=None, use=None):
-+ """
-+ Replace the indicated exceptions, if raised, with the indicated
-+ literal replacement or evaluated expression (if present).
-+
-+ >>> safe_int = except_(ValueError)(int)
-+ >>> safe_int('five')
-+ >>> safe_int('5')
-+ 5
-+
-+ Specify a literal replacement with ``replace``.
-+
-+ >>> safe_int_r = except_(ValueError, replace=0)(int)
-+ >>> safe_int_r('five')
-+ 0
-+
-+ Provide an expression to ``use`` to pass through particular parameters.
-+
-+ >>> safe_int_pt = except_(ValueError, use='args[0]')(int)
-+ >>> safe_int_pt('five')
-+ 'five'
-+
-+ """
-+
-+ def decorate(func):
-+ @functools.wraps(func)
-+ def wrapper(*args, **kwargs):
-+ try:
-+ return func(*args, **kwargs)
-+ except exceptions:
-+ try:
-+ return eval(use)
-+ except TypeError:
-+ return replace
-+
-+ return wrapper
-+
-+ return decorate
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/jaraco/text/__init__.py
b/third_party/python/setuptools/setuptools/_vendor/jaraco/text/__init__.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/_vendor/jaraco/text/__init__.py
-@@ -0,0 +1,599 @@
-+import re
-+import itertools
-+import textwrap
-+import functools
-+
-+try:
-+ from importlib.resources import files # type: ignore
-+except ImportError: # pragma: nocover
-+ from setuptools.extern.importlib_resources import files # type: ignore
-+
-+from setuptools.extern.jaraco.functools import compose, method_cache
-+from setuptools.extern.jaraco.context import ExceptionTrap
-+
-+
-+def substitution(old, new):
-+ """
-+ Return a function that will perform a substitution on a string
-+ """
-+ return lambda s: s.replace(old, new)
-+
-+
-+def multi_substitution(*substitutions):
-+ """
-+ Take a sequence of pairs specifying substitutions, and create
-+ a function that performs those substitutions.
-+
-+ >>> multi_substitution(('foo', 'bar'), ('bar', 'baz'))('foo')
-+ 'baz'
-+ """
-+ substitutions = itertools.starmap(substitution, substitutions)
-+ # compose function applies last function first, so reverse the
-+ # substitutions to get the expected order.
-+ substitutions = reversed(tuple(substitutions))
-+ return compose(*substitutions)
-+
-+
-+class FoldedCase(str):
-+ """
-+ A case insensitive string class; behaves just like str
-+ except compares equal when the only variation is case.
-+
-+ >>> s = FoldedCase('hello world')
-+
-+ >>> s == 'Hello World'
-+ True
-+
-+ >>> 'Hello World' == s
-+ True
-+
-+ >>> s != 'Hello World'
-+ False
-+
-+ >>> s.index('O')
-+ 4
-+
-+ >>> s.split('O')
-+ ['hell', ' w', 'rld']
-+
-+ >>> sorted(map(FoldedCase, ['GAMMA', 'alpha', 'Beta']))
-+ ['alpha', 'Beta', 'GAMMA']
-+
-+ Sequence membership is straightforward.
-+
-+ >>> "Hello World" in [s]
-+ True
-+ >>> s in ["Hello World"]
-+ True
-+
-+ You may test for set inclusion, but candidate and elements
-+ must both be folded.
-+
-+ >>> FoldedCase("Hello World") in {s}
-+ True
-+ >>> s in {FoldedCase("Hello World")}
-+ True
-+
-+ String inclusion works as long as the FoldedCase object
-+ is on the right.
-+
-+ >>> "hello" in FoldedCase("Hello World")
-+ True
-+
-+ But not if the FoldedCase object is on the left:
-+
-+ >>> FoldedCase('hello') in 'Hello World'
-+ False
-+
-+ In that case, use ``in_``:
-+
-+ >>> FoldedCase('hello').in_('Hello World')
-+ True
-+
-+ >>> FoldedCase('hello') > FoldedCase('Hello')
-+ False
-+ """
-+
-+ def __lt__(self, other):
-+ return self.lower() < other.lower()
-+
-+ def __gt__(self, other):
-+ return self.lower() > other.lower()
-+
-+ def __eq__(self, other):
-+ return self.lower() == other.lower()
-+
-+ def __ne__(self, other):
-+ return self.lower() != other.lower()
-+
-+ def __hash__(self):
-+ return hash(self.lower())
-+
-+ def __contains__(self, other):
-+ return super().lower().__contains__(other.lower())
-+
-+ def in_(self, other):
-+ "Does self appear in other?"
-+ return self in FoldedCase(other)
-+
-+ # cache lower since it's likely to be called frequently.
-+ @method_cache
-+ def lower(self):
-+ return super().lower()
-+
-+ def index(self, sub):
-+ return self.lower().index(sub.lower())
-+
-+ def split(self, splitter=' ', maxsplit=0):
-+ pattern = re.compile(re.escape(splitter), re.I)
-+ return pattern.split(self, maxsplit)
-+
-+
-+# Python 3.8 compatibility
-+_unicode_trap = ExceptionTrap(UnicodeDecodeError)
-+
-+
-+@_unicode_trap.passes
-+def is_decodable(value):
-+ r"""
-+ Return True if the supplied value is decodable (using the default
-+ encoding).
-+
-+ >>> is_decodable(b'\xff')
-+ False
-+ >>> is_decodable(b'\x32')
-+ True
-+ """
-+ value.decode()
-+
-+
-+def is_binary(value):
-+ r"""
-+ Return True if the value appears to be binary (that is, it's a byte
-+ string and isn't decodable).
-+
-+ >>> is_binary(b'\xff')
-+ True
-+ >>> is_binary('\xff')
-+ False
-+ """
-+ return isinstance(value, bytes) and not is_decodable(value)
-+
-+
-+def trim(s):
-+ r"""
-+ Trim something like a docstring to remove the whitespace that
-+ is common due to indentation and formatting.
-+
-+ >>> trim("\n\tfoo = bar\n\t\tbar = baz\n")
-+ 'foo = bar\n\tbar = baz'
-+ """
-+ return textwrap.dedent(s).strip()
-+
-+
-+def wrap(s):
-+ """
-+ Wrap lines of text, retaining existing newlines as
-+ paragraph markers.
-+
-+ >>> print(wrap(lorem_ipsum))
-+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
-+ eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
-+ minim veniam, quis nostrud exercitation ullamco laboris nisi ut
-+ aliquip ex ea commodo consequat. Duis aute irure dolor in
-+ reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
-+ pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
-+ culpa qui officia deserunt mollit anim id est laborum.
-+ <BLANKLINE>
-+ Curabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam
-+ varius, turpis et commodo pharetra, est eros bibendum elit, nec luctus
-+ magna felis sollicitudin mauris. Integer in mauris eu nibh euismod
-+ gravida. Duis ac tellus et risus vulputate vehicula. Donec lobortis
-+ risus a elit. Etiam tempor. Ut ullamcorper, ligula eu tempor congue,
-+ eros est euismod turpis, id tincidunt sapien risus a quam. Maecenas
-+ fermentum consequat mi. Donec fermentum. Pellentesque malesuada nulla
-+ a mi. Duis sapien sem, aliquet nec, commodo eget, consequat quis,
-+ neque. Aliquam faucibus, elit ut dictum aliquet, felis nisl adipiscing
-+ sapien, sed malesuada diam lacus eget erat. Cras mollis scelerisque
-+ nunc. Nullam arcu. Aliquam consequat. Curabitur augue lorem, dapibus
-+ quis, laoreet et, pretium ac, nisi. Aenean magna nisl, mollis quis,
-+ molestie eu, feugiat in, orci. In hac habitasse platea dictumst.
-+ """
-+ paragraphs = s.splitlines()
-+ wrapped = ('\n'.join(textwrap.wrap(para)) for para in paragraphs)
-+ return '\n\n'.join(wrapped)
-+
-+
-+def unwrap(s):
-+ r"""
-+ Given a multi-line string, return an unwrapped version.
-+
-+ >>> wrapped = wrap(lorem_ipsum)
-+ >>> wrapped.count('\n')
-+ 20
-+ >>> unwrapped = unwrap(wrapped)
-+ >>> unwrapped.count('\n')
-+ 1
-+ >>> print(unwrapped)
-+ Lorem ipsum dolor sit amet, consectetur adipiscing ...
-+ Curabitur pretium tincidunt lacus. Nulla gravida orci ...
-+
-+ """
-+ paragraphs = re.split(r'\n\n+', s)
-+ cleaned = (para.replace('\n', ' ') for para in paragraphs)
-+ return '\n'.join(cleaned)
-+
-+
-+
-+
-+class Splitter(object):
-+ """object that will split a string with the given arguments for each
call
-+
-+ >>> s = Splitter(',')
-+ >>> s('hello, world, this is your, master calling')
-+ ['hello', ' world', ' this is your', ' master calling']
-+ """
-+
-+ def __init__(self, *args):
-+ self.args = args
-+
-+ def __call__(self, s):
-+ return s.split(*self.args)
-+
-+
-+def indent(string, prefix=' ' * 4):
-+ """
-+ >>> indent('foo')
-+ ' foo'
-+ """
-+ return prefix + string
-+
-+
-+class WordSet(tuple):
-+ """
-+ Given an identifier, return the words that identifier represents,
-+ whether in camel case, underscore-separated, etc.
-+
-+ >>> WordSet.parse("camelCase")
-+ ('camel', 'Case')
-+
-+ >>> WordSet.parse("under_sep")
-+ ('under', 'sep')
-+
-+ Acronyms should be retained
-+
-+ >>> WordSet.parse("firstSNL")
-+ ('first', 'SNL')
-+
-+ >>> WordSet.parse("you_and_I")
-+ ('you', 'and', 'I')
-+
-+ >>> WordSet.parse("A simple test")
-+ ('A', 'simple', 'test')
-+
-+ Multiple caps should not interfere with the first cap of another word.
-+
-+ >>> WordSet.parse("myABCClass")
-+ ('my', 'ABC', 'Class')
-+
-+ The result is a WordSet, so you can get the form you need.
-+
-+ >>> WordSet.parse("myABCClass").underscore_separated()
-+ 'my_ABC_Class'
-+
-+ >>> WordSet.parse('a-command').camel_case()
-+ 'ACommand'
-+
-+ >>> WordSet.parse('someIdentifier').lowered().space_separated()
-+ 'some identifier'
-+
-+ Slices of the result should return another WordSet.
-+
-+ >>> WordSet.parse('taken-out-of-context')[1:].underscore_separated()
-+ 'out_of_context'
-+
-+ >>> WordSet.from_class_name(WordSet()).lowered().space_separated()
-+ 'word set'
-+
-+ >>> example = WordSet.parse('figured it out')
-+ >>> example.headless_camel_case()
-+ 'figuredItOut'
-+ >>> example.dash_separated()
-+ 'figured-it-out'
-+
-+ """
-+
-+ _pattern = re.compile('([A-Z]?[a-z]+)|([A-Z]+(?![a-z]))')
-+
-+ def capitalized(self):
-+ return WordSet(word.capitalize() for word in self)
-+
-+ def lowered(self):
-+ return WordSet(word.lower() for word in self)
-+
-+ def camel_case(self):
-+ return ''.join(self.capitalized())
-+
-+ def headless_camel_case(self):
-+ words = iter(self)
-+ first = next(words).lower()
-+ new_words = itertools.chain((first,), WordSet(words).camel_case())
-+ return ''.join(new_words)
-+
-+ def underscore_separated(self):
-+ return '_'.join(self)
-+
-+ def dash_separated(self):
-+ return '-'.join(self)
-+
-+ def space_separated(self):
-+ return ' '.join(self)
-+
-+ def trim_right(self, item):
-+ """
-+ Remove the item from the end of the set.
-+
-+ >>> WordSet.parse('foo bar').trim_right('foo')
-+ ('foo', 'bar')
-+ >>> WordSet.parse('foo bar').trim_right('bar')
-+ ('foo',)
-+ >>> WordSet.parse('').trim_right('bar')
-+ ()
-+ """
-+ return self[:-1] if self and self[-1] == item else self
-+
-+ def trim_left(self, item):
-+ """
-+ Remove the item from the beginning of the set.
-+
-+ >>> WordSet.parse('foo bar').trim_left('foo')
-+ ('bar',)
-+ >>> WordSet.parse('foo bar').trim_left('bar')
-+ ('foo', 'bar')
-+ >>> WordSet.parse('').trim_left('bar')
-+ ()
-+ """
-+ return self[1:] if self and self[0] == item else self
-+
-+ def trim(self, item):
-+ """
-+ >>> WordSet.parse('foo bar').trim('foo')
-+ ('bar',)
-+ """
-+ return self.trim_left(item).trim_right(item)
-+
-+ def __getitem__(self, item):
-+ result = super(WordSet, self).__getitem__(item)
-+ if isinstance(item, slice):
-+ result = WordSet(result)
-+ return result
-+
-+ @classmethod
-+ def parse(cls, identifier):
-+ matches = cls._pattern.finditer(identifier)
-+ return WordSet(match.group(0) for match in matches)
-+
-+ @classmethod
-+ def from_class_name(cls, subject):
-+ return cls.parse(subject.__class__.__name__)
-+
-+
-+# for backward compatibility
-+words = WordSet.parse
-+
-+
-+def simple_html_strip(s):
-+ r"""
-+ Remove HTML from the string `s`.
-+
-+ >>> str(simple_html_strip(''))
-+ ''
-+
-+ >>> print(simple_html_strip('A <bold>stormy</bold> day in paradise'))
-+ A stormy day in paradise
-+
-+ >>> print(simple_html_strip('Somebody <!-- do not --> tell the truth.'))
-+ Somebody tell the truth.
-+
-+ >>> print(simple_html_strip('What about<br/>\nmultiple lines?'))
-+ What about
-+ multiple lines?
-+ """
-+ html_stripper = re.compile('(<!--.*?-->)|(<[^>]*>)|([^<]+)', re.DOTALL)
-+ texts = (match.group(3) or '' for match in html_stripper.finditer(s))
-+ return ''.join(texts)
-+
-+
-+class SeparatedValues(str):
-+ """
-+ A string separated by a separator. Overrides __iter__ for getting
-+ the values.
-+
-+ >>> list(SeparatedValues('a,b,c'))
-+ ['a', 'b', 'c']
-+
-+ Whitespace is stripped and empty values are discarded.
-+
-+ >>> list(SeparatedValues(' a, b , c, '))
-+ ['a', 'b', 'c']
-+ """
-+
-+ separator = ','
-+
-+ def __iter__(self):
-+ parts = self.split(self.separator)
-+ return filter(None, (part.strip() for part in parts))
-+
-+
-+class Stripper:
-+ r"""
-+ Given a series of lines, find the common prefix and strip it from them.
-+
-+ >>> lines = [
-+ ... 'abcdefg\n',
-+ ... 'abc\n',
-+ ... 'abcde\n',
-+ ... ]
-+ >>> res = Stripper.strip_prefix(lines)
-+ >>> res.prefix
-+ 'abc'
-+ >>> list(res.lines)
-+ ['defg\n', '\n', 'de\n']
-+
-+ If no prefix is common, nothing should be stripped.
-+
-+ >>> lines = [
-+ ... 'abcd\n',
-+ ... '1234\n',
-+ ... ]
-+ >>> res = Stripper.strip_prefix(lines)
-+ >>> res.prefix = ''
-+ >>> list(res.lines)
-+ ['abcd\n', '1234\n']
-+ """
-+
-+ def __init__(self, prefix, lines):
-+ self.prefix = prefix
-+ self.lines = map(self, lines)
-+
-+ @classmethod
-+ def strip_prefix(cls, lines):
-+ prefix_lines, lines = itertools.tee(lines)
-+ prefix = functools.reduce(cls.common_prefix, prefix_lines)
-+ return cls(prefix, lines)
-+
-+ def __call__(self, line):
-+ if not self.prefix:
-+ return line
-+ null, prefix, rest = line.partition(self.prefix)
-+ return rest
-+
-+ @staticmethod
-+ def common_prefix(s1, s2):
-+ """
-+ Return the common prefix of two lines.
-+ """
-+ index = min(len(s1), len(s2))
-+ while s1[:index] != s2[:index]:
-+ index -= 1
-+ return s1[:index]
-+
-+
-+def remove_prefix(text, prefix):
-+ """
-+ Remove the prefix from the text if it exists.
-+
-+ >>> remove_prefix('underwhelming performance', 'underwhelming ')
-+ 'performance'
-+
-+ >>> remove_prefix('something special', 'sample')
-+ 'something special'
-+ """
-+ null, prefix, rest = text.rpartition(prefix)
-+ return rest
-+
-+
-+def remove_suffix(text, suffix):
-+ """
-+ Remove the suffix from the text if it exists.
-+
-+ >>> remove_suffix('name.git', '.git')
-+ 'name'
-+
-+ >>> remove_suffix('something special', 'sample')
-+ 'something special'
-+ """
-+ rest, suffix, null = text.partition(suffix)
-+ return rest
-+
-+
-+def normalize_newlines(text):
-+ r"""
-+ Replace alternate newlines with the canonical newline.
-+
-+ >>> normalize_newlines('Lorem Ipsum\u2029')
-+ 'Lorem Ipsum\n'
-+ >>> normalize_newlines('Lorem Ipsum\r\n')
-+ 'Lorem Ipsum\n'
-+ >>> normalize_newlines('Lorem Ipsum\x85')
-+ 'Lorem Ipsum\n'
-+ """
-+ newlines = ['\r\n', '\r', '\n', '\u0085', '\u2028', '\u2029']
-+ pattern = '|'.join(newlines)
-+ return re.sub(pattern, '\n', text)
-+
-+
-+def _nonblank(str):
-+ return str and not str.startswith('#')
-+
-+
-+@functools.singledispatch
-+def yield_lines(iterable):
-+ r"""
-+ Yield valid lines of a string or iterable.
-+
-+ >>> list(yield_lines(''))
-+ []
-+ >>> list(yield_lines(['foo', 'bar']))
-+ ['foo', 'bar']
-+ >>> list(yield_lines('foo\nbar'))
-+ ['foo', 'bar']
-+ >>> list(yield_lines('\nfoo\n#bar\nbaz #comment'))
-+ ['foo', 'baz #comment']
-+ >>> list(yield_lines(['foo\nbar', 'baz', 'bing\n\n\n']))
-+ ['foo', 'bar', 'baz', 'bing']
-+ """
-+ return itertools.chain.from_iterable(map(yield_lines, iterable))
-+
-+
-+@yield_lines.register(str)
-+def _(text):
-+ return filter(_nonblank, map(str.strip, text.splitlines()))
-+
-+
-+def drop_comment(line):
-+ """
-+ Drop comments.
-+
-+ >>> drop_comment('foo # bar')
-+ 'foo'
-+
-+ A hash without a space may be in a URL.
-+
-+ >>> drop_comment('http://example.com/foo#bar')
-+ 'http://example.com/foo#bar'
-+ """
-+ return line.partition(' #')[0]
-+
-+
-+def join_continuation(lines):
-+ r"""
-+ Join lines continued by a trailing backslash.
-+
-+ >>> list(join_continuation(['foo \\', 'bar', 'baz']))
-+ ['foobar', 'baz']
-+ >>> list(join_continuation(['foo \\', 'bar', 'baz']))
-+ ['foobar', 'baz']
-+ >>> list(join_continuation(['foo \\', 'bar \\', 'baz']))
-+ ['foobarbaz']
-+
-+ Not sure why, but...
-+ The character preceeding the backslash is also elided.
-+
-+ >>> list(join_continuation(['goo\\', 'dly']))
-+ ['godly']
-+
-+ A terrible idea, but...
-+ If no line is available to continue, suppress the lines.
-+
-+ >>> list(join_continuation(['foo', 'bar\\', 'baz\\']))
-+ ['foo']
-+ """
-+ lines = iter(lines)
-+ for item in lines:
-+ while item.endswith('\\'):
-+ try:
-+ item = item[:-2].strip() + next(lines)
-+ except StopIteration:
-+ return
-+ yield item
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/more_itertools/__init__.py
b/third_party/python/setuptools/setuptools/_vendor/more_itertools/__init__.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/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/third_party/python/setuptools/setuptools/_vendor/more_itertools/more.py
b/third_party/python/setuptools/setuptools/_vendor/more_itertools/more.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/_vendor/more_itertools/more.py
-@@ -0,0 +1,3824 @@
-+import warnings
-+
-+from collections import Counter, defaultdict, deque, abc
-+from collections.abc import Sequence
-+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 =
__import__("concurrent.futures").futures.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/third_party/python/setuptools/setuptools/_vendor/more_itertools/recipes.py
b/third_party/python/setuptools/setuptools/_vendor/more_itertools/recipes.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/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 "<stdin>", line 1, in <module>
-+ 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 "<stdin>", line 1, in <module>
-+ 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/third_party/python/setuptools/setuptools/_vendor/packaging/__about__.py
b/third_party/python/setuptools/setuptools/_vendor/packaging/__about__.py
-deleted file mode 100644
---- a/third_party/python/setuptools/setuptools/_vendor/packaging/__about__.py
-+++ /dev/null
-@@ -1,27 +0,0 @@
--# 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 __future__ import absolute_import, division, print_function
--
--__all__ = [
-- "__title__",
-- "__summary__",
-- "__uri__",
-- "__version__",
-- "__author__",
-- "__email__",
-- "__license__",
-- "__copyright__",
--]
--
--__title__ = "packaging"
--__summary__ = "Core utilities for Python packages"
--__uri__ = "https://github.com/pypa/packaging";
--
--__version__ = "20.4"
--
--__author__ = "Donald Stufft and individual contributors"
--__email__ = "donald AT stufft.io"
--
--__license__ = "BSD-2-Clause or Apache-2.0"
--__copyright__ = "Copyright 2014-2019 %s" % __author__
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/packaging/__init__.py
b/third_party/python/setuptools/setuptools/_vendor/packaging/__init__.py
---- a/third_party/python/setuptools/setuptools/_vendor/packaging/__init__.py
-+++ b/third_party/python/setuptools/setuptools/_vendor/packaging/__init__.py
-@@ -1,26 +1,15 @@
- # 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 __future__ import absolute_import, division, print_function
-+
-+__title__ = "packaging"
-+__summary__ = "Core utilities for Python packages"
-+__uri__ = "https://github.com/pypa/packaging";
-
--from .__about__ import (
-- __author__,
-- __copyright__,
-- __email__,
-- __license__,
-- __summary__,
-- __title__,
-- __uri__,
-- __version__,
--)
-+__version__ = "23.1"
-
--__all__ = [
-- "__title__",
-- "__summary__",
-- "__uri__",
-- "__version__",
-- "__author__",
-- "__email__",
-- "__license__",
-- "__copyright__",
--]
-+__author__ = "Donald Stufft and individual contributors"
-+__email__ = "donald AT stufft.io"
-+
-+__license__ = "BSD-2-Clause or Apache-2.0"
-+__copyright__ = "2014-2019 %s" % __author__
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/packaging/_compat.py
b/third_party/python/setuptools/setuptools/_vendor/packaging/_compat.py
-deleted file mode 100644
---- a/third_party/python/setuptools/setuptools/_vendor/packaging/_compat.py
-+++ /dev/null
-@@ -1,38 +0,0 @@
--# 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 __future__ import absolute_import, division, print_function
--
--import sys
--
--from ._typing import TYPE_CHECKING
--
--if TYPE_CHECKING: # pragma: no cover
-- from typing import Any, Dict, Tuple, Type
--
--
--PY2 = sys.version_info[0] == 2
--PY3 = sys.version_info[0] == 3
--
--# flake8: noqa
--
--if PY3:
-- string_types = (str,)
--else:
-- string_types = (basestring,)
--
--
--def with_metaclass(meta, *bases):
-- # type: (Type[Any], Tuple[Type[Any], ...]) -> Any
-- """
-- Create a base class with a metaclass.
-- """
-- # This requires a bit of explanation: the basic idea is to make a dummy
-- # metaclass for one level of class instantiation that replaces itself
with
-- # the actual metaclass.
-- class metaclass(meta): # type: ignore
-- def __new__(cls, name, this_bases, d):
-- # type: (Type[Any], str, Tuple[Any], Dict[Any, Any]) -> Any
-- return meta(name, bases, d)
--
-- return type.__new__(metaclass, "temporary_class", (), {})
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/packaging/_elffile.py
b/third_party/python/setuptools/setuptools/_vendor/packaging/_elffile.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/_vendor/packaging/_elffile.py
-@@ -0,0 +1,108 @@
-+"""
-+ELF file parser.
-+
-+This provides a class ``ELFFile`` that parses an ELF executable in a similar
-+interface to ``ZipFile``. Only the read interface is implemented.
-+
-+Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca
-+ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html
-+"""
-+
-+import enum
-+import os
-+import struct
-+from typing import IO, Optional, Tuple
-+
-+
-+class ELFInvalid(ValueError):
-+ pass
-+
-+
-+class EIClass(enum.IntEnum):
-+ C32 = 1
-+ C64 = 2
-+
-+
-+class EIData(enum.IntEnum):
-+ Lsb = 1
-+ Msb = 2
-+
-+
-+class EMachine(enum.IntEnum):
-+ I386 = 3
-+ S390 = 22
-+ Arm = 40
-+ X8664 = 62
-+ AArc64 = 183
-+
-+
-+class ELFFile:
-+ """
-+ Representation of an ELF executable.
-+ """
-+
-+ def __init__(self, f: IO[bytes]) -> None:
-+ self._f = f
-+
-+ try:
-+ ident = self._read("16B")
-+ except struct.error:
-+ raise ELFInvalid("unable to parse identification")
-+ magic = bytes(ident[:4])
-+ if magic != b"\x7fELF":
-+ raise ELFInvalid(f"invalid magic: {magic!r}")
-+
-+ self.capacity = ident[4] # Format for program header (bitness).
-+ self.encoding = ident[5] # Data structure encoding (endianness).
-+
-+ 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, self._p_fmt, self._p_idx = {
-+ (1, 1): ("<HHIIIIIHHH", "<IIIIIIII", (0, 1, 4)), # 32-bit
LSB.
-+ (1, 2): (">HHIIIIIHHH", ">IIIIIIII", (0, 1, 4)), # 32-bit
MSB.
-+ (2, 1): ("<HHIQQQIHHH", "<IIQQQQQQ", (0, 2, 5)), # 64-bit
LSB.
-+ (2, 2): (">HHIQQQIHHH", ">IIQQQQQQ", (0, 2, 5)), # 64-bit
MSB.
-+ }[(self.capacity, self.encoding)]
-+ except KeyError:
-+ raise ELFInvalid(
-+ f"unrecognized capacity ({self.capacity}) or "
-+ f"encoding ({self.encoding})"
-+ )
-+
-+ try:
-+ (
-+ _,
-+ self.machine, # Architecture type.
-+ _,
-+ _,
-+ self._e_phoff, # Offset of program header.
-+ _,
-+ self.flags, # Processor-specific flags.
-+ _,
-+ self._e_phentsize, # Size of section.
-+ self._e_phnum, # Number of sections.
-+ ) = self._read(e_fmt)
-+ except struct.error as e:
-+ raise ELFInvalid("unable to parse machine and section
information") from e
-+
-+ def _read(self, fmt: str) -> Tuple[int, ...]:
-+ return struct.unpack(fmt, self._f.read(struct.calcsize(fmt)))
-+
-+ @property
-+ def interpreter(self) -> Optional[str]:
-+ """
-+ The path recorded in the ``PT_INTERP`` section header.
-+ """
-+ for index in range(self._e_phnum):
-+ self._f.seek(self._e_phoff + self._e_phentsize * index)
-+ try:
-+ data = self._read(self._p_fmt)
-+ except struct.error:
-+ continue
-+ if data[self._p_idx[0]] != 3: # Not PT_INTERP.
-+ continue
-+ self._f.seek(data[self._p_idx[1]])
-+ return
os.fsdecode(self._f.read(data[self._p_idx[2]])).strip("\0")
-+ return None
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/packaging/_manylinux.py
b/third_party/python/setuptools/setuptools/_vendor/packaging/_manylinux.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/_vendor/packaging/_manylinux.py
-@@ -0,0 +1,240 @@
-+import collections
-+import contextlib
-+import functools
-+import os
-+import re
-+import sys
-+import warnings
-+from typing import Dict, Generator, Iterator, NamedTuple, Optional, Tuple
-+
-+from ._elffile import EIClass, EIData, ELFFile, EMachine
-+
-+EF_ARM_ABIMASK = 0xFF000000
-+EF_ARM_ABI_VER5 = 0x05000000
-+EF_ARM_ABI_FLOAT_HARD = 0x00000400
-+
-+
-+# `os.PathLike` not a generic type until Python 3.9, so sticking with `str`
-+# as the type for `path` until then.
-+@contextlib.contextmanager
-+def _parse_elf(path: str) -> Generator[Optional[ELFFile], None, None]:
-+ try:
-+ with open(path, "rb") as f:
-+ yield ELFFile(f)
-+ except (OSError, TypeError, ValueError):
-+ yield None
-+
-+
-+def _is_linux_armhf(executable: str) -> bool:
-+ # hard-float ABI can be detected from the ELF header of the running
-+ # process
-+ # https://static.docs.arm.com/ihi0044/g/aaelf32.pdf
-+ with _parse_elf(executable) as f:
-+ return (
-+ f is not None
-+ and f.capacity == EIClass.C32
-+ and f.encoding == EIData.Lsb
-+ and f.machine == EMachine.Arm
-+ and f.flags & EF_ARM_ABIMASK == EF_ARM_ABI_VER5
-+ and f.flags & EF_ARM_ABI_FLOAT_HARD == EF_ARM_ABI_FLOAT_HARD
-+ )
-+
-+
-+def _is_linux_i686(executable: str) -> bool:
-+ with _parse_elf(executable) as f:
-+ return (
-+ f is not None
-+ and f.capacity == EIClass.C32
-+ and f.encoding == EIData.Lsb
-+ and f.machine == EMachine.I386
-+ )
-+
-+
-+def _have_compatible_abi(executable: str, arch: str) -> bool:
-+ if arch == "armv7l":
-+ return _is_linux_armhf(executable)
-+ if arch == "i686":
-+ return _is_linux_i686(executable)
-+ 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:
-+ # Should be a string like "glibc 2.17".
-+ version_string: str = getattr(os, "confstr")("CS_GNU_LIBC_VERSION")
-+ assert version_string is not None
-+ _, version = version_string.rsplit()
-+ 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<major>[0-9]+)\.(?P<minor>[0-9]+)", version_str)
-+ if not m:
-+ warnings.warn(
-+ f"Expected glibc version with 2 components major.minor,"
-+ f" got: {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(sys.executable, 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/third_party/python/setuptools/setuptools/_vendor/packaging/_musllinux.py
b/third_party/python/setuptools/setuptools/_vendor/packaging/_musllinux.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/_vendor/packaging/_musllinux.py
-@@ -0,0 +1,80 @@
-+"""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 functools
-+import re
-+import subprocess
-+import sys
-+from typing import Iterator, NamedTuple, Optional
-+
-+from ._elffile import ELFFile
-+
-+
-+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
-+ """
-+ try:
-+ with open(executable, "rb") as f:
-+ ld = ELFFile(f).interpreter
-+ except (OSError, TypeError, ValueError):
-+ return None
-+ if ld is None or "musl" not in 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/third_party/python/setuptools/setuptools/_vendor/packaging/_parser.py
b/third_party/python/setuptools/setuptools/_vendor/packaging/_parser.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/_vendor/packaging/_parser.py
-@@ -0,0 +1,353 @@
-+"""Handwritten parser of dependency specifiers.
-+
-+The docstring for each __parse_* function contains ENBF-inspired grammar
representing
-+the implementation.
-+"""
-+
-+import ast
-+from typing import Any, List, NamedTuple, Optional, Tuple, Union
-+
-+from ._tokenizer import DEFAULT_RULES, Tokenizer
-+
-+
-+class Node:
-+ def __init__(self, value: str) -> None:
-+ self.value = value
-+
-+ def __str__(self) -> str:
-+ return 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)
-+
-+
-+MarkerVar = Union[Variable, Value]
-+MarkerItem = Tuple[MarkerVar, Op, MarkerVar]
-+# MarkerAtom = Union[MarkerItem, List["MarkerAtom"]]
-+# MarkerList = List[Union["MarkerList", MarkerAtom, str]]
-+# mypy does not support recursive type definition
-+# https://github.com/python/mypy/issues/731
-+MarkerAtom = Any
-+MarkerList = List[Any]
-+
-+
-+class ParsedRequirement(NamedTuple):
-+ name: str
-+ url: str
-+ extras: List[str]
-+ specifier: str
-+ marker: Optional[MarkerList]
-+
-+
-+#
--------------------------------------------------------------------------------------
-+# Recursive descent parser for dependency specifier
-+#
--------------------------------------------------------------------------------------
-+def parse_requirement(source: str) -> ParsedRequirement:
-+ return _parse_requirement(Tokenizer(source, rules=DEFAULT_RULES))
-+
-+
-+def _parse_requirement(tokenizer: Tokenizer) -> ParsedRequirement:
-+ """
-+ requirement = WS? IDENTIFIER WS? extras WS? requirement_details
-+ """
-+ tokenizer.consume("WS")
-+
-+ name_token = tokenizer.expect(
-+ "IDENTIFIER", expected="package name at the start of dependency
specifier"
-+ )
-+ name = name_token.text
-+ tokenizer.consume("WS")
-+
-+ extras = _parse_extras(tokenizer)
-+ tokenizer.consume("WS")
-+
-+ url, specifier, marker = _parse_requirement_details(tokenizer)
-+ tokenizer.expect("END", expected="end of dependency specifier")
-+
-+ return ParsedRequirement(name, url, extras, specifier, marker)
-+
-+
-+def _parse_requirement_details(
-+ tokenizer: Tokenizer,
-+) -> Tuple[str, str, Optional[MarkerList]]:
-+ """
-+ requirement_details = AT URL (WS requirement_marker?)?
-+ | specifier WS? (requirement_marker)?
-+ """
-+
-+ specifier = ""
-+ url = ""
-+ marker = None
-+
-+ if tokenizer.check("AT"):
-+ tokenizer.read()
-+ tokenizer.consume("WS")
-+
-+ url_start = tokenizer.position
-+ url = tokenizer.expect("URL", expected="URL after @").text
-+ if tokenizer.check("END", peek=True):
-+ return (url, specifier, marker)
-+
-+ tokenizer.expect("WS", expected="whitespace after URL")
-+
-+ # The input might end after whitespace.
-+ if tokenizer.check("END", peek=True):
-+ return (url, specifier, marker)
-+
-+ marker = _parse_requirement_marker(
-+ tokenizer, span_start=url_start, after="URL and whitespace"
-+ )
-+ else:
-+ specifier_start = tokenizer.position
-+ specifier = _parse_specifier(tokenizer)
-+ tokenizer.consume("WS")
-+
-+ if tokenizer.check("END", peek=True):
-+ return (url, specifier, marker)
-+
-+ marker = _parse_requirement_marker(
-+ tokenizer,
-+ span_start=specifier_start,
-+ after=(
-+ "version specifier"
-+ if specifier
-+ else "name and no valid version specifier"
-+ ),
-+ )
-+
-+ return (url, specifier, marker)
-+
-+
-+def _parse_requirement_marker(
-+ tokenizer: Tokenizer, *, span_start: int, after: str
-+) -> MarkerList:
-+ """
-+ requirement_marker = SEMICOLON marker WS?
-+ """
-+
-+ if not tokenizer.check("SEMICOLON"):
-+ tokenizer.raise_syntax_error(
-+ f"Expected end or semicolon (after {after})",
-+ span_start=span_start,
-+ )
-+ tokenizer.read()
-+
-+ marker = _parse_marker(tokenizer)
-+ tokenizer.consume("WS")
-+
-+ return marker
-+
-+
-+def _parse_extras(tokenizer: Tokenizer) -> List[str]:
-+ """
-+ extras = (LEFT_BRACKET wsp* extras_list? wsp* RIGHT_BRACKET)?
-+ """
-+ if not tokenizer.check("LEFT_BRACKET", peek=True):
-+ return []
-+
-+ with tokenizer.enclosing_tokens(
-+ "LEFT_BRACKET",
-+ "RIGHT_BRACKET",
-+ around="extras",
-+ ):
-+ tokenizer.consume("WS")
-+ extras = _parse_extras_list(tokenizer)
-+ tokenizer.consume("WS")
-+
-+ return extras
-+
-+
-+def _parse_extras_list(tokenizer: Tokenizer) -> List[str]:
-+ """
-+ extras_list = identifier (wsp* ',' wsp* identifier)*
-+ """
-+ extras: List[str] = []
-+
-+ if not tokenizer.check("IDENTIFIER"):
-+ return extras
-+
-+ extras.append(tokenizer.read().text)
-+
-+ while True:
-+ tokenizer.consume("WS")
-+ if tokenizer.check("IDENTIFIER", peek=True):
-+ tokenizer.raise_syntax_error("Expected comma between extra
names")
-+ elif not tokenizer.check("COMMA"):
-+ break
-+
-+ tokenizer.read()
-+ tokenizer.consume("WS")
-+
-+ extra_token = tokenizer.expect("IDENTIFIER", expected="extra name
after comma")
-+ extras.append(extra_token.text)
-+
-+ return extras
-+
-+
-+def _parse_specifier(tokenizer: Tokenizer) -> str:
-+ """
-+ specifier = LEFT_PARENTHESIS WS? version_many WS? RIGHT_PARENTHESIS
-+ | WS? version_many WS?
-+ """
-+ with tokenizer.enclosing_tokens(
-+ "LEFT_PARENTHESIS",
-+ "RIGHT_PARENTHESIS",
-+ around="version specifier",
-+ ):
-+ tokenizer.consume("WS")
-+ parsed_specifiers = _parse_version_many(tokenizer)
-+ tokenizer.consume("WS")
-+
-+ return parsed_specifiers
-+
-+
-+def _parse_version_many(tokenizer: Tokenizer) -> str:
-+ """
-+ version_many = (SPECIFIER (WS? COMMA WS? SPECIFIER)*)?
-+ """
-+ parsed_specifiers = ""
-+ while tokenizer.check("SPECIFIER"):
-+ span_start = tokenizer.position
-+ parsed_specifiers += tokenizer.read().text
-+ if tokenizer.check("VERSION_PREFIX_TRAIL", peek=True):
-+ tokenizer.raise_syntax_error(
-+ ".* suffix can only be used with `==` or `!=` operators",
-+ span_start=span_start,
-+ span_end=tokenizer.position + 1,
-+ )
-+ if tokenizer.check("VERSION_LOCAL_LABEL_TRAIL", peek=True):
-+ tokenizer.raise_syntax_error(
-+ "Local version label can only be used with `==` or `!=`
operators",
-+ span_start=span_start,
-+ span_end=tokenizer.position,
-+ )
-+ tokenizer.consume("WS")
-+ if not tokenizer.check("COMMA"):
-+ break
-+ parsed_specifiers += tokenizer.read().text
-+ tokenizer.consume("WS")
-+
-+ return parsed_specifiers
-+
-+
-+#
--------------------------------------------------------------------------------------
-+# Recursive descent parser for marker expression
-+#
--------------------------------------------------------------------------------------
-+def parse_marker(source: str) -> MarkerList:
-+ return _parse_marker(Tokenizer(source, rules=DEFAULT_RULES))
-+
-+
-+def _parse_marker(tokenizer: Tokenizer) -> MarkerList:
-+ """
-+ marker = marker_atom (BOOLOP marker_atom)+
-+ """
-+ expression = [_parse_marker_atom(tokenizer)]
-+ while tokenizer.check("BOOLOP"):
-+ token = tokenizer.read()
-+ expr_right = _parse_marker_atom(tokenizer)
-+ expression.extend((token.text, expr_right))
-+ return expression
-+
-+
-+def _parse_marker_atom(tokenizer: Tokenizer) -> MarkerAtom:
-+ """
-+ marker_atom = WS? LEFT_PARENTHESIS WS? marker WS? RIGHT_PARENTHESIS WS?
-+ | WS? marker_item WS?
-+ """
-+
-+ tokenizer.consume("WS")
-+ if tokenizer.check("LEFT_PARENTHESIS", peek=True):
-+ with tokenizer.enclosing_tokens(
-+ "LEFT_PARENTHESIS",
-+ "RIGHT_PARENTHESIS",
-+ around="marker expression",
-+ ):
-+ tokenizer.consume("WS")
-+ marker: MarkerAtom = _parse_marker(tokenizer)
-+ tokenizer.consume("WS")
-+ else:
-+ marker = _parse_marker_item(tokenizer)
-+ tokenizer.consume("WS")
-+ return marker
-+
-+
-+def _parse_marker_item(tokenizer: Tokenizer) -> MarkerItem:
-+ """
-+ marker_item = WS? marker_var WS? marker_op WS? marker_var WS?
-+ """
-+ tokenizer.consume("WS")
-+ marker_var_left = _parse_marker_var(tokenizer)
-+ tokenizer.consume("WS")
-+ marker_op = _parse_marker_op(tokenizer)
-+ tokenizer.consume("WS")
-+ marker_var_right = _parse_marker_var(tokenizer)
-+ tokenizer.consume("WS")
-+ return (marker_var_left, marker_op, marker_var_right)
-+
-+
-+def _parse_marker_var(tokenizer: Tokenizer) -> MarkerVar:
-+ """
-+ marker_var = VARIABLE | QUOTED_STRING
-+ """
-+ if tokenizer.check("VARIABLE"):
-+ return process_env_var(tokenizer.read().text.replace(".", "_"))
-+ elif tokenizer.check("QUOTED_STRING"):
-+ return process_python_str(tokenizer.read().text)
-+ else:
-+ tokenizer.raise_syntax_error(
-+ message="Expected a marker variable or quoted string"
-+ )
-+
-+
-+def process_env_var(env_var: str) -> Variable:
-+ if (
-+ env_var == "platform_python_implementation"
-+ or env_var == "python_implementation"
-+ ):
-+ return Variable("platform_python_implementation")
-+ else:
-+ return Variable(env_var)
-+
-+
-+def process_python_str(python_str: str) -> Value:
-+ value = ast.literal_eval(python_str)
-+ return Value(str(value))
-+
-+
-+def _parse_marker_op(tokenizer: Tokenizer) -> Op:
-+ """
-+ marker_op = IN | NOT IN | OP
-+ """
-+ if tokenizer.check("IN"):
-+ tokenizer.read()
-+ return Op("in")
-+ elif tokenizer.check("NOT"):
-+ tokenizer.read()
-+ tokenizer.expect("WS", expected="whitespace after 'not'")
-+ tokenizer.expect("IN", expected="'in' after 'not'")
-+ return Op("not in")
-+ elif tokenizer.check("OP"):
-+ return Op(tokenizer.read().text)
-+ else:
-+ return tokenizer.raise_syntax_error(
-+ "Expected marker operator, one of "
-+ "<=, <, !=, ==, >=, >, ~=, ===, in, not in"
-+ )
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/packaging/_structures.py
b/third_party/python/setuptools/setuptools/_vendor/packaging/_structures.py
----
a/third_party/python/setuptools/setuptools/_vendor/packaging/_structures.py
-+++
b/third_party/python/setuptools/setuptools/_vendor/packaging/_structures.py
-@@ -1,86 +1,61 @@
- # 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 __future__ import absolute_import, division, print_function
-
-
--class InfinityType(object):
-- def __repr__(self):
-- # type: () -> str
-+class InfinityType:
-+ def __repr__(self) -> str:
- return "Infinity"
-
-- def __hash__(self):
-- # type: () -> int
-+ def __hash__(self) -> int:
- return hash(repr(self))
-
-- def __lt__(self, other):
-- # type: (object) -> bool
-+ def __lt__(self, other: object) -> bool:
- return False
-
-- def __le__(self, other):
-- # type: (object) -> bool
-+ def __le__(self, other: object) -> bool:
- return False
-
-- def __eq__(self, other):
-- # type: (object) -> bool
-+ def __eq__(self, other: object) -> bool:
- return isinstance(other, self.__class__)
-
-- def __ne__(self, other):
-- # type: (object) -> bool
-- return not isinstance(other, self.__class__)
--
-- def __gt__(self, other):
-- # type: (object) -> bool
-+ def __gt__(self, other: object) -> bool:
- return True
-
-- def __ge__(self, other):
-- # type: (object) -> bool
-+ def __ge__(self, other: object) -> bool:
- return True
-
-- def __neg__(self):
-- # type: (object) -> NegativeInfinityType
-+ def __neg__(self: object) -> "NegativeInfinityType":
- return NegativeInfinity
-
-
- Infinity = InfinityType()
-
-
--class NegativeInfinityType(object):
-- def __repr__(self):
-- # type: () -> str
-+class NegativeInfinityType:
-+ def __repr__(self) -> str:
- return "-Infinity"
-
-- def __hash__(self):
-- # type: () -> int
-+ def __hash__(self) -> int:
- return hash(repr(self))
-
-- def __lt__(self, other):
-- # type: (object) -> bool
-- return True
--
-- def __le__(self, other):
-- # type: (object) -> bool
-+ def __lt__(self, other: object) -> bool:
- return True
-
-- def __eq__(self, other):
-- # type: (object) -> bool
-+ def __le__(self, other: object) -> bool:
-+ return True
-+
-+ def __eq__(self, other: object) -> bool:
- return isinstance(other, self.__class__)
-
-- def __ne__(self, other):
-- # type: (object) -> bool
-- return not isinstance(other, self.__class__)
--
-- def __gt__(self, other):
-- # type: (object) -> bool
-+ def __gt__(self, other: object) -> bool:
- return False
-
-- def __ge__(self, other):
-- # type: (object) -> bool
-+ def __ge__(self, other: object) -> bool:
- return False
-
-- def __neg__(self):
-- # type: (object) -> InfinityType
-+ def __neg__(self: object) -> InfinityType:
- return Infinity
-
-
- NegativeInfinity = NegativeInfinityType()
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/packaging/_tokenizer.py
b/third_party/python/setuptools/setuptools/_vendor/packaging/_tokenizer.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/_vendor/packaging/_tokenizer.py
-@@ -0,0 +1,192 @@
-+import contextlib
-+import re
-+from dataclasses import dataclass
-+from typing import Dict, Iterator, NoReturn, Optional, Tuple, Union
-+
-+from .specifiers import Specifier
-+
-+
-+@dataclass
-+class Token:
-+ name: str
-+ text: str
-+ position: int
-+
-+
-+class ParserSyntaxError(Exception):
-+ """The provided source text could not be parsed correctly."""
-+
-+ def __init__(
-+ self,
-+ message: str,
-+ *,
-+ source: str,
-+ span: Tuple[int, int],
-+ ) -> None:
-+ self.span = span
-+ self.message = message
-+ self.source = source
-+
-+ super().__init__()
-+
-+ def __str__(self) -> str:
-+ marker = " " * self.span[0] + "~" * (self.span[1] - self.span[0]) +
"^"
-+ return "\n ".join([self.message, self.source, marker])
-+
-+
-+DEFAULT_RULES: "Dict[str, Union[str, re.Pattern[str]]]" = {
-+ "LEFT_PARENTHESIS": r"\(",
-+ "RIGHT_PARENTHESIS": r"\)",
-+ "LEFT_BRACKET": r"\[",
-+ "RIGHT_BRACKET": r"\]",
-+ "SEMICOLON": r";",
-+ "COMMA": r",",
-+ "QUOTED_STRING": re.compile(
-+ r"""
-+ (
-+ ('[^']*')
-+ |
-+ ("[^"]*")
-+ )
-+ """,
-+ re.VERBOSE,
-+ ),
-+ "OP": r"(===|==|~=|!=|<=|>=|<|>)",
-+ "BOOLOP": r"\b(or|and)\b",
-+ "IN": r"\bin\b",
-+ "NOT": r"\bnot\b",
-+ "VARIABLE": re.compile(
-+ r"""
-+ \b(
-+ python_version
-+ |python_full_version
-+ |os[._]name
-+ |sys[._]platform
-+ |platform_(release|system)
-+ |platform[._](version|machine|python_implementation)
-+ |python_implementation
-+ |implementation_(name|version)
-+ |extra
-+ )\b
-+ """,
-+ re.VERBOSE,
-+ ),
-+ "SPECIFIER": re.compile(
-+ Specifier._operator_regex_str + Specifier._version_regex_str,
-+ re.VERBOSE | re.IGNORECASE,
-+ ),
-+ "AT": r"\@",
-+ "URL": r"[^ \t]+",
-+ "IDENTIFIER": r"\b[a-zA-Z0-9][a-zA-Z0-9._-]*\b",
-+ "VERSION_PREFIX_TRAIL": r"\.\*",
-+ "VERSION_LOCAL_LABEL_TRAIL": r"\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*",
-+ "WS": r"[ \t]+",
-+ "END": r"$",
-+}
-+
-+
-+class Tokenizer:
-+ """Context-sensitive token parsing.
-+
-+ Provides methods to examine the input stream to check whether the next
token
-+ matches.
-+ """
-+
-+ def __init__(
-+ self,
-+ source: str,
-+ *,
-+ rules: "Dict[str, Union[str, re.Pattern[str]]]",
-+ ) -> None:
-+ self.source = source
-+ self.rules: Dict[str, re.Pattern[str]] = {
-+ name: re.compile(pattern) for name, pattern in rules.items()
-+ }
-+ self.next_token: Optional[Token] = None
-+ self.position = 0
-+
-+ def consume(self, name: str) -> None:
-+ """Move beyond provided token name, if at current position."""
-+ if self.check(name):
-+ self.read()
-+
-+ def check(self, name: str, *, peek: bool = False) -> bool:
-+ """Check whether the next token has the provided name.
-+
-+ By default, if the check succeeds, the token *must* be read before
-+ another check. If `peek` is set to `True`, the token is not loaded
and
-+ would need to be checked again.
-+ """
-+ assert (
-+ self.next_token is None
-+ ), f"Cannot check for {name!r}, already have {self.next_token!r}"
-+ assert name in self.rules, f"Unknown token name: {name!r}"
-+
-+ expression = self.rules[name]
-+
-+ match = expression.match(self.source, self.position)
-+ if match is None:
-+ return False
-+ if not peek:
-+ self.next_token = Token(name, match[0], self.position)
-+ return True
-+
-+ def expect(self, name: str, *, expected: str) -> Token:
-+ """Expect a certain token name next, failing with a syntax error
otherwise.
-+
-+ The token is *not* read.
-+ """
-+ if not self.check(name):
-+ raise self.raise_syntax_error(f"Expected {expected}")
-+ return self.read()
-+
-+ def read(self) -> Token:
-+ """Consume the next token and return it."""
-+ token = self.next_token
-+ assert token is not None
-+
-+ self.position += len(token.text)
-+ self.next_token = None
-+
-+ return token
-+
-+ def raise_syntax_error(
-+ self,
-+ message: str,
-+ *,
-+ span_start: Optional[int] = None,
-+ span_end: Optional[int] = None,
-+ ) -> NoReturn:
-+ """Raise ParserSyntaxError at the given position."""
-+ span = (
-+ self.position if span_start is None else span_start,
-+ self.position if span_end is None else span_end,
-+ )
-+ raise ParserSyntaxError(
-+ message,
-+ source=self.source,
-+ span=span,
-+ )
-+
-+ @contextlib.contextmanager
-+ def enclosing_tokens(
-+ self, open_token: str, close_token: str, *, around: str
-+ ) -> Iterator[None]:
-+ if self.check(open_token):
-+ open_position = self.position
-+ self.read()
-+ else:
-+ open_position = None
-+
-+ yield
-+
-+ if open_position is None:
-+ return
-+
-+ if not self.check(close_token):
-+ self.raise_syntax_error(
-+ f"Expected matching {close_token} for {open_token}, after
{around}",
-+ span_start=open_position,
-+ )
-+
-+ self.read()
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/packaging/_typing.py
b/third_party/python/setuptools/setuptools/_vendor/packaging/_typing.py
-deleted file mode 100644
---- a/third_party/python/setuptools/setuptools/_vendor/packaging/_typing.py
-+++ /dev/null
-@@ -1,48 +0,0 @@
--"""For neatly implementing static typing in packaging.
--
--`mypy` - the static type analysis tool we use - uses the `typing` module,
which
--provides core functionality fundamental to mypy's functioning.
--
--Generally, `typing` would be imported at runtime and used in that fashion -
--it acts as a no-op at runtime and does not have any run-time overhead by
--design.
--
--As it turns out, `typing` is not vendorable - it uses separate sources for
--Python 2/Python 3. Thus, this codebase can not expect it to be present.
--To work around this, mypy allows the typing import to be behind a False-y
--optional to prevent it from running at runtime and type-comments can be used
--to remove the need for the types to be accessible directly during runtime.
--
--This module provides the False-y guard in a nicely named fashion so that a
--curious maintainer can reach here to read this.
--
--In packaging, all static-typing related imports should be guarded as
follows:
--
-- from packaging._typing import TYPE_CHECKING
--
-- if TYPE_CHECKING:
-- from typing import ...
--
--Ref: https://github.com/python/mypy/issues/3216
--"""
--
--__all__ = ["TYPE_CHECKING", "cast"]
--
--# The TYPE_CHECKING constant defined by the typing module is False at
runtime
--# but True while type checking.
--if False: # pragma: no cover
-- from typing import TYPE_CHECKING
--else:
-- TYPE_CHECKING = False
--
--# typing's cast syntax requires calling typing.cast at runtime, but we don't
--# want to import typing at runtime. Here, we inform the type checkers that
--# we're importing `typing.cast` as `cast` and re-implement typing.cast's
--# runtime behavior in a block that is ignored by type checkers.
--if TYPE_CHECKING: # pragma: no cover
-- # not executed at runtime
-- from typing import cast
--else:
-- # executed at runtime
-- def cast(type_, value): # noqa
-- return value
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/packaging/markers.py
b/third_party/python/setuptools/setuptools/_vendor/packaging/markers.py
---- a/third_party/python/setuptools/setuptools/_vendor/packaging/markers.py
-+++ b/third_party/python/setuptools/setuptools/_vendor/packaging/markers.py
-@@ -1,40 +1,40 @@
- # 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 __future__ import absolute_import, division, print_function
-
- import operator
- import os
- import platform
- import sys
--
--from setuptools.extern.pyparsing import ParseException, ParseResults,
stringStart, stringEnd
--from setuptools.extern.pyparsing import ZeroOrMore, Group, Forward,
QuotedString
--from setuptools.extern.pyparsing import Literal as L # noqa
-+from typing import Any, Callable, Dict, List, Optional, Tuple, Union
-
--from ._compat import string_types
--from ._typing import TYPE_CHECKING
--from .specifiers import Specifier, InvalidSpecifier
--
--if TYPE_CHECKING: # pragma: no cover
-- from typing import Any, Callable, Dict, List, Optional, Tuple, Union
--
-- Operator = Callable[[str, str], bool]
--
-+from ._parser import (
-+ MarkerAtom,
-+ MarkerList,
-+ Op,
-+ Value,
-+ Variable,
-+ parse_marker as _parse_marker,
-+)
-+from ._tokenizer import ParserSyntaxError
-+from .specifiers import InvalidSpecifier, Specifier
-+from .utils import canonicalize_name
-
- __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):
-@@ -45,121 +45,37 @@ class UndefinedComparison(ValueError):
-
- class UndefinedEnvironmentName(ValueError):
- """
- A name was attempted to be used that does not exist inside of the
- environment.
- """
-
-
--class Node(object):
-- def __init__(self, value):
-- # type: (Any) -> None
-- self.value = value
--
-- def __str__(self):
-- # type: () -> str
-- return str(self.value)
--
-- def __repr__(self):
-- # type: () -> str
-- return "<{0}({1!r})>".format(self.__class__.__name__, str(self))
--
-- def serialize(self):
-- # type: () -> str
-- raise NotImplementedError
--
--
--class Variable(Node):
-- def serialize(self):
-- # type: () -> str
-- return str(self)
--
--
--class Value(Node):
-- def serialize(self):
-- # type: () -> str
-- return '"{0}"'.format(self)
--
--
--class Op(Node):
-- def serialize(self):
-- # type: () -> str
-- return str(self)
-+def _normalize_extra_values(results: Any) -> Any:
-+ """
-+ Normalize extra values.
-+ """
-+ if isinstance(results[0], tuple):
-+ lhs, op, rhs = results[0]
-+ if isinstance(lhs, Variable) and lhs.value == "extra":
-+ normalized_extra = canonicalize_name(rhs.value)
-+ rhs = Value(normalized_extra)
-+ elif isinstance(rhs, Variable) and rhs.value == "extra":
-+ normalized_extra = canonicalize_name(lhs.value)
-+ lhs = Value(normalized_extra)
-+ results[0] = lhs, op, rhs
-+ return results
-
-
--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("<")
--)
-+def _format_marker(
-+ marker: Union[List[str], MarkerAtom, str], first: Optional[bool] = True
-+) -> str:
-
--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):
-- # type: (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, first=True):
-- # type: (Union[List[str], Tuple[Node, ...], str], Optional[bool]) -> str
--
-- assert isinstance(marker, (list, tuple, string_types))
-+ 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
-@@ -174,155 +90,163 @@ def _format_marker(marker, first=True):
- else:
- return "(" + " ".join(inner) + ")"
- elif isinstance(marker, tuple):
- return " ".join([m.serialize() for m in marker])
- else:
- return marker
-
-
--_operators = {
-+_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,
--} # type: Dict[str, Operator]
-+}
-
-
--def _eval_op(lhs, op, rhs):
-- # type: (str, Op, str) -> bool
-+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)
-+ return spec.contains(lhs, prereleases=True)
-
-- oper = _operators.get(op.serialize()) # type: Optional[Operator]
-+ oper: Optional[Operator] = _operators.get(op.serialize())
- if oper is None:
-- raise UndefinedComparison(
-- "Undefined {0!r} on {1!r} and {2!r}.".format(op, lhs, rhs)
-- )
-+ raise UndefinedComparison(f"Undefined {op!r} on {lhs!r} and
{rhs!r}.")
-
- return oper(lhs, rhs)
-
-
--class Undefined(object):
-- pass
-+def _normalize(*values: str, key: str) -> Tuple[str, ...]:
-+ # PEP 685 – Comparison of extra names for optional distribution
dependencies
-+ # https://peps.python.org/pep-0685/
-+ # > When comparing extra names, tools MUST normalize the names being
-+ # > compared using the semantics outlined in PEP 503 for names
-+ if key == "extra":
-+ return tuple(canonicalize_name(v) for v in values)
-
--
--_undefined = Undefined()
-+ # other environment markers don't have such standards
-+ return values
-
-
--def _get_env(environment, name):
-- # type: (Dict[str, str], str) -> str
-- value = environment.get(name, _undefined) # type: Union[str, Undefined]
--
-- if isinstance(value, Undefined):
-- raise UndefinedEnvironmentName(
-- "{0!r} does not exist in evaluation environment.".format(name)
-- )
--
-- return value
--
--
--def _evaluate_markers(markers, environment):
-- # type: (List[Any], Dict[str, str]) -> bool
-- groups = [[]] # type: List[List[bool]]
-+def _evaluate_markers(markers: MarkerList, environment: Dict[str, str]) ->
bool:
-+ groups: List[List[bool]] = [[]]
-
- for marker in markers:
-- assert isinstance(marker, (list, tuple, string_types))
-+ 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)
-+ environment_key = lhs.value
-+ lhs_value = environment[environment_key]
- rhs_value = rhs.value
- else:
- lhs_value = lhs.value
-- rhs_value = _get_env(environment, rhs.value)
-+ environment_key = rhs.value
-+ rhs_value = environment[environment_key]
-
-+ lhs_value, rhs_value = _normalize(lhs_value, rhs_value,
key=environment_key)
- 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):
-- # type: (sys._version_info) -> str
-+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():
-- # type: () -> Dict[str, str]
-- if hasattr(sys, "implementation"):
-- # Ignoring the `sys.implementation` reference for type checking due
to
-- # mypy not liking that the attribute doesn't exist in Python 2.7
when
-- # run with the `--py27` flag.
-- iver = format_full_version(sys.implementation.version) # type:
ignore
-- implementation_name = sys.implementation.name # type: ignore
-- else:
-- iver = "0"
-- implementation_name = ""
--
-+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(object):
-- def __init__(self, marker):
-- # type: (str) -> None
-+class Marker:
-+ def __init__(self, marker: str) -> None:
-+ # Note: We create a Marker object without calling this constructor
in
-+ # packaging.requirements.Requirement. If any additional logic
is
-+ # added here, make sure to mirror/adapt Requirement.
- try:
-- self._markers = _coerce_parse_result(MARKER.parseString(marker))
-- except ParseException as e:
-- err_str = "Invalid marker: {0!r}, parse error at {1!r}".format(
-- marker, marker[e.loc : e.loc + 8]
-- )
-- raise InvalidMarker(err_str)
-+ self._markers = _normalize_extra_values(_parse_marker(marker))
-+ # The attribute `_markers` can be described in terms of a
recursive type:
-+ # MarkerList = List[Union[Tuple[Node, ...], str, MarkerList]]
-+ #
-+ # For example, the following expression:
-+ # python_version > "3.6" or (python_version == "3.6" and
os_name == "unix")
-+ #
-+ # is parsed into:
-+ # [
-+ # (<Variable('python_version')>, <Op('>')>, <Value('3.6')>),
-+ # 'and',
-+ # [
-+ # (<Variable('python_version')>, <Op('==')>,
<Value('3.6')>),
-+ # 'or',
-+ # (<Variable('os_name')>, <Op('==')>, <Value('unix')>)
-+ # ]
-+ # ]
-+ except ParserSyntaxError as e:
-+ raise InvalidMarker(str(e)) from e
-
-- def __str__(self):
-- # type: () -> str
-+ def __str__(self) -> str:
- return _format_marker(self._markers)
-
-- def __repr__(self):
-- # type: () -> str
-- return "<Marker({0!r})>".format(str(self))
-+ def __repr__(self) -> str:
-+ return f"<Marker('{self}')>"
-+
-+ def __hash__(self) -> int:
-+ return hash((self.__class__.__name__, str(self)))
-
-- def evaluate(self, environment=None):
-- # type: (Optional[Dict[str, str]]) -> bool
-+ def __eq__(self, other: Any) -> bool:
-+ if not isinstance(other, Marker):
-+ return NotImplemented
-+
-+ return str(self) == str(other)
-+
-+ 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()
-+ current_environment["extra"] = ""
- if environment is not None:
- current_environment.update(environment)
-+ # The API used to allow setting extra to None. We need to
handle this
-+ # case for backwards compatibility.
-+ if current_environment["extra"] is None:
-+ current_environment["extra"] = ""
-
- return _evaluate_markers(self._markers, current_environment)
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/packaging/metadata.py
b/third_party/python/setuptools/setuptools/_vendor/packaging/metadata.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/_vendor/packaging/metadata.py
-@@ -0,0 +1,408 @@
-+import email.feedparser
-+import email.header
-+import email.message
-+import email.parser
-+import email.policy
-+import sys
-+import typing
-+from typing import Dict, List, Optional, Tuple, Union, cast
-+
-+if sys.version_info >= (3, 8): # pragma: no cover
-+ from typing import TypedDict
-+else: # pragma: no cover
-+ if typing.TYPE_CHECKING:
-+ from typing_extensions import TypedDict
-+ else:
-+ try:
-+ from typing_extensions import TypedDict
-+ except ImportError:
-+
-+ class TypedDict:
-+ def __init_subclass__(*_args, **_kwargs):
-+ pass
-+
-+
-+# The RawMetadata class attempts to make as few assumptions about the
underlying
-+# serialization formats as possible. The idea is that as long as a
serialization
-+# formats offer some very basic primitives in *some* way then we can support
-+# serializing to and from that format.
-+class RawMetadata(TypedDict, total=False):
-+ """A dictionary of raw core metadata.
-+
-+ Each field in core metadata maps to a key of this dictionary (when data
is
-+ provided). The key is lower-case and underscores are used instead of
dashes
-+ compared to the equivalent core metadata field. Any core metadata field
that
-+ can be specified multiple times or can hold multiple values in a single
-+ field have a key with a plural name.
-+
-+ Core metadata fields that can be specified multiple times are stored as
a
-+ list or dict depending on which is appropriate for the field. Any fields
-+ which hold multiple values in a single field are stored as a list.
-+
-+ """
-+
-+ # Metadata 1.0 - PEP 241
-+ metadata_version: str
-+ name: str
-+ version: str
-+ platforms: List[str]
-+ summary: str
-+ description: str
-+ keywords: List[str]
-+ home_page: str
-+ author: str
-+ author_email: str
-+ license: str
-+
-+ # Metadata 1.1 - PEP 314
-+ supported_platforms: List[str]
-+ download_url: str
-+ classifiers: List[str]
-+ requires: List[str]
-+ provides: List[str]
-+ obsoletes: List[str]
-+
-+ # Metadata 1.2 - PEP 345
-+ maintainer: str
-+ maintainer_email: str
-+ requires_dist: List[str]
-+ provides_dist: List[str]
-+ obsoletes_dist: List[str]
-+ requires_python: str
-+ requires_external: List[str]
-+ project_urls: Dict[str, str]
-+
-+ # Metadata 2.0
-+ # PEP 426 attempted to completely revamp the metadata format
-+ # but got stuck without ever being able to build consensus on
-+ # it and ultimately ended up withdrawn.
-+ #
-+ # However, a number of tools had started emiting METADATA with
-+ # `2.0` Metadata-Version, so for historical reasons, this version
-+ # was skipped.
-+
-+ # Metadata 2.1 - PEP 566
-+ description_content_type: str
-+ provides_extra: List[str]
-+
-+ # Metadata 2.2 - PEP 643
-+ dynamic: List[str]
-+
-+ # Metadata 2.3 - PEP 685
-+ # No new fields were added in PEP 685, just some edge case were
-+ # tightened up to provide better interoptability.
-+
-+
-+_STRING_FIELDS = {
-+ "author",
-+ "author_email",
-+ "description",
-+ "description_content_type",
-+ "download_url",
-+ "home_page",
-+ "license",
-+ "maintainer",
-+ "maintainer_email",
-+ "metadata_version",
-+ "name",
-+ "requires_python",
-+ "summary",
-+ "version",
-+}
-+
-+_LIST_STRING_FIELDS = {
-+ "classifiers",
-+ "dynamic",
-+ "obsoletes",
-+ "obsoletes_dist",
-+ "platforms",
-+ "provides",
-+ "provides_dist",
-+ "provides_extra",
-+ "requires",
-+ "requires_dist",
-+ "requires_external",
-+ "supported_platforms",
-+}
-+
-+
-+def _parse_keywords(data: str) -> List[str]:
-+ """Split a string of comma-separate keyboards into a list of
keywords."""
-+ return [k.strip() for k in data.split(",")]
-+
-+
-+def _parse_project_urls(data: List[str]) -> Dict[str, str]:
-+ """Parse a list of label/URL string pairings separated by a comma."""
-+ urls = {}
-+ for pair in data:
-+ # Our logic is slightly tricky here as we want to try and do
-+ # *something* reasonable with malformed data.
-+ #
-+ # The main thing that we have to worry about, is data that does
-+ # not have a ',' at all to split the label from the Value. There
-+ # isn't a singular right answer here, and we will fail validation
-+ # later on (if the caller is validating) so it doesn't *really*
-+ # matter, but since the missing value has to be an empty str
-+ # and our return value is dict[str, str], if we let the key
-+ # be the missing value, then they'd have multiple '' values that
-+ # overwrite each other in a accumulating dict.
-+ #
-+ # The other potentional issue is that it's possible to have the
-+ # same label multiple times in the metadata, with no solid "right"
-+ # answer with what to do in that case. As such, we'll do the only
-+ # thing we can, which is treat the field as unparseable and add it
-+ # to our list of unparsed fields.
-+ parts = [p.strip() for p in pair.split(",", 1)]
-+ parts.extend([""] * (max(0, 2 - len(parts)))) # Ensure 2 items
-+
-+ # TODO: The spec doesn't say anything about if the keys should be
-+ # considered case sensitive or not... logically they should
-+ # be case-preserving and case-insensitive, but doing that
-+ # would open up more cases where we might have duplicate
-+ # entries.
-+ label, url = parts
-+ if label in urls:
-+ # The label already exists in our set of urls, so this field
-+ # is unparseable, and we can just add the whole thing to our
-+ # unparseable data and stop processing it.
-+ raise KeyError("duplicate labels in project urls")
-+ urls[label] = url
-+
-+ return urls
-+
-+
-+def _get_payload(msg: email.message.Message, source: Union[bytes, str]) ->
str:
-+ """Get the body of the message."""
-+ # If our source is a str, then our caller has managed encodings for us,
-+ # and we don't need to deal with it.
-+ if isinstance(source, str):
-+ payload: str = msg.get_payload()
-+ return payload
-+ # If our source is a bytes, then we're managing the encoding and we need
-+ # to deal with it.
-+ else:
-+ bpayload: bytes = msg.get_payload(decode=True)
-+ try:
-+ return bpayload.decode("utf8", "strict")
-+ except UnicodeDecodeError:
-+ raise ValueError("payload in an invalid encoding")
-+
-+
-+# The various parse_FORMAT functions here are intended to be as lenient as
-+# possible in their parsing, while still returning a correctly typed
-+# RawMetadata.
-+#
-+# To aid in this, we also generally want to do as little touching of the
-+# data as possible, except where there are possibly some historic holdovers
-+# that make valid data awkward to work with.
-+#
-+# While this is a lower level, intermediate format than our ``Metadata``
-+# class, some light touch ups can make a massive difference in usability.
-+
-+# Map METADATA fields to RawMetadata.
-+_EMAIL_TO_RAW_MAPPING = {
-+ "author": "author",
-+ "author-email": "author_email",
-+ "classifier": "classifiers",
-+ "description": "description",
-+ "description-content-type": "description_content_type",
-+ "download-url": "download_url",
-+ "dynamic": "dynamic",
-+ "home-page": "home_page",
-+ "keywords": "keywords",
-+ "license": "license",
-+ "maintainer": "maintainer",
-+ "maintainer-email": "maintainer_email",
-+ "metadata-version": "metadata_version",
-+ "name": "name",
-+ "obsoletes": "obsoletes",
-+ "obsoletes-dist": "obsoletes_dist",
-+ "platform": "platforms",
-+ "project-url": "project_urls",
-+ "provides": "provides",
-+ "provides-dist": "provides_dist",
-+ "provides-extra": "provides_extra",
-+ "requires": "requires",
-+ "requires-dist": "requires_dist",
-+ "requires-external": "requires_external",
-+ "requires-python": "requires_python",
-+ "summary": "summary",
-+ "supported-platform": "supported_platforms",
-+ "version": "version",
-+}
-+
-+
-+def parse_email(data: Union[bytes, str]) -> Tuple[RawMetadata, Dict[str,
List[str]]]:
-+ """Parse a distribution's metadata.
-+
-+ This function returns a two-item tuple of dicts. The first dict is of
-+ recognized fields from the core metadata specification. Fields that can
be
-+ parsed and translated into Python's built-in types are converted
-+ appropriately. All other fields are left as-is. Fields that are allowed
to
-+ appear multiple times are stored as lists.
-+
-+ The second dict contains all other fields from the metadata. This
includes
-+ any unrecognized fields. It also includes any fields which are expected
to
-+ be parsed into a built-in type but were not formatted appropriately.
Finally,
-+ any fields that are expected to appear only once but are repeated are
-+ included in this dict.
-+
-+ """
-+ raw: Dict[str, Union[str, List[str], Dict[str, str]]] = {}
-+ unparsed: Dict[str, List[str]] = {}
-+
-+ if isinstance(data, str):
-+ parsed =
email.parser.Parser(policy=email.policy.compat32).parsestr(data)
-+ else:
-+ parsed =
email.parser.BytesParser(policy=email.policy.compat32).parsebytes(data)
-+
-+ # We have to wrap parsed.keys() in a set, because in the case of
multiple
-+ # values for a key (a list), the key will appear multiple times in the
-+ # list of keys, but we're avoiding that by using get_all().
-+ for name in frozenset(parsed.keys()):
-+ # Header names in RFC are case insensitive, so we'll normalize to
all
-+ # lower case to make comparisons easier.
-+ name = name.lower()
-+
-+ # We use get_all() here, even for fields that aren't multiple use,
-+ # because otherwise someone could have e.g. two Name fields, and we
-+ # would just silently ignore it rather than doing something about
it.
-+ headers = parsed.get_all(name)
-+
-+ # The way the email module works when parsing bytes is that it
-+ # unconditionally decodes the bytes as ascii using the
surrogateescape
-+ # handler. When you pull that data back out (such as with get_all()
),
-+ # it looks to see if the str has any surrogate escapes, and if it
does
-+ # it wraps it in a Header object instead of returning the string.
-+ #
-+ # As such, we'll look for those Header objects, and fix up the
encoding.
-+ value = []
-+ # Flag if we have run into any issues processing the headers, thus
-+ # signalling that the data belongs in 'unparsed'.
-+ valid_encoding = True
-+ for h in headers:
-+ # It's unclear if this can return more types than just a Header
or
-+ # a str, so we'll just assert here to make sure.
-+ assert isinstance(h, (email.header.Header, str))
-+
-+ # If it's a header object, we need to do our little dance to get
-+ # the real data out of it. In cases where there is invalid data
-+ # we're going to end up with mojibake, but there's no obvious,
good
-+ # way around that without reimplementing parts of the Header
object
-+ # ourselves.
-+ #
-+ # That should be fine since, if mojibacked happens, this key is
-+ # going into the unparsed dict anyways.
-+ if isinstance(h, email.header.Header):
-+ # The Header object stores it's data as chunks, and each
chunk
-+ # can be independently encoded, so we'll need to check each
-+ # of them.
-+ chunks: List[Tuple[bytes, Optional[str]]] = []
-+ for bin, encoding in email.header.decode_header(h):
-+ try:
-+ bin.decode("utf8", "strict")
-+ except UnicodeDecodeError:
-+ # Enable mojibake.
-+ encoding = "latin1"
-+ valid_encoding = False
-+ else:
-+ encoding = "utf8"
-+ chunks.append((bin, encoding))
-+
-+ # Turn our chunks back into a Header object, then let that
-+ # Header object do the right thing to turn them into a
-+ # string for us.
-+ value.append(str(email.header.make_header(chunks)))
-+ # This is already a string, so just add it.
-+ else:
-+ value.append(h)
-+
-+ # We've processed all of our values to get them into a list of str,
-+ # but we may have mojibake data, in which case this is an unparsed
-+ # field.
-+ if not valid_encoding:
-+ unparsed[name] = value
-+ continue
-+
-+ raw_name = _EMAIL_TO_RAW_MAPPING.get(name)
-+ if raw_name is None:
-+ # This is a bit of a weird situation, we've encountered a key
that
-+ # we don't know what it means, so we don't know whether it's
meant
-+ # to be a list or not.
-+ #
-+ # Since we can't really tell one way or another, we'll just
leave it
-+ # as a list, even though it may be a single item list, because
that's
-+ # what makes the most sense for email headers.
-+ unparsed[name] = value
-+ continue
-+
-+ # If this is one of our string fields, then we'll check to see if
our
-+ # value is a list of a single item. If it is then we'll assume that
-+ # it was emitted as a single string, and unwrap the str from inside
-+ # the list.
-+ #
-+ # If it's any other kind of data, then we haven't the faintest clue
-+ # what we should parse it as, and we have to just add it to our list
-+ # of unparsed stuff.
-+ if raw_name in _STRING_FIELDS and len(value) == 1:
-+ raw[raw_name] = value[0]
-+ # If this is one of our list of string fields, then we can just
assign
-+ # the value, since email *only* has strings, and our get_all() call
-+ # above ensures that this is a list.
-+ elif raw_name in _LIST_STRING_FIELDS:
-+ raw[raw_name] = value
-+ # Special Case: Keywords
-+ # The keywords field is implemented in the metadata spec as a str,
-+ # but it conceptually is a list of strings, and is serialized using
-+ # ", ".join(keywords), so we'll do some light data massaging to turn
-+ # this into what it logically is.
-+ elif raw_name == "keywords" and len(value) == 1:
-+ raw[raw_name] = _parse_keywords(value[0])
-+ # Special Case: Project-URL
-+ # The project urls is implemented in the metadata spec as a list of
-+ # specially-formatted strings that represent a key and a value,
which
-+ # is fundamentally a mapping, however the email format doesn't
support
-+ # mappings in a sane way, so it was crammed into a list of strings
-+ # instead.
-+ #
-+ # We will do a little light data massaging to turn this into a map
as
-+ # it logically should be.
-+ elif raw_name == "project_urls":
-+ try:
-+ raw[raw_name] = _parse_project_urls(value)
-+ except KeyError:
-+ unparsed[name] = value
-+ # Nothing that we've done has managed to parse this, so it'll just
-+ # throw it in our unparseable data and move on.
-+ else:
-+ unparsed[name] = value
-+
-+ # We need to support getting the Description from the message payload in
-+ # addition to getting it from the the headers. This does mean, though,
there
-+ # is the possibility of it being set both ways, in which case we put
both
-+ # in 'unparsed' since we don't know which is right.
-+ try:
-+ payload = _get_payload(parsed, data)
-+ except ValueError:
-+ unparsed.setdefault("description", []).append(
-+ parsed.get_payload(decode=isinstance(data, bytes))
-+ )
-+ else:
-+ if payload:
-+ # Check to see if we've already got a description, if so then
both
-+ # it, and this body move to unparseable.
-+ if "description" in raw:
-+ description_header = cast(str, raw.pop("description"))
-+ unparsed.setdefault("description", []).extend(
-+ [description_header, payload]
-+ )
-+ elif "description" in unparsed:
-+ unparsed["description"].append(payload)
-+ else:
-+ raw["description"] = payload
-+
-+ # We need to cast our `raw` to a metadata, because a TypedDict only
support
-+ # literal key names, but we're computing our key names on purpose, but
the
-+ # way this function is implemented, our `TypedDict` can only have valid
key
-+ # names.
-+ return cast(RawMetadata, raw), unparsed
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/packaging/requirements.py
b/third_party/python/setuptools/setuptools/_vendor/packaging/requirements.py
----
a/third_party/python/setuptools/setuptools/_vendor/packaging/requirements.py
-+++
b/third_party/python/setuptools/setuptools/_vendor/packaging/requirements.py
-@@ -1,145 +1,95 @@
- # 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 __future__ import absolute_import, division, print_function
-
--import string
--import re
-+import urllib.parse
-+from typing import Any, List, Optional, Set
-
--from setuptools.extern.pyparsing import stringStart, stringEnd,
originalTextFor, ParseException
--from setuptools.extern.pyparsing import ZeroOrMore, Word, Optional, Regex,
Combine
--from setuptools.extern.pyparsing import Literal as L # noqa
--from urllib import parse as urlparse
--
--from ._typing import TYPE_CHECKING
--from .markers import MARKER_EXPR, Marker
--from .specifiers import LegacySpecifier, Specifier, SpecifierSet
--
--if TYPE_CHECKING: # pragma: no cover
-- from typing import List
-+from ._parser import parse_requirement as _parse_requirement
-+from ._tokenizer import ParserSyntaxError
-+from .markers import Marker, _normalize_extra_values
-+from .specifiers import 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(object):
-+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):
-- # type: (str) -> None
-+ def __init__(self, requirement_string: str) -> None:
- try:
-- req = REQUIREMENT.parseString(requirement_string)
-- except ParseException as e:
-- raise InvalidRequirement(
-- 'Parse error at "{0!r}": {1}'.format(
-- requirement_string[e.loc : e.loc + 8], e.msg
-- )
-- )
-+ parsed = _parse_requirement(requirement_string)
-+ except ParserSyntaxError as e:
-+ raise InvalidRequirement(str(e)) from e
-
-- self.name = req.name
-- if req.url:
-- parsed_url = urlparse.urlparse(req.url)
-+ self.name: str = parsed.name
-+ if parsed.url:
-+ parsed_url = urllib.parse.urlparse(parsed.url)
- if parsed_url.scheme == "file":
-- if urlparse.urlunparse(parsed_url) != req.url:
-+ if urllib.parse.urlunparse(parsed_url) != parsed.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("Invalid URL: {0}".format(req.url))
-- self.url = req.url
-+ raise InvalidRequirement(f"Invalid URL: {parsed.url}")
-+ self.url: Optional[str] = parsed.url
- else:
- self.url = None
-- self.extras = set(req.extras.asList() if req.extras else [])
-- self.specifier = SpecifierSet(req.specifier)
-- self.marker = req.marker if req.marker else None
-+ self.extras: Set[str] = set(parsed.extras if parsed.extras else [])
-+ self.specifier: SpecifierSet = SpecifierSet(parsed.specifier)
-+ self.marker: Optional[Marker] = None
-+ if parsed.marker is not None:
-+ self.marker = Marker.__new__(Marker)
-+ self.marker._markers = _normalize_extra_values(parsed.marker)
-
-- def __str__(self):
-- # type: () -> str
-- parts = [self.name] # type: List[str]
-+ def __str__(self) -> str:
-+ parts: List[str] = [self.name]
-
- if self.extras:
-- parts.append("[{0}]".format(",".join(sorted(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("@ {0}".format(self.url))
-+ parts.append(f"@ {self.url}")
- if self.marker:
- parts.append(" ")
-
- if self.marker:
-- parts.append("; {0}".format(self.marker))
-+ parts.append(f"; {self.marker}")
-
- return "".join(parts)
-
-- def __repr__(self):
-- # type: () -> str
-- return "<Requirement({0!r})>".format(str(self))
-+ def __repr__(self) -> str:
-+ return f"<Requirement('{self}')>"
-+
-+ def __hash__(self) -> int:
-+ return hash((self.__class__.__name__, str(self)))
-+
-+ def __eq__(self, other: Any) -> bool:
-+ if not isinstance(other, Requirement):
-+ return NotImplemented
-+
-+ return (
-+ self.name == other.name
-+ and self.extras == other.extras
-+ and self.specifier == other.specifier
-+ and self.url == other.url
-+ and self.marker == other.marker
-+ )
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/packaging/specifiers.py
b/third_party/python/setuptools/setuptools/_vendor/packaging/specifiers.py
----
a/third_party/python/setuptools/setuptools/_vendor/packaging/specifiers.py
-+++
b/third_party/python/setuptools/setuptools/_vendor/packaging/specifiers.py
-@@ -1,399 +1,185 @@
- # 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 __future__ import absolute_import, division, print_function
-+"""
-+.. testsetup::
-+
-+ from packaging.specifiers import Specifier, SpecifierSet,
InvalidSpecifier
-+ from packaging.version import Version
-+"""
-
- import abc
--import functools
- import itertools
- import re
--
--from ._compat import string_types, with_metaclass
--from ._typing import TYPE_CHECKING
--from .utils import canonicalize_version
--from .version import Version, LegacyVersion, parse
-+from typing import (
-+ Callable,
-+ Iterable,
-+ Iterator,
-+ List,
-+ Optional,
-+ Set,
-+ Tuple,
-+ TypeVar,
-+ Union,
-+)
-
--if TYPE_CHECKING: # pragma: no cover
-- from typing import (
-- List,
-- Dict,
-- Union,
-- Iterable,
-- Iterator,
-- Optional,
-- Callable,
-- Tuple,
-- FrozenSet,
-- )
-+from .utils import canonicalize_version
-+from .version import Version
-
-- ParsedVersion = Union[Version, LegacyVersion]
-- UnparsedVersion = Union[Version, LegacyVersion, str]
-- CallableOperator = Callable[[ParsedVersion, str], bool]
-+UnparsedVersion = Union[Version, str]
-+UnparsedVersionVar = TypeVar("UnparsedVersionVar", bound=UnparsedVersion)
-+CallableOperator = Callable[[Version, str], bool]
-+
-+
-+def _coerce_version(version: UnparsedVersion) -> Version:
-+ if not isinstance(version, Version):
-+ version = Version(version)
-+ return version
-
-
- class InvalidSpecifier(ValueError):
- """
-- An invalid specifier was found, users should refer to PEP 440.
-+ Raised when attempting to create a :class:`Specifier` with a specifier
-+ string that is invalid.
-+
-+ >>> Specifier("lolwat")
-+ Traceback (most recent call last):
-+ ...
-+ packaging.specifiers.InvalidSpecifier: Invalid specifier: 'lolwat'
- """
-
-
--class BaseSpecifier(with_metaclass(abc.ABCMeta, object)): # type: ignore
-+class BaseSpecifier(metaclass=abc.ABCMeta):
- @abc.abstractmethod
-- def __str__(self):
-- # type: () -> str
-+ def __str__(self) -> str:
- """
-- Returns the str representation of this Specifier like object. This
-+ Returns the str representation of this Specifier-like object. This
- should be representative of the Specifier itself.
- """
-
- @abc.abstractmethod
-- def __hash__(self):
-- # type: () -> int
-- """
-- Returns a hash value for this Specifier like object.
-+ def __hash__(self) -> int:
- """
--
-- @abc.abstractmethod
-- def __eq__(self, other):
-- # type: (object) -> bool
-- """
-- Returns a boolean representing whether or not the two Specifier like
-- objects are equal.
-+ Returns a hash value for this Specifier-like object.
- """
-
- @abc.abstractmethod
-- def __ne__(self, other):
-- # type: (object) -> bool
-+ def __eq__(self, other: object) -> bool:
- """
-- Returns a boolean representing whether or not the two Specifier like
-- objects are not equal.
-+ Returns a boolean representing whether or not the two Specifier-like
-+ objects are equal.
-+
-+ :param other: The other object to check against.
- """
-
-- @abc.abstractproperty
-- def prereleases(self):
-- # type: () -> Optional[bool]
-- """
-- Returns whether or not pre-releases as a whole are allowed by this
-- specifier.
-+ @property
-+ @abc.abstractmethod
-+ def prereleases(self) -> Optional[bool]:
-+ """Whether or not pre-releases as a whole are allowed.
-+
-+ This can be set to either ``True`` or ``False`` to explicitly
enable or disable
-+ prereleases or it can be set to ``None`` (the default) to use
default semantics.
- """
-
- @prereleases.setter
-- def prereleases(self, value):
-- # type: (bool) -> None
-- """
-- Sets whether or not pre-releases as a whole are allowed by this
-- specifier.
-+ def prereleases(self, value: bool) -> None:
-+ """Setter for :attr:`prereleases`.
-+
-+ :param value: The value to set.
- """
-
- @abc.abstractmethod
-- def contains(self, item, prereleases=None):
-- # type: (str, Optional[bool]) -> bool
-+ 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, prereleases=None):
-- # type: (Iterable[UnparsedVersion], Optional[bool]) ->
Iterable[UnparsedVersion]
-+ def filter(
-+ self, iterable: Iterable[UnparsedVersionVar], prereleases:
Optional[bool] = None
-+ ) -> Iterator[UnparsedVersionVar]:
- """
- 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 = {} # type: Dict[str, str]
--
-- def __init__(self, spec="", prereleases=None):
-- # type: (str, Optional[bool]) -> None
-- match = self._regex.search(spec)
-- if not match:
-- raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec))
--
-- self._spec = (
-- match.group("operator").strip(),
-- match.group("version").strip(),
-- ) # type: Tuple[str, str]
--
-- # Store whether or not this Specifier should accept prereleases
-- self._prereleases = prereleases
--
-- def __repr__(self):
-- # type: () -> str
-- pre = (
-- ", prereleases={0!r}".format(self.prereleases)
-- if self._prereleases is not None
-- else ""
-- )
--
-- return "<{0}({1!r}{2})>".format(self.__class__.__name__, str(self),
pre)
--
-- def __str__(self):
-- # type: () -> str
-- return "{0}{1}".format(*self._spec)
--
-- @property
-- def _canonical_spec(self):
-- # type: () -> Tuple[str, Union[Version, str]]
-- return self._spec[0], canonicalize_version(self._spec[1])
--
-- def __hash__(self):
-- # type: () -> int
-- return hash(self._canonical_spec)
--
-- def __eq__(self, other):
-- # type: (object) -> bool
-- if isinstance(other, string_types):
-- 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
-+class Specifier(BaseSpecifier):
-+ """This class abstracts handling of version specifiers.
-
-- def __ne__(self, other):
-- # type: (object) -> bool
-- if isinstance(other, string_types):
-- 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):
-- # type: (str) -> CallableOperator
-- operator_callable = getattr(
-- self, "_compare_{0}".format(self._operators[op])
-- ) # type: CallableOperator
-- return operator_callable
--
-- def _coerce_version(self, version):
-- # type: (UnparsedVersion) -> ParsedVersion
-- if not isinstance(version, (LegacyVersion, Version)):
-- version = parse(version)
-- return version
--
-- @property
-- def operator(self):
-- # type: () -> str
-- return self._spec[0]
--
-- @property
-- def version(self):
-- # type: () -> str
-- return self._spec[1]
--
-- @property
-- def prereleases(self):
-- # type: () -> Optional[bool]
-- return self._prereleases
--
-- @prereleases.setter
-- def prereleases(self, value):
-- # type: (bool) -> None
-- self._prereleases = value
--
-- def __contains__(self, item):
-- # type: (str) -> bool
-- return self.contains(item)
--
-- def contains(self, item, prereleases=None):
-- # type: (UnparsedVersion, Optional[bool]) -> 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)
-+ .. tip::
-
-- # 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 = self._get_operator(self.operator) # type:
CallableOperator
-- return operator_callable(normalized_item, self.version)
--
-- def filter(self, iterable, prereleases=None):
-- # type: (Iterable[UnparsedVersion], Optional[bool]) ->
Iterable[UnparsedVersion]
--
-- 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 incase 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<operator>(==|!=|<=|>=|<|>))
-- \s*
-- (?P<version>
-- [^,;\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.
-- )
-- """
-+ It is generally not required to instantiate this manually. You
should instead
-+ prefer to work with :class:`SpecifierSet` instead, which can parse
-+ comma-separated version specifiers (which is what package metadata
contains).
-+ """
-
-- _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 _coerce_version(self, version):
-- # type: (Union[ParsedVersion, str]) -> LegacyVersion
-- if not isinstance(version, LegacyVersion):
-- version = LegacyVersion(str(version))
-- return version
--
-- def _compare_equal(self, prospective, spec):
-- # type: (LegacyVersion, str) -> bool
-- return prospective == self._coerce_version(spec)
--
-- def _compare_not_equal(self, prospective, spec):
-- # type: (LegacyVersion, str) -> bool
-- return prospective != self._coerce_version(spec)
--
-- def _compare_less_than_equal(self, prospective, spec):
-- # type: (LegacyVersion, str) -> bool
-- return prospective <= self._coerce_version(spec)
--
-- def _compare_greater_than_equal(self, prospective, spec):
-- # type: (LegacyVersion, str) -> bool
-- return prospective >= self._coerce_version(spec)
--
-- def _compare_less_than(self, prospective, spec):
-- # type: (LegacyVersion, str) -> bool
-- return prospective < self._coerce_version(spec)
--
-- def _compare_greater_than(self, prospective, spec):
-- # type: (LegacyVersion, str) -> bool
-- return prospective > self._coerce_version(spec)
--
--
--def _require_version_compare(
-- fn # type: (Callable[[Specifier, ParsedVersion, str], bool])
--):
-- # type: (...) -> Callable[[Specifier, ParsedVersion, str], bool]
-- @functools.wraps(fn)
-- def wrapped(self, prospective, spec):
-- # type: (Specifier, ParsedVersion, str) -> bool
-- if not isinstance(prospective, Version):
-- return False
-- return fn(self, prospective, spec)
--
-- return wrapped
--
--
--class Specifier(_IndividualSpecifier):
--
-- _regex_str = r"""
-+ _operator_regex_str = r"""
- (?P<operator>(~=|==|!=|<=|>=|<|>|===))
-+ """
-+ _version_regex_str = r"""
- (?P<version>
- (?:
- # 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.
-+ [^\s;)]* # The arbitrary version can be just about
anything,
-+ # we match everything except for whitespace, a
-+ # semi-colon for marker support, and a closing
paren
-+ # since versions can be enclosed in them.
- )
- |
- (?:
- # 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.
-+ # You cannot use a wild card and a pre-release,
post-release, a dev or
-+ # local version together so group them with a | and make
them optional.
- (?:
-+ \.\* # Wild card syntax of .*
-+ |
-+ (?: # pre release
-+ [-_\.]?
-+ (alpha|beta|preview|pre|a|b|c|rc)
-+ [-_\.]?
-+ [0-9]*
-+ )?
-+ (?: # post release
-+ (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
-+ )?
- (?:[-_\.]?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)
-+ (alpha|beta|preview|pre|a|b|c|rc)
- [-_\.]?
- [0-9]*
- )?
- (?: # post release
- (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
- )?
- (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
- )
-@@ -408,137 +194,270 @@ class Specifier(_IndividualSpecifier):
- # don't match here.
-
- \s*
- v?
- (?:[0-9]+!)? # epoch
- [0-9]+(?:\.[0-9]+)* # release
- (?: # pre release
- [-_\.]?
-- (a|b|c|rc|alpha|beta|pre|preview)
-+ (alpha|beta|preview|pre|a|b|c|rc)
- [-_\.]?
- [0-9]*
- )?
- (?: # post release
- (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
- )?
- (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
- )
- )
- """
-
-- _regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE |
re.IGNORECASE)
-+ _regex = re.compile(
-+ r"^\s*" + _operator_regex_str + _version_regex_str + r"\s*$",
-+ re.VERBOSE | re.IGNORECASE,
-+ )
-
- _operators = {
- "~=": "compatible",
- "==": "equal",
- "!=": "not_equal",
- "<=": "less_than_equal",
- ">=": "greater_than_equal",
- "<": "less_than",
- ">": "greater_than",
- "===": "arbitrary",
- }
-
-- @_require_version_compare
-- def _compare_compatible(self, prospective, spec):
-- # type: (ParsedVersion, str) -> bool
-+ def __init__(self, spec: str = "", prereleases: Optional[bool] = None)
-> None:
-+ """Initialize a Specifier instance.
-+
-+ :param spec:
-+ The string representation of a specifier which will be parsed
and
-+ normalized before use.
-+ :param prereleases:
-+ This tells the specifier if it should accept prerelease
versions if
-+ applicable or not. The default of ``None`` will autodetect it
from the
-+ given specifiers.
-+ :raises InvalidSpecifier:
-+ If the given specifier is invalid (i.e. bad syntax).
-+ """
-+ 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
-+
-+ # https://github.com/python/mypy/pull/13475#pullrequestreview-1079784515
-+ @property # type: ignore[override]
-+ 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 Version(version).is_prerelease:
-+ return True
-+
-+ return False
-+
-+ @prereleases.setter
-+ def prereleases(self, value: bool) -> None:
-+ self._prereleases = value
-+
-+ @property
-+ def operator(self) -> str:
-+ """The operator of this specifier.
-+
-+ >>> Specifier("==1.2.3").operator
-+ '=='
-+ """
-+ return self._spec[0]
-+
-+ @property
-+ def version(self) -> str:
-+ """The version of this specifier.
-+
-+ >>> Specifier("==1.2.3").version
-+ '1.2.3'
-+ """
-+ return self._spec[1]
-+
-+ def __repr__(self) -> str:
-+ """A representation of the Specifier that shows all internal state.
-+
-+ >>> Specifier('>=1.0.0')
-+ <Specifier('>=1.0.0')>
-+ >>> Specifier('>=1.0.0', prereleases=False)
-+ <Specifier('>=1.0.0', prereleases=False)>
-+ >>> Specifier('>=1.0.0', prereleases=True)
-+ <Specifier('>=1.0.0', prereleases=True)>
-+ """
-+ pre = (
-+ f", prereleases={self.prereleases!r}"
-+ if self._prereleases is not None
-+ else ""
-+ )
-+
-+ return f"<{self.__class__.__name__}({str(self)!r}{pre})>"
-+
-+ def __str__(self) -> str:
-+ """A string representation of the Specifier that can be
round-tripped.
-+
-+ >>> str(Specifier('>=1.0.0'))
-+ '>=1.0.0'
-+ >>> str(Specifier('>=1.0.0', prereleases=False))
-+ '>=1.0.0'
-+ """
-+ return "{}{}".format(*self._spec)
-+
-+ @property
-+ def _canonical_spec(self) -> Tuple[str, str]:
-+ canonical_version = canonicalize_version(
-+ self._spec[1],
-+ strip_trailing_zero=(self._spec[0] != "~="),
-+ )
-+ return self._spec[0], canonical_version
-+
-+ def __hash__(self) -> int:
-+ return hash(self._canonical_spec)
-+
-+ def __eq__(self, other: object) -> bool:
-+ """Whether or not the two Specifier-like objects are equal.
-+
-+ :param other: The other object to check against.
-+
-+ The value of :attr:`prereleases` is ignored.
-+
-+ >>> Specifier("==1.2.3") == Specifier("== 1.2.3.0")
-+ True
-+ >>> (Specifier("==1.2.3", prereleases=False) ==
-+ ... Specifier("==1.2.3", prereleases=True))
-+ True
-+ >>> Specifier("==1.2.3") == "==1.2.3"
-+ True
-+ >>> Specifier("==1.2.3") == Specifier("==1.2.4")
-+ False
-+ >>> Specifier("==1.2.3") == Specifier("~=1.2.3")
-+ False
-+ """
-+ 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 _get_operator(self, op: str) -> CallableOperator:
-+ operator_callable: CallableOperator = getattr(
-+ self, f"_compare_{self._operators[op]}"
-+ )
-+ return operator_callable
-+
-+ def _compare_compatible(self, prospective: Version, 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 post and dev releases and we want to treat the pre-release
as
-- # it's own separate segment.
-+ # ignore suffix segments.
- prefix = ".".join(
-- list(
-- itertools.takewhile(
-- lambda x: (not x.startswith("post") and not
x.startswith("dev")),
-- _version_split(spec),
-- )
-- )[:-1]
-+ 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, spec):
-- # type: (ParsedVersion, str) -> bool
-+ def _compare_equal(self, prospective: Version, 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)
-+ normalized_prospective = canonicalize_version(
-+ prospective.public, strip_trailing_zero=False
-+ )
-+ # Get the normalized version string ignoring the trailing .*
-+ normalized_spec = canonicalize_version(spec[:-2],
strip_trailing_zero=False)
- # 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_spec = _version_split(normalized_spec)
-
- # 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))
-+ split_prospective = _version_split(normalized_prospective)
-+
-+ # 0-pad the prospective version before shortening it to get the
correct
-+ # shortened version.
-+ padded_prospective, _ = _pad_version(split_prospective,
split_spec)
-
- # 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)]
-+ shortened_prospective = padded_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
-+ return shortened_prospective == split_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, spec):
-- # type: (ParsedVersion, str) -> bool
-+ def _compare_not_equal(self, prospective: Version, spec: str) -> bool:
- return not self._compare_equal(prospective, spec)
-
-- @_require_version_compare
-- def _compare_less_than_equal(self, prospective, spec):
-- # type: (ParsedVersion, str) -> bool
-+ def _compare_less_than_equal(self, prospective: Version, 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, spec):
-- # type: (ParsedVersion, str) -> bool
-+ def _compare_greater_than_equal(self, prospective: Version, 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, spec_str):
-- # type: (ParsedVersion, str) -> bool
-+ def _compare_less_than(self, prospective: Version, 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.
-@@ -553,19 +472,17 @@ class Specifier(_IndividualSpecifier):
- 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, spec_str):
-- # type: (ParsedVersion, str) -> bool
-+ def _compare_greater_than(self, prospective: Version, 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.
-@@ -586,69 +503,169 @@ class Specifier(_IndividualSpecifier):
- 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, spec):
-- # type: (Version, str) -> bool
-+ def _compare_arbitrary(self, prospective: Version, spec: str) -> bool:
- return str(prospective).lower() == str(spec).lower()
-
-- @property
-- def prereleases(self):
-- # type: () -> bool
-+ def __contains__(self, item: Union[str, Version]) -> bool:
-+ """Return whether or not the item is contained in this specifier.
-+
-+ :param item: The item to check for.
-+
-+ This is used for the ``in`` operator and behaves the same as
-+ :meth:`contains` with no ``prereleases`` argument passed.
-+
-+ >>> "1.2.3" in Specifier(">=1.2.3")
-+ True
-+ >>> Version("1.2.3") in Specifier(">=1.2.3")
-+ True
-+ >>> "1.0.0" in Specifier(">=1.2.3")
-+ False
-+ >>> "1.3.0a1" in Specifier(">=1.2.3")
-+ False
-+ >>> "1.3.0a1" in Specifier(">=1.2.3", prereleases=True)
-+ True
-+ """
-+ return self.contains(item)
-+
-+ def contains(
-+ self, item: UnparsedVersion, prereleases: Optional[bool] = None
-+ ) -> bool:
-+ """Return whether or not the item is contained in this specifier.
-
-- # 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
-+ :param item:
-+ The item to check for, which can be a version string or a
-+ :class:`Version` instance.
-+ :param prereleases:
-+ Whether or not to match prereleases with this Specifier. If set
to
-+ ``None`` (the default), it uses :attr:`prereleases` to determine
-+ whether or not prereleases are allowed.
-+
-+ >>> Specifier(">=1.2.3").contains("1.2.3")
-+ True
-+ >>> Specifier(">=1.2.3").contains(Version("1.2.3"))
-+ True
-+ >>> Specifier(">=1.2.3").contains("1.0.0")
-+ False
-+ >>> Specifier(">=1.2.3").contains("1.3.0a1")
-+ False
-+ >>> Specifier(">=1.2.3", prereleases=True).contains("1.3.0a1")
-+ True
-+ >>> Specifier(">=1.2.3").contains("1.3.0a1", prereleases=True)
-+ True
-+ """
-+
-+ # Determine if prereleases are to be allowed or not.
-+ if prereleases is None:
-+ prereleases = self.prereleases
-+
-+ # Normalize item to a Version, this allows us to have a shortcut for
-+ # "2.0" in Specifier(">=2")
-+ normalized_item = _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
-
-- # 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]
-+ # 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[UnparsedVersionVar], prereleases:
Optional[bool] = None
-+ ) -> Iterator[UnparsedVersionVar]:
-+ """Filter items in the given iterable, that match the specifier.
-+
-+ :param iterable:
-+ An iterable that can contain version strings and
:class:`Version` instances.
-+ The items in the iterable will be filtered according to the
specifier.
-+ :param prereleases:
-+ Whether or not to allow prereleases in the returned iterator.
If set to
-+ ``None`` (the default), it will be intelligently decide whether
to allow
-+ prereleases or not (based on the :attr:`prereleases` attribute,
and
-+ whether the only versions matching are prereleases).
-+
-+ This method is smarter than just ``filter(Specifier().contains,
[...])``
-+ because it implements the rule from :pep:`440` that a prerelease
item
-+ SHOULD be accepted if no other versions match the given specifier.
-
-- # Parse the version, and if it is a pre-release than this
-- # specifier allows pre-releases.
-- if parse(version).is_prerelease:
-- return True
-+ >>> list(Specifier(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
-+ ['1.3']
-+ >>> list(Specifier(">=1.2.3").filter(["1.2", "1.2.3", "1.3",
Version("1.4")]))
-+ ['1.2.3', '1.3', <Version('1.4')>]
-+ >>> list(Specifier(">=1.2.3").filter(["1.2", "1.5a1"]))
-+ ['1.5a1']
-+ >>> list(Specifier(">=1.2.3").filter(["1.3", "1.5a1"],
prereleases=True))
-+ ['1.3', '1.5a1']
-+ >>> list(Specifier(">=1.2.3", prereleases=True).filter(["1.3",
"1.5a1"]))
-+ ['1.3', '1.5a1']
-+ """
-+
-+ yielded = False
-+ found_prereleases = []
-+
-+ kw = {"prereleases": prereleases if prereleases is not None else
True}
-
-- return False
-+ # Attempt to iterate over all the values in the iterable and if any
of
-+ # them match, yield them.
-+ for version in iterable:
-+ parsed_version = _coerce_version(version)
-
-- @prereleases.setter
-- def prereleases(self, value):
-- # type: (bool) -> None
-- self._prereleases = value
-+ 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
-
-
- _prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$")
-
-
--def _version_split(version):
-- # type: (str) -> List[str]
-- result = [] # type: List[str]
-+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 _pad_version(left, right):
-- # type: (List[str], List[str]) -> Tuple[List[str], List[str]]
-+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]) :])
-@@ -657,60 +674,126 @@ def _pad_version(left, right):
- # 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="", prereleases=None):
-- # type: (str, Optional[bool]) -> None
-+ """This class abstracts handling of a set of version specifiers.
-+
-+ It can be passed a single specifier (``>=3.0``), a comma-separated list
of
-+ specifiers (``>=3.0,!=3.1``), or no specifier at all.
-+ """
-+
-+ def __init__(
-+ self, specifiers: str = "", prereleases: Optional[bool] = None
-+ ) -> None:
-+ """Initialize a SpecifierSet instance.
-
-- # Split on , to break each individual specifier into it's own item,
and
-+ :param specifiers:
-+ The string representation of a specifier or a comma-separated
list of
-+ specifiers which will be parsed and normalized before use.
-+ :param prereleases:
-+ This tells the SpecifierSet if it should accept prerelease
versions if
-+ applicable or not. The default of ``None`` will autodetect it
from the
-+ given specifiers.
-+
-+ :raises InvalidSpecifier:
-+ If the given ``specifiers`` are not parseable than this
exception will be
-+ raised.
-+ """
-+
-+ # 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()
-+ # Specifier.
-+ parsed: Set[Specifier] = set()
- for specifier in split_specifiers:
-- try:
-- parsed.add(Specifier(specifier))
-- except InvalidSpecifier:
-- parsed.add(LegacySpecifier(specifier))
-+ parsed.add(Specifier(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):
-- # type: () -> str
-+ @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 __repr__(self) -> str:
-+ """A representation of the specifier set that shows all internal
state.
-+
-+ Note that the ordering of the individual specifiers within the set
may not
-+ match the input string.
-+
-+ >>> SpecifierSet('>=1.0.0,!=2.0.0')
-+ <SpecifierSet('!=2.0.0,>=1.0.0')>
-+ >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=False)
-+ <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=False)>
-+ >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=True)
-+ <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=True)>
-+ """
- pre = (
-- ", prereleases={0!r}".format(self.prereleases)
-+ f", prereleases={self.prereleases!r}"
- if self._prereleases is not None
- else ""
- )
-
-- return "<SpecifierSet({0!r}{1})>".format(str(self), pre)
-+ return f"<SpecifierSet({str(self)!r}{pre})>"
-+
-+ def __str__(self) -> str:
-+ """A string representation of the specifier set that can be
round-tripped.
-
-- def __str__(self):
-- # type: () -> str
-+ Note that the ordering of the individual specifiers within the set
may not
-+ match the input string.
-+
-+ >>> str(SpecifierSet(">=1.0.0,!=1.0.1"))
-+ '!=1.0.1,>=1.0.0'
-+ >>> str(SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False))
-+ '!=1.0.1,>=1.0.0'
-+ """
- return ",".join(sorted(str(s) for s in self._specs))
-
-- def __hash__(self):
-- # type: () -> int
-+ def __hash__(self) -> int:
- return hash(self._specs)
-
-- def __and__(self, other):
-- # type: (Union[SpecifierSet, str]) -> SpecifierSet
-- if isinstance(other, string_types):
-+ def __and__(self, other: Union["SpecifierSet", str]) -> "SpecifierSet":
-+ """Return a SpecifierSet which is a combination of the two sets.
-+
-+ :param other: The other object to combine with.
-+
-+ >>> SpecifierSet(">=1.0.0,!=1.0.1") & '<=2.0.0,!=2.0.1'
-+ <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>
-+ >>> SpecifierSet(">=1.0.0,!=1.0.1") &
SpecifierSet('<=2.0.0,!=2.0.1')
-+ <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>
-+ """
-+ 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:
-@@ -722,142 +805,204 @@ class SpecifierSet(BaseSpecifier):
- else:
- raise ValueError(
- "Cannot combine SpecifierSets with True and False
prerelease "
- "overrides."
- )
-
- return specifier
-
-- def __eq__(self, other):
-- # type: (object) -> bool
-- if isinstance(other, (string_types, _IndividualSpecifier)):
-+ def __eq__(self, other: object) -> bool:
-+ """Whether or not the two SpecifierSet-like objects are equal.
-+
-+ :param other: The other object to check against.
-+
-+ The value of :attr:`prereleases` is ignored.
-+
-+ >>> SpecifierSet(">=1.0.0,!=1.0.1") ==
SpecifierSet(">=1.0.0,!=1.0.1")
-+ True
-+ >>> (SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False) ==
-+ ... SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True))
-+ True
-+ >>> SpecifierSet(">=1.0.0,!=1.0.1") == ">=1.0.0,!=1.0.1"
-+ True
-+ >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0")
-+ False
-+ >>> SpecifierSet(">=1.0.0,!=1.0.1") ==
SpecifierSet(">=1.0.0,!=1.0.2")
-+ False
-+ """
-+ if isinstance(other, (str, Specifier)):
- other = SpecifierSet(str(other))
- elif not isinstance(other, SpecifierSet):
- return NotImplemented
-
- return self._specs == other._specs
-
-- def __ne__(self, other):
-- # type: (object) -> bool
-- if isinstance(other, (string_types, _IndividualSpecifier)):
-- other = SpecifierSet(str(other))
-- elif not isinstance(other, SpecifierSet):
-- return NotImplemented
--
-- return self._specs != other._specs
--
-- def __len__(self):
-- # type: () -> int
-+ def __len__(self) -> int:
-+ """Returns the number of specifiers in this specifier set."""
- return len(self._specs)
-
-- def __iter__(self):
-- # type: () -> Iterator[FrozenSet[_IndividualSpecifier]]
-+ def __iter__(self) -> Iterator[Specifier]:
-+ """
-+ Returns an iterator over all the underlying :class:`Specifier`
instances
-+ in this specifier set.
-+
-+ >>> sorted(SpecifierSet(">=1.0.0,!=1.0.1"), key=str)
-+ [<Specifier('!=1.0.1')>, <Specifier('>=1.0.0')>]
-+ """
- return iter(self._specs)
-
-- @property
-- def prereleases(self):
-- # type: () -> Optional[bool]
-+ def __contains__(self, item: UnparsedVersion) -> bool:
-+ """Return whether or not the item is contained in this specifier.
-
-- # 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
-+ :param item: The item to check for.
-+
-+ This is used for the ``in`` operator and behaves the same as
-+ :meth:`contains` with no ``prereleases`` argument passed.
-
-- # 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):
-- # type: (bool) -> None
-- self._prereleases = value
--
-- def __contains__(self, item):
-- # type: (Union[ParsedVersion, str]) -> bool
-+ >>> "1.2.3" in SpecifierSet(">=1.0.0,!=1.0.1")
-+ True
-+ >>> Version("1.2.3") in SpecifierSet(">=1.0.0,!=1.0.1")
-+ True
-+ >>> "1.0.1" in SpecifierSet(">=1.0.0,!=1.0.1")
-+ False
-+ >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1")
-+ False
-+ >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True)
-+ True
-+ """
- return self.contains(item)
-
-- def contains(self, item, prereleases=None):
-- # type: (Union[ParsedVersion, str], Optional[bool]) -> bool
-+ def contains(
-+ self,
-+ item: UnparsedVersion,
-+ prereleases: Optional[bool] = None,
-+ installed: Optional[bool] = None,
-+ ) -> bool:
-+ """Return whether or not the item is contained in this SpecifierSet.
-+
-+ :param item:
-+ The item to check for, which can be a version string or a
-+ :class:`Version` instance.
-+ :param prereleases:
-+ Whether or not to match prereleases with this SpecifierSet. If
set to
-+ ``None`` (the default), it uses :attr:`prereleases` to determine
-+ whether or not prereleases are allowed.
-
-- # Ensure that our item is a Version or LegacyVersion instance.
-- if not isinstance(item, (LegacyVersion, Version)):
-- item = parse(item)
-+ >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.2.3")
-+ True
-+ >>> SpecifierSet(">=1.0.0,!=1.0.1").contains(Version("1.2.3"))
-+ True
-+ >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.0.1")
-+ False
-+ >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1")
-+ False
-+ >>> SpecifierSet(">=1.0.0,!=1.0.1",
prereleases=True).contains("1.3.0a1")
-+ True
-+ >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1",
prereleases=True)
-+ True
-+ """
-+ # Ensure that our item is a Version instance.
-+ if not isinstance(item, Version):
-+ item = Version(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
-
-+ if installed and item.is_prerelease:
-+ item = Version(item.base_version)
-+
- # 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, # type: Iterable[Union[ParsedVersion, str]]
-- prereleases=None, # type: Optional[bool]
-- ):
-- # type: (...) -> Iterable[Union[ParsedVersion, str]]
-+ self, iterable: Iterable[UnparsedVersionVar], prereleases:
Optional[bool] = None
-+ ) -> Iterator[UnparsedVersionVar]:
-+ """Filter items in the given iterable, that match the specifiers in
this set.
-+
-+ :param iterable:
-+ An iterable that can contain version strings and
:class:`Version` instances.
-+ The items in the iterable will be filtered according to the
specifier.
-+ :param prereleases:
-+ Whether or not to allow prereleases in the returned iterator.
If set to
-+ ``None`` (the default), it will be intelligently decide whether
to allow
-+ prereleases or not (based on the :attr:`prereleases` attribute,
and
-+ whether the only versions matching are prereleases).
-+
-+ This method is smarter than just
``filter(SpecifierSet(...).contains, [...])``
-+ because it implements the rule from :pep:`440` that a prerelease
item
-+ SHOULD be accepted if no other versions match the given specifier.
-
-+ >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
-+ ['1.3']
-+ >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3",
Version("1.4")]))
-+ ['1.3', <Version('1.4')>]
-+ >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.5a1"]))
-+ []
-+ >>> list(SpecifierSet(">=1.2.3").filter(["1.3", "1.5a1"],
prereleases=True))
-+ ['1.3', '1.5a1']
-+ >>> list(SpecifierSet(">=1.2.3", prereleases=True).filter(["1.3",
"1.5a1"]))
-+ ['1.3', '1.5a1']
-+
-+ An "empty" SpecifierSet will filter items based on the presence of
prerelease
-+ versions in the set.
-+
-+ >>> list(SpecifierSet("").filter(["1.3", "1.5a1"]))
-+ ['1.3']
-+ >>> list(SpecifierSet("").filter(["1.5a1"]))
-+ ['1.5a1']
-+ >>> list(SpecifierSet("", prereleases=True).filter(["1.3",
"1.5a1"]))
-+ ['1.3', '1.5a1']
-+ >>> list(SpecifierSet("").filter(["1.3", "1.5a1"],
prereleases=True))
-+ ['1.3', '1.5a1']
-+ """
- # 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
-+ return iter(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.
-+ # releases.
- else:
-- filtered = [] # type: List[Union[ParsedVersion, str]]
-- found_prereleases = [] # type: List[Union[ParsedVersion, str]]
-+ filtered: List[UnparsedVersionVar] = []
-+ found_prereleases: List[UnparsedVersionVar] = []
-
- 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
-+ parsed_version = _coerce_version(item)
-
- # 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 iter(found_prereleases)
-
-- return filtered
-+ return iter(filtered)
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/packaging/tags.py
b/third_party/python/setuptools/setuptools/_vendor/packaging/tags.py
---- a/third_party/python/setuptools/setuptools/_vendor/packaging/tags.py
-+++ b/third_party/python/setuptools/setuptools/_vendor/packaging/tags.py
-@@ -1,182 +1,143 @@
- # 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 __future__ import absolute_import
--
--import distutils.util
--
--try:
-- from importlib.machinery import EXTENSION_SUFFIXES
--except ImportError: # pragma: no cover
-- import imp
--
-- EXTENSION_SUFFIXES = [x[0] for x in imp.get_suffixes()]
-- del imp
- import logging
--import os
- import platform
--import re
--import struct
-+import subprocess
- import sys
- import sysconfig
--import warnings
--
--from ._typing import TYPE_CHECKING, cast
-+from importlib.machinery import EXTENSION_SUFFIXES
-+from typing import (
-+ Dict,
-+ FrozenSet,
-+ Iterable,
-+ Iterator,
-+ List,
-+ Optional,
-+ Sequence,
-+ Tuple,
-+ Union,
-+ cast,
-+)
-
--if TYPE_CHECKING: # pragma: no cover
-- from typing import (
-- Dict,
-- FrozenSet,
-- IO,
-- Iterable,
-- Iterator,
-- List,
-- Optional,
-- Sequence,
-- Tuple,
-- Union,
-- )
--
-- PythonVersion = Sequence[int]
-- MacVersion = Tuple[int, int]
-- GlibcVersion = Tuple[int, int]
--
-+from . import _manylinux, _musllinux
-
- logger = logging.getLogger(__name__)
-
--INTERPRETER_SHORT_NAMES = {
-+PythonVersion = Sequence[int]
-+MacVersion = Tuple[int, int]
-+
-+INTERPRETER_SHORT_NAMES: Dict[str, str] = {
- "python": "py", # Generic.
- "cpython": "cp",
- "pypy": "pp",
- "ironpython": "ip",
- "jython": "jy",
--} # type: Dict[str, str]
-+}
-
-
--_32_BIT_INTERPRETER = sys.maxsize <= 2 ** 32
-+_32_BIT_INTERPRETER = sys.maxsize <= 2**32
-
-
--class Tag(object):
-+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"]
-+ __slots__ = ["_interpreter", "_abi", "_platform", "_hash"]
-
-- def __init__(self, interpreter, abi, platform):
-- # type: (str, str, str) -> None
-+ 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):
-- # type: () -> str
-+ def interpreter(self) -> str:
- return self._interpreter
-
- @property
-- def abi(self):
-- # type: () -> str
-+ def abi(self) -> str:
- return self._abi
-
- @property
-- def platform(self):
-- # type: () -> str
-+ def platform(self) -> str:
- return self._platform
-
-- def __eq__(self, other):
-- # type: (object) -> bool
-+ def __eq__(self, other: object) -> bool:
- if not isinstance(other, Tag):
- return NotImplemented
-
- return (
-- (self.platform == other.platform)
-- and (self.abi == other.abi)
-- and (self.interpreter == other.interpreter)
-+ (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):
-- # type: () -> int
-- return hash((self._interpreter, self._abi, self._platform))
-+ def __hash__(self) -> int:
-+ return self._hash
-
-- def __str__(self):
-- # type: () -> str
-- return "{}-{}-{}".format(self._interpreter, self._abi,
self._platform)
-+ def __str__(self) -> str:
-+ return f"{self._interpreter}-{self._abi}-{self._platform}"
-
-- def __repr__(self):
-- # type: () -> str
-- return "<{self} @ {self_id}>".format(self=self, self_id=id(self))
-+ def __repr__(self) -> str:
-+ return f"<{self} @ {id(self)}>"
-
-
--def parse_tag(tag):
-- # type: (str) -> FrozenSet[Tag]
-+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 _warn_keyword_parameter(func_name, kwargs):
-- # type: (str, Dict[str, bool]) -> bool
-- """
-- Backwards-compatibility with Python 2.7 to allow treating 'warn' as
keyword-only.
-- """
-- if not kwargs:
-- return False
-- elif len(kwargs) > 1 or "warn" not in kwargs:
-- kwargs.pop("warn", None)
-- arg = next(iter(kwargs.keys()))
-- raise TypeError(
-- "{}() got an unexpected keyword argument
{!r}".format(func_name, arg)
-- )
-- return kwargs["warn"]
--
--
--def _get_config_var(name, warn=False):
-- # type: (str, bool) -> Union[int, str, None]
-- value = sysconfig.get_config_var(name)
-+def _get_config_var(name: str, warn: bool = False) -> Union[int, str, None]:
-+ value: Union[int, str, None] = 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):
-- # type: (str) -> str
-- return string.replace(".", "_").replace("-", "_")
-+def _normalize_string(string: str) -> str:
-+ return string.replace(".", "_").replace("-", "_").replace(" ", "_")
-
-
--def _abi3_applies(python_version):
-- # type: (PythonVersion) -> bool
-+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, warn=False):
-- # type: (PythonVersion, bool) -> List[str]
-+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.
-@@ -192,179 +153,206 @@ def _cpython_abis(py_version, warn=False
- 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("cp{version}".format(version=version))
-+ 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=None, # type: Optional[PythonVersion]
-- abis=None, # type: Optional[Iterable[str]]
-- platforms=None, # type: Optional[Iterable[str]]
-- **kwargs # type: bool
--):
-- # type: (...) -> Iterator[Tag]
-+ 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<python_version>-<abi>-<platform>
- - cp<python_version>-abi3-<platform>
- - cp<python_version>-none-<platform>
- - cp<less than python_version>-abi3-<platform> # 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.
- """
-- warn = _warn_keyword_parameter("cpython_tags", kwargs)
- if not python_version:
- python_version = sys.version_info[:2]
-
-- interpreter = "cp{}".format(_version_nodot(python_version[:2]))
-+ interpreter = f"cp{_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())
-+ platforms = list(platforms or platform_tags())
- for abi in abis:
- for platform_ in platforms:
- yield Tag(interpreter, abi, platform_)
- if _abi3_applies(python_version):
-- for tag in (Tag(interpreter, "abi3", platform_) for platform_ in
platforms):
-- yield tag
-- for tag in (Tag(interpreter, "none", platform_) for platform_ in
platforms):
-- yield tag
-+ 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():
-- # type: () -> Iterator[str]
-- abi = sysconfig.get_config_var("SOABI")
-- if abi:
-- yield _normalize_string(abi)
-+def _generic_abi() -> List[str]:
-+ """
-+ Return the ABI tag based on EXT_SUFFIX.
-+ """
-+ # The following are examples of `EXT_SUFFIX`.
-+ # We want to keep the parts which are related to the ABI and remove the
-+ # parts which are related to the platform:
-+ # - linux: '.cpython-310-x86_64-linux-gnu.so' => cp310
-+ # - mac: '.cpython-310-darwin.so' => cp310
-+ # - win: '.cp310-win_amd64.pyd' => cp310
-+ # - win: '.pyd' => cp37 (uses
_cpython_abis())
-+ # - pypy: '.pypy38-pp73-x86_64-linux-gnu.so' => pypy38_pp73
-+ # - graalpy: '.graalpy-38-native-x86_64-darwin.dylib'
-+ # => graalpy_38_native
-+
-+ ext_suffix = _get_config_var("EXT_SUFFIX", warn=True)
-+ if not isinstance(ext_suffix, str) or ext_suffix[0] != ".":
-+ raise SystemError("invalid sysconfig.get_config_var('EXT_SUFFIX')")
-+ parts = ext_suffix.split(".")
-+ if len(parts) < 3:
-+ # CPython3.7 and earlier uses ".pyd" on Windows.
-+ return _cpython_abis(sys.version_info[:2])
-+ soabi = parts[1]
-+ if soabi.startswith("cpython"):
-+ # non-windows
-+ abi = "cp" + soabi.split("-")[1]
-+ elif soabi.startswith("cp"):
-+ # windows
-+ abi = soabi.split("-")[0]
-+ elif soabi.startswith("pypy"):
-+ abi = "-".join(soabi.split("-")[:2])
-+ elif soabi.startswith("graalpy"):
-+ abi = "-".join(soabi.split("-")[:3])
-+ elif soabi:
-+ # pyston, ironpython, others?
-+ abi = soabi
-+ else:
-+ return []
-+ return [_normalize_string(abi)]
-
-
- def generic_tags(
-- interpreter=None, # type: Optional[str]
-- abis=None, # type: Optional[Iterable[str]]
-- platforms=None, # type: Optional[Iterable[str]]
-- **kwargs # type: bool
--):
-- # type: (...) -> Iterator[Tag]
-+ 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:
- - <interpreter>-<abi>-<platform>
-
- The "none" ABI will be added if it was not explicitly provided.
- """
-- warn = _warn_keyword_parameter("generic_tags", kwargs)
- 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)
-+ else:
-+ abis = list(abis)
-+ platforms = list(platforms or platform_tags())
- 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):
-- # type: (PythonVersion) -> Iterator[str]
-+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])
-+ yield f"py{_version_nodot(py_version[:2])}"
-+ yield f"py{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)))
-+ yield f"py{_version_nodot((py_version[0], minor))}"
-
-
- def compatible_tags(
-- python_version=None, # type: Optional[PythonVersion]
-- interpreter=None, # type: Optional[str]
-- platforms=None, # type: Optional[Iterable[str]]
--):
-- # type: (...) -> Iterator[Tag]
-+ 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-<platform>
- - <interpreter>-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())
-+ 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, is_32bit=_32_BIT_INTERPRETER):
-- # type: (str, bool) -> str
-+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, cpu_arch):
-- # type: (MacVersion, str) -> List[str]
-+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):
-@@ -377,375 +365,182 @@ def _mac_binary_formats(version, cpu_arc
- return []
- formats.append("fat64")
-
- elif cpu_arch == "ppc":
- if version > (10, 6):
- return []
- formats.extend(["fat32", "fat"])
-
-- formats.append("universal")
-+ 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=None, arch=None):
-- # type: (Optional[MacVersion], Optional[str]) -> Iterator[str]
-+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() # type: ignore
-+ version_str, _, cpu_arch = platform.mac_ver()
- if version is None:
- version = cast("MacVersion", tuple(map(int,
version_str.split(".")[:2])))
-+ if version == (10, 16):
-+ # When built against an older macOS SDK, Python will report
macOS 10.16
-+ # instead of the real version.
-+ version_str = subprocess.run(
-+ [
-+ sys.executable,
-+ "-sS",
-+ "-c",
-+ "import platform; print(platform.mac_ver()[0])",
-+ ],
-+ check=True,
-+ env={"SYSTEM_VERSION_COMPAT": "0"},
-+ stdout=subprocess.PIPE,
-+ universal_newlines=True,
-+ ).stdout
-+ 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
-- for minor_version in range(version[1], -1, -1):
-- compat_version = version[0], 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,
-- )
--
--
--# From PEP 513.
--def _is_manylinux_compatible(name, glibc_version):
-- # type: (str, GlibcVersion) -> bool
-- # Check for presence of _manylinux module.
-- try:
-- import _manylinux # noqa
--
-- return bool(getattr(_manylinux, name + "_compatible"))
-- except (ImportError, AttributeError):
-- # Fall through to heuristic check below.
-- pass
--
-- return _have_compatible_glibc(*glibc_version)
--
-
--def _glibc_version_string():
-- # type: () -> Optional[str]
-- # Returns glibc version string, or None if not using glibc.
-- return _glibc_version_string_confstr() or _glibc_version_string_ctypes()
--
-+ 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
-+ )
-
--def _glibc_version_string_confstr():
-- # type: () -> 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/fcf1d003bf4f0100c9d0921ff3d70e1127ca1b71/Lib/platform.py#L175-L183
-- try:
-- # os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc
2.17".
-- version_string = os.confstr( # type: ignore[attr-defined] # noqa:
F821
-- "CS_GNU_LIBC_VERSION"
-- )
-- assert version_string is not None
-- _, version = version_string.split() # type: Tuple[str, str]
-- except (AssertionError, AttributeError, OSError, ValueError):
-- # os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad
value)...
-- return None
-- return version
--
-+ 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
-+ )
-
--def _glibc_version_string_ctypes():
-- # type: () -> 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.
-- #
-- # Note: typeshed is wrong here so we are ignoring this line.
-- process_namespace = ctypes.CDLL(None) # type: ignore
-- 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 = gnu_get_libc_version() # type: str
-- # py2 / py3 compatibility:
-- if not isinstance(version_str, str):
-- version_str = version_str.decode("ascii")
--
-- return version_str
--
--
--# Separated out from have_compatible_glibc for easier unit testing.
--def _check_glibc_version(version_str, required_major, minimum_minor):
-- # type: (str, int, int) -> bool
-- # Parse string and check against requested 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<major>[0-9]+)\.(?P<minor>[0-9]+)", version_str)
-- if not m:
-- warnings.warn(
-- "Expected glibc version with 2 components major.minor,"
-- " got: %s" % version_str,
-- RuntimeWarning,
-- )
-- return False
-- return (
-- int(m.group("major")) == required_major
-- and int(m.group("minor")) >= minimum_minor
-- )
--
--
--def _have_compatible_glibc(required_major, minimum_minor):
-- # type: (int, int) -> bool
-- version_str = _glibc_version_string()
-- if version_str is None:
-- return False
-- return _check_glibc_version(version_str, required_major, minimum_minor)
-+ 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,
-+ )
-
-
--# 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(object):
-- #
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):
-- # type: (IO[bytes]) -> None
-- def unpack(fmt):
-- # type: (str) -> int
-- try:
-- (result,) = struct.unpack(
-- fmt, file.read(struct.calcsize(fmt))
-- ) # type: (int, )
-- except struct.error:
-- raise _ELFFileHeader._InvalidELFFileHeader()
-- return result
--
-- 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" if self.e_ident_data == self.ELFDATA2LSB else ">H"
-- format_i = "<I" if self.e_ident_data == self.ELFDATA2LSB else ">I"
-- format_q = "<Q" if self.e_ident_data == self.ELFDATA2LSB else ">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():
-- # type: () -> Optional[_ELFFileHeader]
-- try:
-- with open(sys.executable, "rb") as f:
-- elf_header = _ELFFileHeader(f)
-- except (IOError, OSError, TypeError,
_ELFFileHeader._InvalidELFFileHeader):
-- return None
-- return elf_header
--
--
--def _is_linux_armhf():
-- # type: () -> 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():
-- # type: () -> 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_manylinux_abi(arch):
-- # type: (str) -> bool
-- if arch == "armv7l":
-- return _is_linux_armhf()
-- if arch == "i686":
-- return _is_linux_i686()
-- return True
--
--
--def _linux_platforms(is_32bit=_32_BIT_INTERPRETER):
-- # type: (bool) -> Iterator[str]
-- linux = _normalize_string(distutils.util.get_platform())
-+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"
-- manylinux_support = []
- _, arch = linux.split("_", 1)
-- if _have_compatible_manylinux_abi(arch):
-- if arch in {"x86_64", "i686", "aarch64", "armv7l", "ppc64",
"ppc64le", "s390x"}:
-- manylinux_support.append(
-- ("manylinux2014", (2, 17))
-- ) # CentOS 7 w/ glibc 2.17 (PEP 599)
-- if arch in {"x86_64", "i686"}:
-- manylinux_support.append(
-- ("manylinux2010", (2, 12))
-- ) # CentOS 6 w/ glibc 2.12 (PEP 571)
-- manylinux_support.append(
-- ("manylinux1", (2, 5))
-- ) # CentOS 5 w/ glibc 2.5 (PEP 513)
-- manylinux_support_iter = iter(manylinux_support)
-- for name, glibc_version in manylinux_support_iter:
-- if _is_manylinux_compatible(name, glibc_version):
-- yield linux.replace("linux", name)
-- break
-- # Support for a later manylinux implies support for an earlier version.
-- for name, _ in manylinux_support_iter:
-- yield linux.replace("linux", name)
-+ yield from _manylinux.platform_tags(linux, arch)
-+ yield from _musllinux.platform_tags(arch)
- yield linux
-
-
--def _generic_platforms():
-- # type: () -> Iterator[str]
-- yield _normalize_string(distutils.util.get_platform())
-+def _generic_platforms() -> Iterator[str]:
-+ yield _normalize_string(sysconfig.get_platform())
-
-
--def _platform_tags():
-- # type: () -> Iterator[str]
-+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():
-- # type: () -> str
-+def interpreter_name() -> str:
- """
- Returns the name of the running interpreter.
-+
-+ Some implementations have a reserved, two-letter abbreviation which will
-+ be returned when appropriate.
- """
-- try:
-- name = sys.implementation.name # type: ignore
-- except AttributeError: # pragma: no cover
-- # Python 2.7 compatibility.
-- name = platform.python_implementation().lower()
-+ name = sys.implementation.name
- return INTERPRETER_SHORT_NAMES.get(name) or name
-
-
--def interpreter_version(**kwargs):
-- # type: (bool) -> str
-+def interpreter_version(*, warn: bool = False) -> str:
- """
- Returns the version of the running interpreter.
- """
-- warn = _warn_keyword_parameter("interpreter_version", kwargs)
- 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):
-- # type: (PythonVersion) -> str
-- if any(v >= 10 for v in version):
-- sep = "_"
-- else:
-- sep = ""
-- return sep.join(map(str, version))
-+def _version_nodot(version: PythonVersion) -> str:
-+ return "".join(map(str, version))
-
-
--def sys_tags(**kwargs):
-- # type: (bool) -> Iterator[Tag]
-+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.
- """
-- warn = _warn_keyword_parameter("sys_tags", kwargs)
-
- interp_name = interpreter_name()
- if interp_name == "cp":
-- for tag in cpython_tags(warn=warn):
-- yield tag
-+ yield from cpython_tags(warn=warn)
- else:
-- for tag in generic_tags():
-- yield tag
-+ yield from generic_tags()
-
-- for tag in compatible_tags():
-- yield tag
-+ if interp_name == "pp":
-+ interp = "pp3"
-+ elif interp_name == "cp":
-+ interp = "cp" + interpreter_version(warn=warn)
-+ else:
-+ interp = None
-+ yield from compatible_tags(interpreter=interp)
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/packaging/utils.py
b/third_party/python/setuptools/setuptools/_vendor/packaging/utils.py
---- a/third_party/python/setuptools/setuptools/_vendor/packaging/utils.py
-+++ b/third_party/python/setuptools/setuptools/_vendor/packaging/utils.py
-@@ -1,65 +1,141 @@
- # 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 __future__ import absolute_import, division, print_function
-
- import re
-+from typing import FrozenSet, NewType, Tuple, Union, cast
-
--from ._typing import TYPE_CHECKING, cast
-+from .tags import Tag, parse_tag
- from .version import InvalidVersion, Version
-
--if TYPE_CHECKING: # pragma: no cover
-- from typing import NewType, Union
-+BuildTag = Union[Tuple[()], Tuple[int, str]]
-+NormalizedName = NewType("NormalizedName", str)
-+
-
-- NormalizedName = NewType("NormalizedName", str)
--
--_canonicalize_regex = re.compile(r"[-_.]+")
-+class InvalidWheelFilename(ValueError):
-+ """
-+ An invalid wheel filename was found, users should refer to PEP 427.
-+ """
-
-
--def canonicalize_name(name):
-- # type: (str) -> NormalizedName
-+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)
-+ return cast(NormalizedName, value)
-
-
--def canonicalize_version(_version):
-- # type: (str) -> Union[Version, str]
-+def canonicalize_version(
-+ version: Union[Version, str], *, strip_trailing_zero: bool = True
-+) -> str:
- """
- This is very similar to Version.__str__, but has one subtle difference
- with the way it handles the release segment.
- """
--
-- try:
-- version = Version(_version)
-- except InvalidVersion:
-- # Legacy versions cannot be normalized
-- return _version
-+ if isinstance(version, str):
-+ try:
-+ parsed = Version(version)
-+ except InvalidVersion:
-+ # Legacy versions cannot be normalized
-+ return version
-+ else:
-+ parsed = version
-
- parts = []
-
- # Epoch
-- if version.epoch != 0:
-- parts.append("{0}!".format(version.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
version.release)))
-+ release_segment = ".".join(str(x) for x in parsed.release)
-+ if strip_trailing_zero:
-+ # NB: This strips trailing '.0's to normalize
-+ release_segment = re.sub(r"(\.0)+$", "", release_segment)
-+ parts.append(release_segment)
-
- # Pre-release
-- if version.pre is not None:
-- parts.append("".join(str(x) for x in version.pre))
-+ if parsed.pre is not None:
-+ parts.append("".join(str(x) for x in parsed.pre))
-
- # Post-release
-- if version.post is not None:
-- parts.append(".post{0}".format(version.post))
-+ if parsed.post is not None:
-+ parts.append(f".post{parsed.post}")
-
- # Development release
-- if version.dev is not None:
-- parts.append(".dev{0}".format(version.dev))
-+ if parsed.dev is not None:
-+ parts.append(f".dev{parsed.dev}")
-
- # Local version segment
-- if version.local is not None:
-- parts.append("+{0}".format(version.local))
-+ 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/third_party/python/setuptools/setuptools/_vendor/packaging/version.py
b/third_party/python/setuptools/setuptools/_vendor/packaging/version.py
---- a/third_party/python/setuptools/setuptools/_vendor/packaging/version.py
-+++ b/third_party/python/setuptools/setuptools/_vendor/packaging/version.py
-@@ -1,243 +1,121 @@
- # 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 __future__ import absolute_import, division, print_function
-+"""
-+.. testsetup::
-+
-+ from packaging.version import parse, Version
-+"""
-
- import collections
- import itertools
- import re
--
--from ._structures import Infinity, NegativeInfinity
--from ._typing import TYPE_CHECKING
-+from typing import Any, Callable, Optional, SupportsInt, Tuple, Union
-
--if TYPE_CHECKING: # pragma: no cover
-- from typing import Callable, Iterator, List, Optional, SupportsInt,
Tuple, Union
-+from ._structures import Infinity, InfinityType, NegativeInfinity,
NegativeInfinityType
-
-- from ._structures import InfinityType, NegativeInfinityType
-+__all__ = ["VERSION_PATTERN", "parse", "Version", "InvalidVersion"]
-
-- 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],
-- ],
-- ...,
-+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
-- ]
--
--__all__ = ["parse", "Version", "LegacyVersion", "InvalidVersion",
"VERSION_PATTERN"]
--
-+ ...,
-+ ],
-+]
-+CmpKey = Tuple[
-+ int, Tuple[int, ...], PrePostDevType, PrePostDevType, PrePostDevType,
LocalType
-+]
-+VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]
-
- _Version = collections.namedtuple(
- "_Version", ["epoch", "release", "dev", "pre", "post", "local"]
- )
-
-
--def parse(version):
-- # type: (str) -> Union[LegacyVersion, Version]
-+def parse(version: str) -> "Version":
-+ """Parse the given version string.
-+
-+ >>> parse('1.0.dev1')
-+ <Version('1.0.dev1')>
-+
-+ :param version: The version string to parse.
-+ :raises InvalidVersion: When the version string is not a valid 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)
-+ return Version(version)
-
-
- class InvalidVersion(ValueError):
-- """
-- An invalid version was found, users should refer to PEP 440.
-+ """Raised when a version string is not a valid version.
-+
-+ >>> Version("invalid")
-+ Traceback (most recent call last):
-+ ...
-+ packaging.version.InvalidVersion: Invalid version: 'invalid'
- """
-
-
--class _BaseVersion(object):
-- _key = None # type: Union[CmpKey, LegacyCmpKey]
-+class _BaseVersion:
-+ _key: Tuple[Any, ...]
-
-- def __hash__(self):
-- # type: () -> int
-+ def __hash__(self) -> int:
- return hash(self._key)
-
-- def __lt__(self, other):
-- # type: (_BaseVersion) -> bool
-- return self._compare(other, lambda s, o: s < o)
--
-- def __le__(self, other):
-- # type: (_BaseVersion) -> bool
-- return self._compare(other, lambda s, o: s <= o)
--
-- def __eq__(self, other):
-- # type: (object) -> bool
-- return self._compare(other, lambda s, o: s == o)
--
-- def __ge__(self, other):
-- # type: (_BaseVersion) -> bool
-- return self._compare(other, lambda s, o: s >= o)
--
-- def __gt__(self, other):
-- # type: (_BaseVersion) -> bool
-- return self._compare(other, lambda s, o: s > o)
--
-- def __ne__(self, other):
-- # type: (object) -> bool
-- return self._compare(other, lambda s, o: s != o)
--
-- def _compare(self, other, method):
-- # type: (object, VersionComparisonMethod) -> Union[bool,
NotImplemented]
-+ # 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 method(self._key, other._key)
--
--
--class LegacyVersion(_BaseVersion):
-- def __init__(self, version):
-- # type: (str) -> None
-- self._version = str(version)
-- self._key = _legacy_cmpkey(self._version)
--
-- def __str__(self):
-- # type: () -> str
-- return self._version
-+ return self._key < other._key
-
-- def __repr__(self):
-- # type: () -> str
-- return "<LegacyVersion({0})>".format(repr(str(self)))
--
-- @property
-- def public(self):
-- # type: () -> str
-- return self._version
--
-- @property
-- def base_version(self):
-- # type: () -> str
-- return self._version
-+ def __le__(self, other: "_BaseVersion") -> bool:
-+ if not isinstance(other, _BaseVersion):
-+ return NotImplemented
-
-- @property
-- def epoch(self):
-- # type: () -> int
-- return -1
--
-- @property
-- def release(self):
-- # type: () -> None
-- return None
--
-- @property
-- def pre(self):
-- # type: () -> None
-- return None
-+ return self._key <= other._key
-
-- @property
-- def post(self):
-- # type: () -> None
-- return None
--
-- @property
-- def dev(self):
-- # type: () -> None
-- return None
-+ def __eq__(self, other: object) -> bool:
-+ if not isinstance(other, _BaseVersion):
-+ return NotImplemented
-
-- @property
-- def local(self):
-- # type: () -> None
-- return None
--
-- @property
-- def is_prerelease(self):
-- # type: () -> bool
-- return False
-+ return self._key == other._key
-
-- @property
-- def is_postrelease(self):
-- # type: () -> bool
-- return False
--
-- @property
-- def is_devrelease(self):
-- # type: () -> bool
-- return False
--
--
--_legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)",
re.VERBOSE)
-+ def __ge__(self, other: "_BaseVersion") -> bool:
-+ if not isinstance(other, _BaseVersion):
-+ return NotImplemented
-
--_legacy_version_replacement_map = {
-- "pre": "c",
-- "preview": "c",
-- "-": "final-",
-- "rc": "c",
-- "dev": "@",
--}
--
--
--def _parse_version_parts(s):
-- # type: (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
-+ return self._key >= other._key
-
-- if part[:1] in "0123456789":
-- # pad for numeric comparison
-- yield part.zfill(8)
-- else:
-- yield "*" + part
-+ def __gt__(self, other: "_BaseVersion") -> bool:
-+ if not isinstance(other, _BaseVersion):
-+ return NotImplemented
-
-- # ensure that alpha/beta/candidate are before final
-- yield "*final"
--
--
--def _legacy_cmpkey(version):
-- # type: (str) -> LegacyCmpKey
-+ return self._key > other._key
-
-- # 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
-+ def __ne__(self, other: object) -> bool:
-+ if not isinstance(other, _BaseVersion):
-+ return NotImplemented
-
-- # This scheme is taken from pkg_resources.parse_version setuptools
prior to
-- # it's adoption of the packaging library.
-- parts = [] # type: 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)
-+ return self._key != other._key
-
-
- # Deliberately not anchored to the start and end of the string, to make it
- # easier for 3rd party code to reuse
--VERSION_PATTERN = r"""
-+_VERSION_PATTERN = r"""
- v?
- (?:
- (?:(?P<epoch>[0-9]+)!)? # epoch
- (?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
- (?P<pre> # pre-release
- [-_\.]?
- (?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
- [-_\.]?
-@@ -258,28 +136,71 @@ VERSION_PATTERN = r"""
- (?P<dev_l>dev)
- [-_\.]?
- (?P<dev_n>[0-9]+)?
- )?
- )
- (?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
- """
-
-+VERSION_PATTERN = _VERSION_PATTERN
-+"""
-+A string containing the regular expression used to match a valid version.
-+
-+The pattern is not anchored at either end, and is intended for embedding in
larger
-+expressions (for example, matching a version number as part of a file
name). The
-+regular expression should be compiled with the ``re.VERBOSE`` and
``re.IGNORECASE``
-+flags set.
-+
-+:meta hide-value:
-+"""
-+
-
- class Version(_BaseVersion):
-+ """This class abstracts handling of a project's versions.
-+
-+ A :class:`Version` instance is comparison aware and can be compared and
-+ sorted using the standard Python interfaces.
-+
-+ >>> v1 = Version("1.0a5")
-+ >>> v2 = Version("1.0")
-+ >>> v1
-+ <Version('1.0a5')>
-+ >>> v2
-+ <Version('1.0')>
-+ >>> v1 < v2
-+ True
-+ >>> v1 == v2
-+ False
-+ >>> v1 > v2
-+ False
-+ >>> v1 >= v2
-+ False
-+ >>> v1 <= v2
-+ True
-+ """
-
- _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE |
re.IGNORECASE)
-+ _key: CmpKey
-
-- def __init__(self, version):
-- # type: (str) -> None
-+ def __init__(self, version: str) -> None:
-+ """Initialize a Version object.
-+
-+ :param version:
-+ The string representation of a version which will be parsed and
normalized
-+ before use.
-+ :raises InvalidVersion:
-+ If the ``version`` does not conform to PEP 440 in any way then
this
-+ exception will be raised.
-+ """
-
- # Validate the version and parse it into pieces
- match = self._regex.search(version)
- if not match:
-- raise InvalidVersion("Invalid version: '{0}'".format(version))
-+ 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")
-@@ -293,140 +214,250 @@ class Version(_BaseVersion):
- self._version.epoch,
- self._version.release,
- self._version.pre,
- self._version.post,
- self._version.dev,
- self._version.local,
- )
-
-- def __repr__(self):
-- # type: () -> str
-- return "<Version({0})>".format(repr(str(self)))
-+ def __repr__(self) -> str:
-+ """A representation of the Version that shows all internal state.
-
-- def __str__(self):
-- # type: () -> str
-+ >>> Version('1.0.0')
-+ <Version('1.0.0')>
-+ """
-+ return f"<Version('{self}')>"
-+
-+ def __str__(self) -> str:
-+ """A string representation of the version that can be
rounded-tripped.
-+
-+ >>> str(Version("1.0a5"))
-+ '1.0a5'
-+ """
- parts = []
-
- # Epoch
- if self.epoch != 0:
-- parts.append("{0}!".format(self.epoch))
-+ 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(".post{0}".format(self.post))
-+ parts.append(f".post{self.post}")
-
- # Development release
- if self.dev is not None:
-- parts.append(".dev{0}".format(self.dev))
-+ parts.append(f".dev{self.dev}")
-
- # Local version segment
- if self.local is not None:
-- parts.append("+{0}".format(self.local))
-+ parts.append(f"+{self.local}")
-
- return "".join(parts)
-
- @property
-- def epoch(self):
-- # type: () -> int
-- _epoch = self._version.epoch # type: int
-+ def epoch(self) -> int:
-+ """The epoch of the version.
-+
-+ >>> Version("2.0.0").epoch
-+ 0
-+ >>> Version("1!2.0.0").epoch
-+ 1
-+ """
-+ _epoch: int = self._version.epoch
- return _epoch
-
- @property
-- def release(self):
-- # type: () -> Tuple[int, ...]
-- _release = self._version.release # type: Tuple[int, ...]
-+ def release(self) -> Tuple[int, ...]:
-+ """The components of the "release" segment of the version.
-+
-+ >>> Version("1.2.3").release
-+ (1, 2, 3)
-+ >>> Version("2.0.0").release
-+ (2, 0, 0)
-+ >>> Version("1!2.0.0.post0").release
-+ (2, 0, 0)
-+
-+ Includes trailing zeroes but not the epoch or any pre-release /
development /
-+ post-release suffixes.
-+ """
-+ _release: Tuple[int, ...] = self._version.release
- return _release
-
- @property
-- def pre(self):
-- # type: () -> Optional[Tuple[str, int]]
-- _pre = self._version.pre # type: Optional[Tuple[str, int]]
-+ def pre(self) -> Optional[Tuple[str, int]]:
-+ """The pre-release segment of the version.
-+
-+ >>> print(Version("1.2.3").pre)
-+ None
-+ >>> Version("1.2.3a1").pre
-+ ('a', 1)
-+ >>> Version("1.2.3b1").pre
-+ ('b', 1)
-+ >>> Version("1.2.3rc1").pre
-+ ('rc', 1)
-+ """
-+ _pre: Optional[Tuple[str, int]] = self._version.pre
- return _pre
-
- @property
-- def post(self):
-- # type: () -> Optional[Tuple[str, int]]
-+ def post(self) -> Optional[int]:
-+ """The post-release number of the version.
-+
-+ >>> print(Version("1.2.3").post)
-+ None
-+ >>> Version("1.2.3.post1").post
-+ 1
-+ """
- return self._version.post[1] if self._version.post else None
-
- @property
-- def dev(self):
-- # type: () -> Optional[Tuple[str, int]]
-+ def dev(self) -> Optional[int]:
-+ """The development number of the version.
-+
-+ >>> print(Version("1.2.3").dev)
-+ None
-+ >>> Version("1.2.3.dev1").dev
-+ 1
-+ """
- return self._version.dev[1] if self._version.dev else None
-
- @property
-- def local(self):
-- # type: () -> Optional[str]
-+ def local(self) -> Optional[str]:
-+ """The local version segment of the version.
-+
-+ >>> print(Version("1.2.3").local)
-+ None
-+ >>> Version("1.2.3+abc").local
-+ 'abc'
-+ """
- if self._version.local:
- return ".".join(str(x) for x in self._version.local)
- else:
- return None
-
- @property
-- def public(self):
-- # type: () -> str
-+ def public(self) -> str:
-+ """The public portion of the version.
-+
-+ >>> Version("1.2.3").public
-+ '1.2.3'
-+ >>> Version("1.2.3+abc").public
-+ '1.2.3'
-+ >>> Version("1.2.3+abc.dev1").public
-+ '1.2.3'
-+ """
- return str(self).split("+", 1)[0]
-
- @property
-- def base_version(self):
-- # type: () -> str
-+ def base_version(self) -> str:
-+ """The "base version" of the version.
-+
-+ >>> Version("1.2.3").base_version
-+ '1.2.3'
-+ >>> Version("1.2.3+abc").base_version
-+ '1.2.3'
-+ >>> Version("1!1.2.3+abc.dev1").base_version
-+ '1!1.2.3'
-+
-+ The "base version" is the public version of the project without any
pre or post
-+ release markers.
-+ """
- parts = []
-
- # Epoch
- if self.epoch != 0:
-- parts.append("{0}!".format(self.epoch))
-+ 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):
-- # type: () -> bool
-+ def is_prerelease(self) -> bool:
-+ """Whether this version is a pre-release.
-+
-+ >>> Version("1.2.3").is_prerelease
-+ False
-+ >>> Version("1.2.3a1").is_prerelease
-+ True
-+ >>> Version("1.2.3b1").is_prerelease
-+ True
-+ >>> Version("1.2.3rc1").is_prerelease
-+ True
-+ >>> Version("1.2.3dev1").is_prerelease
-+ True
-+ """
- return self.dev is not None or self.pre is not None
-
- @property
-- def is_postrelease(self):
-- # type: () -> bool
-+ def is_postrelease(self) -> bool:
-+ """Whether this version is a post-release.
-+
-+ >>> Version("1.2.3").is_postrelease
-+ False
-+ >>> Version("1.2.3.post1").is_postrelease
-+ True
-+ """
- return self.post is not None
-
- @property
-- def is_devrelease(self):
-- # type: () -> bool
-+ def is_devrelease(self) -> bool:
-+ """Whether this version is a development release.
-+
-+ >>> Version("1.2.3").is_devrelease
-+ False
-+ >>> Version("1.2.3.dev1").is_devrelease
-+ True
-+ """
- return self.dev is not None
-
- @property
-- def major(self):
-- # type: () -> int
-+ def major(self) -> int:
-+ """The first item of :attr:`release` or ``0`` if unavailable.
-+
-+ >>> Version("1.2.3").major
-+ 1
-+ """
- return self.release[0] if len(self.release) >= 1 else 0
-
- @property
-- def minor(self):
-- # type: () -> int
-+ def minor(self) -> int:
-+ """The second item of :attr:`release` or ``0`` if unavailable.
-+
-+ >>> Version("1.2.3").minor
-+ 2
-+ >>> Version("1").minor
-+ 0
-+ """
- return self.release[1] if len(self.release) >= 2 else 0
-
- @property
-- def micro(self):
-- # type: () -> int
-+ def micro(self) -> int:
-+ """The third item of :attr:`release` or ``0`` if unavailable.
-+
-+ >>> Version("1.2.3").micro
-+ 3
-+ >>> Version("1").micro
-+ 0
-+ """
- return self.release[2] if len(self.release) >= 3 else 0
-
-
- def _parse_letter_version(
-- letter, # type: str
-- number, # type: Union[str, bytes, SupportsInt]
--):
-- # type: (...) -> Optional[Tuple[str, int]]
-+ 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
-@@ -453,78 +484,76 @@ def _parse_letter_version(
- return letter, int(number)
-
- return None
-
-
- _local_version_separators = re.compile(r"[\._-]")
-
-
--def _parse_local_version(local):
-- # type: (str) -> Optional[LocalType]
-+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, # type: int
-- release, # type: Tuple[int, ...]
-- pre, # type: Optional[Tuple[str, int]]
-- post, # type: Optional[Tuple[str, int]]
-- dev, # type: Optional[Tuple[str, int]]
-- local, # type: Optional[Tuple[SubLocalType]]
--):
-- # type: (...) -> 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 = NegativeInfinity # type: PrePostDevType
-+ _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 = NegativeInfinity # type: PrePostDevType
-+ _post: PrePostDevType = NegativeInfinity
-
- else:
- _post = post
-
- # Versions without a development segment should sort after those with
one.
- if dev is None:
-- _dev = Infinity # type: PrePostDevType
-+ _dev: PrePostDevType = Infinity
-
- else:
- _dev = dev
-
- if local is None:
- # Versions without a local segment should sort before those with
one.
-- _local = NegativeInfinity # type: LocalType
-+ _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
-diff --git a/third_party/python/setuptools/setuptools/_vendor/pyparsing.py
b/third_party/python/setuptools/setuptools/_vendor/pyparsing.py
-deleted file mode 100644
---- a/third_party/python/setuptools/setuptools/_vendor/pyparsing.py
-+++ /dev/null
-@@ -1,5742 +0,0 @@
--# 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{"<salutation>, <addressee>!"}), built up using L{Word}, L{Literal}, and
L{And} elements
--(L{'+'<ParserElement.__add__>} 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<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{'+'<And>}, L{'|'<MatchFirst>}, L{'^'<Or>}, and L{'&'<Each>}
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 <ptmcg AT users.sourceforge.net>"
--
--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<And._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.<resultsName>} - 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) # -> <class
'pyparsing.ParseResults'> ['sldkj', 'lsdkj', 'sldkj']
--
-- # Use asList() to create an actual list
-- result_list = result.asList()
-- print(type(result_list), result_list) # -> <class '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)) # -> <class
'pyparsing.ParseResults'> (['12', '/', '31', '/', '1999'], {'day': [('1999',
4)], 'year': [('12', 0)], 'month': [('31', 2)]})
--
-- result_dict = result.asDict()
-- print(type(result_dict), repr(result_dict)) # -> <class '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,
-- "</", resTag, ">" ]
--
-- out += [ nl, indent, "</", selfTag, ">" ]
-- 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}<ParserElement.parseString>} for more
information
-- on parsing strings containing C{<TAB>}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<loc<len(s) and s[loc-1] == '\n' else loc - s.rfind("\n",
0, loc)
--
--def lineno(loc,strg):
-- """Returns current line number within a string, counting newlines as
line separators.
-- The first line 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}<ParserElement.parseString>} for more
information
-- on parsing strings containing C{<TAB>}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 = "<parse action>"
-- 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, <TAB> 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 = "<unknown>" # 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}<parseString>} for more information
-- on parsing strings containing C{<TAB>}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}<setParseAction>}.
--
-- See examples in L{I{copy}<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}<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}<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}<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 <TAB>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{<TAB>}s to spaces before
parsing the input string.
-- Must be called before C{parseString} when the input grammar
contains elements that
-- match C{<TAB>} 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
<exprname> at loc <n>(<line>,<col>)"}
-- 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
(loc<instrlen and instring[loc] in bodychars):
-- throwException = True
--
-- if throwException:
-- raise ParseException(instring, loc, self.errmsg, self)
--
-- return loc, instring[start:loc]
--
-- def __str__( self ):
-- try:
-- return super(Word,self).__str__()
-- except Exception:
-- pass
--
--
-- if self.strRepr is None:
--
-- def charsAsStr(s):
-- if len(s)>4:
-- 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<name>...)}), these will be preserved as
-- named parse results.
--
-- Example::
-- realnum = Regex(r"[+-]?\d+\.\d*")
-- date = Regex(r'(?P<year>\d{4})-(?P<month>\d\d?)-(?P<day>\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 = {
-- " " : "<SPC>",
-- "\t": "<TAB>",
-- "\n": "<LF>",
-- "\r": "<CR>",
-- "\f": "<FF>",
-- }
-- 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):
-- if instring[loc] == "\n":
-- return loc+1, "\n"
-- else:
-- raise ParseException(instring, loc, self.errmsg, self)
-- elif loc == len(instring):
-- return loc+1, []
-- else:
-- raise ParseException(instring, loc, self.errmsg, self)
--
--class StringStart(_PositionToken):
-- """
-- Matches if current position is at the beginning of the parse string
-- """
-- def __init__( self ):
-- super(StringStart,self).__init__()
-- self.errmsg = "Expected start of text"
--
-- def parseImpl( self, instring, loc, doActions=True ):
-- if loc != 0:
-- # see if entire string up to here is just whitespace and
ignoreables
-- if loc != self.preParse( instring, 0 ):
-- raise ParseException(instring, loc, self.errmsg, self)
-- return loc, []
--
--class StringEnd(_PositionToken):
-- """
-- Matches if current position is at the end of the parse string
-- """
-- def __init__( self ):
-- super(StringEnd,self).__init__()
-- self.errmsg = "Expected end of text"
--
-- def parseImpl( self, instring, loc, doActions=True ):
-- if loc < len(instring):
-- raise ParseException(instring, loc, self.errmsg, self)
-- elif loc == len(instring):
-- return loc+1, []
-- elif 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<instrlen:
-- if (instring[loc] in self.wordChars or
-- instring[loc-1] not in self.wordChars):
-- raise ParseException(instring, loc, self.errmsg, self)
-- return loc, []
--
--
--class ParseExpression(ParserElement):
-- """
-- Abstract subclass of ParserElement, for combining and post-processing
parsed tokens.
-- """
-- def __init__( self, exprs, savelist = False ):
-- super(ParseExpression,self).__init__(savelist)
-- if isinstance( exprs, _generatorType ):
-- exprs = list(exprs)
--
-- if isinstance( exprs, basestring ):
-- self.exprs = [ ParserElement._literalStringClass( exprs ) ]
-- elif isinstance( exprs, Iterable ):
-- exprs = list(exprs)
-- # if sequence of strings provided, wrap with Literal
-- if all(isinstance(expr, basestring) for expr in exprs):
-- exprs = map(ParserElement._literalStringClass, exprs)
-- self.exprs = list(exprs)
-- else:
-- try:
-- self.exprs = list( exprs )
-- except TypeError:
-- self.exprs = [ exprs ]
-- self.callPreparse = False
--
-- def __getitem__( self, i ):
-- return self.exprs[i]
--
-- def append( self, other ):
-- self.exprs.append( other )
-- self.strRepr = None
-- return self
--
-- def leaveWhitespace( self ):
-- """Extends C{leaveWhitespace} defined in base class, and also
invokes C{leaveWhitespace} on
-- all contained expressions."""
-- self.skipWhitespace = False
-- self.exprs = [ e.copy() for e in self.exprs ]
-- for e in self.exprs:
-- e.leaveWhitespace()
-- return self
--
-- def ignore( self, other ):
-- if isinstance( other, Suppress ):
-- if other not in self.ignoreExprs:
-- super( ParseExpression, self).ignore( other )
-- for e in self.exprs:
-- e.ignore( self.ignoreExprs[-1] )
-- else:
-- super( ParseExpression, self).ignore( other )
-- for e in self.exprs:
-- e.ignore( self.ignoreExprs[-1] )
-- return self
--
-- def __str__( self ):
-- try:
-- return super(ParseExpression,self).__str__()
-- except Exception:
-- pass
--
-- if self.strRepr is None:
-- self.strRepr = "%s:(%s)" % ( self.__class__.__name__,
_ustr(self.exprs) )
-- return self.strRepr
--
-- def streamline( self ):
-- super(ParseExpression,self).streamline()
--
-- for e in self.exprs:
-- e.streamline()
--
-- # collapse nested And's of the form And( And( And( a,b), c), d) to
And( a,b,c,d )
-- # but only if there are no parse actions or resultsNames on the
nested And's
-- # (likewise for Or's and MatchFirst's)
-- if ( len(self.exprs) == 2 ):
-- other = self.exprs[0]
-- if ( isinstance( other, self.__class__ ) and
-- not(other.parseAction) and
-- other.resultsName is None and
-- not other.debug ):
-- self.exprs = other.exprs[:] + [ self.exprs[1] ]
-- self.strRepr = None
-- self.mayReturnEmpty |= other.mayReturnEmpty
-- self.mayIndexError |= other.mayIndexError
--
-- other = self.exprs[-1]
-- if ( isinstance( other, self.__class__ ) and
-- not(other.parseAction) and
-- other.resultsName is None and
-- not other.debug ):
-- self.exprs = self.exprs[:-1] + other.exprs[:]
-- self.strRepr = None
-- self.mayReturnEmpty |= other.mayReturnEmpty
-- self.mayIndexError |= other.mayIndexError
--
-- self.errmsg = "Expected " + _ustr(self)
--
-- return self
--
-- def setResultsName( self, name, listAllMatches=False ):
-- ret =
super(ParseExpression,self).setResultsName(name,listAllMatches)
-- return ret
--
-- def validate( self, validateTrace=[] ):
-- tmp = validateTrace[:]+[self]
-- for e in self.exprs:
-- e.validate(tmp)
-- self.checkRecursion( [] )
--
-- def copy(self):
-- ret = super(ParseExpression,self).copy()
-- ret.exprs = [e.copy() for e in self.exprs]
-- return ret
--
--class And(ParseExpression):
-- """
-- Requires all given C{ParseExpression}s to be found in the given order.
-- Expressions may be separated by whitespace.
-- May be constructed using the C{'+'} operator.
-- May also be constructed using the C{'-'} operator, which will suppress
backtracking.
--
-- Example::
-- integer = Word(nums)
-- name_expr = OneOrMore(Word(alphas))
--
-- expr = And([integer("id"),name_expr("name"),integer("age")])
-- # more easily written as:
-- expr = integer("id") + name_expr("name") + integer("age")
-- """
--
-- class _ErrorStop(Empty):
-- def __init__(self, *args, **kwargs):
-- super(And._ErrorStop,self).__init__(*args, **kwargs)
-- self.name = '-'
-- self.leaveWhitespace()
--
-- def __init__( self, exprs, savelist = True ):
-- super(And,self).__init__(exprs, savelist)
-- self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs)
-- self.setWhitespaceChars( self.exprs[0].whiteChars )
-- self.skipWhitespace = self.exprs[0].skipWhitespace
-- self.callPreparse = True
--
-- def parseImpl( self, instring, loc, doActions=True ):
-- # pass False as last arg to _parse for first element, since we
already
-- # pre-parsed the string as part of our And pre-parsing
-- loc, resultlist = self.exprs[0]._parse( instring, loc, doActions,
callPreParse=False )
-- errorStop = False
-- for e in self.exprs[1:]:
-- if isinstance(e, And._ErrorStop):
-- errorStop = True
-- continue
-- if errorStop:
-- try:
-- loc, exprtokens = e._parse( instring, loc, doActions )
-- except ParseSyntaxException:
-- raise
-- except ParseBaseException as pe:
-- pe.__traceback__ = None
-- raise ParseSyntaxException._from_exception(pe)
-- except IndexError:
-- raise ParseSyntaxException(instring, len(instring),
self.errmsg, self)
-- else:
-- loc, exprtokens = e._parse( instring, loc, doActions )
-- if exprtokens or exprtokens.haskeys():
-- resultlist += exprtokens
-- return loc, resultlist
--
-- def __iadd__(self, other ):
-- if isinstance( other, basestring ):
-- other = ParserElement._literalStringClass( other )
-- return self.append( other ) #And( [ self, other ] )
--
-- def checkRecursion( self, parseElementList ):
-- subRecCheckList = parseElementList[:] + [ self ]
-- for e in self.exprs:
-- e.checkRecursion( subRecCheckList )
-- if not e.mayReturnEmpty:
-- break
--
-- 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
--
--
--class Or(ParseExpression):
-- """
-- Requires that at least one C{ParseExpression} is found.
-- If two expressions match, the expression that matches the longest
string will be used.
-- May be constructed using the C{'^'} operator.
--
-- Example::
-- # construct Or using '^' operator
--
-- number = Word(nums) ^ Combine(Word(nums) + '.' + Word(nums))
-- print(number.searchString("123 3.1416 789"))
-- prints::
-- [['123'], ['3.1416'], ['789']]
-- """
-- def __init__( self, exprs, savelist = False ):
-- super(Or,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
-- matches = []
-- for e in self.exprs:
-- try:
-- loc2 = e.tryParse( instring, loc )
-- except ParseException as err:
-- err.__traceback__ = None
-- 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)
-- 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'], {}))
-- <<leaving remove_duplicate_chars (ret: 'dfjkls')
-- ['dfjkls']
-- """
-- f = _trim_arity(f)
-- def z(*paArgs):
-- thisFunc = f.__name__
-- s,l,t = paArgs[-3:]
-- if len(paArgs)>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( "<<leaving %s (exception: %s)\n" %
(thisFunc,exc) )
-- raise
-- sys.stderr.write( "<<leaving %s (ret: %r)\n" % (thisFunc,ret) )
-- return ret
-- try:
-- z.__name__ = f.__name__
-- except AttributeError:
-- pass
-- return z
--
--#
--# global helpers
--#
--def delimitedList( expr, delim=",", combine=False ):
-- """
-- Helper to define a delimited list of expressions - the delimiter
defaults to ','.
-- By default, the list elements and delimiters can have intervening
whitespace, and
-- comments, but this can be overridden by passing C{combine=True} in the
constructor.
-- If C{combine} is set to C{True}, the matching tokens are returned as a
single token
-- string, with the delimiters included; otherwise, the matching tokens
are returned
-- as a list of tokens, with the delimiters suppressed.
--
-- Example::
-- delimitedList(Word(alphas)).parseString("aa,bb,cc") # -> ['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 <b> bold <i>text</i> </b> normal text "
-- for tag in ("b","i"):
-- opener,closer = makeHTMLTags(tag)
-- patt = originalTextFor(opener + SkipTo(closer) + closer)
-- print(patt.searchString(src)[0])
-- prints::
-- ['<b> bold <i>text</i> </b>']
-- ['<i>text</i>']
-- """
-- 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{<TAB>} 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<ParserElement.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("</") + tagStr + ">")
--
-- openTag = openTag.setResultsName("start"+"".join(resname.replace(":","
").title().split())).setName("<%s>" % resname)
-- closeTag = closeTag.setResultsName("end"+"".join(resname.replace(":","
").title().split())).setName("</%s>" % 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 = '<td>More info at the <a
href="http://pyparsing.wikispaces.com";>pyparsing</a> wiki page</td>'
-- # 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 <A> 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{<TD>} or C{<DIV>}.
--
-- 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 = '''
-- <div>
-- Some text
-- <div type="grid">1 4 0 1 0</div>
-- <div type="graph">1,3 2,3 1,1</div>
-- <div>this has no type</div>
-- </div>
--
-- '''
-- 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 = '''
-- <div>
-- Some text
-- <div class="grid">1 4 0 1 0</div>
-- <div class="graph">1,3 2,3 1,1</div>
-- <div>this &lt;div&gt; has no class</div>
-- </div>
--
-- '''
-- 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<entity>' + '|'.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"<!--[\s\S]*?-->").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<integer>}, L{reals<real>}, L{scientific
notation<sci_real>})
-- - common L{programming identifiers<identifier>}
-- - network addresses (L{MAC<mac_address>}, L{IPv4<ipv4_address>},
L{IPv6<ipv6_address>})
-- - ISO8601 L{dates<iso8601_date>} and L{datetime<iso8601_datetime>}
-- - L{UUID<uuid>}
-- - L{comma-separated list<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<year>\d{4})(?:-(?P<month>\d\d)(?:-(?P<day>\d\d))?)?').setName("ISO8601
date")
-- "ISO8601 date (C{yyyy-mm-dd})"
--
-- iso8601_datetime =
Regex(r'(?P<year>\d{4})-(?P<month>\d\d)-(?P<day>\d\d)[T
](?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d(\.\d*)?)?)?(?P<tz>Z|[+-]\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 = '<td>More info at the <a
href="http://pyparsing.wikispaces.com";>pyparsing</a> wiki page</td>'
-- 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/third_party/python/setuptools/setuptools/_vendor/tomli/__init__.py
b/third_party/python/setuptools/setuptools/_vendor/tomli/__init__.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/_vendor/tomli/__init__.py
-@@ -0,0 +1,11 @@
-+# SPDX-License-Identifier: MIT
-+# SPDX-FileCopyrightText: 2021 Taneli Hukkinen
-+# Licensed to PSF under a Contributor Agreement.
-+
-+__all__ = ("loads", "load", "TOMLDecodeError")
-+__version__ = "2.0.1" # DO NOT EDIT THIS LINE MANUALLY. LET bump2version
UTILITY DO IT
-+
-+from ._parser import TOMLDecodeError, load, loads
-+
-+# Pretend this exception was created here.
-+TOMLDecodeError.__module__ = __name__
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/tomli/_parser.py
b/third_party/python/setuptools/setuptools/_vendor/tomli/_parser.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/_vendor/tomli/_parser.py
-@@ -0,0 +1,691 @@
-+# SPDX-License-Identifier: MIT
-+# SPDX-FileCopyrightText: 2021 Taneli Hukkinen
-+# Licensed to PSF under a Contributor Agreement.
-+
-+from __future__ import annotations
-+
-+from collections.abc import Iterable
-+import string
-+from types import MappingProxyType
-+from typing import Any, BinaryIO, NamedTuple
-+
-+from ._re import (
-+ RE_DATETIME,
-+ RE_LOCALTIME,
-+ RE_NUMBER,
-+ match_to_datetime,
-+ match_to_localtime,
-+ match_to_number,
-+)
-+from ._types import Key, ParseFloat, Pos
-+
-+ASCII_CTRL = frozenset(chr(i) for i in range(32)) | frozenset(chr(127))
-+
-+# Neither of these sets include quotation mark or backslash. They are
-+# currently handled as separate cases in the parser functions.
-+ILLEGAL_BASIC_STR_CHARS = ASCII_CTRL - frozenset("\t")
-+ILLEGAL_MULTILINE_BASIC_STR_CHARS = ASCII_CTRL - frozenset("\t\n")
-+
-+ILLEGAL_LITERAL_STR_CHARS = ILLEGAL_BASIC_STR_CHARS
-+ILLEGAL_MULTILINE_LITERAL_STR_CHARS = ILLEGAL_MULTILINE_BASIC_STR_CHARS
-+
-+ILLEGAL_COMMENT_CHARS = ILLEGAL_BASIC_STR_CHARS
-+
-+TOML_WS = frozenset(" \t")
-+TOML_WS_AND_NEWLINE = TOML_WS | frozenset("\n")
-+BARE_KEY_CHARS = frozenset(string.ascii_letters + string.digits + "-_")
-+KEY_INITIAL_CHARS = BARE_KEY_CHARS | frozenset("\"'")
-+HEXDIGIT_CHARS = frozenset(string.hexdigits)
-+
-+BASIC_STR_ESCAPE_REPLACEMENTS = MappingProxyType(
-+ {
-+ "\\b": "\u0008", # backspace
-+ "\\t": "\u0009", # tab
-+ "\\n": "\u000A", # linefeed
-+ "\\f": "\u000C", # form feed
-+ "\\r": "\u000D", # carriage return
-+ '\\"': "\u0022", # quote
-+ "\\\\": "\u005C", # backslash
-+ }
-+)
-+
-+
-+class TOMLDecodeError(ValueError):
-+ """An error raised if a document is not valid TOML."""
-+
-+
-+def load(__fp: BinaryIO, *, parse_float: ParseFloat = float) -> dict[str,
Any]:
-+ """Parse TOML from a binary file object."""
-+ b = __fp.read()
-+ try:
-+ s = b.decode()
-+ except AttributeError:
-+ raise TypeError(
-+ "File must be opened in binary mode, e.g. use `open('foo.toml',
'rb')`"
-+ ) from None
-+ return loads(s, parse_float=parse_float)
-+
-+
-+def loads(__s: str, *, parse_float: ParseFloat = float) -> dict[str, Any]:
# noqa: C901
-+ """Parse TOML from a string."""
-+
-+ # The spec allows converting "\r\n" to "\n", even in string
-+ # literals. Let's do so to simplify parsing.
-+ src = __s.replace("\r\n", "\n")
-+ pos = 0
-+ out = Output(NestedDict(), Flags())
-+ header: Key = ()
-+ parse_float = make_safe_parse_float(parse_float)
-+
-+ # Parse one statement at a time
-+ # (typically means one line in TOML source)
-+ while True:
-+ # 1. Skip line leading whitespace
-+ pos = skip_chars(src, pos, TOML_WS)
-+
-+ # 2. Parse rules. Expect one of the following:
-+ # - end of file
-+ # - end of line
-+ # - comment
-+ # - key/value pair
-+ # - append dict to list (and move to its namespace)
-+ # - create dict (and move to its namespace)
-+ # Skip trailing whitespace when applicable.
-+ try:
-+ char = src[pos]
-+ except IndexError:
-+ break
-+ if char == "\n":
-+ pos += 1
-+ continue
-+ if char in KEY_INITIAL_CHARS:
-+ pos = key_value_rule(src, pos, out, header, parse_float)
-+ pos = skip_chars(src, pos, TOML_WS)
-+ elif char == "[":
-+ try:
-+ second_char: str | None = src[pos + 1]
-+ except IndexError:
-+ second_char = None
-+ out.flags.finalize_pending()
-+ if second_char == "[":
-+ pos, header = create_list_rule(src, pos, out)
-+ else:
-+ pos, header = create_dict_rule(src, pos, out)
-+ pos = skip_chars(src, pos, TOML_WS)
-+ elif char != "#":
-+ raise suffixed_err(src, pos, "Invalid statement")
-+
-+ # 3. Skip comment
-+ pos = skip_comment(src, pos)
-+
-+ # 4. Expect end of line or end of file
-+ try:
-+ char = src[pos]
-+ except IndexError:
-+ break
-+ if char != "\n":
-+ raise suffixed_err(
-+ src, pos, "Expected newline or end of document after a
statement"
-+ )
-+ pos += 1
-+
-+ return out.data.dict
-+
-+
-+class Flags:
-+ """Flags that map to parsed keys/namespaces."""
-+
-+ # Marks an immutable namespace (inline array or inline table).
-+ FROZEN = 0
-+ # Marks a nest that has been explicitly created and can no longer
-+ # be opened using the "[table]" syntax.
-+ EXPLICIT_NEST = 1
-+
-+ def __init__(self) -> None:
-+ self._flags: dict[str, dict] = {}
-+ self._pending_flags: set[tuple[Key, int]] = set()
-+
-+ def add_pending(self, key: Key, flag: int) -> None:
-+ self._pending_flags.add((key, flag))
-+
-+ def finalize_pending(self) -> None:
-+ for key, flag in self._pending_flags:
-+ self.set(key, flag, recursive=False)
-+ self._pending_flags.clear()
-+
-+ def unset_all(self, key: Key) -> None:
-+ cont = self._flags
-+ for k in key[:-1]:
-+ if k not in cont:
-+ return
-+ cont = cont[k]["nested"]
-+ cont.pop(key[-1], None)
-+
-+ def set(self, key: Key, flag: int, *, recursive: bool) -> None: #
noqa: A003
-+ cont = self._flags
-+ key_parent, key_stem = key[:-1], key[-1]
-+ for k in key_parent:
-+ if k not in cont:
-+ cont[k] = {"flags": set(), "recursive_flags": set(),
"nested": {}}
-+ cont = cont[k]["nested"]
-+ if key_stem not in cont:
-+ cont[key_stem] = {"flags": set(), "recursive_flags": set(),
"nested": {}}
-+ cont[key_stem]["recursive_flags" if recursive else
"flags"].add(flag)
-+
-+ def is_(self, key: Key, flag: int) -> bool:
-+ if not key:
-+ return False # document root has no flags
-+ cont = self._flags
-+ for k in key[:-1]:
-+ if k not in cont:
-+ return False
-+ inner_cont = cont[k]
-+ if flag in inner_cont["recursive_flags"]:
-+ return True
-+ cont = inner_cont["nested"]
-+ key_stem = key[-1]
-+ if key_stem in cont:
-+ cont = cont[key_stem]
-+ return flag in cont["flags"] or flag in cont["recursive_flags"]
-+ return False
-+
-+
-+class NestedDict:
-+ def __init__(self) -> None:
-+ # The parsed content of the TOML document
-+ self.dict: dict[str, Any] = {}
-+
-+ def get_or_create_nest(
-+ self,
-+ key: Key,
-+ *,
-+ access_lists: bool = True,
-+ ) -> dict:
-+ cont: Any = self.dict
-+ for k in key:
-+ if k not in cont:
-+ cont[k] = {}
-+ cont = cont[k]
-+ if access_lists and isinstance(cont, list):
-+ cont = cont[-1]
-+ if not isinstance(cont, dict):
-+ raise KeyError("There is no nest behind this key")
-+ return cont
-+
-+ def append_nest_to_list(self, key: Key) -> None:
-+ cont = self.get_or_create_nest(key[:-1])
-+ last_key = key[-1]
-+ if last_key in cont:
-+ list_ = cont[last_key]
-+ if not isinstance(list_, list):
-+ raise KeyError("An object other than list found behind this
key")
-+ list_.append({})
-+ else:
-+ cont[last_key] = [{}]
-+
-+
-+class Output(NamedTuple):
-+ data: NestedDict
-+ flags: Flags
-+
-+
-+def skip_chars(src: str, pos: Pos, chars: Iterable[str]) -> Pos:
-+ try:
-+ while src[pos] in chars:
-+ pos += 1
-+ except IndexError:
-+ pass
-+ return pos
-+
-+
-+def skip_until(
-+ src: str,
-+ pos: Pos,
-+ expect: str,
-+ *,
-+ error_on: frozenset[str],
-+ error_on_eof: bool,
-+) -> Pos:
-+ try:
-+ new_pos = src.index(expect, pos)
-+ except ValueError:
-+ new_pos = len(src)
-+ if error_on_eof:
-+ raise suffixed_err(src, new_pos, f"Expected {expect!r}") from
None
-+
-+ if not error_on.isdisjoint(src[pos:new_pos]):
-+ while src[pos] not in error_on:
-+ pos += 1
-+ raise suffixed_err(src, pos, f"Found invalid character
{src[pos]!r}")
-+ return new_pos
-+
-+
-+def skip_comment(src: str, pos: Pos) -> Pos:
-+ try:
-+ char: str | None = src[pos]
-+ except IndexError:
-+ char = None
-+ if char == "#":
-+ return skip_until(
-+ src, pos + 1, "\n", error_on=ILLEGAL_COMMENT_CHARS,
error_on_eof=False
-+ )
-+ return pos
-+
-+
-+def skip_comments_and_array_ws(src: str, pos: Pos) -> Pos:
-+ while True:
-+ pos_before_skip = pos
-+ pos = skip_chars(src, pos, TOML_WS_AND_NEWLINE)
-+ pos = skip_comment(src, pos)
-+ if pos == pos_before_skip:
-+ return pos
-+
-+
-+def create_dict_rule(src: str, pos: Pos, out: Output) -> tuple[Pos, Key]:
-+ pos += 1 # Skip "["
-+ pos = skip_chars(src, pos, TOML_WS)
-+ pos, key = parse_key(src, pos)
-+
-+ if out.flags.is_(key, Flags.EXPLICIT_NEST) or out.flags.is_(key,
Flags.FROZEN):
-+ raise suffixed_err(src, pos, f"Cannot declare {key} twice")
-+ out.flags.set(key, Flags.EXPLICIT_NEST, recursive=False)
-+ try:
-+ out.data.get_or_create_nest(key)
-+ except KeyError:
-+ raise suffixed_err(src, pos, "Cannot overwrite a value") from None
-+
-+ if not src.startswith("]", pos):
-+ raise suffixed_err(src, pos, "Expected ']' at the end of a table
declaration")
-+ return pos + 1, key
-+
-+
-+def create_list_rule(src: str, pos: Pos, out: Output) -> tuple[Pos, Key]:
-+ pos += 2 # Skip "[["
-+ pos = skip_chars(src, pos, TOML_WS)
-+ pos, key = parse_key(src, pos)
-+
-+ if out.flags.is_(key, Flags.FROZEN):
-+ raise suffixed_err(src, pos, f"Cannot mutate immutable namespace
{key}")
-+ # Free the namespace now that it points to another empty list item...
-+ out.flags.unset_all(key)
-+ # ...but this key precisely is still prohibited from table declaration
-+ out.flags.set(key, Flags.EXPLICIT_NEST, recursive=False)
-+ try:
-+ out.data.append_nest_to_list(key)
-+ except KeyError:
-+ raise suffixed_err(src, pos, "Cannot overwrite a value") from None
-+
-+ if not src.startswith("]]", pos):
-+ raise suffixed_err(src, pos, "Expected ']]' at the end of an array
declaration")
-+ return pos + 2, key
-+
-+
-+def key_value_rule(
-+ src: str, pos: Pos, out: Output, header: Key, parse_float: ParseFloat
-+) -> Pos:
-+ pos, key, value = parse_key_value_pair(src, pos, parse_float)
-+ key_parent, key_stem = key[:-1], key[-1]
-+ abs_key_parent = header + key_parent
-+
-+ relative_path_cont_keys = (header + key[:i] for i in range(1, len(key)))
-+ for cont_key in relative_path_cont_keys:
-+ # Check that dotted key syntax does not redefine an existing table
-+ if out.flags.is_(cont_key, Flags.EXPLICIT_NEST):
-+ raise suffixed_err(src, pos, f"Cannot redefine namespace
{cont_key}")
-+ # Containers in the relative path can't be opened with the table
syntax or
-+ # dotted key/value syntax in following table sections.
-+ out.flags.add_pending(cont_key, Flags.EXPLICIT_NEST)
-+
-+ if out.flags.is_(abs_key_parent, Flags.FROZEN):
-+ raise suffixed_err(
-+ src, pos, f"Cannot mutate immutable namespace {abs_key_parent}"
-+ )
-+
-+ try:
-+ nest = out.data.get_or_create_nest(abs_key_parent)
-+ except KeyError:
-+ raise suffixed_err(src, pos, "Cannot overwrite a value") from None
-+ if key_stem in nest:
-+ raise suffixed_err(src, pos, "Cannot overwrite a value")
-+ # Mark inline table and array namespaces recursively immutable
-+ if isinstance(value, (dict, list)):
-+ out.flags.set(header + key, Flags.FROZEN, recursive=True)
-+ nest[key_stem] = value
-+ return pos
-+
-+
-+def parse_key_value_pair(
-+ src: str, pos: Pos, parse_float: ParseFloat
-+) -> tuple[Pos, Key, Any]:
-+ pos, key = parse_key(src, pos)
-+ try:
-+ char: str | None = src[pos]
-+ except IndexError:
-+ char = None
-+ if char != "=":
-+ raise suffixed_err(src, pos, "Expected '=' after a key in a
key/value pair")
-+ pos += 1
-+ pos = skip_chars(src, pos, TOML_WS)
-+ pos, value = parse_value(src, pos, parse_float)
-+ return pos, key, value
-+
-+
-+def parse_key(src: str, pos: Pos) -> tuple[Pos, Key]:
-+ pos, key_part = parse_key_part(src, pos)
-+ key: Key = (key_part,)
-+ pos = skip_chars(src, pos, TOML_WS)
-+ while True:
-+ try:
-+ char: str | None = src[pos]
-+ except IndexError:
-+ char = None
-+ if char != ".":
-+ return pos, key
-+ pos += 1
-+ pos = skip_chars(src, pos, TOML_WS)
-+ pos, key_part = parse_key_part(src, pos)
-+ key += (key_part,)
-+ pos = skip_chars(src, pos, TOML_WS)
-+
-+
-+def parse_key_part(src: str, pos: Pos) -> tuple[Pos, str]:
-+ try:
-+ char: str | None = src[pos]
-+ except IndexError:
-+ char = None
-+ if char in BARE_KEY_CHARS:
-+ start_pos = pos
-+ pos = skip_chars(src, pos, BARE_KEY_CHARS)
-+ return pos, src[start_pos:pos]
-+ if char == "'":
-+ return parse_literal_str(src, pos)
-+ if char == '"':
-+ return parse_one_line_basic_str(src, pos)
-+ raise suffixed_err(src, pos, "Invalid initial character for a key part")
-+
-+
-+def parse_one_line_basic_str(src: str, pos: Pos) -> tuple[Pos, str]:
-+ pos += 1
-+ return parse_basic_str(src, pos, multiline=False)
-+
-+
-+def parse_array(src: str, pos: Pos, parse_float: ParseFloat) -> tuple[Pos,
list]:
-+ pos += 1
-+ array: list = []
-+
-+ pos = skip_comments_and_array_ws(src, pos)
-+ if src.startswith("]", pos):
-+ return pos + 1, array
-+ while True:
-+ pos, val = parse_value(src, pos, parse_float)
-+ array.append(val)
-+ pos = skip_comments_and_array_ws(src, pos)
-+
-+ c = src[pos : pos + 1]
-+ if c == "]":
-+ return pos + 1, array
-+ if c != ",":
-+ raise suffixed_err(src, pos, "Unclosed array")
-+ pos += 1
-+
-+ pos = skip_comments_and_array_ws(src, pos)
-+ if src.startswith("]", pos):
-+ return pos + 1, array
-+
-+
-+def parse_inline_table(src: str, pos: Pos, parse_float: ParseFloat) ->
tuple[Pos, dict]:
-+ pos += 1
-+ nested_dict = NestedDict()
-+ flags = Flags()
-+
-+ pos = skip_chars(src, pos, TOML_WS)
-+ if src.startswith("}", pos):
-+ return pos + 1, nested_dict.dict
-+ while True:
-+ pos, key, value = parse_key_value_pair(src, pos, parse_float)
-+ key_parent, key_stem = key[:-1], key[-1]
-+ if flags.is_(key, Flags.FROZEN):
-+ raise suffixed_err(src, pos, f"Cannot mutate immutable
namespace {key}")
-+ try:
-+ nest = nested_dict.get_or_create_nest(key_parent,
access_lists=False)
-+ except KeyError:
-+ raise suffixed_err(src, pos, "Cannot overwrite a value") from
None
-+ if key_stem in nest:
-+ raise suffixed_err(src, pos, f"Duplicate inline table key
{key_stem!r}")
-+ nest[key_stem] = value
-+ pos = skip_chars(src, pos, TOML_WS)
-+ c = src[pos : pos + 1]
-+ if c == "}":
-+ return pos + 1, nested_dict.dict
-+ if c != ",":
-+ raise suffixed_err(src, pos, "Unclosed inline table")
-+ if isinstance(value, (dict, list)):
-+ flags.set(key, Flags.FROZEN, recursive=True)
-+ pos += 1
-+ pos = skip_chars(src, pos, TOML_WS)
-+
-+
-+def parse_basic_str_escape(
-+ src: str, pos: Pos, *, multiline: bool = False
-+) -> tuple[Pos, str]:
-+ escape_id = src[pos : pos + 2]
-+ pos += 2
-+ if multiline and escape_id in {"\\ ", "\\\t", "\\\n"}:
-+ # Skip whitespace until next non-whitespace character or end of
-+ # the doc. Error if non-whitespace is found before newline.
-+ if escape_id != "\\\n":
-+ pos = skip_chars(src, pos, TOML_WS)
-+ try:
-+ char = src[pos]
-+ except IndexError:
-+ return pos, ""
-+ if char != "\n":
-+ raise suffixed_err(src, pos, "Unescaped '\\' in a string")
-+ pos += 1
-+ pos = skip_chars(src, pos, TOML_WS_AND_NEWLINE)
-+ return pos, ""
-+ if escape_id == "\\u":
-+ return parse_hex_char(src, pos, 4)
-+ if escape_id == "\\U":
-+ return parse_hex_char(src, pos, 8)
-+ try:
-+ return pos, BASIC_STR_ESCAPE_REPLACEMENTS[escape_id]
-+ except KeyError:
-+ raise suffixed_err(src, pos, "Unescaped '\\' in a string") from None
-+
-+
-+def parse_basic_str_escape_multiline(src: str, pos: Pos) -> tuple[Pos, str]:
-+ return parse_basic_str_escape(src, pos, multiline=True)
-+
-+
-+def parse_hex_char(src: str, pos: Pos, hex_len: int) -> tuple[Pos, str]:
-+ hex_str = src[pos : pos + hex_len]
-+ if len(hex_str) != hex_len or not HEXDIGIT_CHARS.issuperset(hex_str):
-+ raise suffixed_err(src, pos, "Invalid hex value")
-+ pos += hex_len
-+ hex_int = int(hex_str, 16)
-+ if not is_unicode_scalar_value(hex_int):
-+ raise suffixed_err(src, pos, "Escaped character is not a Unicode
scalar value")
-+ return pos, chr(hex_int)
-+
-+
-+def parse_literal_str(src: str, pos: Pos) -> tuple[Pos, str]:
-+ pos += 1 # Skip starting apostrophe
-+ start_pos = pos
-+ pos = skip_until(
-+ src, pos, "'", error_on=ILLEGAL_LITERAL_STR_CHARS, error_on_eof=True
-+ )
-+ return pos + 1, src[start_pos:pos] # Skip ending apostrophe
-+
-+
-+def parse_multiline_str(src: str, pos: Pos, *, literal: bool) -> tuple[Pos,
str]:
-+ pos += 3
-+ if src.startswith("\n", pos):
-+ pos += 1
-+
-+ if literal:
-+ delim = "'"
-+ end_pos = skip_until(
-+ src,
-+ pos,
-+ "'''",
-+ error_on=ILLEGAL_MULTILINE_LITERAL_STR_CHARS,
-+ error_on_eof=True,
-+ )
-+ result = src[pos:end_pos]
-+ pos = end_pos + 3
-+ else:
-+ delim = '"'
-+ pos, result = parse_basic_str(src, pos, multiline=True)
-+
-+ # Add at maximum two extra apostrophes/quotes if the end sequence
-+ # is 4 or 5 chars long instead of just 3.
-+ if not src.startswith(delim, pos):
-+ return pos, result
-+ pos += 1
-+ if not src.startswith(delim, pos):
-+ return pos, result + delim
-+ pos += 1
-+ return pos, result + (delim * 2)
-+
-+
-+def parse_basic_str(src: str, pos: Pos, *, multiline: bool) -> tuple[Pos,
str]:
-+ if multiline:
-+ error_on = ILLEGAL_MULTILINE_BASIC_STR_CHARS
-+ parse_escapes = parse_basic_str_escape_multiline
-+ else:
-+ error_on = ILLEGAL_BASIC_STR_CHARS
-+ parse_escapes = parse_basic_str_escape
-+ result = ""
-+ start_pos = pos
-+ while True:
-+ try:
-+ char = src[pos]
-+ except IndexError:
-+ raise suffixed_err(src, pos, "Unterminated string") from None
-+ if char == '"':
-+ if not multiline:
-+ return pos + 1, result + src[start_pos:pos]
-+ if src.startswith('"""', pos):
-+ return pos + 3, result + src[start_pos:pos]
-+ pos += 1
-+ continue
-+ if char == "\\":
-+ result += src[start_pos:pos]
-+ pos, parsed_escape = parse_escapes(src, pos)
-+ result += parsed_escape
-+ start_pos = pos
-+ continue
-+ if char in error_on:
-+ raise suffixed_err(src, pos, f"Illegal character {char!r}")
-+ pos += 1
-+
-+
-+def parse_value( # noqa: C901
-+ src: str, pos: Pos, parse_float: ParseFloat
-+) -> tuple[Pos, Any]:
-+ try:
-+ char: str | None = src[pos]
-+ except IndexError:
-+ char = None
-+
-+ # IMPORTANT: order conditions based on speed of checking and likelihood
-+
-+ # Basic strings
-+ if char == '"':
-+ if src.startswith('"""', pos):
-+ return parse_multiline_str(src, pos, literal=False)
-+ return parse_one_line_basic_str(src, pos)
-+
-+ # Literal strings
-+ if char == "'":
-+ if src.startswith("'''", pos):
-+ return parse_multiline_str(src, pos, literal=True)
-+ return parse_literal_str(src, pos)
-+
-+ # Booleans
-+ if char == "t":
-+ if src.startswith("true", pos):
-+ return pos + 4, True
-+ if char == "f":
-+ if src.startswith("false", pos):
-+ return pos + 5, False
-+
-+ # Arrays
-+ if char == "[":
-+ return parse_array(src, pos, parse_float)
-+
-+ # Inline tables
-+ if char == "{":
-+ return parse_inline_table(src, pos, parse_float)
-+
-+ # Dates and times
-+ datetime_match = RE_DATETIME.match(src, pos)
-+ if datetime_match:
-+ try:
-+ datetime_obj = match_to_datetime(datetime_match)
-+ except ValueError as e:
-+ raise suffixed_err(src, pos, "Invalid date or datetime") from e
-+ return datetime_match.end(), datetime_obj
-+ localtime_match = RE_LOCALTIME.match(src, pos)
-+ if localtime_match:
-+ return localtime_match.end(), match_to_localtime(localtime_match)
-+
-+ # Integers and "normal" floats.
-+ # The regex will greedily match any type starting with a decimal
-+ # char, so needs to be located after handling of dates and times.
-+ number_match = RE_NUMBER.match(src, pos)
-+ if number_match:
-+ return number_match.end(), match_to_number(number_match,
parse_float)
-+
-+ # Special floats
-+ first_three = src[pos : pos + 3]
-+ if first_three in {"inf", "nan"}:
-+ return pos + 3, parse_float(first_three)
-+ first_four = src[pos : pos + 4]
-+ if first_four in {"-inf", "+inf", "-nan", "+nan"}:
-+ return pos + 4, parse_float(first_four)
-+
-+ raise suffixed_err(src, pos, "Invalid value")
-+
-+
-+def suffixed_err(src: str, pos: Pos, msg: str) -> TOMLDecodeError:
-+ """Return a `TOMLDecodeError` where error message is suffixed with
-+ coordinates in source."""
-+
-+ def coord_repr(src: str, pos: Pos) -> str:
-+ if pos >= len(src):
-+ return "end of document"
-+ line = src.count("\n", 0, pos) + 1
-+ if line == 1:
-+ column = pos + 1
-+ else:
-+ column = pos - src.rindex("\n", 0, pos)
-+ return f"line {line}, column {column}"
-+
-+ return TOMLDecodeError(f"{msg} (at {coord_repr(src, pos)})")
-+
-+
-+def is_unicode_scalar_value(codepoint: int) -> bool:
-+ return (0 <= codepoint <= 55295) or (57344 <= codepoint <= 1114111)
-+
-+
-+def make_safe_parse_float(parse_float: ParseFloat) -> ParseFloat:
-+ """A decorator to make `parse_float` safe.
-+
-+ `parse_float` must not return dicts or lists, because these types
-+ would be mixed with parsed TOML tables and arrays, thus confusing
-+ the parser. The returned decorated callable raises `ValueError`
-+ instead of returning illegal types.
-+ """
-+ # The default `float` callable never returns illegal types. Optimize it.
-+ if parse_float is float: # type: ignore[comparison-overlap]
-+ return float
-+
-+ def safe_parse_float(float_str: str) -> Any:
-+ float_value = parse_float(float_str)
-+ if isinstance(float_value, (dict, list)):
-+ raise ValueError("parse_float must not return dicts or lists")
-+ return float_value
-+
-+ return safe_parse_float
-diff --git a/third_party/python/setuptools/setuptools/_vendor/tomli/_re.py
b/third_party/python/setuptools/setuptools/_vendor/tomli/_re.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/_vendor/tomli/_re.py
-@@ -0,0 +1,107 @@
-+# SPDX-License-Identifier: MIT
-+# SPDX-FileCopyrightText: 2021 Taneli Hukkinen
-+# Licensed to PSF under a Contributor Agreement.
-+
-+from __future__ import annotations
-+
-+from datetime import date, datetime, time, timedelta, timezone, tzinfo
-+from functools import lru_cache
-+import re
-+from typing import Any
-+
-+from ._types import ParseFloat
-+
-+# E.g.
-+# - 00:32:00.999999
-+# - 00:32:00
-+_TIME_RE_STR =
r"([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(?:\.([0-9]{1,6})[0-9]*)?"
-+
-+RE_NUMBER = re.compile(
-+ r"""
-+0
-+(?:
-+ x[0-9A-Fa-f](?:_?[0-9A-Fa-f])* # hex
-+ |
-+ b[01](?:_?[01])* # bin
-+ |
-+ o[0-7](?:_?[0-7])* # oct
-+)
-+|
-+[+-]?(?:0|[1-9](?:_?[0-9])*) # dec, integer part
-+(?P<floatpart>
-+ (?:\.[0-9](?:_?[0-9])*)? # optional fractional part
-+ (?:[eE][+-]?[0-9](?:_?[0-9])*)? # optional exponent part
-+)
-+""",
-+ flags=re.VERBOSE,
-+)
-+RE_LOCALTIME = re.compile(_TIME_RE_STR)
-+RE_DATETIME = re.compile(
-+ rf"""
-+([0-9]{{4}})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]) # date, e.g.
1988-10-27
-+(?:
-+ [Tt ]
-+ {_TIME_RE_STR}
-+ (?:([Zz])|([+-])([01][0-9]|2[0-3]):([0-5][0-9]))? # optional time
offset
-+)?
-+""",
-+ flags=re.VERBOSE,
-+)
-+
-+
-+def match_to_datetime(match: re.Match) -> datetime | date:
-+ """Convert a `RE_DATETIME` match to `datetime.datetime` or
`datetime.date`.
-+
-+ Raises ValueError if the match does not correspond to a valid date
-+ or datetime.
-+ """
-+ (
-+ year_str,
-+ month_str,
-+ day_str,
-+ hour_str,
-+ minute_str,
-+ sec_str,
-+ micros_str,
-+ zulu_time,
-+ offset_sign_str,
-+ offset_hour_str,
-+ offset_minute_str,
-+ ) = match.groups()
-+ year, month, day = int(year_str), int(month_str), int(day_str)
-+ if hour_str is None:
-+ return date(year, month, day)
-+ hour, minute, sec = int(hour_str), int(minute_str), int(sec_str)
-+ micros = int(micros_str.ljust(6, "0")) if micros_str else 0
-+ if offset_sign_str:
-+ tz: tzinfo | None = cached_tz(
-+ offset_hour_str, offset_minute_str, offset_sign_str
-+ )
-+ elif zulu_time:
-+ tz = timezone.utc
-+ else: # local date-time
-+ tz = None
-+ return datetime(year, month, day, hour, minute, sec, micros, tzinfo=tz)
-+
-+
-+@lru_cache(maxsize=None)
-+def cached_tz(hour_str: str, minute_str: str, sign_str: str) -> timezone:
-+ sign = 1 if sign_str == "+" else -1
-+ return timezone(
-+ timedelta(
-+ hours=sign * int(hour_str),
-+ minutes=sign * int(minute_str),
-+ )
-+ )
-+
-+
-+def match_to_localtime(match: re.Match) -> time:
-+ hour_str, minute_str, sec_str, micros_str = match.groups()
-+ micros = int(micros_str.ljust(6, "0")) if micros_str else 0
-+ return time(int(hour_str), int(minute_str), int(sec_str), micros)
-+
-+
-+def match_to_number(match: re.Match, parse_float: ParseFloat) -> Any:
-+ if match.group("floatpart"):
-+ return parse_float(match.group())
-+ return int(match.group(), 0)
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/tomli/_types.py
b/third_party/python/setuptools/setuptools/_vendor/tomli/_types.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/_vendor/tomli/_types.py
-@@ -0,0 +1,10 @@
-+# SPDX-License-Identifier: MIT
-+# SPDX-FileCopyrightText: 2021 Taneli Hukkinen
-+# Licensed to PSF under a Contributor Agreement.
-+
-+from typing import Any, Callable, Tuple
-+
-+# Type annotations
-+ParseFloat = Callable[[str], Any]
-+Key = Tuple[str, ...]
-+Pos = int
-diff --git
a/third_party/python/setuptools/setuptools/_vendor/typing_extensions.py
b/third_party/python/setuptools/setuptools/_vendor/typing_extensions.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/_vendor/typing_extensions.py
-@@ -0,0 +1,2296 @@
-+import abc
-+import collections
-+import collections.abc
-+import operator
-+import sys
-+import typing
-+
-+# After PEP 560, internal typing API was substantially reworked.
-+# This is especially important for Protocol class which uses internal APIs
-+# quite extensively.
-+PEP_560 = sys.version_info[:3] >= (3, 7, 0)
-+
-+if PEP_560:
-+ GenericMeta = type
-+else:
-+ # 3.6
-+ from typing import GenericMeta, _type_vars # noqa
-+
-+# The two functions below are copies of typing internal helpers.
-+# They are needed by _ProtocolMeta
-+
-+
-+def _no_slots_copy(dct):
-+ dict_copy = dict(dct)
-+ if '__slots__' in dict_copy:
-+ for slot in dict_copy['__slots__']:
-+ dict_copy.pop(slot, None)
-+ return dict_copy
-+
-+
-+def _check_generic(cls, parameters):
-+ if not cls.__parameters__:
-+ raise TypeError(f"{cls} is not a generic class")
-+ alen = len(parameters)
-+ elen = len(cls.__parameters__)
-+ if alen != elen:
-+ raise TypeError(f"Too {'many' if alen > elen else 'few'} arguments
for {cls};"
-+ f" actual {alen}, expected {elen}")
-+
-+
-+# Please keep __all__ alphabetized within each category.
-+__all__ = [
-+ # Super-special typing primitives.
-+ 'ClassVar',
-+ 'Concatenate',
-+ 'Final',
-+ 'ParamSpec',
-+ 'Self',
-+ 'Type',
-+
-+ # ABCs (from collections.abc).
-+ 'Awaitable',
-+ 'AsyncIterator',
-+ 'AsyncIterable',
-+ 'Coroutine',
-+ 'AsyncGenerator',
-+ 'AsyncContextManager',
-+ 'ChainMap',
-+
-+ # Concrete collection types.
-+ 'ContextManager',
-+ 'Counter',
-+ 'Deque',
-+ 'DefaultDict',
-+ 'OrderedDict',
-+ 'TypedDict',
-+
-+ # Structural checks, a.k.a. protocols.
-+ 'SupportsIndex',
-+
-+ # One-off things.
-+ 'Annotated',
-+ 'final',
-+ 'IntVar',
-+ 'Literal',
-+ 'NewType',
-+ 'overload',
-+ 'Protocol',
-+ 'runtime',
-+ 'runtime_checkable',
-+ 'Text',
-+ 'TypeAlias',
-+ 'TypeGuard',
-+ 'TYPE_CHECKING',
-+]
-+
-+if PEP_560:
-+ __all__.extend(["get_args", "get_origin", "get_type_hints"])
-+
-+# 3.6.2+
-+if hasattr(typing, 'NoReturn'):
-+ NoReturn = typing.NoReturn
-+# 3.6.0-3.6.1
-+else:
-+ class _NoReturn(typing._FinalTypingBase, _root=True):
-+ """Special type indicating functions that never return.
-+ Example::
-+
-+ from typing import NoReturn
-+
-+ def stop() -> NoReturn:
-+ raise Exception('no way')
-+
-+ This type is invalid in other positions, e.g., ``List[NoReturn]``
-+ will fail in static type checkers.
-+ """
-+ __slots__ = ()
-+
-+ def __instancecheck__(self, obj):
-+ raise TypeError("NoReturn cannot be used with isinstance().")
-+
-+ def __subclasscheck__(self, cls):
-+ raise TypeError("NoReturn cannot be used with issubclass().")
-+
-+ NoReturn = _NoReturn(_root=True)
-+
-+# Some unconstrained type variables. These are used by the container types.
-+# (These are not for export.)
-+T = typing.TypeVar('T') # Any type.
-+KT = typing.TypeVar('KT') # Key type.
-+VT = typing.TypeVar('VT') # Value type.
-+T_co = typing.TypeVar('T_co', covariant=True) # Any type covariant
containers.
-+T_contra = typing.TypeVar('T_contra', contravariant=True) # Ditto
contravariant.
-+
-+ClassVar = typing.ClassVar
-+
-+# On older versions of typing there is an internal class named "Final".
-+# 3.8+
-+if hasattr(typing, 'Final') and sys.version_info[:2] >= (3, 7):
-+ Final = typing.Final
-+# 3.7
-+elif sys.version_info[:2] >= (3, 7):
-+ class _FinalForm(typing._SpecialForm, _root=True):
-+
-+ def __repr__(self):
-+ return 'typing_extensions.' + self._name
-+
-+ def __getitem__(self, parameters):
-+ item = typing._type_check(parameters,
-+ f'{self._name} accepts only single
type')
-+ return typing._GenericAlias(self, (item,))
-+
-+ Final = _FinalForm('Final',
-+ doc="""A special typing construct to indicate that a
name
-+ cannot be re-assigned or overridden in a subclass.
-+ For example:
-+
-+ MAX_SIZE: Final = 9000
-+ MAX_SIZE += 1 # Error reported by type checker
-+
-+ class Connection:
-+ TIMEOUT: Final[int] = 10
-+ class FastConnector(Connection):
-+ TIMEOUT = 1 # Error reported by type checker
-+
-+ There is no runtime checking of these properties.""")
-+# 3.6
-+else:
-+ class _Final(typing._FinalTypingBase, _root=True):
-+ """A special typing construct to indicate that a name
-+ cannot be re-assigned or overridden in a subclass.
-+ For example:
-+
-+ MAX_SIZE: Final = 9000
-+ MAX_SIZE += 1 # Error reported by type checker
-+
-+ class Connection:
-+ TIMEOUT: Final[int] = 10
-+ class FastConnector(Connection):
-+ TIMEOUT = 1 # Error reported by type checker
-+
-+ There is no runtime checking of these properties.
-+ """
-+
-+ __slots__ = ('__type__',)
-+
-+ def __init__(self, tp=None, **kwds):
-+ self.__type__ = tp
-+
-+ def __getitem__(self, item):
-+ cls = type(self)
-+ if self.__type__ is None:
-+ return cls(typing._type_check(item,
-+ f'{cls.__name__[1:]} accepts only single type.'),
-+ _root=True)
-+ raise TypeError(f'{cls.__name__[1:]} cannot be further
subscripted')
-+
-+ def _eval_type(self, globalns, localns):
-+ new_tp = typing._eval_type(self.__type__, globalns, localns)
-+ if new_tp == self.__type__:
-+ return self
-+ return type(self)(new_tp, _root=True)
-+
-+ def __repr__(self):
-+ r = super().__repr__()
-+ if self.__type__ is not None:
-+ r += f'[{typing._type_repr(self.__type__)}]'
-+ return r
-+
-+ def __hash__(self):
-+ return hash((type(self).__name__, self.__type__))
-+
-+ def __eq__(self, other):
-+ if not isinstance(other, _Final):
-+ return NotImplemented
-+ if self.__type__ is not None:
-+ return self.__type__ == other.__type__
-+ return self is other
-+
-+ Final = _Final(_root=True)
-+
-+
-+# 3.8+
-+if hasattr(typing, 'final'):
-+ final = typing.final
-+# 3.6-3.7
-+else:
-+ def final(f):
-+ """This decorator can be used to indicate to type checkers that
-+ the decorated method cannot be overridden, and decorated class
-+ cannot be subclassed. For example:
-+
-+ class Base:
-+ @final
-+ def done(self) -> None:
-+ ...
-+ class Sub(Base):
-+ def done(self) -> None: # Error reported by type checker
-+ ...
-+ @final
-+ class Leaf:
-+ ...
-+ class Other(Leaf): # Error reported by type checker
-+ ...
-+
-+ There is no runtime checking of these properties.
-+ """
-+ return f
-+
-+
-+def IntVar(name):
-+ return typing.TypeVar(name)
-+
-+
-+# 3.8+:
-+if hasattr(typing, 'Literal'):
-+ Literal = typing.Literal
-+# 3.7:
-+elif sys.version_info[:2] >= (3, 7):
-+ class _LiteralForm(typing._SpecialForm, _root=True):
-+
-+ def __repr__(self):
-+ return 'typing_extensions.' + self._name
-+
-+ def __getitem__(self, parameters):
-+ return typing._GenericAlias(self, parameters)
-+
-+ Literal = _LiteralForm('Literal',
-+ doc="""A type that can be used to indicate to
type checkers
-+ that the corresponding value has a value
literally equivalent
-+ to the provided parameter. For example:
-+
-+ var: Literal[4] = 4
-+
-+ The type checker understands that 'var' is
literally equal to
-+ the value 4 and no other value.
-+
-+ Literal[...] cannot be subclassed. There is no
runtime
-+ checking verifying that the parameter is
actually a value
-+ instead of a type.""")
-+# 3.6:
-+else:
-+ class _Literal(typing._FinalTypingBase, _root=True):
-+ """A type that can be used to indicate to type checkers that the
-+ corresponding value has a value literally equivalent to the
-+ provided parameter. For example:
-+
-+ var: Literal[4] = 4
-+
-+ The type checker understands that 'var' is literally equal to the
-+ value 4 and no other value.
-+
-+ Literal[...] cannot be subclassed. There is no runtime checking
-+ verifying that the parameter is actually a value instead of a type.
-+ """
-+
-+ __slots__ = ('__values__',)
-+
-+ def __init__(self, values=None, **kwds):
-+ self.__values__ = values
-+
-+ def __getitem__(self, values):
-+ cls = type(self)
-+ if self.__values__ is None:
-+ if not isinstance(values, tuple):
-+ values = (values,)
-+ return cls(values, _root=True)
-+ raise TypeError(f'{cls.__name__[1:]} cannot be further
subscripted')
-+
-+ def _eval_type(self, globalns, localns):
-+ return self
-+
-+ def __repr__(self):
-+ r = super().__repr__()
-+ if self.__values__ is not None:
-+ r += f'[{", ".join(map(typing._type_repr,
self.__values__))}]'
-+ return r
-+
-+ def __hash__(self):
-+ return hash((type(self).__name__, self.__values__))
-+
-+ def __eq__(self, other):
-+ if not isinstance(other, _Literal):
-+ return NotImplemented
-+ if self.__values__ is not None:
-+ return self.__values__ == other.__values__
-+ return self is other
-+
-+ Literal = _Literal(_root=True)
-+
-+
-+_overload_dummy = typing._overload_dummy # noqa
-+overload = typing.overload
-+
-+
-+# This is not a real generic class. Don't use outside annotations.
-+Type = typing.Type
-+
-+# Various ABCs mimicking those in collections.abc.
-+# A few are simply re-exported for completeness.
-+
-+
-+class _ExtensionsGenericMeta(GenericMeta):
-+ def __subclasscheck__(self, subclass):
-+ """This mimics a more modern GenericMeta.__subclasscheck__() logic
-+ (that does not have problems with recursion) to work around
interactions
-+ between collections, typing, and typing_extensions on older
-+ versions of Python, see https://github.com/python/typing/issues/501.
-+ """
-+ if self.__origin__ is not None:
-+ if sys._getframe(1).f_globals['__name__'] not in ['abc',
'functools']:
-+ raise TypeError("Parameterized generics cannot be used with
class "
-+ "or instance checks")
-+ return False
-+ if not self.__extra__:
-+ return super().__subclasscheck__(subclass)
-+ res = self.__extra__.__subclasshook__(subclass)
-+ if res is not NotImplemented:
-+ return res
-+ if self.__extra__ in subclass.__mro__:
-+ return True
-+ for scls in self.__extra__.__subclasses__():
-+ if isinstance(scls, GenericMeta):
-+ continue
-+ if issubclass(subclass, scls):
-+ return True
-+ return False
-+
-+
-+Awaitable = typing.Awaitable
-+Coroutine = typing.Coroutine
-+AsyncIterable = typing.AsyncIterable
-+AsyncIterator = typing.AsyncIterator
-+
-+# 3.6.1+
-+if hasattr(typing, 'Deque'):
-+ Deque = typing.Deque
-+# 3.6.0
-+else:
-+ class Deque(collections.deque, typing.MutableSequence[T],
-+ metaclass=_ExtensionsGenericMeta,
-+ extra=collections.deque):
-+ __slots__ = ()
-+
-+ def __new__(cls, *args, **kwds):
-+ if cls._gorg is Deque:
-+ return collections.deque(*args, **kwds)
-+ return typing._generic_new(collections.deque, cls, *args,
**kwds)
-+
-+ContextManager = typing.ContextManager
-+# 3.6.2+
-+if hasattr(typing, 'AsyncContextManager'):
-+ AsyncContextManager = typing.AsyncContextManager
-+# 3.6.0-3.6.1
-+else:
-+ from _collections_abc import _check_methods as _check_methods_in_mro #
noqa
-+
-+ class AsyncContextManager(typing.Generic[T_co]):
-+ __slots__ = ()
-+
-+ async def __aenter__(self):
-+ return self
-+
-+ @abc.abstractmethod
-+ async def __aexit__(self, exc_type, exc_value, traceback):
-+ return None
-+
-+ @classmethod
-+ def __subclasshook__(cls, C):
-+ if cls is AsyncContextManager:
-+ return _check_methods_in_mro(C, "__aenter__", "__aexit__")
-+ return NotImplemented
-+
-+DefaultDict = typing.DefaultDict
-+
-+# 3.7.2+
-+if hasattr(typing, 'OrderedDict'):
-+ OrderedDict = typing.OrderedDict
-+# 3.7.0-3.7.2
-+elif (3, 7, 0) <= sys.version_info[:3] < (3, 7, 2):
-+ OrderedDict = typing._alias(collections.OrderedDict, (KT, VT))
-+# 3.6
-+else:
-+ class OrderedDict(collections.OrderedDict, typing.MutableMapping[KT,
VT],
-+ metaclass=_ExtensionsGenericMeta,
-+ extra=collections.OrderedDict):
-+
-+ __slots__ = ()
-+
-+ def __new__(cls, *args, **kwds):
-+ if cls._gorg is OrderedDict:
-+ return collections.OrderedDict(*args, **kwds)
-+ return typing._generic_new(collections.OrderedDict, cls, *args,
**kwds)
-+
-+# 3.6.2+
-+if hasattr(typing, 'Counter'):
-+ Counter = typing.Counter
-+# 3.6.0-3.6.1
-+else:
-+ class Counter(collections.Counter,
-+ typing.Dict[T, int],
-+ metaclass=_ExtensionsGenericMeta,
extra=collections.Counter):
-+
-+ __slots__ = ()
-+
-+ def __new__(cls, *args, **kwds):
-+ if cls._gorg is Counter:
-+ return collections.Counter(*args, **kwds)
-+ return typing._generic_new(collections.Counter, cls, *args,
**kwds)
-+
-+# 3.6.1+
-+if hasattr(typing, 'ChainMap'):
-+ ChainMap = typing.ChainMap
-+elif hasattr(collections, 'ChainMap'):
-+ class ChainMap(collections.ChainMap, typing.MutableMapping[KT, VT],
-+ metaclass=_ExtensionsGenericMeta,
-+ extra=collections.ChainMap):
-+
-+ __slots__ = ()
-+
-+ def __new__(cls, *args, **kwds):
-+ if cls._gorg is ChainMap:
-+ return collections.ChainMap(*args, **kwds)
-+ return typing._generic_new(collections.ChainMap, cls, *args,
**kwds)
-+
-+# 3.6.1+
-+if hasattr(typing, 'AsyncGenerator'):
-+ AsyncGenerator = typing.AsyncGenerator
-+# 3.6.0
-+else:
-+ class AsyncGenerator(AsyncIterator[T_co], typing.Generic[T_co,
T_contra],
-+ metaclass=_ExtensionsGenericMeta,
-+ extra=collections.abc.AsyncGenerator):
-+ __slots__ = ()
-+
-+NewType = typing.NewType
-+Text = typing.Text
-+TYPE_CHECKING = typing.TYPE_CHECKING
-+
-+
-+def _gorg(cls):
-+ """This function exists for compatibility with old typing versions."""
-+ assert isinstance(cls, GenericMeta)
-+ if hasattr(cls, '_gorg'):
-+ return cls._gorg
-+ while cls.__origin__ is not None:
-+ cls = cls.__origin__
-+ return cls
-+
-+
-+_PROTO_WHITELIST = ['Callable', 'Awaitable',
-+ 'Iterable', 'Iterator', 'AsyncIterable',
'AsyncIterator',
-+ 'Hashable', 'Sized', 'Container', 'Collection',
'Reversible',
-+ 'ContextManager', 'AsyncContextManager']
-+
-+
-+def _get_protocol_attrs(cls):
-+ attrs = set()
-+ for base in cls.__mro__[:-1]: # without object
-+ if base.__name__ in ('Protocol', 'Generic'):
-+ continue
-+ annotations = getattr(base, '__annotations__', {})
-+ for attr in list(base.__dict__.keys()) + list(annotations.keys()):
-+ if (not attr.startswith('_abc_') and attr not in (
-+ '__abstractmethods__', '__annotations__', '__weakref__',
-+ '_is_protocol', '_is_runtime_protocol', '__dict__',
-+ '__args__', '__slots__',
-+ '__next_in_mro__', '__parameters__', '__origin__',
-+ '__orig_bases__', '__extra__', '__tree_hash__',
-+ '__doc__', '__subclasshook__', '__init__', '__new__',
-+ '__module__', '_MutableMapping__marker', '_gorg')):
-+ attrs.add(attr)
-+ return attrs
-+
-+
-+def _is_callable_members_only(cls):
-+ return all(callable(getattr(cls, attr, None)) for attr in
_get_protocol_attrs(cls))
-+
-+
-+# 3.8+
-+if hasattr(typing, 'Protocol'):
-+ Protocol = typing.Protocol
-+# 3.7
-+elif PEP_560:
-+ from typing import _collect_type_vars # noqa
-+
-+ def _no_init(self, *args, **kwargs):
-+ if type(self)._is_protocol:
-+ raise TypeError('Protocols cannot be instantiated')
-+
-+ class _ProtocolMeta(abc.ABCMeta):
-+ # This metaclass is a bit unfortunate and exists only because of
the lack
-+ # of __instancehook__.
-+ def __instancecheck__(cls, instance):
-+ # We need this method for situations where attributes are
-+ # assigned in __init__.
-+ if ((not getattr(cls, '_is_protocol', False) or
-+ _is_callable_members_only(cls)) and
-+ issubclass(instance.__class__, cls)):
-+ return True
-+ if cls._is_protocol:
-+ if all(hasattr(instance, attr) and
-+ (not callable(getattr(cls, attr, None)) or
-+ getattr(instance, attr) is not None)
-+ for attr in _get_protocol_attrs(cls)):
-+ return True
-+ return super().__instancecheck__(instance)
-+
-+ class Protocol(metaclass=_ProtocolMeta):
-+ # There is quite a lot of overlapping code with typing.Generic.
-+ # Unfortunately it is hard to avoid this while these live in two
different
-+ # modules. The duplicated code will be removed when Protocol is
moved to typing.
-+ """Base class for protocol classes. Protocol classes are defined
as::
-+
-+ class Proto(Protocol):
-+ def meth(self) -> int:
-+ ...
-+
-+ Such classes are primarily used with static type checkers that
recognize
-+ structural subtyping (static duck-typing), for example::
-+
-+ class C:
-+ def meth(self) -> int:
-+ return 0
-+
-+ def func(x: Proto) -> int:
-+ return x.meth()
-+
-+ func(C()) # Passes static type check
-+
-+ See PEP 544 for details. Protocol classes decorated with
-+ @typing_extensions.runtime act as simple-minded runtime protocol
that checks
-+ only the presence of given attributes, ignoring their type
signatures.
-+
-+ Protocol classes can be generic, they are defined as::
-+
-+ class GenProto(Protocol[T]):
-+ def meth(self) -> T:
-+ ...
-+ """
-+ __slots__ = ()
-+ _is_protocol = True
-+
-+ def __new__(cls, *args, **kwds):
-+ if cls is Protocol:
-+ raise TypeError("Type Protocol cannot be instantiated; "
-+ "it can only be used as a base class")
-+ return super().__new__(cls)
-+
-+ @typing._tp_cache
-+ def __class_getitem__(cls, params):
-+ if not isinstance(params, tuple):
-+ params = (params,)
-+ if not params and cls is not typing.Tuple:
-+ raise TypeError(
-+ f"Parameter list to {cls.__qualname__}[...] cannot be
empty")
-+ msg = "Parameters to generic types must be types."
-+ params = tuple(typing._type_check(p, msg) for p in params) #
noqa
-+ if cls is Protocol:
-+ # Generic can only be subscripted with unique type
variables.
-+ if not all(isinstance(p, typing.TypeVar) for p in params):
-+ i = 0
-+ while isinstance(params[i], typing.TypeVar):
-+ i += 1
-+ raise TypeError(
-+ "Parameters to Protocol[...] must all be type
variables."
-+ f" Parameter {i + 1} is {params[i]}")
-+ if len(set(params)) != len(params):
-+ raise TypeError(
-+ "Parameters to Protocol[...] must all be unique")
-+ else:
-+ # Subscripting a regular Generic subclass.
-+ _check_generic(cls, params)
-+ return typing._GenericAlias(cls, params)
-+
-+ def __init_subclass__(cls, *args, **kwargs):
-+ tvars = []
-+ if '__orig_bases__' in cls.__dict__:
-+ error = typing.Generic in cls.__orig_bases__
-+ else:
-+ error = typing.Generic in cls.__bases__
-+ if error:
-+ raise TypeError("Cannot inherit from plain Generic")
-+ if '__orig_bases__' in cls.__dict__:
-+ tvars = _collect_type_vars(cls.__orig_bases__)
-+ # Look for Generic[T1, ..., Tn] or Protocol[T1, ..., Tn].
-+ # If found, tvars must be a subset of it.
-+ # If not found, tvars is it.
-+ # Also check for and reject plain Generic,
-+ # and reject multiple Generic[...] and/or Protocol[...].
-+ gvars = None
-+ for base in cls.__orig_bases__:
-+ if (isinstance(base, typing._GenericAlias) and
-+ base.__origin__ in (typing.Generic, Protocol)):
-+ # for error messages
-+ the_base = base.__origin__.__name__
-+ if gvars is not None:
-+ raise TypeError(
-+ "Cannot inherit from Generic[...]"
-+ " and/or Protocol[...] multiple types.")
-+ gvars = base.__parameters__
-+ if gvars is None:
-+ gvars = tvars
-+ else:
-+ tvarset = set(tvars)
-+ gvarset = set(gvars)
-+ if not tvarset <= gvarset:
-+ s_vars = ', '.join(str(t) for t in tvars if t not
in gvarset)
-+ s_args = ', '.join(str(g) for g in gvars)
-+ raise TypeError(f"Some type variables ({s_vars})
are"
-+ f" not listed in
{the_base}[{s_args}]")
-+ tvars = gvars
-+ cls.__parameters__ = tuple(tvars)
-+
-+ # Determine if this is a protocol or a concrete subclass.
-+ if not cls.__dict__.get('_is_protocol', None):
-+ cls._is_protocol = any(b is Protocol for b in cls.__bases__)
-+
-+ # Set (or override) the protocol subclass hook.
-+ def _proto_hook(other):
-+ if not cls.__dict__.get('_is_protocol', None):
-+ return NotImplemented
-+ if not getattr(cls, '_is_runtime_protocol', False):
-+ if sys._getframe(2).f_globals['__name__'] in ['abc',
'functools']:
-+ return NotImplemented
-+ raise TypeError("Instance and class checks can only be
used with"
-+ " @runtime protocols")
-+ if not _is_callable_members_only(cls):
-+ if sys._getframe(2).f_globals['__name__'] in ['abc',
'functools']:
-+ return NotImplemented
-+ raise TypeError("Protocols with non-method members"
-+ " don't support issubclass()")
-+ if not isinstance(other, type):
-+ # Same error as for issubclass(1, int)
-+ raise TypeError('issubclass() arg 1 must be a class')
-+ for attr in _get_protocol_attrs(cls):
-+ for base in other.__mro__:
-+ if attr in base.__dict__:
-+ if base.__dict__[attr] is None:
-+ return NotImplemented
-+ break
-+ annotations = getattr(base, '__annotations__', {})
-+ if (isinstance(annotations, typing.Mapping) and
-+ attr in annotations and
-+ isinstance(other, _ProtocolMeta) and
-+ other._is_protocol):
-+ break
-+ else:
-+ return NotImplemented
-+ return True
-+ if '__subclasshook__' not in cls.__dict__:
-+ cls.__subclasshook__ = _proto_hook
-+
-+ # We have nothing more to do for non-protocols.
-+ if not cls._is_protocol:
-+ return
-+
-+ # Check consistency of bases.
-+ for base in cls.__bases__:
-+ if not (base in (object, typing.Generic) or
-+ base.__module__ == 'collections.abc' and
-+ base.__name__ in _PROTO_WHITELIST or
-+ isinstance(base, _ProtocolMeta) and
base._is_protocol):
-+ raise TypeError('Protocols can only inherit from other'
-+ f' protocols, got {repr(base)}')
-+ cls.__init__ = _no_init
-+# 3.6
-+else:
-+ from typing import _next_in_mro, _type_check # noqa
-+
-+ def _no_init(self, *args, **kwargs):
-+ if type(self)._is_protocol:
-+ raise TypeError('Protocols cannot be instantiated')
-+
-+ class _ProtocolMeta(GenericMeta):
-+ """Internal metaclass for Protocol.
-+
-+ This exists so Protocol classes can be generic without deriving
-+ from Generic.
-+ """
-+ def __new__(cls, name, bases, namespace,
-+ tvars=None, args=None, origin=None, extra=None,
orig_bases=None):
-+ # This is just a version copied from GenericMeta.__new__ that
-+ # includes "Protocol" special treatment. (Comments removed for
brevity.)
-+ assert extra is None # Protocols should not have extra
-+ if tvars is not None:
-+ assert origin is not None
-+ assert all(isinstance(t, typing.TypeVar) for t in tvars),
tvars
-+ else:
-+ tvars = _type_vars(bases)
-+ gvars = None
-+ for base in bases:
-+ if base is typing.Generic:
-+ raise TypeError("Cannot inherit from plain Generic")
-+ if (isinstance(base, GenericMeta) and
-+ base.__origin__ in (typing.Generic, Protocol)):
-+ if gvars is not None:
-+ raise TypeError(
-+ "Cannot inherit from Generic[...] or"
-+ " Protocol[...] multiple times.")
-+ gvars = base.__parameters__
-+ if gvars is None:
-+ gvars = tvars
-+ else:
-+ tvarset = set(tvars)
-+ gvarset = set(gvars)
-+ if not tvarset <= gvarset:
-+ s_vars = ", ".join(str(t) for t in tvars if t not
in gvarset)
-+ s_args = ", ".join(str(g) for g in gvars)
-+ cls_name = "Generic" if any(b.__origin__ is
typing.Generic
-+ for b in bases) else
"Protocol"
-+ raise TypeError(f"Some type variables ({s_vars})
are"
-+ f" not listed in
{cls_name}[{s_args}]")
-+ tvars = gvars
-+
-+ initial_bases = bases
-+ if (extra is not None and type(extra) is abc.ABCMeta and
-+ extra not in bases):
-+ bases = (extra,) + bases
-+ bases = tuple(_gorg(b) if isinstance(b, GenericMeta) else b
-+ for b in bases)
-+ if any(isinstance(b, GenericMeta) and b is not typing.Generic
for b in bases):
-+ bases = tuple(b for b in bases if b is not typing.Generic)
-+ namespace.update({'__origin__': origin, '__extra__': extra})
-+ self = super(GenericMeta, cls).__new__(cls, name, bases,
namespace,
-+ _root=True)
-+ super(GenericMeta, self).__setattr__('_gorg',
-+ self if not origin else
-+ _gorg(origin))
-+ self.__parameters__ = tvars
-+ self.__args__ = tuple(... if a is typing._TypingEllipsis else
-+ () if a is typing._TypingEmpty else
-+ a for a in args) if args else None
-+ self.__next_in_mro__ = _next_in_mro(self)
-+ if orig_bases is None:
-+ self.__orig_bases__ = initial_bases
-+ elif origin is not None:
-+ self._abc_registry = origin._abc_registry
-+ self._abc_cache = origin._abc_cache
-+ if hasattr(self, '_subs_tree'):
-+ self.__tree_hash__ = (hash(self._subs_tree()) if origin else
-+ super(GenericMeta, self).__hash__())
-+ return self
-+
-+ def __init__(cls, *args, **kwargs):
-+ super().__init__(*args, **kwargs)
-+ if not cls.__dict__.get('_is_protocol', None):
-+ cls._is_protocol = any(b is Protocol or
-+ isinstance(b, _ProtocolMeta) and
-+ b.__origin__ is Protocol
-+ for b in cls.__bases__)
-+ if cls._is_protocol:
-+ for base in cls.__mro__[1:]:
-+ if not (base in (object, typing.Generic) or
-+ base.__module__ == 'collections.abc' and
-+ base.__name__ in _PROTO_WHITELIST or
-+ isinstance(base, typing.TypingMeta) and
base._is_protocol or
-+ isinstance(base, GenericMeta) and
-+ base.__origin__ is typing.Generic):
-+ raise TypeError(f'Protocols can only inherit from
other'
-+ f' protocols, got {repr(base)}')
-+
-+ cls.__init__ = _no_init
-+
-+ def _proto_hook(other):
-+ if not cls.__dict__.get('_is_protocol', None):
-+ return NotImplemented
-+ if not isinstance(other, type):
-+ # Same error as for issubclass(1, int)
-+ raise TypeError('issubclass() arg 1 must be a class')
-+ for attr in _get_protocol_attrs(cls):
-+ for base in other.__mro__:
-+ if attr in base.__dict__:
-+ if base.__dict__[attr] is None:
-+ return NotImplemented
-+ break
-+ annotations = getattr(base, '__annotations__', {})
-+ if (isinstance(annotations, typing.Mapping) and
-+ attr in annotations and
-+ isinstance(other, _ProtocolMeta) and
-+ other._is_protocol):
-+ break
-+ else:
-+ return NotImplemented
-+ return True
-+ if '__subclasshook__' not in cls.__dict__:
-+ cls.__subclasshook__ = _proto_hook
-+
-+ def __instancecheck__(self, instance):
-+ # We need this method for situations where attributes are
-+ # assigned in __init__.
-+ if ((not getattr(self, '_is_protocol', False) or
-+ _is_callable_members_only(self)) and
-+ issubclass(instance.__class__, self)):
-+ return True
-+ if self._is_protocol:
-+ if all(hasattr(instance, attr) and
-+ (not callable(getattr(self, attr, None)) or
-+ getattr(instance, attr) is not None)
-+ for attr in _get_protocol_attrs(self)):
-+ return True
-+ return super(GenericMeta, self).__instancecheck__(instance)
-+
-+ def __subclasscheck__(self, cls):
-+ if self.__origin__ is not None:
-+ if sys._getframe(1).f_globals['__name__'] not in ['abc',
'functools']:
-+ raise TypeError("Parameterized generics cannot be used
with class "
-+ "or instance checks")
-+ return False
-+ if (self.__dict__.get('_is_protocol', None) and
-+ not self.__dict__.get('_is_runtime_protocol', None)):
-+ if sys._getframe(1).f_globals['__name__'] in ['abc',
-+ 'functools',
-+ 'typing']:
-+ return False
-+ raise TypeError("Instance and class checks can only be used
with"
-+ " @runtime protocols")
-+ if (self.__dict__.get('_is_runtime_protocol', None) and
-+ not _is_callable_members_only(self)):
-+ if sys._getframe(1).f_globals['__name__'] in ['abc',
-+ 'functools',
-+ 'typing']:
-+ return super(GenericMeta, self).__subclasscheck__(cls)
-+ raise TypeError("Protocols with non-method members"
-+ " don't support issubclass()")
-+ return super(GenericMeta, self).__subclasscheck__(cls)
-+
-+ @typing._tp_cache
-+ def __getitem__(self, params):
-+ # We also need to copy this from GenericMeta.__getitem__ to get
-+ # special treatment of "Protocol". (Comments removed for
brevity.)
-+ if not isinstance(params, tuple):
-+ params = (params,)
-+ if not params and _gorg(self) is not typing.Tuple:
-+ raise TypeError(
-+ f"Parameter list to {self.__qualname__}[...] cannot be
empty")
-+ msg = "Parameters to generic types must be types."
-+ params = tuple(_type_check(p, msg) for p in params)
-+ if self in (typing.Generic, Protocol):
-+ if not all(isinstance(p, typing.TypeVar) for p in params):
-+ raise TypeError(
-+ f"Parameters to {repr(self)}[...] must all be type
variables")
-+ if len(set(params)) != len(params):
-+ raise TypeError(
-+ f"Parameters to {repr(self)}[...] must all be
unique")
-+ tvars = params
-+ args = params
-+ elif self in (typing.Tuple, typing.Callable):
-+ tvars = _type_vars(params)
-+ args = params
-+ elif self.__origin__ in (typing.Generic, Protocol):
-+ raise TypeError(f"Cannot subscript already-subscripted
{repr(self)}")
-+ else:
-+ _check_generic(self, params)
-+ tvars = _type_vars(params)
-+ args = params
-+
-+ prepend = (self,) if self.__origin__ is None else ()
-+ return self.__class__(self.__name__,
-+ prepend + self.__bases__,
-+ _no_slots_copy(self.__dict__),
-+ tvars=tvars,
-+ args=args,
-+ origin=self,
-+ extra=self.__extra__,
-+ orig_bases=self.__orig_bases__)
-+
-+ class Protocol(metaclass=_ProtocolMeta):
-+ """Base class for protocol classes. Protocol classes are defined
as::
-+
-+ class Proto(Protocol):
-+ def meth(self) -> int:
-+ ...
-+
-+ Such classes are primarily used with static type checkers that
recognize
-+ structural subtyping (static duck-typing), for example::
-+
-+ class C:
-+ def meth(self) -> int:
-+ return 0
-+
-+ def func(x: Proto) -> int:
-+ return x.meth()
-+
-+ func(C()) # Passes static type check
-+
-+ See PEP 544 for details. Protocol classes decorated with
-+ @typing_extensions.runtime act as simple-minded runtime protocol
that checks
-+ only the presence of given attributes, ignoring their type
signatures.
-+
-+ Protocol classes can be generic, they are defined as::
-+
-+ class GenProto(Protocol[T]):
-+ def meth(self) -> T:
-+ ...
-+ """
-+ __slots__ = ()
-+ _is_protocol = True
-+
-+ def __new__(cls, *args, **kwds):
-+ if _gorg(cls) is Protocol:
-+ raise TypeError("Type Protocol cannot be instantiated; "
-+ "it can be used only as a base class")
-+ return typing._generic_new(cls.__next_in_mro__, cls, *args,
**kwds)
-+
-+
-+# 3.8+
-+if hasattr(typing, 'runtime_checkable'):
-+ runtime_checkable = typing.runtime_checkable
-+# 3.6-3.7
-+else:
-+ def runtime_checkable(cls):
-+ """Mark a protocol class as a runtime protocol, so that it
-+ can be used with isinstance() and issubclass(). Raise TypeError
-+ if applied to a non-protocol class.
-+
-+ This allows a simple-minded structural check very similar to the
-+ one-offs in collections.abc such as Hashable.
-+ """
-+ if not isinstance(cls, _ProtocolMeta) or not cls._is_protocol:
-+ raise TypeError('@runtime_checkable can be only applied to
protocol classes,'
-+ f' got {cls!r}')
-+ cls._is_runtime_protocol = True
-+ return cls
-+
-+
-+# Exists for backwards compatibility.
-+runtime = runtime_checkable
-+
-+
-+# 3.8+
-+if hasattr(typing, 'SupportsIndex'):
-+ SupportsIndex = typing.SupportsIndex
-+# 3.6-3.7
-+else:
-+ @runtime_checkable
-+ class SupportsIndex(Protocol):
-+ __slots__ = ()
-+
-+ @abc.abstractmethod
-+ def __index__(self) -> int:
-+ pass
-+
-+
-+if sys.version_info >= (3, 9, 2):
-+ # The standard library TypedDict in Python 3.8 does not store runtime
information
-+ # about which (if any) keys are optional. See
https://bugs.python.org/issue38834
-+ # The standard library TypedDict in Python 3.9.0/1 does not honour the
"total"
-+ # keyword with old-style TypedDict(). See
https://bugs.python.org/issue42059
-+ TypedDict = typing.TypedDict
-+else:
-+ def _check_fails(cls, other):
-+ try:
-+ if sys._getframe(1).f_globals['__name__'] not in ['abc',
-+ 'functools',
-+ 'typing']:
-+ # Typed dicts are only for static structural subtyping.
-+ raise TypeError('TypedDict does not support instance and
class checks')
-+ except (AttributeError, ValueError):
-+ pass
-+ return False
-+
-+ def _dict_new(*args, **kwargs):
-+ if not args:
-+ raise TypeError('TypedDict.__new__(): not enough arguments')
-+ _, args = args[0], args[1:] # allow the "cls" keyword be passed
-+ return dict(*args, **kwargs)
-+
-+ _dict_new.__text_signature__ = '($cls, _typename, _fields=None, /,
**kwargs)'
-+
-+ def _typeddict_new(*args, total=True, **kwargs):
-+ if not args:
-+ raise TypeError('TypedDict.__new__(): not enough arguments')
-+ _, args = args[0], args[1:] # allow the "cls" keyword be passed
-+ if args:
-+ typename, args = args[0], args[1:] # allow the "_typename"
keyword be passed
-+ elif '_typename' in kwargs:
-+ typename = kwargs.pop('_typename')
-+ import warnings
-+ warnings.warn("Passing '_typename' as keyword argument is
deprecated",
-+ DeprecationWarning, stacklevel=2)
-+ else:
-+ raise TypeError("TypedDict.__new__() missing 1 required
positional "
-+ "argument: '_typename'")
-+ if args:
-+ try:
-+ fields, = args # allow the "_fields" keyword be passed
-+ except ValueError:
-+ raise TypeError('TypedDict.__new__() takes from 2 to 3 '
-+ f'positional arguments but {len(args) + 2} '
-+ 'were given')
-+ elif '_fields' in kwargs and len(kwargs) == 1:
-+ fields = kwargs.pop('_fields')
-+ import warnings
-+ warnings.warn("Passing '_fields' as keyword argument is
deprecated",
-+ DeprecationWarning, stacklevel=2)
-+ else:
-+ fields = None
-+
-+ if fields is None:
-+ fields = kwargs
-+ elif kwargs:
-+ raise TypeError("TypedDict takes either a dict or keyword
arguments,"
-+ " but not both")
-+
-+ ns = {'__annotations__': dict(fields)}
-+ try:
-+ # Setting correct module is necessary to make typed dict
classes pickleable.
-+ ns['__module__'] = sys._getframe(1).f_globals.get('__name__',
'__main__')
-+ except (AttributeError, ValueError):
-+ pass
-+
-+ return _TypedDictMeta(typename, (), ns, total=total)
-+
-+ _typeddict_new.__text_signature__ = ('($cls, _typename, _fields=None,'
-+ ' /, *, total=True, **kwargs)')
-+
-+ class _TypedDictMeta(type):
-+ def __init__(cls, name, bases, ns, total=True):
-+ super().__init__(name, bases, ns)
-+
-+ def __new__(cls, name, bases, ns, total=True):
-+ # Create new typed dict class object.
-+ # This method is called directly when TypedDict is subclassed,
-+ # or via _typeddict_new when TypedDict is instantiated. This way
-+ # TypedDict supports all three syntaxes described in its
docstring.
-+ # Subclasses and instances of TypedDict return actual
dictionaries
-+ # via _dict_new.
-+ ns['__new__'] = _typeddict_new if name == 'TypedDict' else
_dict_new
-+ tp_dict = super().__new__(cls, name, (dict,), ns)
-+
-+ annotations = {}
-+ own_annotations = ns.get('__annotations__', {})
-+ own_annotation_keys = set(own_annotations.keys())
-+ msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be
a type"
-+ own_annotations = {
-+ n: typing._type_check(tp, msg) for n, tp in
own_annotations.items()
-+ }
-+ required_keys = set()
-+ optional_keys = set()
-+
-+ for base in bases:
-+ annotations.update(base.__dict__.get('__annotations__', {}))
-+ required_keys.update(base.__dict__.get('__required_keys__',
()))
-+ optional_keys.update(base.__dict__.get('__optional_keys__',
()))
-+
-+ annotations.update(own_annotations)
-+ if total:
-+ required_keys.update(own_annotation_keys)
-+ else:
-+ optional_keys.update(own_annotation_keys)
-+
-+ tp_dict.__annotations__ = annotations
-+ tp_dict.__required_keys__ = frozenset(required_keys)
-+ tp_dict.__optional_keys__ = frozenset(optional_keys)
-+ if not hasattr(tp_dict, '__total__'):
-+ tp_dict.__total__ = total
-+ return tp_dict
-+
-+ __instancecheck__ = __subclasscheck__ = _check_fails
-+
-+ TypedDict = _TypedDictMeta('TypedDict', (dict,), {})
-+ TypedDict.__module__ = __name__
-+ TypedDict.__doc__ = \
-+ """A simple typed name space. At runtime it is equivalent to a
plain dict.
-+
-+ TypedDict creates a dictionary type that expects all of its
-+ instances to have a certain set of keys, with each key
-+ associated with a value of a consistent type. This expectation
-+ is not checked at runtime but is only enforced by type checkers.
-+ Usage::
-+
-+ class Point2D(TypedDict):
-+ x: int
-+ y: int
-+ label: str
-+
-+ a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK
-+ b: Point2D = {'z': 3, 'label': 'bad'} # Fails type
check
-+
-+ assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2,
label='first')
-+
-+ The type info can be accessed via the Point2D.__annotations__ dict,
and
-+ the Point2D.__required_keys__ and Point2D.__optional_keys__
frozensets.
-+ TypedDict supports two additional equivalent forms::
-+
-+ Point2D = TypedDict('Point2D', x=int, y=int, label=str)
-+ Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label':
str})
-+
-+ The class syntax is only supported in Python 3.6+, while two other
-+ syntax forms work for Python 2.7 and 3.2+
-+ """
-+
-+
-+# Python 3.9+ has PEP 593 (Annotated and modified get_type_hints)
-+if hasattr(typing, 'Annotated'):
-+ Annotated = typing.Annotated
-+ get_type_hints = typing.get_type_hints
-+ # Not exported and not a public API, but needed for get_origin() and
get_args()
-+ # to work.
-+ _AnnotatedAlias = typing._AnnotatedAlias
-+# 3.7-3.8
-+elif PEP_560:
-+ class _AnnotatedAlias(typing._GenericAlias, _root=True):
-+ """Runtime representation of an annotated type.
-+
-+ At its core 'Annotated[t, dec1, dec2, ...]' is an alias for the
type 't'
-+ with extra annotations. The alias behaves like a normal typing
alias,
-+ instantiating is the same as instantiating the underlying type,
binding
-+ it to types is also the same.
-+ """
-+ def __init__(self, origin, metadata):
-+ if isinstance(origin, _AnnotatedAlias):
-+ metadata = origin.__metadata__ + metadata
-+ origin = origin.__origin__
-+ super().__init__(origin, origin)
-+ self.__metadata__ = metadata
-+
-+ def copy_with(self, params):
-+ assert len(params) == 1
-+ new_type = params[0]
-+ return _AnnotatedAlias(new_type, self.__metadata__)
-+
-+ def __repr__(self):
-+ return
(f"typing_extensions.Annotated[{typing._type_repr(self.__origin__)}, "
-+ f"{', '.join(repr(a) for a in self.__metadata__)}]")
-+
-+ def __reduce__(self):
-+ return operator.getitem, (
-+ Annotated, (self.__origin__,) + self.__metadata__
-+ )
-+
-+ def __eq__(self, other):
-+ if not isinstance(other, _AnnotatedAlias):
-+ return NotImplemented
-+ if self.__origin__ != other.__origin__:
-+ return False
-+ return self.__metadata__ == other.__metadata__
-+
-+ def __hash__(self):
-+ return hash((self.__origin__, self.__metadata__))
-+
-+ class Annotated:
-+ """Add context specific metadata to a type.
-+
-+ Example: Annotated[int, runtime_check.Unsigned] indicates to the
-+ hypothetical runtime_check module that this type is an unsigned int.
-+ Every other consumer of this type can ignore this metadata and treat
-+ this type as int.
-+
-+ The first argument to Annotated must be a valid type (and will be in
-+ the __origin__ field), the remaining arguments are kept as a tuple
in
-+ the __extra__ field.
-+
-+ Details:
-+
-+ - It's an error to call `Annotated` with less than two arguments.
-+ - Nested Annotated are flattened::
-+
-+ Annotated[Annotated[T, Ann1, Ann2], Ann3] == Annotated[T, Ann1,
Ann2, Ann3]
-+
-+ - Instantiating an annotated type is equivalent to instantiating the
-+ underlying type::
-+
-+ Annotated[C, Ann1](5) == C(5)
-+
-+ - Annotated can be used as a generic type alias::
-+
-+ Optimized = Annotated[T, runtime.Optimize()]
-+ Optimized[int] == Annotated[int, runtime.Optimize()]
-+
-+ OptimizedList = Annotated[List[T], runtime.Optimize()]
-+ OptimizedList[int] == Annotated[List[int], runtime.Optimize()]
-+ """
-+
-+ __slots__ = ()
-+
-+ def __new__(cls, *args, **kwargs):
-+ raise TypeError("Type Annotated cannot be instantiated.")
-+
-+ @typing._tp_cache
-+ def __class_getitem__(cls, params):
-+ if not isinstance(params, tuple) or len(params) < 2:
-+ raise TypeError("Annotated[...] should be used "
-+ "with at least two arguments (a type and an
"
-+ "annotation).")
-+ msg = "Annotated[t, ...]: t must be a type."
-+ origin = typing._type_check(params[0], msg)
-+ metadata = tuple(params[1:])
-+ return _AnnotatedAlias(origin, metadata)
-+
-+ def __init_subclass__(cls, *args, **kwargs):
-+ raise TypeError(
-+ f"Cannot subclass {cls.__module__}.Annotated"
-+ )
-+
-+ def _strip_annotations(t):
-+ """Strips the annotations from a given type.
-+ """
-+ if isinstance(t, _AnnotatedAlias):
-+ return _strip_annotations(t.__origin__)
-+ if isinstance(t, typing._GenericAlias):
-+ stripped_args = tuple(_strip_annotations(a) for a in t.__args__)
-+ if stripped_args == t.__args__:
-+ return t
-+ res = t.copy_with(stripped_args)
-+ res._special = t._special
-+ return res
-+ return t
-+
-+ def get_type_hints(obj, globalns=None, localns=None,
include_extras=False):
-+ """Return type hints for an object.
-+
-+ This is often the same as obj.__annotations__, but it handles
-+ forward references encoded as string literals, adds Optional[t] if a
-+ default value equal to None is set and recursively replaces all
-+ 'Annotated[T, ...]' with 'T' (unless 'include_extras=True').
-+
-+ The argument may be a module, class, method, or function. The
annotations
-+ are returned as a dictionary. For classes, annotations include also
-+ inherited members.
-+
-+ TypeError is raised if the argument is not of a type that can
contain
-+ annotations, and an empty dictionary is returned if no annotations
are
-+ present.
-+
-+ BEWARE -- the behavior of globalns and localns is counterintuitive
-+ (unless you are familiar with how eval() and exec() work). The
-+ search order is locals first, then globals.
-+
-+ - If no dict arguments are passed, an attempt is made to use the
-+ globals from obj (or the respective module's globals for classes),
-+ and these are also used as the locals. If the object does not
appear
-+ to have globals, an empty dictionary is used.
-+
-+ - If one dict argument is passed, it is used for both globals and
-+ locals.
-+
-+ - If two dict arguments are passed, they specify globals and
-+ locals, respectively.
-+ """
-+ hint = typing.get_type_hints(obj, globalns=globalns,
localns=localns)
-+ if include_extras:
-+ return hint
-+ return {k: _strip_annotations(t) for k, t in hint.items()}
-+# 3.6
-+else:
-+
-+ def _is_dunder(name):
-+ """Returns True if name is a __dunder_variable_name__."""
-+ return len(name) > 4 and name.startswith('__') and
name.endswith('__')
-+
-+ # Prior to Python 3.7 types did not have `copy_with`. A lot of the
equality
-+ # checks, argument expansion etc. are done on the _subs_tre. As a
result we
-+ # can't provide a get_type_hints function that strips out annotations.
-+
-+ class AnnotatedMeta(typing.GenericMeta):
-+ """Metaclass for Annotated"""
-+
-+ def __new__(cls, name, bases, namespace, **kwargs):
-+ if any(b is not object for b in bases):
-+ raise TypeError("Cannot subclass " + str(Annotated))
-+ return super().__new__(cls, name, bases, namespace, **kwargs)
-+
-+ @property
-+ def __metadata__(self):
-+ return self._subs_tree()[2]
-+
-+ def _tree_repr(self, tree):
-+ cls, origin, metadata = tree
-+ if not isinstance(origin, tuple):
-+ tp_repr = typing._type_repr(origin)
-+ else:
-+ tp_repr = origin[0]._tree_repr(origin)
-+ metadata_reprs = ", ".join(repr(arg) for arg in metadata)
-+ return f'{cls}[{tp_repr}, {metadata_reprs}]'
-+
-+ def _subs_tree(self, tvars=None, args=None): # noqa
-+ if self is Annotated:
-+ return Annotated
-+ res = super()._subs_tree(tvars=tvars, args=args)
-+ # Flatten nested Annotated
-+ if isinstance(res[1], tuple) and res[1][0] is Annotated:
-+ sub_tp = res[1][1]
-+ sub_annot = res[1][2]
-+ return (Annotated, sub_tp, sub_annot + res[2])
-+ return res
-+
-+ def _get_cons(self):
-+ """Return the class used to create instance of this type."""
-+ if self.__origin__ is None:
-+ raise TypeError("Cannot get the underlying type of a "
-+ "non-specialized Annotated type.")
-+ tree = self._subs_tree()
-+ while isinstance(tree, tuple) and tree[0] is Annotated:
-+ tree = tree[1]
-+ if isinstance(tree, tuple):
-+ return tree[0]
-+ else:
-+ return tree
-+
-+ @typing._tp_cache
-+ def __getitem__(self, params):
-+ if not isinstance(params, tuple):
-+ params = (params,)
-+ if self.__origin__ is not None: # specializing an instantiated
type
-+ return super().__getitem__(params)
-+ elif not isinstance(params, tuple) or len(params) < 2:
-+ raise TypeError("Annotated[...] should be instantiated "
-+ "with at least two arguments (a type and an
"
-+ "annotation).")
-+ else:
-+ msg = "Annotated[t, ...]: t must be a type."
-+ tp = typing._type_check(params[0], msg)
-+ metadata = tuple(params[1:])
-+ return self.__class__(
-+ self.__name__,
-+ self.__bases__,
-+ _no_slots_copy(self.__dict__),
-+ tvars=_type_vars((tp,)),
-+ # Metadata is a tuple so it won't be touched by
_replace_args et al.
-+ args=(tp, metadata),
-+ origin=self,
-+ )
-+
-+ def __call__(self, *args, **kwargs):
-+ cons = self._get_cons()
-+ result = cons(*args, **kwargs)
-+ try:
-+ result.__orig_class__ = self
-+ except AttributeError:
-+ pass
-+ return result
-+
-+ def __getattr__(self, attr):
-+ # For simplicity we just don't relay all dunder names
-+ if self.__origin__ is not None and not _is_dunder(attr):
-+ return getattr(self._get_cons(), attr)
-+ raise AttributeError(attr)
-+
-+ def __setattr__(self, attr, value):
-+ if _is_dunder(attr) or attr.startswith('_abc_'):
-+ super().__setattr__(attr, value)
-+ elif self.__origin__ is None:
-+ raise AttributeError(attr)
-+ else:
-+ setattr(self._get_cons(), attr, value)
-+
-+ def __instancecheck__(self, obj):
-+ raise TypeError("Annotated cannot be used with isinstance().")
-+
-+ def __subclasscheck__(self, cls):
-+ raise TypeError("Annotated cannot be used with issubclass().")
-+
-+ class Annotated(metaclass=AnnotatedMeta):
-+ """Add context specific metadata to a type.
-+
-+ Example: Annotated[int, runtime_check.Unsigned] indicates to the
-+ hypothetical runtime_check module that this type is an unsigned int.
-+ Every other consumer of this type can ignore this metadata and treat
-+ this type as int.
-+
-+ The first argument to Annotated must be a valid type, the remaining
-+ arguments are kept as a tuple in the __metadata__ field.
-+
-+ Details:
-+
-+ - It's an error to call `Annotated` with less than two arguments.
-+ - Nested Annotated are flattened::
-+
-+ Annotated[Annotated[T, Ann1, Ann2], Ann3] == Annotated[T, Ann1,
Ann2, Ann3]
-+
-+ - Instantiating an annotated type is equivalent to instantiating the
-+ underlying type::
-+
-+ Annotated[C, Ann1](5) == C(5)
-+
-+ - Annotated can be used as a generic type alias::
-+
-+ Optimized = Annotated[T, runtime.Optimize()]
-+ Optimized[int] == Annotated[int, runtime.Optimize()]
-+
-+ OptimizedList = Annotated[List[T], runtime.Optimize()]
-+ OptimizedList[int] == Annotated[List[int], runtime.Optimize()]
-+ """
-+
-+# Python 3.8 has get_origin() and get_args() but those implementations
aren't
-+# Annotated-aware, so we can't use those. Python 3.9's versions don't
support
-+# ParamSpecArgs and ParamSpecKwargs, so only Python 3.10's versions will do.
-+if sys.version_info[:2] >= (3, 10):
-+ get_origin = typing.get_origin
-+ get_args = typing.get_args
-+# 3.7-3.9
-+elif PEP_560:
-+ try:
-+ # 3.9+
-+ from typing import _BaseGenericAlias
-+ except ImportError:
-+ _BaseGenericAlias = typing._GenericAlias
-+ try:
-+ # 3.9+
-+ from typing import GenericAlias
-+ except ImportError:
-+ GenericAlias = typing._GenericAlias
-+
-+ def get_origin(tp):
-+ """Get the unsubscripted version of a type.
-+
-+ This supports generic types, Callable, Tuple, Union, Literal,
Final, ClassVar
-+ and Annotated. Return None for unsupported types. Examples::
-+
-+ get_origin(Literal[42]) is Literal
-+ get_origin(int) is None
-+ get_origin(ClassVar[int]) is ClassVar
-+ get_origin(Generic) is Generic
-+ get_origin(Generic[T]) is Generic
-+ get_origin(Union[T, int]) is Union
-+ get_origin(List[Tuple[T, T]][int]) == list
-+ get_origin(P.args) is P
-+ """
-+ if isinstance(tp, _AnnotatedAlias):
-+ return Annotated
-+ if isinstance(tp, (typing._GenericAlias, GenericAlias,
_BaseGenericAlias,
-+ ParamSpecArgs, ParamSpecKwargs)):
-+ return tp.__origin__
-+ if tp is typing.Generic:
-+ return typing.Generic
-+ return None
-+
-+ def get_args(tp):
-+ """Get type arguments with all substitutions performed.
-+
-+ For unions, basic simplifications used by Union constructor are
performed.
-+ Examples::
-+ get_args(Dict[str, int]) == (str, int)
-+ get_args(int) == ()
-+ get_args(Union[int, Union[T, int], str][int]) == (int, str)
-+ get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str,
int])
-+ get_args(Callable[[], T][int]) == ([], int)
-+ """
-+ if isinstance(tp, _AnnotatedAlias):
-+ return (tp.__origin__,) + tp.__metadata__
-+ if isinstance(tp, (typing._GenericAlias, GenericAlias)):
-+ if getattr(tp, "_special", False):
-+ return ()
-+ res = tp.__args__
-+ if get_origin(tp) is collections.abc.Callable and res[0] is not
Ellipsis:
-+ res = (list(res[:-1]), res[-1])
-+ return res
-+ return ()
-+
-+
-+# 3.10+
-+if hasattr(typing, 'TypeAlias'):
-+ TypeAlias = typing.TypeAlias
-+# 3.9
-+elif sys.version_info[:2] >= (3, 9):
-+ class _TypeAliasForm(typing._SpecialForm, _root=True):
-+ def __repr__(self):
-+ return 'typing_extensions.' + self._name
-+
-+ @_TypeAliasForm
-+ def TypeAlias(self, parameters):
-+ """Special marker indicating that an assignment should
-+ be recognized as a proper type alias definition by type
-+ checkers.
-+
-+ For example::
-+
-+ Predicate: TypeAlias = Callable[..., bool]
-+
-+ It's invalid when used anywhere except as in the example above.
-+ """
-+ raise TypeError(f"{self} is not subscriptable")
-+# 3.7-3.8
-+elif sys.version_info[:2] >= (3, 7):
-+ class _TypeAliasForm(typing._SpecialForm, _root=True):
-+ def __repr__(self):
-+ return 'typing_extensions.' + self._name
-+
-+ TypeAlias = _TypeAliasForm('TypeAlias',
-+ doc="""Special marker indicating that an
assignment should
-+ be recognized as a proper type alias
definition by type
-+ checkers.
-+
-+ For example::
-+
-+ Predicate: TypeAlias = Callable[...,
bool]
-+
-+ It's invalid when used anywhere except as in
the example
-+ above.""")
-+# 3.6
-+else:
-+ class _TypeAliasMeta(typing.TypingMeta):
-+ """Metaclass for TypeAlias"""
-+
-+ def __repr__(self):
-+ return 'typing_extensions.TypeAlias'
-+
-+ class _TypeAliasBase(typing._FinalTypingBase, metaclass=_TypeAliasMeta,
_root=True):
-+ """Special marker indicating that an assignment should
-+ be recognized as a proper type alias definition by type
-+ checkers.
-+
-+ For example::
-+
-+ Predicate: TypeAlias = Callable[..., bool]
-+
-+ It's invalid when used anywhere except as in the example above.
-+ """
-+ __slots__ = ()
-+
-+ def __instancecheck__(self, obj):
-+ raise TypeError("TypeAlias cannot be used with isinstance().")
-+
-+ def __subclasscheck__(self, cls):
-+ raise TypeError("TypeAlias cannot be used with issubclass().")
-+
-+ def __repr__(self):
-+ return 'typing_extensions.TypeAlias'
-+
-+ TypeAlias = _TypeAliasBase(_root=True)
-+
-+
-+# Python 3.10+ has PEP 612
-+if hasattr(typing, 'ParamSpecArgs'):
-+ ParamSpecArgs = typing.ParamSpecArgs
-+ ParamSpecKwargs = typing.ParamSpecKwargs
-+# 3.6-3.9
-+else:
-+ class _Immutable:
-+ """Mixin to indicate that object should not be copied."""
-+ __slots__ = ()
-+
-+ def __copy__(self):
-+ return self
-+
-+ def __deepcopy__(self, memo):
-+ return self
-+
-+ class ParamSpecArgs(_Immutable):
-+ """The args for a ParamSpec object.
-+
-+ Given a ParamSpec object P, P.args is an instance of ParamSpecArgs.
-+
-+ ParamSpecArgs objects have a reference back to their ParamSpec:
-+
-+ P.args.__origin__ is P
-+
-+ This type is meant for runtime introspection and has no special
meaning to
-+ static type checkers.
-+ """
-+ def __init__(self, origin):
-+ self.__origin__ = origin
-+
-+ def __repr__(self):
-+ return f"{self.__origin__.__name__}.args"
-+
-+ class ParamSpecKwargs(_Immutable):
-+ """The kwargs for a ParamSpec object.
-+
-+ Given a ParamSpec object P, P.kwargs is an instance of
ParamSpecKwargs.
-+
-+ ParamSpecKwargs objects have a reference back to their ParamSpec:
-+
-+ P.kwargs.__origin__ is P
-+
-+ This type is meant for runtime introspection and has no special
meaning to
-+ static type checkers.
-+ """
-+ def __init__(self, origin):
-+ self.__origin__ = origin
-+
-+ def __repr__(self):
-+ return f"{self.__origin__.__name__}.kwargs"
-+
-+# 3.10+
-+if hasattr(typing, 'ParamSpec'):
-+ ParamSpec = typing.ParamSpec
-+# 3.6-3.9
-+else:
-+
-+ # Inherits from list as a workaround for Callable checks in Python <
3.9.2.
-+ class ParamSpec(list):
-+ """Parameter specification variable.
-+
-+ Usage::
-+
-+ P = ParamSpec('P')
-+
-+ Parameter specification variables exist primarily for the benefit
of static
-+ type checkers. They are used to forward the parameter types of one
-+ callable to another callable, a pattern commonly found in higher
order
-+ functions and decorators. They are only valid when used in
``Concatenate``,
-+ or s the first argument to ``Callable``. In Python 3.10 and higher,
-+ they are also supported in user-defined Generics at runtime.
-+ See class Generic for more information on generic types. An
-+ example for annotating a decorator::
-+
-+ T = TypeVar('T')
-+ P = ParamSpec('P')
-+
-+ def add_logging(f: Callable[P, T]) -> Callable[P, T]:
-+ '''A type-safe decorator to add logging to a function.'''
-+ def inner(*args: P.args, **kwargs: P.kwargs) -> T:
-+ logging.info(f'{f.__name__} was called')
-+ return f(*args, **kwargs)
-+ return inner
-+
-+ @add_logging
-+ def add_two(x: float, y: float) -> float:
-+ '''Add two numbers together.'''
-+ return x + y
-+
-+ Parameter specification variables defined with covariant=True or
-+ contravariant=True can be used to declare covariant or contravariant
-+ generic types. These keyword arguments are valid, but their actual
semantics
-+ are yet to be decided. See PEP 612 for details.
-+
-+ Parameter specification variables can be introspected. e.g.:
-+
-+ P.__name__ == 'T'
-+ P.__bound__ == None
-+ P.__covariant__ == False
-+ P.__contravariant__ == False
-+
-+ Note that only parameter specification variables defined in global
scope can
-+ be pickled.
-+ """
-+
-+ # Trick Generic __parameters__.
-+ __class__ = typing.TypeVar
-+
-+ @property
-+ def args(self):
-+ return ParamSpecArgs(self)
-+
-+ @property
-+ def kwargs(self):
-+ return ParamSpecKwargs(self)
-+
-+ def __init__(self, name, *, bound=None, covariant=False,
contravariant=False):
-+ super().__init__([self])
-+ self.__name__ = name
-+ self.__covariant__ = bool(covariant)
-+ self.__contravariant__ = bool(contravariant)
-+ if bound:
-+ self.__bound__ = typing._type_check(bound, 'Bound must be a
type.')
-+ else:
-+ self.__bound__ = None
-+
-+ # for pickling:
-+ try:
-+ def_mod = sys._getframe(1).f_globals.get('__name__',
'__main__')
-+ except (AttributeError, ValueError):
-+ def_mod = None
-+ if def_mod != 'typing_extensions':
-+ self.__module__ = def_mod
-+
-+ def __repr__(self):
-+ if self.__covariant__:
-+ prefix = '+'
-+ elif self.__contravariant__:
-+ prefix = '-'
-+ else:
-+ prefix = '~'
-+ return prefix + self.__name__
-+
-+ def __hash__(self):
-+ return object.__hash__(self)
-+
-+ def __eq__(self, other):
-+ return self is other
-+
-+ def __reduce__(self):
-+ return self.__name__
-+
-+ # Hack to get typing._type_check to pass.
-+ def __call__(self, *args, **kwargs):
-+ pass
-+
-+ if not PEP_560:
-+ # Only needed in 3.6.
-+ def _get_type_vars(self, tvars):
-+ if self not in tvars:
-+ tvars.append(self)
-+
-+
-+# 3.6-3.9
-+if not hasattr(typing, 'Concatenate'):
-+ # Inherits from list as a workaround for Callable checks in Python <
3.9.2.
-+ class _ConcatenateGenericAlias(list):
-+
-+ # Trick Generic into looking into this for __parameters__.
-+ if PEP_560:
-+ __class__ = typing._GenericAlias
-+ else:
-+ __class__ = typing._TypingBase
-+
-+ # Flag in 3.8.
-+ _special = False
-+ # Attribute in 3.6 and earlier.
-+ _gorg = typing.Generic
-+
-+ def __init__(self, origin, args):
-+ super().__init__(args)
-+ self.__origin__ = origin
-+ self.__args__ = args
-+
-+ def __repr__(self):
-+ _type_repr = typing._type_repr
-+ return (f'{_type_repr(self.__origin__)}'
-+ f'[{", ".join(_type_repr(arg) for arg in
self.__args__)}]')
-+
-+ def __hash__(self):
-+ return hash((self.__origin__, self.__args__))
-+
-+ # Hack to get typing._type_check to pass in Generic.
-+ def __call__(self, *args, **kwargs):
-+ pass
-+
-+ @property
-+ def __parameters__(self):
-+ return tuple(
-+ tp for tp in self.__args__ if isinstance(tp,
(typing.TypeVar, ParamSpec))
-+ )
-+
-+ if not PEP_560:
-+ # Only required in 3.6.
-+ def _get_type_vars(self, tvars):
-+ if self.__origin__ and self.__parameters__:
-+ typing._get_type_vars(self.__parameters__, tvars)
-+
-+
-+# 3.6-3.9
-+@typing._tp_cache
-+def _concatenate_getitem(self, parameters):
-+ if parameters == ():
-+ raise TypeError("Cannot take a Concatenate of no types.")
-+ if not isinstance(parameters, tuple):
-+ parameters = (parameters,)
-+ if not isinstance(parameters[-1], ParamSpec):
-+ raise TypeError("The last parameter to Concatenate should be a "
-+ "ParamSpec variable.")
-+ msg = "Concatenate[arg, ...]: each arg must be a type."
-+ parameters = tuple(typing._type_check(p, msg) for p in parameters)
-+ return _ConcatenateGenericAlias(self, parameters)
-+
-+
-+# 3.10+
-+if hasattr(typing, 'Concatenate'):
-+ Concatenate = typing.Concatenate
-+ _ConcatenateGenericAlias = typing._ConcatenateGenericAlias # noqa
-+# 3.9
-+elif sys.version_info[:2] >= (3, 9):
-+ @_TypeAliasForm
-+ def Concatenate(self, parameters):
-+ """Used in conjunction with ``ParamSpec`` and ``Callable`` to
represent a
-+ higher order function which adds, removes or transforms parameters
of a
-+ callable.
-+
-+ For example::
-+
-+ Callable[Concatenate[int, P], int]
-+
-+ See PEP 612 for detailed information.
-+ """
-+ return _concatenate_getitem(self, parameters)
-+# 3.7-8
-+elif sys.version_info[:2] >= (3, 7):
-+ class _ConcatenateForm(typing._SpecialForm, _root=True):
-+ def __repr__(self):
-+ return 'typing_extensions.' + self._name
-+
-+ def __getitem__(self, parameters):
-+ return _concatenate_getitem(self, parameters)
-+
-+ Concatenate = _ConcatenateForm(
-+ 'Concatenate',
-+ doc="""Used in conjunction with ``ParamSpec`` and ``Callable`` to
represent a
-+ higher order function which adds, removes or transforms parameters
of a
-+ callable.
-+
-+ For example::
-+
-+ Callable[Concatenate[int, P], int]
-+
-+ See PEP 612 for detailed information.
-+ """)
-+# 3.6
-+else:
-+ class _ConcatenateAliasMeta(typing.TypingMeta):
-+ """Metaclass for Concatenate."""
-+
-+ def __repr__(self):
-+ return 'typing_extensions.Concatenate'
-+
-+ class _ConcatenateAliasBase(typing._FinalTypingBase,
-+ metaclass=_ConcatenateAliasMeta,
-+ _root=True):
-+ """Used in conjunction with ``ParamSpec`` and ``Callable`` to
represent a
-+ higher order function which adds, removes or transforms parameters
of a
-+ callable.
-+
-+ For example::
-+
-+ Callable[Concatenate[int, P], int]
-+
-+ See PEP 612 for detailed information.
-+ """
-+ __slots__ = ()
-+
-+ def __instancecheck__(self, obj):
-+ raise TypeError("Concatenate cannot be used with isinstance().")
-+
-+ def __subclasscheck__(self, cls):
-+ raise TypeError("Concatenate cannot be used with issubclass().")
-+
-+ def __repr__(self):
-+ return 'typing_extensions.Concatenate'
-+
-+ def __getitem__(self, parameters):
-+ return _concatenate_getitem(self, parameters)
-+
-+ Concatenate = _ConcatenateAliasBase(_root=True)
-+
-+# 3.10+
-+if hasattr(typing, 'TypeGuard'):
-+ TypeGuard = typing.TypeGuard
-+# 3.9
-+elif sys.version_info[:2] >= (3, 9):
-+ class _TypeGuardForm(typing._SpecialForm, _root=True):
-+ def __repr__(self):
-+ return 'typing_extensions.' + self._name
-+
-+ @_TypeGuardForm
-+ def TypeGuard(self, parameters):
-+ """Special typing form used to annotate the return type of a
user-defined
-+ type guard function. ``TypeGuard`` only accepts a single type
argument.
-+ At runtime, functions marked this way should return a boolean.
-+
-+ ``TypeGuard`` aims to benefit *type narrowing* -- a technique used
by static
-+ type checkers to determine a more precise type of an expression
within a
-+ program's code flow. Usually type narrowing is done by analyzing
-+ conditional code flow and applying the narrowing to a block of
code. The
-+ conditional expression here is sometimes referred to as a "type
guard".
-+
-+ Sometimes it would be convenient to use a user-defined boolean
function
-+ as a type guard. Such a function should use ``TypeGuard[...]`` as
its
-+ return type to alert static type checkers to this intention.
-+
-+ Using ``-> TypeGuard`` tells the static type checker that for a
given
-+ function:
-+
-+ 1. The return value is a boolean.
-+ 2. If the return value is ``True``, the type of its argument
-+ is the type inside ``TypeGuard``.
-+
-+ For example::
-+
-+ def is_str(val: Union[str, float]):
-+ # "isinstance" type guard
-+ if isinstance(val, str):
-+ # Type of ``val`` is narrowed to ``str``
-+ ...
-+ else:
-+ # Else, type of ``val`` is narrowed to ``float``.
-+ ...
-+
-+ Strict type narrowing is not enforced -- ``TypeB`` need not be a
narrower
-+ form of ``TypeA`` (it can even be a wider form) and this may lead to
-+ type-unsafe results. The main reason is to allow for things like
-+ narrowing ``List[object]`` to ``List[str]`` even though the latter
is not
-+ a subtype of the former, since ``List`` is invariant. The
responsibility of
-+ writing type-safe type guards is left to the user.
-+
-+ ``TypeGuard`` also works with type variables. For more
information, see
-+ PEP 647 (User-Defined Type Guards).
-+ """
-+ item = typing._type_check(parameters, f'{self} accepts only single
type.')
-+ return typing._GenericAlias(self, (item,))
-+# 3.7-3.8
-+elif sys.version_info[:2] >= (3, 7):
-+ class _TypeGuardForm(typing._SpecialForm, _root=True):
-+
-+ def __repr__(self):
-+ return 'typing_extensions.' + self._name
-+
-+ def __getitem__(self, parameters):
-+ item = typing._type_check(parameters,
-+ f'{self._name} accepts only a single
type')
-+ return typing._GenericAlias(self, (item,))
-+
-+ TypeGuard = _TypeGuardForm(
-+ 'TypeGuard',
-+ doc="""Special typing form used to annotate the return type of a
user-defined
-+ type guard function. ``TypeGuard`` only accepts a single type
argument.
-+ At runtime, functions marked this way should return a boolean.
-+
-+ ``TypeGuard`` aims to benefit *type narrowing* -- a technique used
by static
-+ type checkers to determine a more precise type of an expression
within a
-+ program's code flow. Usually type narrowing is done by analyzing
-+ conditional code flow and applying the narrowing to a block of
code. The
-+ conditional expression here is sometimes referred to as a "type
guard".
-+
-+ Sometimes it would be convenient to use a user-defined boolean
function
-+ as a type guard. Such a function should use ``TypeGuard[...]`` as
its
-+ return type to alert static type checkers to this intention.
-+
-+ Using ``-> TypeGuard`` tells the static type checker that for a
given
-+ function:
-+
-+ 1. The return value is a boolean.
-+ 2. If the return value is ``True``, the type of its argument
-+ is the type inside ``TypeGuard``.
-+
-+ For example::
-+
-+ def is_str(val: Union[str, float]):
-+ # "isinstance" type guard
-+ if isinstance(val, str):
-+ # Type of ``val`` is narrowed to ``str``
-+ ...
-+ else:
-+ # Else, type of ``val`` is narrowed to ``float``.
-+ ...
-+
-+ Strict type narrowing is not enforced -- ``TypeB`` need not be a
narrower
-+ form of ``TypeA`` (it can even be a wider form) and this may lead to
-+ type-unsafe results. The main reason is to allow for things like
-+ narrowing ``List[object]`` to ``List[str]`` even though the latter
is not
-+ a subtype of the former, since ``List`` is invariant. The
responsibility of
-+ writing type-safe type guards is left to the user.
-+
-+ ``TypeGuard`` also works with type variables. For more
information, see
-+ PEP 647 (User-Defined Type Guards).
-+ """)
-+# 3.6
-+else:
-+ class _TypeGuard(typing._FinalTypingBase, _root=True):
-+ """Special typing form used to annotate the return type of a
user-defined
-+ type guard function. ``TypeGuard`` only accepts a single type
argument.
-+ At runtime, functions marked this way should return a boolean.
-+
-+ ``TypeGuard`` aims to benefit *type narrowing* -- a technique used
by static
-+ type checkers to determine a more precise type of an expression
within a
-+ program's code flow. Usually type narrowing is done by analyzing
-+ conditional code flow and applying the narrowing to a block of
code. The
-+ conditional expression here is sometimes referred to as a "type
guard".
-+
-+ Sometimes it would be convenient to use a user-defined boolean
function
-+ as a type guard. Such a function should use ``TypeGuard[...]`` as
its
-+ return type to alert static type checkers to this intention.
-+
-+ Using ``-> TypeGuard`` tells the static type checker that for a
given
-+ function:
-+
-+ 1. The return value is a boolean.
-+ 2. If the return value is ``True``, the type of its argument
-+ is the type inside ``TypeGuard``.
-+
-+ For example::
-+
-+ def is_str(val: Union[str, float]):
-+ # "isinstance" type guard
-+ if isinstance(val, str):
-+ # Type of ``val`` is narrowed to ``str``
-+ ...
-+ else:
-+ # Else, type of ``val`` is narrowed to ``float``.
-+ ...
-+
-+ Strict type narrowing is not enforced -- ``TypeB`` need not be a
narrower
-+ form of ``TypeA`` (it can even be a wider form) and this may lead to
-+ type-unsafe results. The main reason is to allow for things like
-+ narrowing ``List[object]`` to ``List[str]`` even though the latter
is not
-+ a subtype of the former, since ``List`` is invariant. The
responsibility of
-+ writing type-safe type guards is left to the user.
-+
-+ ``TypeGuard`` also works with type variables. For more
information, see
-+ PEP 647 (User-Defined Type Guards).
-+ """
-+
-+ __slots__ = ('__type__',)
-+
-+ def __init__(self, tp=None, **kwds):
-+ self.__type__ = tp
-+
-+ def __getitem__(self, item):
-+ cls = type(self)
-+ if self.__type__ is None:
-+ return cls(typing._type_check(item,
-+ f'{cls.__name__[1:]} accepts only a single
type.'),
-+ _root=True)
-+ raise TypeError(f'{cls.__name__[1:]} cannot be further
subscripted')
-+
-+ def _eval_type(self, globalns, localns):
-+ new_tp = typing._eval_type(self.__type__, globalns, localns)
-+ if new_tp == self.__type__:
-+ return self
-+ return type(self)(new_tp, _root=True)
-+
-+ def __repr__(self):
-+ r = super().__repr__()
-+ if self.__type__ is not None:
-+ r += f'[{typing._type_repr(self.__type__)}]'
-+ return r
-+
-+ def __hash__(self):
-+ return hash((type(self).__name__, self.__type__))
-+
-+ def __eq__(self, other):
-+ if not isinstance(other, _TypeGuard):
-+ return NotImplemented
-+ if self.__type__ is not None:
-+ return self.__type__ == other.__type__
-+ return self is other
-+
-+ TypeGuard = _TypeGuard(_root=True)
-+
-+if hasattr(typing, "Self"):
-+ Self = typing.Self
-+elif sys.version_info[:2] >= (3, 7):
-+ # Vendored from cpython typing._SpecialFrom
-+ class _SpecialForm(typing._Final, _root=True):
-+ __slots__ = ('_name', '__doc__', '_getitem')
-+
-+ def __init__(self, getitem):
-+ self._getitem = getitem
-+ self._name = getitem.__name__
-+ self.__doc__ = getitem.__doc__
-+
-+ def __getattr__(self, item):
-+ if item in {'__name__', '__qualname__'}:
-+ return self._name
-+
-+ raise AttributeError(item)
-+
-+ def __mro_entries__(self, bases):
-+ raise TypeError(f"Cannot subclass {self!r}")
-+
-+ def __repr__(self):
-+ return f'typing_extensions.{self._name}'
-+
-+ def __reduce__(self):
-+ return self._name
-+
-+ def __call__(self, *args, **kwds):
-+ raise TypeError(f"Cannot instantiate {self!r}")
-+
-+ def __or__(self, other):
-+ return typing.Union[self, other]
-+
-+ def __ror__(self, other):
-+ return typing.Union[other, self]
-+
-+ def __instancecheck__(self, obj):
-+ raise TypeError(f"{self} cannot be used with isinstance()")
-+
-+ def __subclasscheck__(self, cls):
-+ raise TypeError(f"{self} cannot be used with issubclass()")
-+
-+ @typing._tp_cache
-+ def __getitem__(self, parameters):
-+ return self._getitem(self, parameters)
-+
-+ @_SpecialForm
-+ def Self(self, params):
-+ """Used to spell the type of "self" in classes.
-+
-+ Example::
-+
-+ from typing import Self
-+
-+ class ReturnsSelf:
-+ def parse(self, data: bytes) -> Self:
-+ ...
-+ return self
-+
-+ """
-+
-+ raise TypeError(f"{self} is not subscriptable")
-+else:
-+ class _Self(typing._FinalTypingBase, _root=True):
-+ """Used to spell the type of "self" in classes.
-+
-+ Example::
-+
-+ from typing import Self
-+
-+ class ReturnsSelf:
-+ def parse(self, data: bytes) -> Self:
-+ ...
-+ return self
-+
-+ """
-+
-+ __slots__ = ()
-+
-+ def __instancecheck__(self, obj):
-+ raise TypeError(f"{self} cannot be used with isinstance().")
-+
-+ def __subclasscheck__(self, cls):
-+ raise TypeError(f"{self} cannot be used with issubclass().")
-+
-+ Self = _Self(_root=True)
-+
-+
-+if hasattr(typing, 'Required'):
-+ Required = typing.Required
-+ NotRequired = typing.NotRequired
-+elif sys.version_info[:2] >= (3, 9):
-+ class _ExtensionsSpecialForm(typing._SpecialForm, _root=True):
-+ def __repr__(self):
-+ return 'typing_extensions.' + self._name
-+
-+ @_ExtensionsSpecialForm
-+ def Required(self, parameters):
-+ """A special typing construct to mark a key of a total=False
TypedDict
-+ as required. For example:
-+
-+ class Movie(TypedDict, total=False):
-+ title: Required[str]
-+ year: int
-+
-+ m = Movie(
-+ title='The Matrix', # typechecker error if key is omitted
-+ year=1999,
-+ )
-+
-+ There is no runtime checking that a required key is actually
provided
-+ when instantiating a related TypedDict.
-+ """
-+ item = typing._type_check(parameters, f'{self._name} accepts only
single type')
-+ return typing._GenericAlias(self, (item,))
-+
-+ @_ExtensionsSpecialForm
-+ def NotRequired(self, parameters):
-+ """A special typing construct to mark a key of a TypedDict as
-+ potentially missing. For example:
-+
-+ class Movie(TypedDict):
-+ title: str
-+ year: NotRequired[int]
-+
-+ m = Movie(
-+ title='The Matrix', # typechecker error if key is omitted
-+ year=1999,
-+ )
-+ """
-+ item = typing._type_check(parameters, f'{self._name} accepts only
single type')
-+ return typing._GenericAlias(self, (item,))
-+
-+elif sys.version_info[:2] >= (3, 7):
-+ class _RequiredForm(typing._SpecialForm, _root=True):
-+ def __repr__(self):
-+ return 'typing_extensions.' + self._name
-+
-+ def __getitem__(self, parameters):
-+ item = typing._type_check(parameters,
-+ '{} accepts only single
type'.format(self._name))
-+ return typing._GenericAlias(self, (item,))
-+
-+ Required = _RequiredForm(
-+ 'Required',
-+ doc="""A special typing construct to mark a key of a total=False
TypedDict
-+ as required. For example:
-+
-+ class Movie(TypedDict, total=False):
-+ title: Required[str]
-+ year: int
-+
-+ m = Movie(
-+ title='The Matrix', # typechecker error if key is omitted
-+ year=1999,
-+ )
-+
-+ There is no runtime checking that a required key is actually
provided
-+ when instantiating a related TypedDict.
-+ """)
-+ NotRequired = _RequiredForm(
-+ 'NotRequired',
-+ doc="""A special typing construct to mark a key of a TypedDict as
-+ potentially missing. For example:
-+
-+ class Movie(TypedDict):
-+ title: str
-+ year: NotRequired[int]
-+
-+ m = Movie(
-+ title='The Matrix', # typechecker error if key is omitted
-+ year=1999,
-+ )
-+ """)
-+else:
-+ # NOTE: Modeled after _Final's implementation when _FinalTypingBase
available
-+ class _MaybeRequired(typing._FinalTypingBase, _root=True):
-+ __slots__ = ('__type__',)
-+
-+ def __init__(self, tp=None, **kwds):
-+ self.__type__ = tp
-+
-+ def __getitem__(self, item):
-+ cls = type(self)
-+ if self.__type__ is None:
-+ return cls(typing._type_check(item,
-+ '{} accepts only single
type.'.format(cls.__name__[1:])),
-+ _root=True)
-+ raise TypeError('{} cannot be further subscripted'
-+ .format(cls.__name__[1:]))
-+
-+ def _eval_type(self, globalns, localns):
-+ new_tp = typing._eval_type(self.__type__, globalns, localns)
-+ if new_tp == self.__type__:
-+ return self
-+ return type(self)(new_tp, _root=True)
-+
-+ def __repr__(self):
-+ r = super().__repr__()
-+ if self.__type__ is not None:
-+ r += '[{}]'.format(typing._type_repr(self.__type__))
-+ return r
-+
-+ def __hash__(self):
-+ return hash((type(self).__name__, self.__type__))
-+
-+ def __eq__(self, other):
-+ if not isinstance(other, type(self)):
-+ return NotImplemented
-+ if self.__type__ is not None:
-+ return self.__type__ == other.__type__
-+ return self is other
-+
-+ class _Required(_MaybeRequired, _root=True):
-+ """A special typing construct to mark a key of a total=False
TypedDict
-+ as required. For example:
-+
-+ class Movie(TypedDict, total=False):
-+ title: Required[str]
-+ year: int
-+
-+ m = Movie(
-+ title='The Matrix', # typechecker error if key is omitted
-+ year=1999,
-+ )
-+
-+ There is no runtime checking that a required key is actually
provided
-+ when instantiating a related TypedDict.
-+ """
-+
-+ class _NotRequired(_MaybeRequired, _root=True):
-+ """A special typing construct to mark a key of a TypedDict as
-+ potentially missing. For example:
-+
-+ class Movie(TypedDict):
-+ title: str
-+ year: NotRequired[int]
-+
-+ m = Movie(
-+ title='The Matrix', # typechecker error if key is omitted
-+ year=1999,
-+ )
-+ """
-+
-+ Required = _Required(_root=True)
-+ NotRequired = _NotRequired(_root=True)
-diff --git a/third_party/python/setuptools/setuptools/_vendor/zipp.py
b/third_party/python/setuptools/setuptools/_vendor/zipp.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/_vendor/zipp.py
-@@ -0,0 +1,329 @@
-+import io
-+import posixpath
-+import zipfile
-+import itertools
-+import contextlib
-+import sys
-+import pathlib
-+
-+if sys.version_info < (3, 7):
-+ from collections import OrderedDict
-+else:
-+ OrderedDict = dict
-+
-+
-+__all__ = ['Path']
-+
-+
-+def _parents(path):
-+ """
-+ Given a path with elements separated by
-+ posixpath.sep, generate all parents of that path.
-+
-+ >>> list(_parents('b/d'))
-+ ['b']
-+ >>> list(_parents('/b/d/'))
-+ ['/b']
-+ >>> list(_parents('b/d/f/'))
-+ ['b/d', 'b']
-+ >>> list(_parents('b'))
-+ []
-+ >>> list(_parents(''))
-+ []
-+ """
-+ return itertools.islice(_ancestry(path), 1, None)
-+
-+
-+def _ancestry(path):
-+ """
-+ Given a path with elements separated by
-+ posixpath.sep, generate all elements of that path
-+
-+ >>> list(_ancestry('b/d'))
-+ ['b/d', 'b']
-+ >>> list(_ancestry('/b/d/'))
-+ ['/b/d', '/b']
-+ >>> list(_ancestry('b/d/f/'))
-+ ['b/d/f', 'b/d', 'b']
-+ >>> list(_ancestry('b'))
-+ ['b']
-+ >>> list(_ancestry(''))
-+ []
-+ """
-+ path = path.rstrip(posixpath.sep)
-+ while path and path != posixpath.sep:
-+ yield path
-+ path, tail = posixpath.split(path)
-+
-+
-+_dedupe = OrderedDict.fromkeys
-+"""Deduplicate an iterable in original order"""
-+
-+
-+def _difference(minuend, subtrahend):
-+ """
-+ Return items in minuend not in subtrahend, retaining order
-+ with O(1) lookup.
-+ """
-+ return itertools.filterfalse(set(subtrahend).__contains__, minuend)
-+
-+
-+class CompleteDirs(zipfile.ZipFile):
-+ """
-+ A ZipFile subclass that ensures that implied directories
-+ are always included in the namelist.
-+ """
-+
-+ @staticmethod
-+ def _implied_dirs(names):
-+ parents = itertools.chain.from_iterable(map(_parents, names))
-+ as_dirs = (p + posixpath.sep for p in parents)
-+ return _dedupe(_difference(as_dirs, names))
-+
-+ def namelist(self):
-+ names = super(CompleteDirs, self).namelist()
-+ return names + list(self._implied_dirs(names))
-+
-+ def _name_set(self):
-+ return set(self.namelist())
-+
-+ def resolve_dir(self, name):
-+ """
-+ If the name represents a directory, return that name
-+ as a directory (with the trailing slash).
-+ """
-+ names = self._name_set()
-+ dirname = name + '/'
-+ dir_match = name not in names and dirname in names
-+ return dirname if dir_match else name
-+
-+ @classmethod
-+ def make(cls, source):
-+ """
-+ Given a source (filename or zipfile), return an
-+ appropriate CompleteDirs subclass.
-+ """
-+ if isinstance(source, CompleteDirs):
-+ return source
-+
-+ if not isinstance(source, zipfile.ZipFile):
-+ return cls(_pathlib_compat(source))
-+
-+ # Only allow for FastLookup when supplied zipfile is read-only
-+ if 'r' not in source.mode:
-+ cls = CompleteDirs
-+
-+ source.__class__ = cls
-+ return source
-+
-+
-+class FastLookup(CompleteDirs):
-+ """
-+ ZipFile subclass to ensure implicit
-+ dirs exist and are resolved rapidly.
-+ """
-+
-+ def namelist(self):
-+ with contextlib.suppress(AttributeError):
-+ return self.__names
-+ self.__names = super(FastLookup, self).namelist()
-+ return self.__names
-+
-+ def _name_set(self):
-+ with contextlib.suppress(AttributeError):
-+ return self.__lookup
-+ self.__lookup = super(FastLookup, self)._name_set()
-+ return self.__lookup
-+
-+
-+def _pathlib_compat(path):
-+ """
-+ For path-like objects, convert to a filename for compatibility
-+ on Python 3.6.1 and earlier.
-+ """
-+ try:
-+ return path.__fspath__()
-+ except AttributeError:
-+ return str(path)
-+
-+
-+class Path:
-+ """
-+ A pathlib-compatible interface for zip files.
-+
-+ Consider a zip file with this structure::
-+
-+ .
-+ ├── a.txt
-+ └── b
-+ ├── c.txt
-+ └── d
-+ └── e.txt
-+
-+ >>> data = io.BytesIO()
-+ >>> zf = zipfile.ZipFile(data, 'w')
-+ >>> zf.writestr('a.txt', 'content of a')
-+ >>> zf.writestr('b/c.txt', 'content of c')
-+ >>> zf.writestr('b/d/e.txt', 'content of e')
-+ >>> zf.filename = 'mem/abcde.zip'
-+
-+ Path accepts the zipfile object itself or a filename
-+
-+ >>> root = Path(zf)
-+
-+ From there, several path operations are available.
-+
-+ Directory iteration (including the zip file itself):
-+
-+ >>> a, b = root.iterdir()
-+ >>> a
-+ Path('mem/abcde.zip', 'a.txt')
-+ >>> b
-+ Path('mem/abcde.zip', 'b/')
-+
-+ name property:
-+
-+ >>> b.name
-+ 'b'
-+
-+ join with divide operator:
-+
-+ >>> c = b / 'c.txt'
-+ >>> c
-+ Path('mem/abcde.zip', 'b/c.txt')
-+ >>> c.name
-+ 'c.txt'
-+
-+ Read text:
-+
-+ >>> c.read_text()
-+ 'content of c'
-+
-+ existence:
-+
-+ >>> c.exists()
-+ True
-+ >>> (b / 'missing.txt').exists()
-+ False
-+
-+ Coercion to string:
-+
-+ >>> import os
-+ >>> str(c).replace(os.sep, posixpath.sep)
-+ 'mem/abcde.zip/b/c.txt'
-+
-+ At the root, ``name``, ``filename``, and ``parent``
-+ resolve to the zipfile. Note these attributes are not
-+ valid and will raise a ``ValueError`` if the zipfile
-+ has no filename.
-+
-+ >>> root.name
-+ 'abcde.zip'
-+ >>> str(root.filename).replace(os.sep, posixpath.sep)
-+ 'mem/abcde.zip'
-+ >>> str(root.parent)
-+ 'mem'
-+ """
-+
-+ __repr = "{self.__class__.__name__}({self.root.filename!r},
{self.at!r})"
-+
-+ def __init__(self, root, at=""):
-+ """
-+ Construct a Path from a ZipFile or filename.
-+
-+ Note: When the source is an existing ZipFile object,
-+ its type (__class__) will be mutated to a
-+ specialized type. If the caller wishes to retain the
-+ original type, the caller should either create a
-+ separate ZipFile object or pass a filename.
-+ """
-+ self.root = FastLookup.make(root)
-+ self.at = at
-+
-+ def open(self, mode='r', *args, pwd=None, **kwargs):
-+ """
-+ Open this entry as text or binary following the semantics
-+ of ``pathlib.Path.open()`` by passing arguments through
-+ to io.TextIOWrapper().
-+ """
-+ if self.is_dir():
-+ raise IsADirectoryError(self)
-+ zip_mode = mode[0]
-+ if not self.exists() and zip_mode == 'r':
-+ raise FileNotFoundError(self)
-+ stream = self.root.open(self.at, zip_mode, pwd=pwd)
-+ if 'b' in mode:
-+ if args or kwargs:
-+ raise ValueError("encoding args invalid for binary
operation")
-+ return stream
-+ return io.TextIOWrapper(stream, *args, **kwargs)
-+
-+ @property
-+ def name(self):
-+ return pathlib.Path(self.at).name or self.filename.name
-+
-+ @property
-+ def suffix(self):
-+ return pathlib.Path(self.at).suffix or self.filename.suffix
-+
-+ @property
-+ def suffixes(self):
-+ return pathlib.Path(self.at).suffixes or self.filename.suffixes
-+
-+ @property
-+ def stem(self):
-+ return pathlib.Path(self.at).stem or self.filename.stem
-+
-+ @property
-+ def filename(self):
-+ return pathlib.Path(self.root.filename).joinpath(self.at)
-+
-+ def read_text(self, *args, **kwargs):
-+ with self.open('r', *args, **kwargs) as strm:
-+ return strm.read()
-+
-+ def read_bytes(self):
-+ with self.open('rb') as strm:
-+ return strm.read()
-+
-+ def _is_child(self, path):
-+ return posixpath.dirname(path.at.rstrip("/")) == self.at.rstrip("/")
-+
-+ def _next(self, at):
-+ return self.__class__(self.root, at)
-+
-+ def is_dir(self):
-+ return not self.at or self.at.endswith("/")
-+
-+ def is_file(self):
-+ return self.exists() and not self.is_dir()
-+
-+ def exists(self):
-+ return self.at in self.root._name_set()
-+
-+ def iterdir(self):
-+ if not self.is_dir():
-+ raise ValueError("Can't listdir a file")
-+ subs = map(self._next, self.root.namelist())
-+ return filter(self._is_child, subs)
-+
-+ def __str__(self):
-+ return posixpath.join(self.root.filename, self.at)
-+
-+ def __repr__(self):
-+ return self.__repr.format(self=self)
-+
-+ def joinpath(self, *other):
-+ next = posixpath.join(self.at, *map(_pathlib_compat, other))
-+ return self._next(self.root.resolve_dir(next))
-+
-+ __truediv__ = joinpath
-+
-+ @property
-+ def parent(self):
-+ if not self.at:
-+ return self.filename.parent
-+ parent_at = posixpath.dirname(self.at.rstrip('/'))
-+ if parent_at:
-+ parent_at += '/'
-+ return self._next(parent_at)
-diff --git a/third_party/python/setuptools/setuptools/archive_util.py
b/third_party/python/setuptools/setuptools/archive_util.py
---- a/third_party/python/setuptools/setuptools/archive_util.py
-+++ b/third_party/python/setuptools/setuptools/archive_util.py
-@@ -3,17 +3,17 @@
- import zipfile
- import tarfile
- import os
- import shutil
- import posixpath
- import contextlib
- from distutils.errors import DistutilsError
-
--from pkg_resources import ensure_directory
-+from ._path import ensure_directory
-
- __all__ = [
- "unpack_archive", "unpack_zipfile", "unpack_tarfile", "default_filter",
- "UnrecognizedFormat", "extraction_drivers", "unpack_directory",
- ]
-
-
- class UnrecognizedFormat(DistutilsError):
-@@ -95,81 +95,119 @@ def unpack_zipfile(filename, extract_dir
- 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
-+ _unpack_zipfile_obj(z, extract_dir, progress_filter)
-+
-+
-+def _unpack_zipfile_obj(zipfile_obj, extract_dir,
progress_filter=default_filter):
-+ """Internal/private API used by other parts of setuptools.
-+ Similar to ``unpack_zipfile``, but receives an already opened
:obj:`zipfile.ZipFile`
-+ object instead of a filename.
-+ """
-+ for info in zipfile_obj.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 = zipfile_obj.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
-
-- target = os.path.join(extract_dir, *name.split('/'))
-- target = progress_filter(name, target)
-- if not target:
-+ prelim_dst = os.path.join(extract_dir, *name.split('/'))
-+
-+ try:
-+ member = _resolve_tar_file_or_dir(tar_obj, member)
-+ except LookupError:
- 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)
-+
-+ 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
-- with contextlib.closing(tarobj):
-- # don't do any chowning!
-- tarobj.chown = lambda *args: None
-- for member in tarobj:
-- name = member.name
-- # don't extract absolute paths or ones with .. in them
-- if not name.startswith('/') and '..' not in name.split('/'):
-- prelim_dst = os.path.join(extract_dir, *name.split('/'))
-
-- # resolve any links and to extract the link targets as
normal
-- # files
-- while member is not None and (
-- member.islnk() or member.issym()):
-- linkpath = member.linkname
-- if member.issym():
-- base = posixpath.dirname(member.name)
-- linkpath = posixpath.join(base, linkpath)
-- linkpath = posixpath.normpath(linkpath)
-- member = tarobj._getmember(linkpath)
-+ 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
-
-- if member is not None and (member.isfile() or
member.isdir()):
-- final_dst = progress_filter(name, prelim_dst)
-- if final_dst:
-- if final_dst.endswith(os.sep):
-- final_dst = final_dst[:-1]
-- try:
-- # XXX Ugh
-- tarobj._extract_member(member, final_dst)
-- except tarfile.ExtractError:
-- # chown/chmod/mkfifo/mknode/makedev failed
-- pass
-- return True
-+ return True
-
-
- extraction_drivers = unpack_directory, unpack_zipfile, unpack_tarfile
-diff --git a/third_party/python/setuptools/setuptools/build_meta.py
b/third_party/python/setuptools/setuptools/build_meta.py
---- a/third_party/python/setuptools/setuptools/build_meta.py
-+++ b/third_party/python/setuptools/setuptools/build_meta.py
-@@ -23,44 +23,58 @@ bug reports or API stability):
- - `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 shlex
- import sys
- import tokenize
- import shutil
- import contextlib
- import tempfile
-+import warnings
-+from pathlib import Path
-+from typing import Dict, Iterator, List, Optional, Union
-
- import setuptools
- import distutils
-+from . import errors
-+from ._path import same_path
-+from ._reqs import parse_strings
-+from .warnings import SetuptoolsDeprecationWarning
-+from distutils.util import strtobool
-
--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',
-+ 'get_requires_for_build_editable',
-+ 'prepare_metadata_for_build_editable',
-+ 'build_editable',
- '__legacy__',
- 'SetupRequirementsError']
-
-+SETUPTOOLS_ENABLE_FEATURES = os.getenv("SETUPTOOLS_ENABLE_FEATURES",
"").lower()
-+LEGACY_EDITABLE = "legacy-editable" in
SETUPTOOLS_ENABLE_FEATURES.replace("_", "-")
-+
-
- 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)))
-+ specifier_list = list(parse_strings(specifiers))
-
- raise SetupRequirementsError(specifier_list)
-
- @classmethod
- @contextlib.contextmanager
- def patch(cls):
- """
- Replace
-@@ -96,136 +110,356 @@ def _get_immediate_subdirectories(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)
- )
-- file, = matching
-+ 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)
-
-
--class _BuildMetaBackend(object):
-+@contextlib.contextmanager
-+def suppress_known_deprecation():
-+ with warnings.catch_warnings():
-+ warnings.filterwarnings('ignore', 'setup.py install is deprecated')
-+ yield
-+
-+
-+_ConfigSettings = Optional[Dict[str, Union[str, List[str], None]]]
-+"""
-+Currently the user can run::
-+
-+ pip install -e . --config-settings key=value
-+ python -m build -C--key=value -C key=value
-+
-+- pip will pass both key and value as strings and overwriting repeated keys
-+ (pypa/pip#11059).
-+- build will accumulate values associated with repeated keys in a list.
-+ It will also accept keys with no associated value.
-+ This means that an option passed by build can be ``str | list[str] |
None``.
-+- PEP 517 specifies that ``config_settings`` is an optional dict.
-+"""
-+
-+
-+class _ConfigSettingsTranslator:
-+ """Translate ``config_settings`` into distutils-style command arguments.
-+ Only a limited number of options is currently supported.
-+ """
-+ # See pypa/setuptools#1928 pypa/setuptools#2491
-+
-+ def _get_config(self, key: str, config_settings: _ConfigSettings) ->
List[str]:
-+ """
-+ Get the value of a specific key in ``config_settings`` as a list of
strings.
-+
-+ >>> fn = _ConfigSettingsTranslator()._get_config
-+ >>> fn("--global-option", None)
-+ []
-+ >>> fn("--global-option", {})
-+ []
-+ >>> fn("--global-option", {'--global-option': 'foo'})
-+ ['foo']
-+ >>> fn("--global-option", {'--global-option': ['foo']})
-+ ['foo']
-+ >>> fn("--global-option", {'--global-option': 'foo'})
-+ ['foo']
-+ >>> fn("--global-option", {'--global-option': 'foo bar'})
-+ ['foo', 'bar']
-+ """
-+ cfg = config_settings or {}
-+ opts = cfg.get(key) or []
-+ return shlex.split(opts) if isinstance(opts, str) else opts
-+
-+ def _valid_global_options(self):
-+ """Global options accepted by setuptools (e.g. quiet or verbose)."""
-+ options = (opt[:2] for opt in
setuptools.dist.Distribution.global_options)
-+ return {flag for long_and_short in options for flag in
long_and_short if flag}
-+
-+ def _global_args(self, config_settings: _ConfigSettings) ->
Iterator[str]:
-+ """
-+ Let the user specify ``verbose`` or ``quiet`` + escape hatch via
-+ ``--global-option``.
-+ Note: ``-v``, ``-vv``, ``-vvv`` have similar effects in setuptools,
-+ so we just have to cover the basic scenario ``-v``.
-+
-+ >>> fn = _ConfigSettingsTranslator()._global_args
-+ >>> list(fn(None))
-+ []
-+ >>> list(fn({"verbose": "False"}))
-+ ['-q']
-+ >>> list(fn({"verbose": "1"}))
-+ ['-v']
-+ >>> list(fn({"--verbose": None}))
-+ ['-v']
-+ >>> list(fn({"verbose": "true", "--global-option": "-q
--no-user-cfg"}))
-+ ['-v', '-q', '--no-user-cfg']
-+ >>> list(fn({"--quiet": None}))
-+ ['-q']
-+ """
-+ cfg = config_settings or {}
-+ falsey = {"false", "no", "0", "off"}
-+ if "verbose" in cfg or "--verbose" in cfg:
-+ level = str(cfg.get("verbose") or cfg.get("--verbose") or "1")
-+ yield ("-q" if level.lower() in falsey else "-v")
-+ if "quiet" in cfg or "--quiet" in cfg:
-+ level = str(cfg.get("quiet") or cfg.get("--quiet") or "1")
-+ yield ("-v" if level.lower() in falsey else "-q")
-+
-+ valid = self._valid_global_options()
-+ args = self._get_config("--global-option", config_settings)
-+ yield from (arg for arg in args if arg.strip("-") in valid)
-
-- def _fix_config(self, config_settings):
-- config_settings = config_settings or {}
-- config_settings.setdefault('--global-option', [])
-- return config_settings
-+ def __dist_info_args(self, config_settings: _ConfigSettings) ->
Iterator[str]:
-+ """
-+ The ``dist_info`` command accepts ``tag-date`` and ``tag-build``.
-+
-+ .. warning::
-+ We cannot use this yet as it requires the ``sdist`` and
``bdist_wheel``
-+ commands run in ``build_sdist`` and ``build_wheel`` to re-use
the egg-info
-+ directory created in ``prepare_metadata_for_build_wheel``.
-+
-+ >>> fn =
_ConfigSettingsTranslator()._ConfigSettingsTranslator__dist_info_args
-+ >>> list(fn(None))
-+ []
-+ >>> list(fn({"tag-date": "False"}))
-+ ['--no-date']
-+ >>> list(fn({"tag-date": None}))
-+ ['--no-date']
-+ >>> list(fn({"tag-date": "true", "tag-build": ".a"}))
-+ ['--tag-date', '--tag-build', '.a']
-+ """
-+ cfg = config_settings or {}
-+ if "tag-date" in cfg:
-+ val = strtobool(str(cfg["tag-date"] or "false"))
-+ yield ("--tag-date" if val else "--no-date")
-+ if "tag-build" in cfg:
-+ yield from ["--tag-build", str(cfg["tag-build"])]
-+
-+ def _editable_args(self, config_settings: _ConfigSettings) ->
Iterator[str]:
-+ """
-+ The ``editable_wheel`` command accepts ``editable-mode=strict``.
-+
-+ >>> fn = _ConfigSettingsTranslator()._editable_args
-+ >>> list(fn(None))
-+ []
-+ >>> list(fn({"editable-mode": "strict"}))
-+ ['--mode', 'strict']
-+ """
-+ cfg = config_settings or {}
-+ mode = cfg.get("editable-mode") or cfg.get("editable_mode")
-+ if not mode:
-+ return
-+ yield from ["--mode", str(mode)]
-+
-+ def _arbitrary_args(self, config_settings: _ConfigSettings) ->
Iterator[str]:
-+ """
-+ Users may expect to pass arbitrary lists of arguments to a command
-+ via "--global-option" (example provided in PEP 517 of a "escape
hatch").
-
-- def _get_build_requires(self, config_settings, requirements):
-- config_settings = self._fix_config(config_settings)
-+ >>> fn = _ConfigSettingsTranslator()._arbitrary_args
-+ >>> list(fn(None))
-+ []
-+ >>> list(fn({}))
-+ []
-+ >>> list(fn({'--build-option': 'foo'}))
-+ ['foo']
-+ >>> list(fn({'--build-option': ['foo']}))
-+ ['foo']
-+ >>> list(fn({'--build-option': 'foo'}))
-+ ['foo']
-+ >>> list(fn({'--build-option': 'foo bar'}))
-+ ['foo', 'bar']
-+ >>> warnings.simplefilter('error', SetuptoolsDeprecationWarning)
-+ >>> list(fn({'--global-option': 'foo'})) # doctest:
+IGNORE_EXCEPTION_DETAIL
-+ Traceback (most recent call last):
-+ SetuptoolsDeprecationWarning: ...arguments given via
`--global-option`...
-+ """
-+ args = self._get_config("--global-option", config_settings)
-+ global_opts = self._valid_global_options()
-+ bad_args = []
-
-- sys.argv = sys.argv[:1] + ['egg_info'] + \
-- config_settings["--global-option"]
-+ for arg in args:
-+ if arg.strip("-") not in global_opts:
-+ bad_args.append(arg)
-+ yield arg
-+
-+ yield from self._get_config("--build-option", config_settings)
-+
-+ if bad_args:
-+ SetuptoolsDeprecationWarning.emit(
-+ "Incompatible `config_settings` passed to build backend.",
-+ f"""
-+ The arguments {bad_args!r} were given via `--global-option`.
-+ Please use `--build-option` instead,
-+ `--global-option` is reserved for flags like `--verbose` or
`--quiet`.
-+ """,
-+ due_date=(2023, 9, 26), # Warning introduced in v64.0.1,
11/Aug/2022.
-+ )
-+
-+
-+class _BuildMetaBackend(_ConfigSettingsTranslator):
-+ def _get_build_requires(self, config_settings, requirements):
-+ sys.argv = [
-+ *sys.argv[:1],
-+ *self._global_args(config_settings),
-+ "egg_info",
-+ *self._arbitrary_args(config_settings),
-+ ]
- 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
-+ __file__ = os.path.abspath(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())
-+ exec(code, 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'])
-+ 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 _bubble_up_info_directory(self, metadata_directory: str, suffix:
str) -> str:
-+ """
-+ PEP 517 requires that the .dist-info directory be placed in the
-+ metadata_directory. To comply, we MUST copy the directory to the
root.
-+
-+ Returns the basename of the info directory, e.g.
`proj-0.0.0.dist-info`.
-+ """
-+ info_dir = self._find_info_directory(metadata_directory, suffix)
-+ if not same_path(info_dir.parent, metadata_directory):
-+ shutil.move(str(info_dir), metadata_directory)
-+ # PEP 517 allow other files and dirs to exist in
metadata_directory
-+ return info_dir.name
-+
-+ def _find_info_directory(self, metadata_directory: str, suffix: str) ->
Path:
-+ for parent, dirs, _ in os.walk(metadata_directory):
-+ candidates = [f for f in dirs if f.endswith(suffix)]
-+
-+ if len(candidates) != 0 or len(dirs) != 1:
-+ assert len(candidates) == 1, f"Multiple {suffix}
directories found"
-+ return Path(parent, candidates[0])
-+
-+ msg = f"No {suffix} directory found in {metadata_directory}"
-+ raise errors.InternalError(msg)
-+
- def prepare_metadata_for_build_wheel(self, metadata_directory,
- config_settings=None):
-- sys.argv = sys.argv[:1] + [
-- 'dist_info', '--egg-base', metadata_directory]
-+ sys.argv = [
-+ *sys.argv[:1],
-+ *self._global_args(config_settings),
-+ "dist_info",
-+ "--output-dir", metadata_directory,
-+ "--keep-egg-info",
-+ ]
- 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]
-+ self._bubble_up_info_directory(metadata_directory, ".egg-info")
-+ return self._bubble_up_info_directory(metadata_directory,
".dist-info")
-
- 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"])
-+ temp_opts = {"prefix": ".tmp-", "dir": result_directory}
-+ with tempfile.TemporaryDirectory(**temp_opts) as tmp_dist_dir:
-+ sys.argv = [
-+ *sys.argv[:1],
-+ *self._global_args(config_settings),
-+ *setup_command,
-+ "--dist-dir", tmp_dist_dir,
-+ *self._arbitrary_args(config_settings),
-+ ]
- 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):
-- return self._build_with_temp_dir(['bdist_wheel'], '.whl',
-- wheel_directory, config_settings)
-+ 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)
-
-+ def _get_dist_info_dir(self, metadata_directory: Optional[str]) ->
Optional[str]:
-+ if not metadata_directory:
-+ return None
-+ dist_info_candidates =
list(Path(metadata_directory).glob("*.dist-info"))
-+ assert len(dist_info_candidates) <= 1
-+ return str(dist_info_candidates[0]) if dist_info_candidates else
None
-+
-+ if not LEGACY_EDITABLE:
-+
-+ # PEP660 hooks:
-+ # build_editable
-+ # get_requires_for_build_editable
-+ # prepare_metadata_for_build_editable
-+ def build_editable(
-+ self, wheel_directory, config_settings=None,
metadata_directory=None
-+ ):
-+ # XXX can or should we hide our editable_wheel command normally?
-+ info_dir = self._get_dist_info_dir(metadata_directory)
-+ opts = ["--dist-info-dir", info_dir] if info_dir else []
-+ cmd = ["editable_wheel", *opts,
*self._editable_args(config_settings)]
-+ with suppress_known_deprecation():
-+ return self._build_with_temp_dir(
-+ cmd, ".whl", wheel_directory, config_settings
-+ )
-+
-+ def get_requires_for_build_editable(self, config_settings=None):
-+ return self.get_requires_for_build_wheel(config_settings)
-+
-+ def prepare_metadata_for_build_editable(self, metadata_directory,
-+ config_settings=None):
-+ return self.prepare_metadata_for_build_wheel(
-+ metadata_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
-@@ -266,11 +500,16 @@ class _BuildMetaLegacyBackend(_BuildMeta
- _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
-
-+if not LEGACY_EDITABLE:
-+ get_requires_for_build_editable =
_BACKEND.get_requires_for_build_editable
-+ prepare_metadata_for_build_editable =
_BACKEND.prepare_metadata_for_build_editable
-+ build_editable = _BACKEND.build_editable
-+
-
- # The legacy backend
- __legacy__ = _BuildMetaLegacyBackend()
-diff --git a/third_party/python/setuptools/setuptools/command/__init__.py
b/third_party/python/setuptools/setuptools/command/__init__.py
---- a/third_party/python/setuptools/setuptools/command/__init__.py
-+++ b/third_party/python/setuptools/setuptools/command/__init__.py
-@@ -1,17 +1,12 @@
--__all__ = [
-- 'alias', 'bdist_egg', 'bdist_rpm', 'build_ext', 'build_py', 'develop',
-- 'easy_install', 'egg_info', 'install', 'install_lib', 'rotate',
'saveopts',
-- 'sdist', 'setopt', 'test', 'install_egg_info', 'install_scripts',
-- 'bdist_wininst', 'upload_docs', 'build_clib', 'dist_info',
--]
--
- from distutils.command.bdist import bdist
- import sys
-
--from setuptools.command import install_scripts
--
- if 'egg' not in bdist.format_commands:
-- bdist.format_command['egg'] = ('bdist_egg', "Python .egg file")
-- bdist.format_commands.append('egg')
-+ try:
-+ bdist.format_commands['egg'] = ('bdist_egg', "Python .egg file")
-+ except TypeError:
-+ # For backward compatibility with older distutils (stdlib)
-+ bdist.format_command['egg'] = ('bdist_egg', "Python .egg file")
-+ bdist.format_commands.append('egg')
-
- del bdist, sys
-diff --git a/third_party/python/setuptools/setuptools/command/bdist_egg.py
b/third_party/python/setuptools/setuptools/command/bdist_egg.py
---- a/third_party/python/setuptools/setuptools/command/bdist_egg.py
-+++ b/third_party/python/setuptools/setuptools/command/bdist_egg.py
-@@ -1,27 +1,24 @@
- """setuptools.command.bdist_egg
-
- Build .egg distributions"""
-
--from distutils.errors import DistutilsSetupError
- 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
--import warnings
-
--from pkg_resources import get_build_platform, Distribution, ensure_directory
--from pkg_resources import EntryPoint
- from setuptools.extension import Library
--from setuptools import Command, SetuptoolsDeprecationWarning
-+from setuptools import Command
-+from .._path import ensure_directory
-
- from sysconfig import get_path, get_python_version
-
-
- def _get_purelib():
- return get_path("purelib")
-
-
-@@ -61,17 +58,17 @@ def write_stub(resource, pyfile):
-
- 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()),
-+ "(by default uses
`pkg_resources.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,
-@@ -95,28 +92,28 @@ class bdist_egg(Command):
- 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:
-+ from pkg_resources import get_build_platform
-+
- 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()
-+ basename = ei_cmd._get_egg_basename(
-+ py_version=get_python_version(),
-+ platform=self.distribution.has_ext_modules() and
self.plat_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()))
-@@ -148,17 +145,17 @@ class bdist_egg(Command):
- 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):
-+ 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
-@@ -263,59 +260,17 @@ class bdist_egg(Command):
- 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):
-- epm = EntryPoint.parse_map(self.distribution.entry_points or '')
-- ep = epm.get('setuptools.installation', {}).get('eggsecutable')
-- if ep is None:
-- return 'w' # not an eggsecutable, do it the usual way.
--
-- warnings.warn(
-- "Eggsecutables are deprecated and will be removed in a future "
-- "version.",
-- SetuptoolsDeprecationWarning
-- )
--
-- if not ep.attrs or ep.extras:
-- raise DistutilsSetupError(
-- "eggsecutable entry point (%r) cannot have 'extras' "
-- "or refer to a module" % (ep,)
-- )
--
-- pyver = '{}.{}'.format(*sys.version_info)
-- pkg = ep.module_name
-- full = '.'.join(ep.attrs)
-- base = ep.attrs[0]
-- basename = os.path.basename(self.egg_output)
--
-- header = (
-- "#!/bin/sh\n"
-- 'if [ `basename $0` = "%(basename)s" ]\n'
-- 'then exec python%(pyver)s -c "'
-- "import sys, os; sys.path.insert(0, os.path.abspath('$0')); "
-- "from %(pkg)s import %(base)s; sys.exit(%(full)s())"
-- '" "$@"\n'
-- 'else\n'
-- ' echo $0 is not the correct name for this egg file.\n'
-- ' echo Please rename it back to %(basename)s and try again.\n'
-- ' exec false\n'
-- 'fi\n'
-- ) % locals()
--
-- if not self.dry_run:
-- mkpath(os.path.dirname(self.egg_output), dry_run=self.dry_run)
-- f = open(self.egg_output, 'w')
-- f.write(header)
-- f.close()
-- return 'a'
-+ 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:
-diff --git a/third_party/python/setuptools/setuptools/command/bdist_rpm.py
b/third_party/python/setuptools/setuptools/command/bdist_rpm.py
---- a/third_party/python/setuptools/setuptools/command/bdist_rpm.py
-+++ b/third_party/python/setuptools/setuptools/command/bdist_rpm.py
-@@ -1,21 +1,33 @@
- import distutils.command.bdist_rpm as orig
-
-+from ..warnings 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):
-+ SetuptoolsDeprecationWarning.emit(
-+ "Deprecated command",
-+ """
-+ bdist_rpm is deprecated and will be removed in a future version.
-+ Use bdist_wheel (wheel packages) instead.
-+ """,
-+ see_url="https://github.com/pypa/setuptools/issues/1988";,
-+ due_date=(2023, 10, 30) # Deprecation introduced in 22 Oct
2021.
-+ )
-+
- # 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 = [
-diff --git
a/third_party/python/setuptools/setuptools/command/bdist_wininst.py
b/third_party/python/setuptools/setuptools/command/bdist_wininst.py
-deleted file mode 100644
---- a/third_party/python/setuptools/setuptools/command/bdist_wininst.py
-+++ /dev/null
-@@ -1,30 +0,0 @@
--import distutils.command.bdist_wininst as orig
--import warnings
--
--from setuptools import SetuptoolsDeprecationWarning
--
--
--class bdist_wininst(orig.bdist_wininst):
-- def reinitialize_command(self, command, reinit_subcommands=0):
-- """
-- Supplement reinitialize_command to work around
-- http://bugs.python.org/issue20819
-- """
-- cmd = self.distribution.reinitialize_command(
-- command, reinit_subcommands)
-- if command in ('install', 'install_lib'):
-- cmd.install_lib = None
-- return cmd
--
-- def run(self):
-- warnings.warn(
-- "bdist_wininst is deprecated and will be removed in a future "
-- "version. Use bdist_wheel (wheel packages) instead.",
-- SetuptoolsDeprecationWarning
-- )
--
-- self._is_running = True
-- try:
-- orig.bdist_wininst.run(self)
-- finally:
-- self._is_running = False
-diff --git a/third_party/python/setuptools/setuptools/command/build.py
b/third_party/python/setuptools/setuptools/command/build.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/command/build.py
-@@ -0,0 +1,149 @@
-+import sys
-+from typing import TYPE_CHECKING, List, Dict
-+from distutils.command.build import build as _build
-+
-+from ..warnings import SetuptoolsDeprecationWarning
-+
-+if sys.version_info >= (3, 8):
-+ from typing import Protocol
-+elif TYPE_CHECKING:
-+ from typing_extensions import Protocol
-+else:
-+ from abc import ABC as Protocol
-+
-+
-+_ORIGINAL_SUBCOMMANDS = {"build_py", "build_clib", "build_ext",
"build_scripts"}
-+
-+
-+class build(_build):
-+ # copy to avoid sharing the object with parent class
-+ sub_commands = _build.sub_commands[:]
-+
-+ def get_sub_commands(self):
-+ subcommands = {cmd[0] for cmd in _build.sub_commands}
-+ if subcommands - _ORIGINAL_SUBCOMMANDS:
-+ SetuptoolsDeprecationWarning.emit(
-+ "Direct usage of `distutils` commands",
-+ """
-+ It seems that you are using `distutils.command.build` to add
-+ new subcommands. Using `distutils` directly is considered
deprecated,
-+ please use `setuptools.command.build`.
-+ """,
-+ due_date=(2023, 12, 13), # Warning introduced in 13 Jun
2022.
-+ see_url="https://peps.python.org/pep-0632/";,
-+ )
-+ self.sub_commands = _build.sub_commands
-+ return super().get_sub_commands()
-+
-+
-+class SubCommand(Protocol):
-+ """In order to support editable installations (see :pep:`660`) all
-+ build subcommands **SHOULD** implement this protocol. They also
**MUST** inherit
-+ from ``setuptools.Command``.
-+
-+ When creating an :pep:`editable wheel <660>`, ``setuptools`` will try
to evaluate
-+ custom ``build`` subcommands using the following procedure:
-+
-+ 1. ``setuptools`` will set the ``editable_mode`` attribute to ``True``
-+ 2. ``setuptools`` will execute the ``run()`` command.
-+
-+ .. important::
-+ Subcommands **SHOULD** take advantage of ``editable_mode=True``
to adequate
-+ its behaviour or perform optimisations.
-+
-+ For example, if a subcommand doesn't need to generate an extra
file and
-+ all it does is to copy a source file into the build directory,
-+ ``run()`` **SHOULD** simply "early return".
-+
-+ Similarly, if the subcommand creates files that would be placed
alongside
-+ Python files in the final distribution, during an editable install
-+ the command **SHOULD** generate these files "in place" (i.e.
write them to
-+ the original source directory, instead of using the build
directory).
-+ Note that ``get_output_mapping()`` should reflect that and
include mappings
-+ for "in place" builds accordingly.
-+
-+ 3. ``setuptools`` use any knowledge it can derive from the return
values of
-+ ``get_outputs()`` and ``get_output_mapping()`` to create an editable
wheel.
-+ When relevant ``setuptools`` **MAY** attempt to use file links based
on the value
-+ of ``get_output_mapping()``. Alternatively, ``setuptools`` **MAY**
attempt to use
-+ :doc:`import hooks <python:reference/import>` to redirect any
attempt to import
-+ to the directory with the original source code and other files built
in place.
-+
-+ Please note that custom sub-commands **SHOULD NOT** rely on ``run()``
being
-+ executed (or not) to provide correct return values for
``get_outputs()``,
-+ ``get_output_mapping()`` or ``get_source_files()``. The ``get_*``
methods should
-+ work independently of ``run()``.
-+ """
-+
-+ editable_mode: bool = False
-+ """Boolean flag that will be set to ``True`` when setuptools is used
for an
-+ editable installation (see :pep:`660`).
-+ Implementations **SHOULD** explicitly set the default value of this
attribute to
-+ ``False``.
-+ When subcommands run, they can use this flag to perform optimizations
or change
-+ their behaviour accordingly.
-+ """
-+
-+ build_lib: str
-+ """String representing the directory where the build artifacts should
be stored,
-+ e.g. ``build/lib``.
-+ For example, if a distribution wants to provide a Python module named
``pkg.mod``,
-+ then a corresponding file should be written to
``{build_lib}/package/module.py``.
-+ A way of thinking about this is that the files saved under ``build_lib``
-+ would be eventually copied to one of the directories in
:obj:`site.PREFIXES`
-+ upon installation.
-+
-+ A command that produces platform-independent files (e.g. compiling text
templates
-+ into Python functions), **CAN** initialize ``build_lib`` by copying its
value from
-+ the ``build_py`` command. On the other hand, a command that produces
-+ platform-specific files **CAN** initialize ``build_lib`` by copying its
value from
-+ the ``build_ext`` command. In general this is done inside the
``finalize_options``
-+ method with the help of the ``set_undefined_options`` command::
-+
-+ def finalize_options(self):
-+ self.set_undefined_options("build_py", ("build_lib",
"build_lib"))
-+ ...
-+ """
-+
-+ def initialize_options(self):
-+ """(Required by the original :class:`setuptools.Command`
interface)"""
-+
-+ def finalize_options(self):
-+ """(Required by the original :class:`setuptools.Command`
interface)"""
-+
-+ def run(self):
-+ """(Required by the original :class:`setuptools.Command`
interface)"""
-+
-+ def get_source_files(self) -> List[str]:
-+ """
-+ Return a list of all files that are used by the command to create
the expected
-+ outputs.
-+ For example, if your build command transpiles Java files into
Python, you should
-+ list here all the Java files.
-+ The primary purpose of this function is to help populating the
``sdist``
-+ with all the files necessary to build the distribution.
-+ All files should be strings relative to the project root directory.
-+ """
-+
-+ def get_outputs(self) -> List[str]:
-+ """
-+ Return a list of files intended for distribution as they would have
been
-+ produced by the build.
-+ These files should be strings in the form of
-+ ``"{build_lib}/destination/file/path"``.
-+
-+ .. note::
-+ The return value of ``get_output()`` should include all files
used as keys
-+ in ``get_output_mapping()`` plus files that are generated during
the build
-+ and don't correspond to any source file already present in the
project.
-+ """
-+
-+ def get_output_mapping(self) -> Dict[str, str]:
-+ """
-+ Return a mapping between destination files as they would be
produced by the
-+ build (dict keys) into the respective existing (source) files (dict
values).
-+ Existing (source) files should be represented as strings relative
to the project
-+ root directory.
-+ Destination files should be strings in the form of
-+ ``"{build_lib}/destination/file/path"``.
-+ """
-diff --git a/third_party/python/setuptools/setuptools/command/build_clib.py
b/third_party/python/setuptools/setuptools/command/build_clib.py
---- a/third_party/python/setuptools/setuptools/command/build_clib.py
-+++ b/third_party/python/setuptools/setuptools/command/build_clib.py
-@@ -23,17 +23,17 @@ class build_clib(orig.build_clib):
- 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)
-+ sources = sorted(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):
-diff --git a/third_party/python/setuptools/setuptools/command/build_ext.py
b/third_party/python/setuptools/setuptools/command/build_ext.py
---- a/third_party/python/setuptools/setuptools/command/build_ext.py
-+++ b/third_party/python/setuptools/setuptools/command/build_ext.py
-@@ -1,20 +1,22 @@
- import os
- import sys
- import itertools
- from importlib.machinery import EXTENSION_SUFFIXES
-+from importlib.util import cache_from_source as _compiled_file_name
-+from typing import Dict, Iterator, List, Tuple
-+
- 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
-+from setuptools.errors import BaseError
-+from setuptools.extension import Extension, 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:
-@@ -68,68 +70,113 @@ def get_abi3_suffix():
- for suffix in EXTENSION_SUFFIXES:
- if '.abi3' in suffix: # Unix
- return suffix
- elif suffix == '.pyd': # Windows
- return suffix
-
-
- class build_ext(_build_ext):
-+ editable_mode: bool = False
-+ inplace: bool = False
-+
- 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 _get_inplace_equivalent(self, build_py, ext: Extension) ->
Tuple[str, str]:
-+ 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)
-+ inplace_file = os.path.join(package_dir, os.path.basename(filename))
-+ regular_file = os.path.join(self.build_lib, filename)
-+ return (inplace_file, regular_file)
-+
- 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)
-+ inplace_file, regular_file =
self._get_inplace_equivalent(build_py, ext)
-
- # 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 os.path.exists(regular_file) or not ext.optional:
-+ self.copy_file(regular_file, inplace_file,
level=self.verbose)
-+
- if ext._needs_stub:
-- self.write_stub(package_dir or os.curdir, ext, True)
-+ inplace_stub = self._get_equivalent_stub(ext, inplace_file)
-+ self._write_stub_file(inplace_stub, ext, compile=True)
-+ # Always compile stub and remove the original (leave the
cache behind)
-+ # (this behaviour was observed in previous iterations of
the code)
-+
-+ def _get_equivalent_stub(self, ext: Extension, output_file: str) -> str:
-+ dir_ = os.path.dirname(output_file)
-+ _, _, name = ext.name.rpartition(".")
-+ return f"{os.path.join(dir_, name)}.py"
-+
-+ def _get_output_mapping(self) -> Iterator[Tuple[str, str]]:
-+ if not self.inplace:
-+ return
-+
-+ build_py = self.get_finalized_command('build_py')
-+ opt = self.get_finalized_command('install_lib').optimize or ""
-+
-+ for ext in self.extensions:
-+ inplace_file, regular_file =
self._get_inplace_equivalent(build_py, ext)
-+ yield (regular_file, inplace_file)
-+
-+ if ext._needs_stub:
-+ # This version of `build_ext` always builds artifacts in
another dir,
-+ # when "inplace=True" is given it just copies them back.
-+ # This is done in the `copy_extensions_to_source` function,
which
-+ # always compile stub files via `_compile_and_remove_stub`.
-+ # At the end of the process, a `.pyc` stub file is created
without the
-+ # corresponding `.py`.
-+
-+ inplace_stub = self._get_equivalent_stub(ext, inplace_file)
-+ regular_stub = self._get_equivalent_stub(ext, regular_file)
-+ inplace_cache = _compiled_file_name(inplace_stub,
optimization=opt)
-+ output_cache = _compiled_file_name(regular_stub,
optimization=opt)
-+ yield (output_cache, inplace_cache)
-
- def get_ext_filename(self, fullname):
-- filename = _build_ext.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:
-- so_ext = get_config_var('EXT_SUFFIX')
- filename = filename[:-len(so_ext)]
-- filename = filename + get_abi3_suffix()
-+ 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 = {}
-+ self.editable_mode = False
-
- 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:
-@@ -150,16 +197,19 @@ class build_ext(_build_ext):
- 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)
-
-+ if self.editable_mode:
-+ self.inplace = True
-+
- 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)
-@@ -190,32 +240,39 @@ class build_ext(_build_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)
-+ build_lib = self.get_finalized_command('build_py').build_lib
-+ self.write_stub(build_lib, 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_outputs(self) -> List[str]:
-+ if self.inplace:
-+ return list(self.get_output_mapping().keys())
-+ return sorted(_build_ext.get_outputs(self) +
self.__get_stubs_outputs())
-+
-+ def get_output_mapping(self) -> Dict[str, str]:
-+ """See :class:`setuptools.commands.build.SubCommand`"""
-+ mapping = self._get_output_mapping()
-+ return dict(sorted(mapping, key=lambda x: x[0]))
-
- 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
- )
-@@ -225,22 +282,23 @@ class build_ext(_build_ext):
-
- 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')
-+ stub_file = os.path.join(output_dir, *ext._full_name.split('.')) +
'.py'
-+ self._write_stub_file(stub_file, ext, compile)
-+
-+ def _write_stub_file(self, stub_file: str, ext: Extension,
compile=False):
-+ log.info("writing stub loader for %s to %s", ext._full_name,
stub_file)
- if compile and os.path.exists(stub_file):
-- raise DistutilsError(stub_file + " already exists! Please
delete.")
-+ raise BaseError(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"),
-@@ -263,26 +321,29 @@ class build_ext(_build_ext):
- if_dl(" sys.setdlopenflags(old_flags)"),
- " os.chdir(old_dir)",
- "__bootstrap__()",
- "" # terminal \n
- ])
- )
- f.close()
- if compile:
-- from distutils.util import byte_compile
-+ self._compile_and_remove_stub(stub_file)
-+
-+ def _compile_and_remove_stub(self, stub_file: str):
-+ from distutils.util import byte_compile
-
-- byte_compile([stub_file], optimize=0,
-+ 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)
-- 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 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,
-diff --git a/third_party/python/setuptools/setuptools/command/build_py.py
b/third_party/python/setuptools/setuptools/command/build_py.py
---- a/third_party/python/setuptools/setuptools/command/build_py.py
-+++ b/third_party/python/setuptools/setuptools/command/build_py.py
-@@ -1,91 +1,103 @@
-+from functools import partial
- 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 pathlib import Path
-+from typing import Dict, Iterable, Iterator, List, Optional, Tuple
-
--try:
-- from setuptools.lib2to3_ex import Mixin2to3
--except Exception:
--
-- class Mixin2to3:
-- def run_2to3(self, files, doctests=True):
-- "do nothing"
-+from ..extern.more_itertools import unique_everseen
-+from ..warnings import SetuptoolsDeprecationWarning
-
-
- def make_writable(target):
- os.chmod(target, os.stat(target).st_mode | stat.S_IWRITE)
-
-
--class build_py(orig.build_py, Mixin2to3):
-+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.
- """
-+ editable_mode: bool = False
-+ existing_egg_info_dir: Optional[str] = None #: Private API, internal
use only.
-
- 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
-- {})
-+ self.exclude_package_data = self.distribution.exclude_package_data
or {}
- if 'data_files' in self.__dict__:
- del self.__dict__['data_files']
- self.__updated_files = []
-- self.__doctests_2to3 = []
-+
-+ def copy_file(self, infile, outfile, preserve_mode=1, preserve_times=1,
-+ link=None, level=1):
-+ # Overwrite base class to allow using links
-+ if link:
-+ infile = str(Path(infile).resolve())
-+ outfile = str(Path(outfile).resolve())
-+ return super().copy_file(infile, outfile, preserve_mode,
preserve_times,
-+ link, level)
-
- def run(self):
- """Build modules, packages, and copy data files to build
directory"""
-- if not self.py_modules and not self.packages:
-+ if not (self.py_modules or self.packages) or self.editable_mode:
- return
-
- if self.py_modules:
- self.build_modules()
-
- if self.packages:
- self.build_packages()
- self.build_package_data()
-
-- self.run_2to3(self.__updated_files, False)
-- self.run_2to3(self.__updated_files, True)
-- self.run_2to3(self.__doctests_2to3, True)
--
- # 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)
-+ 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
-@@ -97,64 +109,122 @@ class build_py(orig.build_py, Mixin2to3)
-
- 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)
-+ globs_expanded = map(partial(glob, recursive=True), 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"""
-+ def get_outputs(self, include_bytecode=1) -> List[str]:
-+ """See :class:`setuptools.commands.build.SubCommand`"""
-+ if self.editable_mode:
-+ return list(self.get_output_mapping().keys())
-+ return super().get_outputs(include_bytecode)
-+
-+ def get_output_mapping(self) -> Dict[str, str]:
-+ """See :class:`setuptools.commands.build.SubCommand`"""
-+ mapping = itertools.chain(
-+ self._get_package_data_output_mapping(),
-+ self._get_module_mapping(),
-+ )
-+ return dict(sorted(mapping, key=lambda x: x[0]))
-+
-+ def _get_module_mapping(self) -> Iterator[Tuple[str, str]]:
-+ """Iterate over all modules producing (dest, src) pairs."""
-+ for (package, module, module_file) in self.find_all_modules():
-+ package = package.split('.')
-+ filename = self.get_module_outfile(self.build_lib, package,
module)
-+ yield (filename, module_file)
-+
-+ def _get_package_data_output_mapping(self) -> Iterator[Tuple[str, str]]:
-+ """Iterate over package data producing (dest, src) pairs."""
- 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)
-- if (copied and
-- srcfile in self.distribution.convert_2to3_doctests):
-- self.__doctests_2to3.append(outf)
-+ yield (target, srcfile)
-+
-+ def build_package_data(self):
-+ """Copy data files into build directory"""
-+ for target, srcfile in self._get_package_data_output_mapping():
-+ self.mkpath(os.path.dirname(target))
-+ _outf, _copied = self.copy_file(srcfile, target)
-+ make_writable(target)
-
- 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:
-+ if (
-+ getattr(self, 'existing_egg_info_dir', None)
-+ and Path(self.existing_egg_info_dir, "SOURCES.txt").exists()
-+ ):
-+ egg_info_dir = self.existing_egg_info_dir
-+ manifest = Path(egg_info_dir, "SOURCES.txt")
-+ files = manifest.read_text(encoding="utf-8").splitlines()
-+ else:
-+ self.run_command('egg_info')
-+ ei_cmd = self.get_finalized_command('egg_info')
-+ egg_info_dir = ei_cmd.egg_info
-+ files = ei_cmd.filelist.files
-+
-+ check = _IncludePackageDataAbuse()
-+ for path in self._filter_build_files(files, egg_info_dir):
- 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
-+ if f == oldf:
-+ if check.is_module(f):
-+ continue # it's a module, not data
-+ else:
-+ importable = check.importable_subpackage(src_dirs[d], f)
-+ if importable:
-+ check.warn(importable)
- mf.setdefault(src_dirs[d], []).append(path)
-
-+ def _filter_build_files(self, files: Iterable[str], egg_info: str) ->
Iterator[str]:
-+ """
-+ ``build_meta`` may try to create egg_info outside of the project
directory,
-+ and this can be problematic for certain plugins (reported in issue
#3500).
-+
-+ Extensions might also include between their sources files created
on the
-+ ``build_lib`` and ``build_temp`` directories.
-+
-+ This function should filter this case of invalid files out.
-+ """
-+ build = self.get_finalized_command("build")
-+ build_dirs = (egg_info, self.build_lib, build.build_temp,
build.build_base)
-+ norm_dirs = [os.path.normpath(p) for p in build_dirs if p]
-+
-+ for file in files:
-+ norm_path = os.path.normpath(file)
-+ if not os.path.isabs(file) or all(d not in norm_path for d in
norm_dirs):
-+ yield file
-+
- 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:
-@@ -181,45 +251,40 @@ class build_py(orig.build_py, Mixin2to3)
- '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)
-+ self.editable_mode = False
-+ self.existing_egg_info_dir = None
-
- 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
-- )
-+ 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
-- )
-+ keepers = (fn for fn in files if fn not in bad)
- # ditch dupes
-- return list(_unique_everseen(keepers))
-+ 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.
-@@ -230,41 +295,92 @@ class build_py(orig.build_py, Mixin2to3)
- )
- 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
- )
-
-
--# from Python docs
--def _unique_everseen(iterable, key=None):
-- "List unique elements, preserving order. Remember all elements ever
seen."
-- # unique_everseen('AAAABBBCCDAABBB') --> A B C D
-- # unique_everseen('ABBCcAD', str.lower) --> A B C D
-- seen = set()
-- seen_add = seen.add
-- if key is None:
-- for element in itertools.filterfalse(seen.__contains__, iterable):
-- seen_add(element)
-- yield element
-- else:
-- for element in iterable:
-- k = key(element)
-- if k not in seen:
-- seen_add(k)
-- yield element
--
--
- def assert_relative(path):
- if not os.path.isabs(path):
- return path
- from distutils.errors import DistutilsSetupError
-
-- msg = textwrap.dedent("""
-+ 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
-+ """
-+ ).lstrip()
-+ % path
-+ )
- raise DistutilsSetupError(msg)
-+
-+
-+class _IncludePackageDataAbuse:
-+ """Inform users that package or module is included as 'data file'"""
-+
-+ class _Warning(SetuptoolsDeprecationWarning):
-+ _SUMMARY = """
-+ Package {importable!r} is absent from the `packages` configuration.
-+ """
-+
-+ _DETAILS = """
-+ ############################
-+ # Package would be ignored #
-+ ############################
-+ Python recognizes {importable!r} as an importable package[^1],
-+ but it is absent from setuptools' `packages` configuration.
-+
-+ This leads to an ambiguous overall configuration. If you want to
distribute this
-+ package, please make sure that {importable!r} is explicitly added
-+ to the `packages` configuration field.
-+
-+ Alternatively, you can also rely on setuptools' discovery methods
-+ (for example by using
`find_namespace_packages(...)`/`find_namespace:`
-+ instead of `find_packages(...)`/`find:`).
-+
-+ You can read more about "package discovery" on setuptools
documentation page:
-+
-+ -
https://setuptools.pypa.io/en/latest/userguide/package_discovery.html
-+
-+ If you don't want {importable!r} to be distributed and are
-+ already explicitly excluding {importable!r} via
-+ `find_namespace_packages(...)/find_namespace` or
`find_packages(...)/find`,
-+ you can try to use `exclude_package_data`, or
`include-package-data=False` in
-+ combination with a more fine grained `package-data` configuration.
-+
-+ You can read more about "package data files" on setuptools
documentation page:
-+
-+ - https://setuptools.pypa.io/en/latest/userguide/datafiles.html
-+
-+
-+ [^1]: For Python, any directory (with suitable naming) can be
imported,
-+ even if it does not contain any `.py` files.
-+ On the other hand, currently there is no concept of package
data
-+ directory, all directories are treated like packages.
-+ """
-+ # _DUE_DATE: still not defined as this is particularly
controversial.
-+ # Warning initially introduced in May 2022. See issue #3340 for
discussion.
-+
-+ def __init__(self):
-+ self._already_warned = set()
-+
-+ def is_module(self, file):
-+ return file.endswith(".py") and file[:-len(".py")].isidentifier()
-+
-+ def importable_subpackage(self, parent, file):
-+ pkg = Path(file).parent
-+ parts = list(itertools.takewhile(str.isidentifier, pkg.parts))
-+ if parts:
-+ return ".".join([parent, *parts])
-+ return None
-+
-+ def warn(self, importable):
-+ if importable not in self._already_warned:
-+ self._Warning.emit(importable=importable)
-+ self._already_warned.add(importable)
-diff --git a/third_party/python/setuptools/setuptools/command/develop.py
b/third_party/python/setuptools/setuptools/command/develop.py
---- a/third_party/python/setuptools/setuptools/command/develop.py
-+++ b/third_party/python/setuptools/setuptools/command/develop.py
-@@ -1,17 +1,17 @@
- from distutils.util import convert_path
- from distutils import log
--from distutils.errors import DistutilsError, DistutilsOptionError
-+from distutils.errors import DistutilsOptionError
- import os
- import glob
- import io
-
--import pkg_resources
- from setuptools.command.easy_install import easy_install
-+from setuptools import _path
- from setuptools import namespaces
- import setuptools
-
-
- class develop(namespaces.DevelopInstaller, easy_install):
- """Set up package for development"""
-
- description = "install package in 'development mode'"
-@@ -37,49 +37,46 @@ class develop(namespaces.DevelopInstalle
- 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):
-+ import pkg_resources
-+
- 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))
-+ target = _path.normpath(self.egg_base)
-+ egg_path = _path.normpath(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
-+ project_name=ei.egg_name,
- )
-
- self.setup_path = self._resolve_setup_path(
- self.egg_base,
- self.install_dir,
- self.egg_path,
- )
-
-@@ -88,57 +85,35 @@ class develop(namespaces.DevelopInstalle
- """
- 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(
-+ resolved = _path.normpath(
- os.path.join(install_dir, egg_path, path_to_setup)
- )
-- if resolved != pkg_resources.normalize_path(os.curdir):
-+ curdir = _path.normpath(os.curdir)
-+ if resolved != curdir:
- raise DistutilsOptionError(
- "Can't get a consistent path to setup script from"
-- " installation directory", resolved,
-- pkg_resources.normalize_path(os.curdir))
-+ " installation directory",
-+ resolved,
-+ curdir,
-+ )
- return path_to_setup
-
- def install_for_development(self):
-- if getattr(self.distribution, 'use_2to3', False):
-- # If we run 2to3 we can not do this inplace:
--
-- # Ensure metadata is up-to-date
-- self.reinitialize_command('build_py', inplace=0)
-- self.run_command('build_py')
-- bpy_cmd = self.get_finalized_command("build_py")
-- build_path = pkg_resources.normalize_path(bpy_cmd.build_lib)
--
-- # Build extensions
-- self.reinitialize_command('egg_info', egg_base=build_path)
-- self.run_command('egg_info')
-+ self.run_command('egg_info')
-
-- self.reinitialize_command('build_ext', inplace=0)
-- self.run_command('build_ext')
--
-- # Fixup egg-link and easy-install.pth
-- ei_cmd = self.get_finalized_command("egg_info")
-- self.egg_path = build_path
-- self.dist.location = build_path
-- # XXX
-- self.dist._provider = pkg_resources.PathMetadata(
-- build_path, ei_cmd.egg_info)
-- else:
-- # Without 2to3 inplace works fine:
-- self.run_command('egg_info')
--
-- # Build extensions in-place
-- self.reinitialize_command('build_ext', inplace=1)
-- self.run_command('build_ext')
-+ # 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
-@@ -151,18 +126,17 @@ class develop(namespaces.DevelopInstalle
- 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]):
-+ 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!
-diff --git a/third_party/python/setuptools/setuptools/command/dist_info.py
b/third_party/python/setuptools/setuptools/command/dist_info.py
---- a/third_party/python/setuptools/setuptools/command/dist_info.py
-+++ b/third_party/python/setuptools/setuptools/command/dist_info.py
-@@ -1,36 +1,119 @@
- """
- Create a dist_info directory
- As defined in the wheel specification
- """
-
- import os
--
-+import shutil
-+import sys
-+from contextlib import contextmanager
-+from distutils import log
- from distutils.core import Command
--from distutils import log
-+from pathlib import Path
-+
-+from .. import _normalization
-+from ..warnings import SetuptoolsDeprecationWarning
-
-
- class dist_info(Command):
-+ """
-+ This command is private and reserved for internal use of setuptools,
-+ users should rely on ``setuptools.build_meta`` APIs.
-+ """
-
-- description = 'create a .dist-info directory'
-+ description = "DO NOT CALL DIRECTLY, INTERNAL ONLY: create .dist-info
directory"
-
- user_options = [
- ('egg-base=', 'e', "directory containing .egg-info directories"
-- " (default: top of the source tree)"),
-+ " (default: top of the source tree)"
-+ " DEPRECATED: use --output-dir."),
-+ ('output-dir=', 'o', "directory inside of which the .dist-info will
be"
-+ "created (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]"),
-+ ('keep-egg-info', None, "*TRANSITIONAL* will be removed in the
future"),
- ]
-
-+ boolean_options = ['tag-date', 'keep-egg-info']
-+ negative_opt = {'no-date': 'tag-date'}
-+
- def initialize_options(self):
- self.egg_base = None
-+ self.output_dir = None
-+ self.name = None
-+ self.dist_info_dir = None
-+ self.tag_date = None
-+ self.tag_build = None
-+ self.keep_egg_info = False
-
- def finalize_options(self):
-- pass
-+ if self.egg_base:
-+ msg = "--egg-base is deprecated for dist_info command. Use
--output-dir."
-+ SetuptoolsDeprecationWarning.emit(msg, due_date=(2023, 9, 26))
-+ # This command is internal to setuptools, therefore it should
be safe
-+ # to remove the deprecated support soon.
-+ self.output_dir = self.egg_base or self.output_dir
-+
-+ dist = self.distribution
-+ project_dir = dist.src_root or os.curdir
-+ self.output_dir = Path(self.output_dir or project_dir)
-+
-+ egg_info = self.reinitialize_command("egg_info")
-+ egg_info.egg_base = str(self.output_dir)
-+
-+ if self.tag_date:
-+ egg_info.tag_date = self.tag_date
-+ else:
-+ self.tag_date = egg_info.tag_date
-+
-+ if self.tag_build:
-+ egg_info.tag_build = self.tag_build
-+ else:
-+ self.tag_build = egg_info.tag_build
-+
-+ egg_info.finalize_options()
-+ self.egg_info = egg_info
-+
-+ name = _normalization.safer_name(dist.get_name())
-+ version =
_normalization.safer_best_effort_version(dist.get_version())
-+ self.name = f"{name}-{version}"
-+ self.dist_info_dir = os.path.join(self.output_dir,
f"{self.name}.dist-info")
-+
-+ @contextmanager
-+ def _maybe_bkp_dir(self, dir_path: str, requires_bkp: bool):
-+ if requires_bkp:
-+ bkp_name = f"{dir_path}.__bkp__"
-+ _rm(bkp_name, ignore_errors=True)
-+ _copy(dir_path, bkp_name, dirs_exist_ok=True, symlinks=True)
-+ try:
-+ yield
-+ finally:
-+ _rm(dir_path, ignore_errors=True)
-+ shutil.move(bkp_name, dir_path)
-+ else:
-+ yield
-
- 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)))
-+ self.output_dir.mkdir(parents=True, exist_ok=True)
-+ self.egg_info.run()
-+ egg_info_dir = self.egg_info.egg_info
-+ assert os.path.isdir(egg_info_dir), ".egg-info dir should have been
created"
-+
-+ log.info("creating
'{}'".format(os.path.abspath(self.dist_info_dir)))
-+ bdist_wheel = self.get_finalized_command('bdist_wheel')
-
-- bdist_wheel = self.get_finalized_command('bdist_wheel')
-- bdist_wheel.egg2dist(egg_info.egg_info, dist_info_dir)
-+ # TODO: if bdist_wheel if merged into setuptools, just add
"keep_egg_info" there
-+ with self._maybe_bkp_dir(egg_info_dir, self.keep_egg_info):
-+ bdist_wheel.egg2dist(egg_info_dir, self.dist_info_dir)
-+
-+
-+def _rm(dir_name, **opts):
-+ if os.path.isdir(dir_name):
-+ shutil.rmtree(dir_name, **opts)
-+
-+
-+def _copy(src, dst, **opts):
-+ if sys.version_info < (3, 8):
-+ opts.pop("dirs_exist_ok", None)
-+ shutil.copytree(src, dst, **opts)
-diff --git
a/third_party/python/setuptools/setuptools/command/easy_install.py
b/third_party/python/setuptools/setuptools/command/easy_install.py
---- a/third_party/python/setuptools/setuptools/command/easy_install.py
-+++ b/third_party/python/setuptools/setuptools/command/easy_install.py
-@@ -1,31 +1,31 @@
- """
- 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.readthedocs.io/en/latest/easy_install.html
-+__ 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
-+from distutils.command import install
- import sys
- import os
- import zipimport
- import shutil
- import tempfile
- import zipfile
- import re
- import stat
-@@ -34,68 +34,56 @@ import textwrap
- import warnings
- import site
- import struct
- import contextlib
- import subprocess
- import shlex
- import io
- import configparser
-+import sysconfig
-
-
--from sysconfig import get_config_vars, get_path
--
--from setuptools import SetuptoolsDeprecationWarning
-+from sysconfig import get_path
-
- 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.warnings import SetuptoolsDeprecationWarning,
SetuptoolsWarning
- from setuptools.wheel import Wheel
- from pkg_resources import (
-- yield_lines, normalize_path, resource_string, ensure_directory,
-+ normalize_path, resource_string,
- get_distribution, find_distributions, Environment, Requirement,
- Distribution, PathMetadata, EggMetadata, WorkingSet,
DistributionNotFound,
- VersionConflict, DEVELOP_DIST,
- )
- import pkg_resources
-+from .. import py312compat
-+from .._path import ensure_directory
-+from ..extern.jaraco.text import yield_lines
-+
-
- # Turn on PEP440Warnings
- warnings.filterwarnings("default", category=pkg_resources.PEP440Warning)
-
- __all__ = [
-- 'samefile', 'easy_install', 'PthDistributions', 'extract_wininst_cfg',
-- 'main', 'get_exe_prefixes',
-+ '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
-@@ -148,16 +136,18 @@ class easy_install(Command):
- 'no-deps', 'local-snapshots-ok', 'version',
- 'user'
- ]
-
- negative_opt = {'always-unzip': 'zip-ok'}
- create_index = PackageIndex
-
- def initialize_options(self):
-+ EasyInstallDeprecationWarning.emit()
-+
- # 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
-@@ -170,22 +160,18 @@ class easy_install(Command):
- 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.install_userbase = site.USER_BASE
-+ self.install_usersite = site.USER_SITE
- 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 = {}
- # Always read easy_install options, even if we are subclassed, or
have
-@@ -207,56 +193,66 @@ class easy_install(Command):
- 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 = _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):
-+ 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 = {
-+ self.config_vars = dict(sysconfig.get_config_vars())
-+
-+ self.config_vars.update({
- '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,
-+ 'py_version_short':
f'{sys.version_info.major}.{sys.version_info.minor}',
-+ 'py_version_nodot':
f'{sys.version_info.major}{sys.version_info.minor}',
-+ 'sys_prefix': self.config_vars['prefix'],
-+ 'sys_exec_prefix': self.config_vars['exec_prefix'],
- # Only python 3.2+ has abiflags
- 'abiflags': getattr(sys, 'abiflags', ''),
-- }
-+ 'platlibdir': getattr(sys, 'platlibdir', 'lib'),
-+ })
-+ with contextlib.suppress(AttributeError):
-+ # only for distutils outside stdlib
-+ self.config_vars.update({
-+ 'implementation_lower':
install._get_implementation().lower(),
-+ 'implementation': install._get_implementation(),
-+ })
-
-- if site.ENABLE_USER_SITE:
-- self.config_vars['userbase'] = self.install_userbase
-- self.config_vars['usersite'] = self.install_usersite
-+ # pypa/distutils#113 Python 3.9 compat
-+ self.config_vars.setdefault(
-+ 'py_version_nodot_plat',
-+ getattr(sys, 'windir', '').replace('.', ''),
-+ )
-
-- elif self.user:
-+ self.config_vars['userbase'] = self.install_userbase
-+ self.config_vars['usersite'] = self.install_usersite
-+ if self.user and not site.ENABLE_USER_SITE:
- log.warn("WARNING: The user site-packages directory is
disabled.")
-
- self._fix_install_dir_for_user_site()
-
- self.expand_basedirs()
- self.expand_dirs()
-
- self._expand(
-@@ -282,37 +278,24 @@ class easy_install(Command):
- '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
- # 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))
-+ self.all_site_dirs.extend(self._process_site_dirs(self.site_dirs))
-+
- if not self.editable:
- self.check_site_dir()
-- self.index_url = self.index_url or "https://pypi.org/simple/";
-+ default_index = os.getenv("__EASYINSTALL_INDEX",
"https://pypi.org/simple/";)
-+ # ^ Private API for testing purposes only
-+ self.index_url = self.index_url or default_index
- 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:
-@@ -328,49 +311,74 @@ class easy_install(Command):
- 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
-+ self.optimize = self._validate_optimize(self.optimize)
-
- 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 = []
-
-+ @staticmethod
-+ def _process_site_dirs(site_dirs):
-+ if site_dirs is None:
-+ return
-+
-+ normpath = map(normalize_path, sys.path)
-+ site_dirs = [
-+ os.path.expanduser(s.strip()) for s in
-+ 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:
-+ yield normalize_path(d)
-+
-+ @staticmethod
-+ def _validate_optimize(value):
-+ try:
-+ value = int(value)
-+ if value not in range(3):
-+ raise ValueError
-+ except ValueError as e:
-+ raise DistutilsOptionError(
-+ "--optimize must be 0, 1, or 2"
-+ ) from e
-+
-+ return value
-+
- 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:
-+ if not self.user:
- 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'
-+ scheme_name = f'{os.name}_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)
-@@ -432,17 +440,17 @@ class easy_install(Command):
- 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):
-+ 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)
-@@ -508,17 +516,17 @@ class easy_install(Command):
- 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.readthedocs.io/en/latest/easy_install.html
-+ 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):
-@@ -628,17 +636,17 @@ class easy_install(Command):
-
- @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)
-+ 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)
-@@ -700,25 +708,26 @@ class easy_install(Command):
- 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])
-+ try:
-+ install._select_scheme(self, name)
-+ except AttributeError:
-+ # stdlib distutils
-+ install.install.select_scheme(self, name.replace('posix',
'unix'))
-
-- def process_distribution(self, requirement, dist, deps=True, *info):
-+ # 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))
-@@ -832,22 +841,29 @@ class easy_install(Command):
- 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
-- if dist_filename.lower().endswith('.egg'):
-- return [self.install_egg(dist_filename, tmpdir)]
-- elif dist_filename.lower().endswith('.exe'):
-- return [self.install_exe(dist_filename, tmpdir)]
-- elif dist_filename.lower().endswith('.whl'):
-- return [self.install_wheel(dist_filename, tmpdir)]
-+ 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)
-
-@@ -882,27 +898,30 @@ class easy_install(Command):
- 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)
-
-- def install_egg(self, egg_path, tmpdir):
-+ # 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 not (
-+ os.path.exists(destination) and os.path.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,
- )
-@@ -981,17 +1000,18 @@ class easy_install(Command):
- ])
- # 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)
-
-- def exe_to_egg(self, dist_filename, egg_tmp):
-+ # 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):
-@@ -1153,17 +1173,17 @@ class easy_install(Command):
- 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)
-+ _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.
-@@ -1173,58 +1193,62 @@ class easy_install(Command):
- 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.replace('_', '-')] = val[1]
-+ 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):
-+ 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 self.multi_version or d.location != dist.location:
-- 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 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 not self.dry_run:
-+ if self.dry_run:
-+ return
-
-- self.pth_file.save()
-+ self.pth_file.save()
-+
-+ if dist.key != 'setuptools':
-+ return
-
-- if dist.key == 'setuptools':
-- # 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)
-- f = open(filename, 'wt')
-- f.write(self.pth_file.make_relative(dist.location) + '\n')
-- f.close()
-+ # 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 = []
-@@ -1285,28 +1309,28 @@ class easy_install(Command):
-
- * 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.readthedocs.io/en/latest/easy_install.html#custom-installation-locations
-+
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():
-+ for path in only_strs(self.config_vars.values()):
- if path.startswith(home) and not os.path.isdir(path):
- self.debug_print("os.makedirs('%s', 0o700)" % path)
- os.makedirs(path, 0o700)
-
- INSTALL_SCHEMES = dict(
- posix=dict(
- install_dir='$base/lib/python$py_version_short/site-packages',
- script_dir='$base/bin',
-@@ -1318,17 +1342,17 @@ class easy_install(Command):
- script_dir='$base/Scripts',
- )
-
- def _expand(self, *attrs):
- config_vars = self.get_finalized_command('install').config_vars
-
- if self.prefix:
- # Set default install_dir/scripts from --prefix
-- config_vars = config_vars.copy()
-+ config_vars = dict(config_vars)
- config_vars['base'] = self.prefix
- scheme = self.INSTALL_SCHEMES.get(os.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
-
-@@ -1355,68 +1379,73 @@ def get_site_dirs():
-
- # 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 prefix:
-- if sys.platform in ('os2emx', 'riscos'):
-- sitedirs.append(os.path.join(prefix, "Lib",
"site-packages"))
-- elif os.sep == '/':
-- sitedirs.extend([
-- os.path.join(
-- prefix,
-- "lib",
-- "python{}.{}".format(*sys.version_info),
-- "site-packages",
-- ),
-- os.path.join(prefix, "lib", "site-python"),
-- ])
-- else:
-- sitedirs.extend([
-+ 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,
-- os.path.join(prefix, "lib", "site-packages"),
-- ])
-- if sys.platform == 'darwin':
-- # 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' in prefix:
-- home = os.environ.get('HOME')
-- if home:
-- home_sp = os.path.join(
-- home,
-- 'Library',
-- 'Python',
-- '{}.{}'.format(*sys.version_info),
-- 'site-packages',
-- )
-- sitedirs.append(home_sp)
-+ "lib",
-+ "python{}.{}".format(*sys.version_info),
-+ "site-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')
-- for site_lib in lib_paths:
-- if site_lib not in sitedirs:
-- sitedirs.append(site_lib)
-+
-+ sitedirs.extend(s for s in lib_paths if s not in sitedirs)
-
- if site.ENABLE_USER_SITE:
- sitedirs.append(site.USER_SITE)
-
-- try:
-+ with contextlib.suppress(AttributeError):
- sitedirs.extend(site.getsitepackages())
-- except AttributeError:
-- pass
-
- sitedirs = list(map(normalize_path, sitedirs))
-
- return sitedirs
-
-
--def expand_paths(inputs):
-+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
-@@ -1438,23 +1467,28 @@ def expand_paths(inputs):
-
- # 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 not line.startswith("import"):
-- line = normalize_path(line.rstrip())
-- if line not in seen:
-- seen[line] = 1
-- if not os.path.isdir(line):
-- continue
-- yield line, os.listdir(line)
-+ 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')
-@@ -1477,17 +1511,17 @@ def extract_wininst_cfg(dist_filename):
- cfg = configparser.RawConfigParser(init)
- try:
- part = f.read(cfglen)
- # Read up to the first null byte.
- config = part.split(b'\0', 1)[0]
- # Now the config is in bytes, but for RawConfigParser, it should
- # be text, so decode it.
- config = config.decode(sys.getfilesystemencoding())
-- cfg.readfp(io.StringIO(config))
-+ cfg.read_file(io.StringIO(config))
- except configparser.Error:
- return None
- if not cfg.has_section('metadata') or not cfg.has_section('Setup'):
- return None
- return cfg
-
- finally:
- f.close()
-@@ -1528,79 +1562,107 @@ def get_exe_prefixes(exe_filename):
- 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)
-+ self.paths, self.dirty = self._load()
-+ # keep a copy if someone manually updates the paths attribute on
the instance
-+ self._init_paths = self.paths[:]
-+ super().__init__([], None, None)
- for path in yield_lines(self.paths):
- list(map(self.add, find_distributions(path, True)))
-
-+ def _load_raw(self):
-+ paths = []
-+ dirty = saw_import = False
-+ seen = dict.fromkeys(self.sitedirs)
-+ f = open(self.filename, 'rt')
-+ for line in f:
-+ path = line.rstrip()
-+ # still keep imports and empty/commented lines for formatting
-+ paths.append(path)
-+ if line.startswith(('import ', 'from ')):
-+ saw_import = True
-+ continue
-+ stripped_path = path.strip()
-+ if not stripped_path or stripped_path.startswith('#'):
-+ continue
-+ # skip non-existent paths, in case somebody deleted a package
-+ # manually, and duplicate paths as well
-+ normalized_path = normalize_path(os.path.join(self.basedir,
path))
-+ if normalized_path in seen or not
os.path.exists(normalized_path):
-+ log.debug("cleaned up dirty or duplicated %r", path)
-+ dirty = True
-+ paths.pop()
-+ continue
-+ seen[normalized_path] = 1
-+ f.close()
-+ # remove any trailing empty/blank line
-+ while paths and not paths[-1].strip():
-+ paths.pop()
-+ dirty = True
-+ return paths, dirty or (paths and saw_import)
-+
- 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()
-+ return self._load_raw()
-+ return [], False
-
- def save(self):
- """Write changed .pth file back to disk"""
-+ # first reload the file
-+ last_paths, last_dirty = self._load()
-+ # and check that there are no difference with what we have.
-+ # there can be difference if someone else has written to the file
-+ # since we first loaded it.
-+ # we don't want to lose the eventual new paths added since then.
-+ for path in last_paths[:]:
-+ if path not in self.paths:
-+ self.paths.append(path)
-+ log.info("detected new path %r", path)
-+ last_dirty = True
-+ else:
-+ last_paths.remove(path)
-+ # also, re-check that all paths are still valid before saving them
-+ for path in self.paths[:]:
-+ if path not in last_paths \
-+ and not path.startswith(('import ', 'from ', '#')):
-+ absolute_path = os.path.join(self.basedir, path)
-+ if not os.path.exists(absolute_path):
-+ self.paths.remove(path)
-+ log.info("removing now non-existent path %r", path)
-+ last_dirty = True
-+
-+ self.dirty |= last_dirty or self.paths != self._init_paths
- 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
-+ self._init_paths[:] = self.paths[:]
-
- @staticmethod
- def _wrap_lines(lines):
- return lines
-
- def add(self, dist):
- """Add `dist` to the distribution map"""
- new_path = (
-@@ -1608,24 +1670,24 @@ class PthDistributions(Environment):
- 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)
-+ super().add(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)
-+ super().remove(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:
-@@ -1806,17 +1868,17 @@ def _update_zipimporter_cache(normalized
- # 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
-+ #
https://foss.heptapod.net/pypy/pypy/-/blob/144c4e65cb6accb8e592f3a7584ea38265d1873c/pypy/module/zipimport/interp_zipimport.py
- 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):
-@@ -2049,33 +2111,16 @@ class ScriptWriter:
- 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())
-@@ -2093,22 +2138,16 @@ class ScriptWriter:
- """
- 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
-@@ -2125,22 +2164,16 @@ class ScriptWriter:
- 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,
- )
-@@ -2152,27 +2185,27 @@ class WindowsScriptWriter(ScriptWriter):
- 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)
-+ SetuptoolsWarning.emit(msg)
- 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 and 'python' is used for
-+ 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)
-@@ -2217,102 +2250,58 @@ class WindowsExecutableLauncherWriter(Wi
- # 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():
-- launcher_fn = launcher_fn.replace(".", "-64.")
-+ 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 _rmtree(path, ignore_errors=False, onexc=auto_chmod):
-+ return py312compat.shutil_rmtree(path, ignore_errors, onexc)
-
-
- def current_umask():
- tmp = os.umask(0o022)
- os.umask(tmp)
- return tmp
-
-
--def bootstrap():
-- # This function is called when setuptools*.egg is run using /bin/sh
-- import setuptools
--
-- argv0 = os.path.dirname(setuptools.__path__[0])
-- sys.argv[0] = argv0
-- sys.argv.append(argv0)
-- main()
--
--
--def main(argv=None, **kw):
-- from setuptools import setup
-- from setuptools.dist import Distribution
--
-- class DistributionWithoutHelpCommands(Distribution):
-- common_usage = ""
--
-- def _show_help(self, *args, **kw):
-- with _patch_usage():
-- Distribution._show_help(self, *args, **kw)
--
-- if argv is None:
-- argv = sys.argv[1:]
--
-- with _patch_usage():
-- setup(
-- script_args=['-q', 'easy_install', '-v'] + argv,
-- script_name=sys.argv[0] or 'easy_install',
-- distclass=DistributionWithoutHelpCommands,
-- **kw
-- )
--
--
--@contextlib.contextmanager
--def _patch_usage():
-- import distutils.core
-- USAGE = textwrap.dedent("""
-- usage: %(script)s [options] requirement_or_url ...
-- or: %(script)s --help
-- """).lstrip()
--
-- def gen_usage(script_name):
-- return USAGE % dict(
-- script=os.path.basename(script_name),
-- )
--
-- saved = distutils.core.gen_usage
-- distutils.core.gen_usage = gen_usage
-- try:
-- yield
-- finally:
-- distutils.core.gen_usage = saved
-+def only_strs(values):
-+ """
-+ Exclude non-str values. Ref #3063.
-+ """
-+ return filter(lambda val: isinstance(val, str), values)
-
-
- class EasyInstallDeprecationWarning(SetuptoolsDeprecationWarning):
-+ _SUMMARY = "easy_install command is deprecated."
-+ _DETAILS = """
-+ Please avoid running ``setup.py`` and ``easy_install``.
-+ Instead, use pypa/build, pypa/installer or other
-+ standards-based tools.
- """
-- Warning for EasyInstall deprecations, bypassing suppression.
-- """
-+ _SEE_URL = "https://github.com/pypa/setuptools/issues/917";
-+ # _DUE_DATE not defined yet
-diff --git
a/third_party/python/setuptools/setuptools/command/editable_wheel.py
b/third_party/python/setuptools/setuptools/command/editable_wheel.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/command/editable_wheel.py
-@@ -0,0 +1,857 @@
-+"""
-+Create a wheel that, when installed, will make the source package 'editable'
-+(add it to the interpreter's path, including metadata) per PEP 660. Replaces
-+'setup.py develop'.
-+
-+.. note::
-+ One of the mechanisms briefly mentioned in PEP 660 to implement editable
installs is
-+ to create a separated directory inside ``build`` and use a .pth file to
point to that
-+ directory. In the context of this file such directory is referred as
-+ *auxiliary build directory* or ``auxiliary_dir``.
-+"""
-+
-+import logging
-+import os
-+import shutil
-+import sys
-+import traceback
-+from contextlib import suppress
-+from enum import Enum
-+from inspect import cleandoc
-+from itertools import chain
-+from pathlib import Path
-+from tempfile import TemporaryDirectory
-+from typing import (
-+ TYPE_CHECKING,
-+ Dict,
-+ Iterable,
-+ Iterator,
-+ List,
-+ Mapping,
-+ Optional,
-+ Tuple,
-+ TypeVar,
-+ Union,
-+)
-+
-+from .. import (
-+ Command,
-+ _normalization,
-+ _path,
-+ errors,
-+ namespaces,
-+)
-+from ..discovery import find_package_path
-+from ..dist import Distribution
-+from ..warnings import (
-+ InformationOnly,
-+ SetuptoolsDeprecationWarning,
-+ SetuptoolsWarning,
-+)
-+from .build_py import build_py as build_py_cls
-+
-+if TYPE_CHECKING:
-+ from wheel.wheelfile import WheelFile # noqa
-+
-+if sys.version_info >= (3, 8):
-+ from typing import Protocol
-+elif TYPE_CHECKING:
-+ from typing_extensions import Protocol
-+else:
-+ from abc import ABC as Protocol
-+
-+_Path = Union[str, Path]
-+_P = TypeVar("_P", bound=_Path)
-+_logger = logging.getLogger(__name__)
-+
-+
-+class _EditableMode(Enum):
-+ """
-+ Possible editable installation modes:
-+ `lenient` (new files automatically added to the package - DEFAULT);
-+ `strict` (requires a new installation when files are added/removed); or
-+ `compat` (attempts to emulate `python setup.py develop` - DEPRECATED).
-+ """
-+
-+ STRICT = "strict"
-+ LENIENT = "lenient"
-+ COMPAT = "compat" # TODO: Remove `compat` after Dec/2022.
-+
-+ @classmethod
-+ def convert(cls, mode: Optional[str]) -> "_EditableMode":
-+ if not mode:
-+ return _EditableMode.LENIENT # default
-+
-+ _mode = mode.upper()
-+ if _mode not in _EditableMode.__members__:
-+ raise errors.OptionError(f"Invalid editable mode: {mode!r}.
Try: 'strict'.")
-+
-+ if _mode == "COMPAT":
-+ SetuptoolsDeprecationWarning.emit(
-+ "Compat editable installs",
-+ """
-+ The 'compat' editable mode is transitional and will be
removed
-+ in future versions of `setuptools`.
-+ Please adapt your code accordingly to use either the
'strict' or the
-+ 'lenient' modes.
-+ """,
-+ see_docs="userguide/development_mode.html",
-+ # TODO: define due_date
-+ # There is a series of shortcomings with the available
editable install
-+ # methods, and they are very controversial. This is
something that still
-+ # needs work.
-+ # Moreover, `pip` is still hiding this warning, so users
are not aware.
-+ )
-+
-+ return _EditableMode[_mode]
-+
-+
-+_STRICT_WARNING = """
-+New or renamed files may not be automatically picked up without a new
installation.
-+"""
-+
-+_LENIENT_WARNING = """
-+Options like `package-data`, `include/exclude-package-data` or
-+`packages.find.exclude/include` may have no effect.
-+"""
-+
-+
-+class editable_wheel(Command):
-+ """Build 'editable' wheel for development.
-+ This command is private and reserved for internal use of setuptools,
-+ users should rely on ``setuptools.build_meta`` APIs.
-+ """
-+
-+ description = "DO NOT CALL DIRECTLY, INTERNAL ONLY: create PEP 660
editable wheel"
-+
-+ user_options = [
-+ ("dist-dir=", "d", "directory to put final built distributions in"),
-+ ("dist-info-dir=", "I", "path to a pre-build .dist-info directory"),
-+ ("mode=", None, cleandoc(_EditableMode.__doc__ or "")),
-+ ]
-+
-+ def initialize_options(self):
-+ self.dist_dir = None
-+ self.dist_info_dir = None
-+ self.project_dir = None
-+ self.mode = None
-+
-+ def finalize_options(self):
-+ dist = self.distribution
-+ self.project_dir = dist.src_root or os.curdir
-+ self.package_dir = dist.package_dir or {}
-+ self.dist_dir = Path(self.dist_dir or
os.path.join(self.project_dir, "dist"))
-+
-+ def run(self):
-+ try:
-+ self.dist_dir.mkdir(exist_ok=True)
-+ self._ensure_dist_info()
-+
-+ # Add missing dist_info files
-+ self.reinitialize_command("bdist_wheel")
-+ bdist_wheel = self.get_finalized_command("bdist_wheel")
-+ bdist_wheel.write_wheelfile(self.dist_info_dir)
-+
-+ self._create_wheel_file(bdist_wheel)
-+ except Exception:
-+ traceback.print_exc()
-+ project = self.distribution.name or self.distribution.get_name()
-+ _DebuggingTips.emit(project=project)
-+ raise
-+
-+ def _ensure_dist_info(self):
-+ if self.dist_info_dir is None:
-+ dist_info = self.reinitialize_command("dist_info")
-+ dist_info.output_dir = self.dist_dir
-+ dist_info.ensure_finalized()
-+ dist_info.run()
-+ self.dist_info_dir = dist_info.dist_info_dir
-+ else:
-+ assert str(self.dist_info_dir).endswith(".dist-info")
-+ assert Path(self.dist_info_dir, "METADATA").exists()
-+
-+ def _install_namespaces(self, installation_dir, pth_prefix):
-+ # XXX: Only required to support the deprecated namespace practice
-+ dist = self.distribution
-+ if not dist.namespace_packages:
-+ return
-+
-+ src_root = Path(self.project_dir, self.package_dir.get("",
".")).resolve()
-+ installer = _NamespaceInstaller(dist, installation_dir, pth_prefix,
src_root)
-+ installer.install_namespaces()
-+
-+ def _find_egg_info_dir(self) -> Optional[str]:
-+ parent_dir = Path(self.dist_info_dir).parent if self.dist_info_dir
else Path()
-+ candidates = map(str, parent_dir.glob("*.egg-info"))
-+ return next(candidates, None)
-+
-+ def _configure_build(
-+ self, name: str, unpacked_wheel: _Path, build_lib: _Path, tmp_dir:
_Path
-+ ):
-+ """Configure commands to behave in the following ways:
-+
-+ - Build commands can write to ``build_lib`` if they really want
to...
-+ (but this folder is expected to be ignored and modules are
expected to live
-+ in the project directory...)
-+ - Binary extensions should be built in-place (editable_mode = True)
-+ - Data/header/script files are not part of the "editable"
specification
-+ so they are written directly to the unpacked_wheel directory.
-+ """
-+ # Non-editable files (data, headers, scripts) are written directly
to the
-+ # unpacked_wheel
-+
-+ dist = self.distribution
-+ wheel = str(unpacked_wheel)
-+ build_lib = str(build_lib)
-+ data = str(Path(unpacked_wheel, f"{name}.data", "data"))
-+ headers = str(Path(unpacked_wheel, f"{name}.data", "headers"))
-+ scripts = str(Path(unpacked_wheel, f"{name}.data", "scripts"))
-+
-+ # egg-info may be generated again to create a manifest (used for
package data)
-+ egg_info = dist.reinitialize_command("egg_info",
reinit_subcommands=True)
-+ egg_info.egg_base = str(tmp_dir)
-+ egg_info.ignore_egg_info_in_manifest = True
-+
-+ build = dist.reinitialize_command("build", reinit_subcommands=True)
-+ install = dist.reinitialize_command("install",
reinit_subcommands=True)
-+
-+ build.build_platlib = build.build_purelib = build.build_lib =
build_lib
-+ install.install_purelib = install.install_platlib =
install.install_lib = wheel
-+ install.install_scripts = build.build_scripts = scripts
-+ install.install_headers = headers
-+ install.install_data = data
-+
-+ install_scripts = dist.get_command_obj("install_scripts")
-+ install_scripts.no_ep = True
-+
-+ build.build_temp = str(tmp_dir)
-+
-+ build_py = dist.get_command_obj("build_py")
-+ build_py.compile = False
-+ build_py.existing_egg_info_dir = self._find_egg_info_dir()
-+
-+ self._set_editable_mode()
-+
-+ build.ensure_finalized()
-+ install.ensure_finalized()
-+
-+ def _set_editable_mode(self):
-+ """Set the ``editable_mode`` flag in the build sub-commands"""
-+ dist = self.distribution
-+ build = dist.get_command_obj("build")
-+ for cmd_name in build.get_sub_commands():
-+ cmd = dist.get_command_obj(cmd_name)
-+ if hasattr(cmd, "editable_mode"):
-+ cmd.editable_mode = True
-+ elif hasattr(cmd, "inplace"):
-+ cmd.inplace = True # backward compatibility with distutils
-+
-+ def _collect_build_outputs(self) -> Tuple[List[str], Dict[str, str]]:
-+ files: List[str] = []
-+ mapping: Dict[str, str] = {}
-+ build = self.get_finalized_command("build")
-+
-+ for cmd_name in build.get_sub_commands():
-+ cmd = self.get_finalized_command(cmd_name)
-+ if hasattr(cmd, "get_outputs"):
-+ files.extend(cmd.get_outputs() or [])
-+ if hasattr(cmd, "get_output_mapping"):
-+ mapping.update(cmd.get_output_mapping() or {})
-+
-+ return files, mapping
-+
-+ def _run_build_commands(
-+ self, dist_name: str, unpacked_wheel: _Path, build_lib: _Path,
tmp_dir: _Path
-+ ) -> Tuple[List[str], Dict[str, str]]:
-+ self._configure_build(dist_name, unpacked_wheel, build_lib, tmp_dir)
-+ self._run_build_subcommands()
-+ files, mapping = self._collect_build_outputs()
-+ self._run_install("headers")
-+ self._run_install("scripts")
-+ self._run_install("data")
-+ return files, mapping
-+
-+ def _run_build_subcommands(self):
-+ """
-+ Issue #3501 indicates that some plugins/customizations might rely
on:
-+
-+ 1. ``build_py`` not running
-+ 2. ``build_py`` always copying files to ``build_lib``
-+
-+ However both these assumptions may be false in editable_wheel.
-+ This method implements a temporary workaround to support the
ecosystem
-+ while the implementations catch up.
-+ """
-+ # TODO: Once plugins/customisations had the chance to catch up,
replace
-+ # `self._run_build_subcommands()` with
`self.run_command("build")`.
-+ # Also remove _safely_run, TestCustomBuildPy. Suggested date:
Aug/2023.
-+ build: Command = self.get_finalized_command("build")
-+ for name in build.get_sub_commands():
-+ cmd = self.get_finalized_command(name)
-+ if name == "build_py" and type(cmd) != build_py_cls:
-+ self._safely_run(name)
-+ else:
-+ self.run_command(name)
-+
-+ def _safely_run(self, cmd_name: str):
-+ try:
-+ return self.run_command(cmd_name)
-+ except Exception:
-+ SetuptoolsDeprecationWarning.emit(
-+ "Customization incompatible with editable install",
-+ f"""
-+ {traceback.format_exc()}
-+
-+ If you are seeing this warning it is very likely that a
setuptools
-+ plugin or customization overrides the `{cmd_name}` command,
without
-+ taking into consideration how editable installs run build
steps
-+ starting from setuptools v64.0.0.
-+
-+ Plugin authors and developers relying on custom build steps
are
-+ encouraged to update their `{cmd_name}` implementation
considering the
-+ information about editable installs in
-+
https://setuptools.pypa.io/en/latest/userguide/extension.html.
-+
-+ For the time being `setuptools` will silence this error and
ignore
-+ the faulty command, but this behaviour will change in
future versions.
-+ """,
-+ # TODO: define due_date
-+ # There is a series of shortcomings with the available
editable install
-+ # methods, and they are very controversial. This is
something that still
-+ # needs work.
-+ )
-+
-+ def _create_wheel_file(self, bdist_wheel):
-+ from wheel.wheelfile import WheelFile
-+
-+ dist_info = self.get_finalized_command("dist_info")
-+ dist_name = dist_info.name
-+ tag = "-".join(bdist_wheel.get_tag())
-+ build_tag = "0.editable" # According to PEP 427 needs to start
with digit
-+ archive_name = f"{dist_name}-{build_tag}-{tag}.whl"
-+ wheel_path = Path(self.dist_dir, archive_name)
-+ if wheel_path.exists():
-+ wheel_path.unlink()
-+
-+ unpacked_wheel = TemporaryDirectory(suffix=archive_name)
-+ build_lib = TemporaryDirectory(suffix=".build-lib")
-+ build_tmp = TemporaryDirectory(suffix=".build-temp")
-+
-+ with unpacked_wheel as unpacked, build_lib as lib, build_tmp as tmp:
-+ unpacked_dist_info = Path(unpacked,
Path(self.dist_info_dir).name)
-+ shutil.copytree(self.dist_info_dir, unpacked_dist_info)
-+ self._install_namespaces(unpacked, dist_info.name)
-+ files, mapping = self._run_build_commands(dist_name, unpacked,
lib, tmp)
-+ strategy = self._select_strategy(dist_name, tag, lib)
-+ with strategy, WheelFile(wheel_path, "w") as wheel_obj:
-+ strategy(wheel_obj, files, mapping)
-+ wheel_obj.write_files(unpacked)
-+
-+ return wheel_path
-+
-+ def _run_install(self, category: str):
-+ has_category = getattr(self.distribution, f"has_{category}", None)
-+ if has_category and has_category():
-+ _logger.info(f"Installing {category} as non editable")
-+ self.run_command(f"install_{category}")
-+
-+ def _select_strategy(
-+ self,
-+ name: str,
-+ tag: str,
-+ build_lib: _Path,
-+ ) -> "EditableStrategy":
-+ """Decides which strategy to use to implement an editable
installation."""
-+ build_name = f"__editable__.{name}-{tag}"
-+ project_dir = Path(self.project_dir)
-+ mode = _EditableMode.convert(self.mode)
-+
-+ if mode is _EditableMode.STRICT:
-+ auxiliary_dir = _empty_dir(Path(self.project_dir, "build",
build_name))
-+ return _LinkTree(self.distribution, name, auxiliary_dir,
build_lib)
-+
-+ packages = _find_packages(self.distribution)
-+ has_simple_layout = _simple_layout(packages, self.package_dir,
project_dir)
-+ is_compat_mode = mode is _EditableMode.COMPAT
-+ if set(self.package_dir) == {""} and has_simple_layout or
is_compat_mode:
-+ # src-layout(ish) is relatively safe for a simple pth file
-+ src_dir = self.package_dir.get("", ".")
-+ return _StaticPth(self.distribution, name, [Path(project_dir,
src_dir)])
-+
-+ # Use a MetaPathFinder to avoid adding accidental top-level
packages/modules
-+ return _TopLevelFinder(self.distribution, name)
-+
-+
-+class EditableStrategy(Protocol):
-+ def __call__(self, wheel: "WheelFile", files: List[str], mapping:
Dict[str, str]):
-+ ...
-+
-+ def __enter__(self):
-+ ...
-+
-+ def __exit__(self, _exc_type, _exc_value, _traceback):
-+ ...
-+
-+
-+class _StaticPth:
-+ def __init__(self, dist: Distribution, name: str, path_entries:
List[Path]):
-+ self.dist = dist
-+ self.name = name
-+ self.path_entries = path_entries
-+
-+ def __call__(self, wheel: "WheelFile", files: List[str], mapping:
Dict[str, str]):
-+ entries = "\n".join((str(p.resolve()) for p in self.path_entries))
-+ contents = bytes(f"{entries}\n", "utf-8")
-+ wheel.writestr(f"__editable__.{self.name}.pth", contents)
-+
-+ def __enter__(self):
-+ msg = f"""
-+ Editable install will be performed using .pth file to extend
`sys.path` with:
-+ {list(map(os.fspath, self.path_entries))!r}
-+ """
-+ _logger.warning(msg + _LENIENT_WARNING)
-+ return self
-+
-+ def __exit__(self, _exc_type, _exc_value, _traceback):
-+ ...
-+
-+
-+class _LinkTree(_StaticPth):
-+ """
-+ Creates a ``.pth`` file that points to a link tree in the
``auxiliary_dir``.
-+
-+ This strategy will only link files (not dirs), so it can be implemented
in
-+ any OS, even if that means using hardlinks instead of symlinks.
-+
-+ By collocating ``auxiliary_dir`` and the original source code,
limitations
-+ with hardlinks should be avoided.
-+ """
-+ def __init__(
-+ self, dist: Distribution,
-+ name: str,
-+ auxiliary_dir: _Path,
-+ build_lib: _Path,
-+ ):
-+ self.auxiliary_dir = Path(auxiliary_dir)
-+ self.build_lib = Path(build_lib).resolve()
-+ self._file = dist.get_command_obj("build_py").copy_file
-+ super().__init__(dist, name, [self.auxiliary_dir])
-+
-+ def __call__(self, wheel: "WheelFile", files: List[str], mapping:
Dict[str, str]):
-+ self._create_links(files, mapping)
-+ super().__call__(wheel, files, mapping)
-+
-+ def _normalize_output(self, file: str) -> Optional[str]:
-+ # Files relative to build_lib will be normalized to None
-+ with suppress(ValueError):
-+ path = Path(file).resolve().relative_to(self.build_lib)
-+ return str(path).replace(os.sep, '/')
-+ return None
-+
-+ def _create_file(self, relative_output: str, src_file: str, link=None):
-+ dest = self.auxiliary_dir / relative_output
-+ if not dest.parent.is_dir():
-+ dest.parent.mkdir(parents=True)
-+ self._file(src_file, dest, link=link)
-+
-+ def _create_links(self, outputs, output_mapping):
-+ self.auxiliary_dir.mkdir(parents=True, exist_ok=True)
-+ link_type = "sym" if _can_symlink_files(self.auxiliary_dir) else
"hard"
-+ mappings = {
-+ self._normalize_output(k): v
-+ for k, v in output_mapping.items()
-+ }
-+ mappings.pop(None, None) # remove files that are not relative to
build_lib
-+
-+ for output in outputs:
-+ relative = self._normalize_output(output)
-+ if relative and relative not in mappings:
-+ self._create_file(relative, output)
-+
-+ for relative, src in mappings.items():
-+ self._create_file(relative, src, link=link_type)
-+
-+ def __enter__(self):
-+ msg = "Strict editable install will be performed using a link
tree.\n"
-+ _logger.warning(msg + _STRICT_WARNING)
-+ return self
-+
-+ def __exit__(self, _exc_type, _exc_value, _traceback):
-+ msg = f"""\n
-+ Strict editable installation performed using the auxiliary
directory:
-+ {self.auxiliary_dir}
-+
-+ Please be careful to not remove this directory, otherwise you might
not be able
-+ to import/use your package.
-+ """
-+ InformationOnly.emit("Editable installation.", msg)
-+
-+
-+class _TopLevelFinder:
-+ def __init__(self, dist: Distribution, name: str):
-+ self.dist = dist
-+ self.name = name
-+
-+ def __call__(self, wheel: "WheelFile", files: List[str], mapping:
Dict[str, str]):
-+ src_root = self.dist.src_root or os.curdir
-+ top_level = chain(_find_packages(self.dist),
_find_top_level_modules(self.dist))
-+ package_dir = self.dist.package_dir or {}
-+ roots = _find_package_roots(top_level, package_dir, src_root)
-+
-+ namespaces_: Dict[str, List[str]] = dict(chain(
-+ _find_namespaces(self.dist.packages or [], roots),
-+ ((ns, []) for ns in _find_virtual_namespaces(roots)),
-+ ))
-+
-+ name = f"__editable__.{self.name}.finder"
-+ finder = _normalization.safe_identifier(name)
-+ content = bytes(_finder_template(name, roots, namespaces_), "utf-8")
-+ wheel.writestr(f"{finder}.py", content)
-+
-+ content = bytes(f"import {finder}; {finder}.install()", "utf-8")
-+ wheel.writestr(f"__editable__.{self.name}.pth", content)
-+
-+ def __enter__(self):
-+ msg = "Editable install will be performed using a meta path
finder.\n"
-+ _logger.warning(msg + _LENIENT_WARNING)
-+ return self
-+
-+ def __exit__(self, _exc_type, _exc_value, _traceback):
-+ msg = """\n
-+ Please be careful with folders in your working directory with the
same
-+ name as your package as they may take precedence during imports.
-+ """
-+ InformationOnly.emit("Editable installation.", msg)
-+
-+
-+def _can_symlink_files(base_dir: Path) -> bool:
-+ with TemporaryDirectory(dir=str(base_dir.resolve())) as tmp:
-+ path1, path2 = Path(tmp, "file1.txt"), Path(tmp, "file2.txt")
-+ path1.write_text("file1", encoding="utf-8")
-+ with suppress(AttributeError, NotImplementedError, OSError):
-+ os.symlink(path1, path2)
-+ if path2.is_symlink() and path2.read_text(encoding="utf-8") ==
"file1":
-+ return True
-+
-+ try:
-+ os.link(path1, path2) # Ensure hard links can be created
-+ except Exception as ex:
-+ msg = (
-+ "File system does not seem to support either symlinks or
hard links. "
-+ "Strict editable installs require one of them to be
supported."
-+ )
-+ raise LinksNotSupported(msg) from ex
-+ return False
-+
-+
-+def _simple_layout(
-+ packages: Iterable[str], package_dir: Dict[str, str], project_dir: Path
-+) -> bool:
-+ """Return ``True`` if:
-+ - all packages are contained by the same parent directory, **and**
-+ - all packages become importable if the parent directory is added to
``sys.path``.
-+
-+ >>> _simple_layout(['a'], {"": "src"}, "/tmp/myproj")
-+ True
-+ >>> _simple_layout(['a', 'a.b'], {"": "src"}, "/tmp/myproj")
-+ True
-+ >>> _simple_layout(['a', 'a.b'], {}, "/tmp/myproj")
-+ True
-+ >>> _simple_layout(['a', 'a.a1', 'a.a1.a2', 'b'], {"": "src"},
"/tmp/myproj")
-+ True
-+ >>> _simple_layout(['a', 'a.a1', 'a.a1.a2', 'b'], {"a": "a", "b": "b"},
".")
-+ True
-+ >>> _simple_layout(['a', 'a.a1', 'a.a1.a2', 'b'], {"a": "_a", "b":
"_b"}, ".")
-+ False
-+ >>> _simple_layout(['a', 'a.a1', 'a.a1.a2', 'b'], {"a": "_a"},
"/tmp/myproj")
-+ False
-+ >>> _simple_layout(['a', 'a.a1', 'a.a1.a2', 'b'], {"a.a1.a2": "_a2"},
".")
-+ False
-+ >>> _simple_layout(['a', 'a.b'], {"": "src", "a.b": "_ab"},
"/tmp/myproj")
-+ False
-+ >>> # Special cases, no packages yet:
-+ >>> _simple_layout([], {"": "src"}, "/tmp/myproj")
-+ True
-+ >>> _simple_layout([], {"a": "_a", "": "src"}, "/tmp/myproj")
-+ False
-+ """
-+ layout = {
-+ pkg: find_package_path(pkg, package_dir, project_dir)
-+ for pkg in packages
-+ }
-+ if not layout:
-+ return set(package_dir) in ({}, {""})
-+ parent = os.path.commonpath([_parent_path(k, v) for k, v in
layout.items()])
-+ return all(
-+ _path.same_path(Path(parent, *key.split('.')), value)
-+ for key, value in layout.items()
-+ )
-+
-+
-+def _parent_path(pkg, pkg_path):
-+ """Infer the parent path containing a package, that if added to
``sys.path`` would
-+ allow importing that package.
-+ When ``pkg`` is directly mapped into a directory with a different name,
return its
-+ own path.
-+ >>> _parent_path("a", "src/a")
-+ 'src'
-+ >>> _parent_path("b", "src/c")
-+ 'src/c'
-+ """
-+ parent = pkg_path[:-len(pkg)] if pkg_path.endswith(pkg) else pkg_path
-+ return parent.rstrip("/" + os.sep)
-+
-+
-+def _find_packages(dist: Distribution) -> Iterator[str]:
-+ yield from iter(dist.packages or [])
-+
-+ py_modules = dist.py_modules or []
-+ nested_modules = [mod for mod in py_modules if "." in mod]
-+ if dist.ext_package:
-+ yield dist.ext_package
-+ else:
-+ ext_modules = dist.ext_modules or []
-+ nested_modules += [x.name for x in ext_modules if "." in x.name]
-+
-+ for module in nested_modules:
-+ package, _, _ = module.rpartition(".")
-+ yield package
-+
-+
-+def _find_top_level_modules(dist: Distribution) -> Iterator[str]:
-+ py_modules = dist.py_modules or []
-+ yield from (mod for mod in py_modules if "." not in mod)
-+
-+ if not dist.ext_package:
-+ ext_modules = dist.ext_modules or []
-+ yield from (x.name for x in ext_modules if "." not in x.name)
-+
-+
-+def _find_package_roots(
-+ packages: Iterable[str],
-+ package_dir: Mapping[str, str],
-+ src_root: _Path,
-+) -> Dict[str, str]:
-+ pkg_roots: Dict[str, str] = {
-+ pkg: _absolute_root(find_package_path(pkg, package_dir, src_root))
-+ for pkg in sorted(packages)
-+ }
-+
-+ return _remove_nested(pkg_roots)
-+
-+
-+def _absolute_root(path: _Path) -> str:
-+ """Works for packages and top-level modules"""
-+ path_ = Path(path)
-+ parent = path_.parent
-+
-+ if path_.exists():
-+ return str(path_.resolve())
-+ else:
-+ return str(parent.resolve() / path_.name)
-+
-+
-+def _find_virtual_namespaces(pkg_roots: Dict[str, str]) -> Iterator[str]:
-+ """By carefully designing ``package_dir``, it is possible to implement
the logical
-+ structure of PEP 420 in a package without the corresponding directories.
-+
-+ Moreover a parent package can be purposefully/accidentally skipped in
the discovery
-+ phase (e.g. ``find_packages(include=["mypkg.*"])``, when ``mypkg.foo``
is included
-+ by ``mypkg`` itself is not).
-+ We consider this case to also be a virtual namespace (ignoring the
original
-+ directory) to emulate a non-editable installation.
-+
-+ This function will try to find these kinds of namespaces.
-+ """
-+ for pkg in pkg_roots:
-+ if "." not in pkg:
-+ continue
-+ parts = pkg.split(".")
-+ for i in range(len(parts) - 1, 0, -1):
-+ partial_name = ".".join(parts[:i])
-+ path = Path(find_package_path(partial_name, pkg_roots, ""))
-+ if not path.exists() or partial_name not in pkg_roots:
-+ # partial_name not in pkg_roots ==>
purposefully/accidentally skipped
-+ yield partial_name
-+
-+
-+def _find_namespaces(
-+ packages: List[str], pkg_roots: Dict[str, str]
-+) -> Iterator[Tuple[str, List[str]]]:
-+ for pkg in packages:
-+ path = find_package_path(pkg, pkg_roots, "")
-+ if Path(path).exists() and not Path(path, "__init__.py").exists():
-+ yield (pkg, [path])
-+
-+
-+def _remove_nested(pkg_roots: Dict[str, str]) -> Dict[str, str]:
-+ output = dict(pkg_roots.copy())
-+
-+ for pkg, path in reversed(list(pkg_roots.items())):
-+ if any(
-+ pkg != other and _is_nested(pkg, path, other, other_path)
-+ for other, other_path in pkg_roots.items()
-+ ):
-+ output.pop(pkg)
-+
-+ return output
-+
-+
-+def _is_nested(pkg: str, pkg_path: str, parent: str, parent_path: str) ->
bool:
-+ """
-+ Return ``True`` if ``pkg`` is nested inside ``parent`` both logically
and in the
-+ file system.
-+ >>> _is_nested("a.b", "path/a/b", "a", "path/a")
-+ True
-+ >>> _is_nested("a.b", "path/a/b", "a", "otherpath/a")
-+ False
-+ >>> _is_nested("a.b", "path/a/b", "c", "path/c")
-+ False
-+ >>> _is_nested("a.a", "path/a/a", "a", "path/a")
-+ True
-+ >>> _is_nested("b.a", "path/b/a", "a", "path/a")
-+ False
-+ """
-+ norm_pkg_path = _path.normpath(pkg_path)
-+ rest = pkg.replace(parent, "", 1).strip(".").split(".")
-+ return (
-+ pkg.startswith(parent)
-+ and norm_pkg_path == _path.normpath(Path(parent_path, *rest))
-+ )
-+
-+
-+def _empty_dir(dir_: _P) -> _P:
-+ """Create a directory ensured to be empty. Existing files may be
removed."""
-+ shutil.rmtree(dir_, ignore_errors=True)
-+ os.makedirs(dir_)
-+ return dir_
-+
-+
-+class _NamespaceInstaller(namespaces.Installer):
-+ def __init__(self, distribution, installation_dir, editable_name,
src_root):
-+ self.distribution = distribution
-+ self.src_root = src_root
-+ self.installation_dir = installation_dir
-+ self.editable_name = editable_name
-+ self.outputs = []
-+ self.dry_run = False
-+
-+ def _get_target(self):
-+ """Installation target."""
-+ return os.path.join(self.installation_dir, self.editable_name)
-+
-+ def _get_root(self):
-+ """Where the modules/packages should be loaded from."""
-+ return repr(str(self.src_root))
-+
-+
-+_FINDER_TEMPLATE = """\
-+import sys
-+from importlib.machinery import ModuleSpec
-+from importlib.machinery import all_suffixes as module_suffixes
-+from importlib.util import spec_from_file_location
-+from itertools import chain
-+from pathlib import Path
-+
-+MAPPING = {mapping!r}
-+NAMESPACES = {namespaces!r}
-+PATH_PLACEHOLDER = {name!r} + ".__path_hook__"
-+
-+
-+class _EditableFinder: # MetaPathFinder
-+ @classmethod
-+ def find_spec(cls, fullname, path=None, target=None):
-+ for pkg, pkg_path in reversed(list(MAPPING.items())):
-+ if fullname == pkg or fullname.startswith(f"{{pkg}}."):
-+ rest = fullname.replace(pkg, "", 1).strip(".").split(".")
-+ return cls._find_spec(fullname, Path(pkg_path, *rest))
-+
-+ return None
-+
-+ @classmethod
-+ def _find_spec(cls, fullname, candidate_path):
-+ init = candidate_path / "__init__.py"
-+ candidates = (candidate_path.with_suffix(x) for x in
module_suffixes())
-+ for candidate in chain([init], candidates):
-+ if candidate.exists():
-+ return spec_from_file_location(fullname, candidate)
-+
-+
-+class _EditableNamespaceFinder: # PathEntryFinder
-+ @classmethod
-+ def _path_hook(cls, path):
-+ if path == PATH_PLACEHOLDER:
-+ return cls
-+ raise ImportError
-+
-+ @classmethod
-+ def _paths(cls, fullname):
-+ # Ensure __path__ is not empty for the spec to be considered a
namespace.
-+ return NAMESPACES[fullname] or MAPPING.get(fullname) or
[PATH_PLACEHOLDER]
-+
-+ @classmethod
-+ def find_spec(cls, fullname, target=None):
-+ if fullname in NAMESPACES:
-+ spec = ModuleSpec(fullname, None, is_package=True)
-+ spec.submodule_search_locations = cls._paths(fullname)
-+ return spec
-+ return None
-+
-+ @classmethod
-+ def find_module(cls, fullname):
-+ return None
-+
-+
-+def install():
-+ if not any(finder == _EditableFinder for finder in sys.meta_path):
-+ sys.meta_path.append(_EditableFinder)
-+
-+ if not NAMESPACES:
-+ return
-+
-+ if not any(hook == _EditableNamespaceFinder._path_hook for hook in
sys.path_hooks):
-+ # PathEntryFinder is needed to create NamespaceSpec without private
APIS
-+ sys.path_hooks.append(_EditableNamespaceFinder._path_hook)
-+ if PATH_PLACEHOLDER not in sys.path:
-+ sys.path.append(PATH_PLACEHOLDER) # Used just to trigger the path
hook
-+"""
-+
-+
-+def _finder_template(
-+ name: str, mapping: Mapping[str, str], namespaces: Dict[str, List[str]]
-+) -> str:
-+ """Create a string containing the code for the``MetaPathFinder`` and
-+ ``PathEntryFinder``.
-+ """
-+ mapping = dict(sorted(mapping.items(), key=lambda p: p[0]))
-+ return _FINDER_TEMPLATE.format(name=name, mapping=mapping,
namespaces=namespaces)
-+
-+
-+class LinksNotSupported(errors.FileError):
-+ """File system does not seem to support either symlinks or hard
links."""
-+
-+
-+class _DebuggingTips(SetuptoolsWarning):
-+ _SUMMARY = "Problem in editable installation."
-+ _DETAILS = """
-+ An error happened while installing `{project}` in editable mode.
-+
-+ The following steps are recommended to help debug this problem:
-+
-+ - Try to install the project normally, without using the editable mode.
-+ Does the error still persist?
-+ (If it does, try fixing the problem before attempting the editable
mode).
-+ - If you are using binary extensions, make sure you have all OS-level
-+ dependencies installed (e.g. compilers, toolchains, binary libraries,
...).
-+ - Try the latest version of setuptools (maybe the error was already
fixed).
-+ - If you (or your project dependencies) are using any setuptools
extension
-+ or customization, make sure they support the editable mode.
-+
-+ After following the steps above, if the problem still persists and
-+ you think this is related to how setuptools handles editable
installations,
-+ please submit a reproducible example
-+ (see https://stackoverflow.com/help/minimal-reproducible-example) to:
-+
-+ https://github.com/pypa/setuptools/issues
-+ """
-+ _SEE_DOCS = "userguide/development_mode.html"
-diff --git a/third_party/python/setuptools/setuptools/command/egg_info.py
b/third_party/python/setuptools/setuptools/command/egg_info.py
---- a/third_party/python/setuptools/setuptools/command/egg_info.py
-+++ b/third_party/python/setuptools/setuptools/command/egg_info.py
-@@ -3,40 +3,44 @@
- 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 .._importlib import metadata
-+from .. import _entry_points, _normalization
-+
- 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
-+from setuptools.extern.jaraco.text import yield_lines
-+from ..warnings import SetuptoolsDeprecationWarning
-
-
--def translate_pattern(glob):
-+PY_MAJOR = '{}.{}'.format(*sys.version_info)
-+
-+
-+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 = ''
-
-@@ -115,37 +119,48 @@ def translate_pattern(glob):
-
-
- class InfoCommon:
- tag_build = None
- tag_date = None
-
- @property
- def name(self):
-- return safe_name(self.distribution.get_name())
-+ return _normalization.safe_name(self.distribution.get_name())
-
- def tagged_version(self):
-- return
safe_version(self._maybe_tag(self.distribution.get_version()))
-+ tagged = self._maybe_tag(self.distribution.get_version())
-+ return _normalization.best_effort_version(tagged)
-
- 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)
-+ version if self.vtags and self._already_tagged(version)
- else version + self.vtags
- )
-
-- def tags(self):
-+ def _already_tagged(self, version: str) -> bool:
-+ # Depending on their format, tags may change with version
normalization.
-+ # So in addition the regular tags, we have to search for the
normalized ones.
-+ return version.endswith(self.vtags) or
version.endswith(self._safe_tags())
-+
-+ def _safe_tags(self) -> str:
-+ # To implement this we can rely on `safe_version` pretending to be
version 0
-+ # followed by tags. Then we simply discard the starting 0 (fake
version number)
-+ return _normalization.best_effort_version(f"0{self.vtags}")[1:]
-+
-+ def tags(self) -> str:
- version = ''
- if self.tag_build:
- version += self.tag_build
- if self.tag_date:
-- version += time.strftime("-%Y%m%d")
-+ 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 = [
-@@ -161,17 +176,17 @@ class egg_info(InfoCommon, Command):
- '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
-+ self.ignore_egg_info_in_manifest = False
-
- ####################################
- # allow the 'tag_svn_revision' to be detected and
- # set, supporting sdists built on older Setuptools.
- @property
- def tag_svn_revision(self):
- pass
-
-@@ -195,58 +210,57 @@ class egg_info(InfoCommon, Command):
-
- 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)
-+ parsed_version = packaging.version.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))
-- )
-+ spec = "%s==%s" if is_version else "%s===%s"
-+ packaging.requirements.Requirement(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'
-+ self.egg_info = _normalization.filename_component(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():
-+ key = getattr(pd, "key", None) or getattr(pd, "name", None)
-+ if pd is not None and key == self.egg_name.lower():
- pd._version = self.egg_version
-- pd._parsed_version = parse_version(self.egg_version)
-+ pd._parsed_version = packaging.version.Version(self.egg_version)
- self.distribution._patched_dist = None
-
-+ def _get_egg_basename(self, py_version=PY_MAJOR, platform=None):
-+ """Compute filename of the output egg. Private API."""
-+ return _egg_basename(self.egg_name, self.egg_version, py_version,
platform)
-+
- 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).
-@@ -278,129 +292,125 @@ class egg_info(InfoCommon, Command):
- 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()
-+ try:
-+ os.utime(self.egg_info, None)
-+ except OSError as e:
-+ msg = f"Cannot update time stamp of directory '{self.egg_info}'"
-+ raise distutils.errors.DistutilsFileError(msg) from e
-+ for ep in metadata.entry_points(group='egg_info.writers'):
-+ writer = ep.load()
- 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.ignore_egg_info_dir = self.ignore_egg_info_in_manifest
- 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 __init__(self, warn=None, debug_print=None,
ignore_egg_info_dir=False):
-+ super().__init__(warn, debug_print)
-+ self.ignore_egg_info_dir = ignore_egg_info_dir
-+
- 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.
-- if action == 'include':
-- self.debug_print("include " + ' '.join(patterns))
-- for pattern in patterns:
-- if not self.include(pattern):
-- 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):
-- 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.global_include(pattern):
-- 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.global_exclude(pattern):
-- log.warn(("warning: no previously-included files
matching "
-- "'%s' found anywhere in distribution"),
-- pattern)
-+ 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]
-
-- elif action == 'recursive-include':
-- self.debug_print("recursive-include %s %s" %
-- (dir, ' '.join(patterns)))
-- for pattern in patterns:
-- if not self.recursive_include(dir, pattern):
-- log.warn(("warning: no files found matching '%s' "
-- "under directory '%s'"),
-- pattern, dir)
--
-- elif action == 'recursive-exclude':
-- self.debug_print("recursive-exclude %s %s" %
-- (dir, ' '.join(patterns)))
-- for pattern in patterns:
-- if not self.recursive_exclude(dir, pattern):
-- 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.graft(dir_pattern):
-- log.warn("warning: no directories found matching '%s'",
-- dir_pattern)
--
-- elif action == 'prune':
-- self.debug_print("prune " + dir_pattern)
-- if not self.prune(dir_pattern):
-- log.warn(("no previously-included directories found "
-- "matching '%s'"), dir_pattern)
--
-- else:
-- raise DistutilsInternalError(
-- "this cannot happen: invalid action '%s'" % 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):
-@@ -504,43 +514,50 @@ class FileList(_FileList):
-
- # 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:
-+ # ignore egg-info paths
-+ is_egg_info = ".egg-info" in u_path or b".egg-info" in utf8_path
-+ if self.ignore_egg_info_dir and is_egg_info:
-+ return False
- # 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
-+ self.ignore_egg_info_dir = False
-
- def finalize_options(self):
- pass
-
- def run(self):
-- self.filelist = FileList()
-+ self.filelist =
FileList(ignore_egg_info_dir=self.ignore_egg_info_dir)
- 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._add_referenced_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, '/')
-@@ -565,42 +582,79 @@ class manifest_maker(sdist):
- 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.check_license()
- 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)
-+ self.filelist.extend(license_files)
-+
-+ def _add_referenced_files(self):
-+ """Add files referenced by the config (e.g. `file:` directive) to
filelist"""
-+ referenced = getattr(self.distribution, '_referenced_files', [])
-+ # ^-- fallback if dist comes from distutils or is a custom class
-+ for rf in referenced:
-+ log.debug("adding file referenced by config '%s'", rf)
-+ self.filelist.extend(referenced)
-+
- 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()
-+
-+ SetuptoolsDeprecationWarning.emit(
-+ "`build_py` command does not inherit from setuptools'
`build_py`.",
-+ """
-+ Custom 'build_py' does not implement
'get_data_files_without_manifest'.
-+ Please extend command classes from setuptools instead of
distutils.
-+ """,
-+ see_url="https://peps.python.org/pep-0632/";,
-+ # due_date not defined yet, old projects might still do it?
-+ )
-+ 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
-@@ -625,21 +679,23 @@ def write_pkg_info(cmd, basename, filena
- 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."
-- )
-+ """
-+ Unused: left to avoid errors when updating (from source) from <= 67.8.
-+ Old installations have a .dist-info directory with the entry-point
-+ ``depends.txt = setuptools.command.egg_info:warn_depends_obsolete``.
-+ This may trigger errors when running the first egg_info in build_meta.
-+ TODO: Remove this function in a version sufficiently > 68.
-+ """
-
-
- def _write_requirements(stream, reqs):
- lines = yield_lines(reqs or ())
-
- def append_cr(line):
- return line + '\n'
- lines = map(append_cr, lines)
-@@ -681,42 +737,25 @@ def write_arg(cmd, basename, filename, f
- 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)
-+ eps = _entry_points.load(cmd.distribution.entry_points)
-+ defn = _entry_points.render(eps)
-+ cmd.write_or_delete_file('entry points', filename, defn, 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
-+def _egg_basename(egg_name, egg_version, py_version=None, platform=None):
-+ """Compute filename of the output egg. Private API."""
-+ name = _normalization.filename_component(egg_name)
-+ version = _normalization.filename_component(egg_version)
-+ egg = f"{name}-{version}-py{py_version or PY_MAJOR}"
-+ if platform:
-+ egg += f"-{platform}"
-+ return egg
-
-
- class EggInfoDeprecationWarning(SetuptoolsDeprecationWarning):
- """Deprecated behavior warning for EggInfo, bypassing suppression."""
-diff --git a/third_party/python/setuptools/setuptools/command/install.py
b/third_party/python/setuptools/setuptools/command/install.py
---- a/third_party/python/setuptools/setuptools/command/install.py
-+++ b/third_party/python/setuptools/setuptools/command/install.py
-@@ -1,16 +1,16 @@
- from distutils.errors import DistutilsArgError
- import inspect
- import glob
--import warnings
- import platform
- import distutils.command.install as orig
-
- import setuptools
-+from ..warnings import SetuptoolsDeprecationWarning, SetuptoolsWarning
-
- # 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"""
-@@ -25,16 +25,29 @@ class install(orig.install):
- ]
- new_commands = [
- ('install_egg_info', lambda self: True),
- ('install_scripts', lambda self: True),
- ]
- _nc = dict(new_commands)
-
- def initialize_options(self):
-+ SetuptoolsDeprecationWarning.emit(
-+ "setup.py install is deprecated.",
-+ """
-+ Please avoid running ``setup.py`` directly.
-+ Instead, use pypa/build, pypa/installer or other
-+ standards-based tools.
-+ """,
-+
see_url="https://blog.ganssle.io/articles/2021/10/setup-py-deprecated.html";,
-+ # TODO: Document how to bootstrap setuptools without install
-+ # (e.g. by unziping the wheel file)
-+ # and then add a due_date to this warning.
-+ )
-+
- 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
-@@ -74,29 +87,36 @@ class install(orig.install):
- '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)
-+ SetuptoolsWarning.emit(msg)
- if platform.python_implementation() == 'IronPython':
- msg = "For best results, pass -X:Frames to enable call
stack."
-- warnings.warn(msg)
-+ SetuptoolsWarning.emit(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'
-- )
-+
-+ frames = inspect.getouterframes(run_frame)
-+ for frame in frames[2:4]:
-+ caller, = frame[:1]
-+ info = inspect.getframeinfo(caller)
-+ caller_module = caller.f_globals.get('__name__', '')
-+
-+ if caller_module == "setuptools.dist" and info.function ==
"run_command":
-+ # Starting from v61.0.0 setuptools overwrites
dist.run_command
-+ continue
-+
-+ 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,
- )
-diff --git
a/third_party/python/setuptools/setuptools/command/install_egg_info.py
b/third_party/python/setuptools/setuptools/command/install_egg_info.py
---- a/third_party/python/setuptools/setuptools/command/install_egg_info.py
-+++ b/third_party/python/setuptools/setuptools/command/install_egg_info.py
-@@ -1,15 +1,15 @@
- from distutils import log, dir_util
- import os
-
- from setuptools import Command
- from setuptools import namespaces
- from setuptools.archive_util import unpack_archive
--import pkg_resources
-+from .._path import ensure_directory
-
-
- 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 = [
-@@ -18,31 +18,29 @@ class install_egg_info(namespaces.Instal
-
- def initialize_options(self):
- self.install_dir = None
-
- def finalize_options(self):
- self.set_undefined_options('install_lib',
- ('install_dir', 'install_dir'))
- 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'
-+ basename = f"{ei_cmd._get_egg_basename()}.egg-info"
- 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)
-+ 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
-
-diff --git
a/third_party/python/setuptools/setuptools/command/install_scripts.py
b/third_party/python/setuptools/setuptools/command/install_scripts.py
---- a/third_party/python/setuptools/setuptools/command/install_scripts.py
-+++ b/third_party/python/setuptools/setuptools/command/install_scripts.py
-@@ -1,46 +1,51 @@
- 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
-+from .._path import 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
-+ self._install_ep_scripts()
-+
-+ def _install_ep_scripts(self):
-+ # Delay import side-effects
-+ from pkg_resources import Distribution, PathMetadata
-+ from . import easy_install as ei
-
- 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:
-+ 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.
-diff --git a/third_party/python/setuptools/setuptools/command/py36compat.py
b/third_party/python/setuptools/setuptools/command/py36compat.py
-deleted file mode 100644
---- a/third_party/python/setuptools/setuptools/command/py36compat.py
-+++ /dev/null
-@@ -1,134 +0,0 @@
--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/third_party/python/setuptools/setuptools/command/sdist.py
b/third_party/python/setuptools/setuptools/command/sdist.py
---- a/third_party/python/setuptools/setuptools/command/sdist.py
-+++ b/third_party/python/setuptools/setuptools/command/sdist.py
-@@ -1,43 +1,45 @@
- from distutils import log
- import distutils.command.sdist as orig
- import os
- import sys
- import io
- import contextlib
--
--from setuptools.extern import ordered_set
-+from itertools import chain
-
--from .py36compat import sdist_add_defaults
--
--import pkg_resources
-+from .._importlib import metadata
-+from .build import _ORIGINAL_SUBCOMMANDS
-
- _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 ep in metadata.entry_points(group='setuptools.file_finders'):
- for item in ep.load()(dirname):
- yield item
-
-
--class sdist(sdist_add_defaults, orig.sdist):
-+class sdist(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):
-@@ -93,36 +95,51 @@ class sdist(sdist_add_defaults, orig.sdi
- except Exception:
- pass
- try:
- yield
- finally:
- if orig_val is not NoValue:
- setattr(os, 'link', orig_val)
-
-+ def add_defaults(self):
-+ super().add_defaults()
-+ self._add_defaults_build_sub_commands()
-+
- 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 _add_defaults_build_sub_commands(self):
-+ build = self.get_finalized_command("build")
-+ missing_cmds = set(build.get_sub_commands()) - _ORIGINAL_SUBCOMMANDS
-+ # ^-- the original built-in sub-commands are already handled by
default.
-+ cmds = (self.get_finalized_command(c) for c in missing_cmds)
-+ files = (c.get_source_files() for c in cmds if hasattr(c,
"get_source_files"))
-+ self.filelist.extend(chain.from_iterable(files))
-+
- def _safe_data_files(self, build_py):
- """
-- Extracting data_files from build_py is known to cause
-- infinite recursion errors when `include_package_data`
-- is enabled, so suppress it in that case.
-+ 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.
- """
-- if self.distribution.include_package_data:
-- return ()
- 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)
-@@ -184,39 +201,8 @@ class sdist(sdist_add_defaults, orig.sdi
- 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()
--
-- def check_license(self):
-- """Checks if license_file' or 'license_files' is configured and
adds any
-- valid paths to 'self.filelist'.
-- """
--
-- files = ordered_set.OrderedSet()
--
-- opts = self.distribution.get_option_dict('metadata')
--
-- # ignore the source of the value
-- _, license_file = opts.get('license_file', (None, None))
--
-- if license_file is None:
-- log.debug("'license_file' option was not specified")
-- else:
-- files.add(license_file)
--
-- try:
-- files.update(self.distribution.metadata.license_files)
-- except TypeError:
-- log.warn("warning: 'license_files' option is malformed")
--
-- for f in files:
-- if not os.path.exists(f):
-- log.warn(
-- "warning: Failed to find the configured license file
'%s'",
-- f)
-- files.remove(f)
--
-- self.filelist.extend(files)
-diff --git a/third_party/python/setuptools/setuptools/command/setopt.py
b/third_party/python/setuptools/setuptools/command/setopt.py
---- a/third_party/python/setuptools/setuptools/command/setopt.py
-+++ b/third_party/python/setuptools/setuptools/command/setopt.py
-@@ -34,16 +34,17 @@ def edit_config(filename, settings, dry_
-
- `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)
-diff --git a/third_party/python/setuptools/setuptools/command/test.py
b/third_party/python/setuptools/setuptools/command/test.py
---- a/third_party/python/setuptools/setuptools/command/test.py
-+++ b/third_party/python/setuptools/setuptools/command/test.py
-@@ -3,25 +3,32 @@ 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, _namespace_packages,
evaluate_marker,
-- add_activation_listener, require, EntryPoint)
-+from pkg_resources import (
-+ resource_listdir,
-+ resource_exists,
-+ normalize_path,
-+ working_set,
-+ evaluate_marker,
-+ add_activation_listener,
-+ require,
-+)
-+from .._importlib import metadata
- from setuptools import Command
--from .build_py import _unique_everseen
-+from setuptools.extern.more_itertools import unique_everseen
-+from setuptools.extern.jaraco.functools import pass_none
-
-
- 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.
-@@ -68,18 +75,21 @@ class NonDataProperty:
-
- 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-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
-@@ -103,56 +113,37 @@ class test(Command):
- 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):
-+ if not self.test_suite:
- 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=[]):
-- with_2to3 = getattr(self.distribution, 'use_2to3', False)
--
-- if with_2to3:
-- # If we run 2to3 we can not do this inplace:
--
-- # Ensure metadata is up-to-date
-- self.reinitialize_command('build_py', inplace=0)
-- self.run_command('build_py')
-- bpy_cmd = self.get_finalized_command("build_py")
-- build_path = normalize_path(bpy_cmd.build_lib)
-+ self.run_command('egg_info')
-
-- # Build extensions
-- self.reinitialize_command('egg_info', egg_base=build_path)
-- self.run_command('egg_info')
--
-- self.reinitialize_command('build_ext', inplace=0)
-- self.run_command('build_ext')
-- else:
-- # Without 2to3 inplace works fine:
-- self.run_command('egg_info')
--
-- # Build extensions in-place
-- self.reinitialize_command('build_ext', inplace=1)
-- self.run_command('build_ext')
-+ # 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)
-@@ -177,17 +168,17 @@ class test(Command):
- 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))
-+ 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)
-@@ -198,17 +189,18 @@ class test(Command):
- 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()
-+ 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 "
-@@ -227,48 +219,33 @@ class test(Command):
- 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):
-- # Purge modules under test from sys.modules. The test loader will
-- # re-import them from the build location. Required when 2to3 is used
-- # with namespace packages.
-- if getattr(self.distribution, 'use_2to3', False):
-- module = self.test_suite.split('.')[0]
-- if module in _namespace_packages:
-- del_modules = []
-- if module in sys.modules:
-- del_modules.append(module)
-- module += '.'
-- for name in sys.modules:
-- if name.startswith(module):
-- del_modules.append(name)
-- list(map(sys.modules.__delitem__, del_modules))
--
- test = unittest.main(
-- None, None, self._argv,
-+ 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
-+ @pass_none
- 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()()
-+ return metadata.EntryPoint(value=val, name=None,
group=None).load()()
-diff --git a/third_party/python/setuptools/setuptools/command/upload_docs.py
b/third_party/python/setuptools/setuptools/command/upload_docs.py
---- a/third_party/python/setuptools/setuptools/command/upload_docs.py
-+++ b/third_party/python/setuptools/setuptools/command/upload_docs.py
-@@ -1,78 +1,81 @@
--# -*- coding: utf-8 -*-
- """upload_docs
-
- Implements a Distutils 'upload_docs' subcommand (upload documentation to
--PyPI's pythonhosted.org).
-+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 .._importlib import metadata
-+from ..warnings import SetuptoolsDeprecationWarning
-+
- 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 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
-+ return bool(
-+ self.upload_dir is None
-+ and metadata.entry_points(group='distutils.commands',
name='build_sphinx')
-+ )
-
- 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):
-+ log.warn(
-+ "Upload_docs command is deprecated. Use Read the Docs "
-+ "(https://readthedocs.org) instead.")
- 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 = build_sphinx.builder_target_dir
-+ 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. 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:
-@@ -82,16 +85,26 @@ class upload_docs(upload):
- 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):
-+ SetuptoolsDeprecationWarning.emit(
-+ "Deprecated command",
-+ """
-+ upload_docs is deprecated and will be removed in a future
version.
-+ Instead, use tools like devpi and Read the Docs; or lower level
tools like
-+ httpie and curl to interact directly with your hosting service
API.
-+ """,
-+ due_date=(2023, 9, 26), # warning introduced in 27 Jul 2022
-+ )
-+
- # 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:
-diff --git a/third_party/python/setuptools/setuptools/config.py
b/third_party/python/setuptools/setuptools/config.py
-deleted file mode 100644
---- a/third_party/python/setuptools/setuptools/config.py
-+++ /dev/null
-@@ -1,693 +0,0 @@
--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
--import contextlib
--
--from distutils.errors import DistutilsOptionError, DistutilsFileError
--from setuptools.extern.packaging.version import LegacyVersion, parse
--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_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_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
-- if isinstance(parse(version), LegacyVersion):
-- 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
--
-- return {
-- 'zip_safe': parse_bool,
-- 'use_2to3': parse_bool,
-- 'include_package_data': parse_bool,
-- 'package_dir': parse_dict,
-- 'use_2to3_fixers': parse_list,
-- 'use_2to3_exclude_fixers': parse_list,
-- 'convert_2to3_doctests': parse_list,
-- '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,
-- }
--
-- 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)
-- self['data_files'] = [(k, v) for k, v in parsed.items()]
-diff --git a/third_party/python/setuptools/setuptools/config/__init__.py
b/third_party/python/setuptools/setuptools/config/__init__.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/config/__init__.py
-@@ -0,0 +1,42 @@
-+"""For backward compatibility, expose main functions from
-+``setuptools.config.setupcfg``
-+"""
-+from functools import wraps
-+from typing import Callable, TypeVar, cast
-+
-+from ..warnings import SetuptoolsDeprecationWarning
-+from . import setupcfg
-+
-+Fn = TypeVar("Fn", bound=Callable)
-+
-+__all__ = ('parse_configuration', 'read_configuration')
-+
-+
-+def _deprecation_notice(fn: Fn) -> Fn:
-+ @wraps(fn)
-+ def _wrapper(*args, **kwargs):
-+ SetuptoolsDeprecationWarning.emit(
-+ "Deprecated API usage.",
-+ f"""
-+ As setuptools moves its configuration towards `pyproject.toml`,
-+ `{__name__}.{fn.__name__}` became deprecated.
-+
-+ For the time being, you can use the `{setupcfg.__name__}` module
-+ to access a backward compatible API, but this module is
provisional
-+ and might be removed in the future.
-+
-+ To read project metadata, consider using
-+ ``build.util.project_wheel_metadata``
(https://pypi.org/project/build/).
-+ For simple scenarios, you can also try parsing the file directly
-+ with the help of ``configparser``.
-+ """,
-+ # due_date not defined yet, because the community still heavily
relies on it
-+ # Warning introduced in 24 Mar 2022
-+ )
-+ return fn(*args, **kwargs)
-+
-+ return cast(Fn, _wrapper)
-+
-+
-+read_configuration = _deprecation_notice(setupcfg.read_configuration)
-+parse_configuration = _deprecation_notice(setupcfg.parse_configuration)
-diff --git
a/third_party/python/setuptools/setuptools/config/_apply_pyprojecttoml.py
b/third_party/python/setuptools/setuptools/config/_apply_pyprojecttoml.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/config/_apply_pyprojecttoml.py
-@@ -0,0 +1,386 @@
-+"""Translation layer between pyproject config and setuptools distribution
and
-+metadata objects.
-+
-+The distribution and metadata objects are modeled after (an old version of)
-+core metadata, therefore configs in the format specified for
``pyproject.toml``
-+need to be processed before being applied.
-+
-+**PRIVATE MODULE**: API reserved for setuptools internal usage only.
-+"""
-+import logging
-+import os
-+from collections.abc import Mapping
-+from email.headerregistry import Address
-+from functools import partial, reduce
-+from itertools import chain
-+from types import MappingProxyType
-+from typing import (TYPE_CHECKING, Any, Callable, Dict, List, Optional,
Set, Tuple,
-+ Type, Union, cast)
-+
-+from ..warnings import SetuptoolsWarning, SetuptoolsDeprecationWarning
-+
-+if TYPE_CHECKING:
-+ from setuptools._importlib import metadata # noqa
-+ from setuptools.dist import Distribution # noqa
-+
-+EMPTY: Mapping = MappingProxyType({}) # Immutable dict-like
-+_Path = Union[os.PathLike, str]
-+_DictOrStr = Union[dict, str]
-+_CorrespFn = Callable[["Distribution", Any, _Path], None]
-+_Correspondence = Union[str, _CorrespFn]
-+
-+_logger = logging.getLogger(__name__)
-+
-+
-+def apply(dist: "Distribution", config: dict, filename: _Path) ->
"Distribution":
-+ """Apply configuration dict read with :func:`read_configuration`"""
-+
-+ if not config:
-+ return dist # short-circuit unrelated pyproject.toml file
-+
-+ root_dir = os.path.dirname(filename) or "."
-+
-+ _apply_project_table(dist, config, root_dir)
-+ _apply_tool_table(dist, config, filename)
-+
-+ current_directory = os.getcwd()
-+ os.chdir(root_dir)
-+ try:
-+ dist._finalize_requires()
-+ dist._finalize_license_files()
-+ finally:
-+ os.chdir(current_directory)
-+
-+ return dist
-+
-+
-+def _apply_project_table(dist: "Distribution", config: dict, root_dir:
_Path):
-+ project_table = config.get("project", {}).copy()
-+ if not project_table:
-+ return # short-circuit
-+
-+ _handle_missing_dynamic(dist, project_table)
-+ _unify_entry_points(project_table)
-+
-+ for field, value in project_table.items():
-+ norm_key = json_compatible_key(field)
-+ corresp = PYPROJECT_CORRESPONDENCE.get(norm_key, norm_key)
-+ if callable(corresp):
-+ corresp(dist, value, root_dir)
-+ else:
-+ _set_config(dist, corresp, value)
-+
-+
-+def _apply_tool_table(dist: "Distribution", config: dict, filename: _Path):
-+ tool_table = config.get("tool", {}).get("setuptools", {})
-+ if not tool_table:
-+ return # short-circuit
-+
-+ for field, value in tool_table.items():
-+ norm_key = json_compatible_key(field)
-+
-+ if norm_key in TOOL_TABLE_DEPRECATIONS:
-+ suggestion, kwargs = TOOL_TABLE_DEPRECATIONS[norm_key]
-+ msg = f"The parameter `{norm_key}` is deprecated, {suggestion}"
-+ SetuptoolsDeprecationWarning.emit(
-+ "Deprecated config", msg, **kwargs # type: ignore
-+ )
-+
-+ norm_key = TOOL_TABLE_RENAMES.get(norm_key, norm_key)
-+ _set_config(dist, norm_key, value)
-+
-+ _copy_command_options(config, dist, filename)
-+
-+
-+def _handle_missing_dynamic(dist: "Distribution", project_table: dict):
-+ """Be temporarily forgiving with ``dynamic`` fields not listed in
``dynamic``"""
-+ # TODO: Set fields back to `None` once the feature stabilizes
-+ dynamic = set(project_table.get("dynamic", []))
-+ for field, getter in _PREVIOUSLY_DEFINED.items():
-+ if not (field in project_table or field in dynamic):
-+ value = getter(dist)
-+ if value:
-+ _WouldIgnoreField.emit(field=field, value=value)
-+
-+
-+def json_compatible_key(key: str) -> str:
-+ """As defined in :pep:`566#json-compatible-metadata`"""
-+ return key.lower().replace("-", "_")
-+
-+
-+def _set_config(dist: "Distribution", field: str, value: Any):
-+ setter = getattr(dist.metadata, f"set_{field}", None)
-+ if setter:
-+ setter(value)
-+ elif hasattr(dist.metadata, field) or field in SETUPTOOLS_PATCHES:
-+ setattr(dist.metadata, field, value)
-+ else:
-+ setattr(dist, field, value)
-+
-+
-+_CONTENT_TYPES = {
-+ ".md": "text/markdown",
-+ ".rst": "text/x-rst",
-+ ".txt": "text/plain",
-+}
-+
-+
-+def _guess_content_type(file: str) -> Optional[str]:
-+ _, ext = os.path.splitext(file.lower())
-+ if not ext:
-+ return None
-+
-+ if ext in _CONTENT_TYPES:
-+ return _CONTENT_TYPES[ext]
-+
-+ valid = ", ".join(f"{k} ({v})" for k, v in _CONTENT_TYPES.items())
-+ msg = f"only the following file extensions are recognized: {valid}."
-+ raise ValueError(f"Undefined content type for {file}, {msg}")
-+
-+
-+def _long_description(dist: "Distribution", val: _DictOrStr, root_dir:
_Path):
-+ from setuptools.config import expand
-+
-+ if isinstance(val, str):
-+ file: Union[str, list] = val
-+ text = expand.read_files(file, root_dir)
-+ ctype = _guess_content_type(val)
-+ else:
-+ file = val.get("file") or []
-+ text = val.get("text") or expand.read_files(file, root_dir)
-+ ctype = val["content-type"]
-+
-+ _set_config(dist, "long_description", text)
-+
-+ if ctype:
-+ _set_config(dist, "long_description_content_type", ctype)
-+
-+ if file:
-+ dist._referenced_files.add(cast(str, file))
-+
-+
-+def _license(dist: "Distribution", val: dict, root_dir: _Path):
-+ from setuptools.config import expand
-+
-+ if "file" in val:
-+ _set_config(dist, "license", expand.read_files([val["file"]],
root_dir))
-+ dist._referenced_files.add(val["file"])
-+ else:
-+ _set_config(dist, "license", val["text"])
-+
-+
-+def _people(dist: "Distribution", val: List[dict], _root_dir: _Path, kind:
str):
-+ field = []
-+ email_field = []
-+ for person in val:
-+ if "name" not in person:
-+ email_field.append(person["email"])
-+ elif "email" not in person:
-+ field.append(person["name"])
-+ else:
-+ addr = Address(display_name=person["name"],
addr_spec=person["email"])
-+ email_field.append(str(addr))
-+
-+ if field:
-+ _set_config(dist, kind, ", ".join(field))
-+ if email_field:
-+ _set_config(dist, f"{kind}_email", ", ".join(email_field))
-+
-+
-+def _project_urls(dist: "Distribution", val: dict, _root_dir):
-+ _set_config(dist, "project_urls", val)
-+
-+
-+def _python_requires(dist: "Distribution", val: dict, _root_dir):
-+ from setuptools.extern.packaging.specifiers import SpecifierSet
-+
-+ _set_config(dist, "python_requires", SpecifierSet(val))
-+
-+
-+def _dependencies(dist: "Distribution", val: list, _root_dir):
-+ if getattr(dist, "install_requires", []):
-+ msg = "`install_requires` overwritten in `pyproject.toml`
(dependencies)"
-+ SetuptoolsWarning.emit(msg)
-+ _set_config(dist, "install_requires", val)
-+
-+
-+def _optional_dependencies(dist: "Distribution", val: dict, _root_dir):
-+ existing = getattr(dist, "extras_require", {})
-+ _set_config(dist, "extras_require", {**existing, **val})
-+
-+
-+def _unify_entry_points(project_table: dict):
-+ project = project_table
-+ entry_points = project.pop("entry-points", project.pop("entry_points",
{}))
-+ renaming = {"scripts": "console_scripts", "gui_scripts": "gui_scripts"}
-+ for key, value in list(project.items()): # eager to allow modifications
-+ norm_key = json_compatible_key(key)
-+ if norm_key in renaming and value:
-+ entry_points[renaming[norm_key]] = project.pop(key)
-+
-+ if entry_points:
-+ project["entry-points"] = {
-+ name: [f"{k} = {v}" for k, v in group.items()]
-+ for name, group in entry_points.items()
-+ }
-+
-+
-+def _copy_command_options(pyproject: dict, dist: "Distribution", filename:
_Path):
-+ tool_table = pyproject.get("tool", {})
-+ cmdclass = tool_table.get("setuptools", {}).get("cmdclass", {})
-+ valid_options = _valid_command_options(cmdclass)
-+
-+ cmd_opts = dist.command_options
-+ for cmd, config in pyproject.get("tool", {}).get("distutils",
{}).items():
-+ cmd = json_compatible_key(cmd)
-+ valid = valid_options.get(cmd, set())
-+ cmd_opts.setdefault(cmd, {})
-+ for key, value in config.items():
-+ key = json_compatible_key(key)
-+ cmd_opts[cmd][key] = (str(filename), value)
-+ if key not in valid:
-+ # To avoid removing options that are specified dynamically
we
-+ # just log a warn...
-+ _logger.warning(f"Command option {cmd}.{key} is not
defined")
-+
-+
-+def _valid_command_options(cmdclass: Mapping = EMPTY) -> Dict[str,
Set[str]]:
-+ from .._importlib import metadata
-+ from setuptools.dist import Distribution
-+
-+ valid_options = {"global":
_normalise_cmd_options(Distribution.global_options)}
-+
-+ unloaded_entry_points =
metadata.entry_points(group='distutils.commands')
-+ loaded_entry_points = (_load_ep(ep) for ep in unloaded_entry_points)
-+ entry_points = (ep for ep in loaded_entry_points if ep)
-+ for cmd, cmd_class in chain(entry_points, cmdclass.items()):
-+ opts = valid_options.get(cmd, set())
-+ opts = opts | _normalise_cmd_options(getattr(cmd_class,
"user_options", []))
-+ valid_options[cmd] = opts
-+
-+ return valid_options
-+
-+
-+def _load_ep(ep: "metadata.EntryPoint") -> Optional[Tuple[str, Type]]:
-+ # Ignore all the errors
-+ try:
-+ return (ep.name, ep.load())
-+ except Exception as ex:
-+ msg = f"{ex.__class__.__name__} while trying to load entry-point
{ep.name}"
-+ _logger.warning(f"{msg}: {ex}")
-+ return None
-+
-+
-+def _normalise_cmd_option_key(name: str) -> str:
-+ return json_compatible_key(name).strip("_=")
-+
-+
-+def _normalise_cmd_options(desc: List[Tuple[str, Optional[str], str]]) ->
Set[str]:
-+ return {_normalise_cmd_option_key(fancy_option[0]) for fancy_option in
desc}
-+
-+
-+def _get_previous_entrypoints(dist: "Distribution") -> Dict[str, list]:
-+ ignore = ("console_scripts", "gui_scripts")
-+ value = getattr(dist, "entry_points", None) or {}
-+ return {k: v for k, v in value.items() if k not in ignore}
-+
-+
-+def _attrgetter(attr):
-+ """
-+ Similar to ``operator.attrgetter`` but returns None if ``attr`` is not
found
-+ >>> from types import SimpleNamespace
-+ >>> obj = SimpleNamespace(a=42, b=SimpleNamespace(c=13))
-+ >>> _attrgetter("a")(obj)
-+ 42
-+ >>> _attrgetter("b.c")(obj)
-+ 13
-+ >>> _attrgetter("d")(obj) is None
-+ True
-+ """
-+ return partial(reduce, lambda acc, x: getattr(acc, x, None),
attr.split("."))
-+
-+
-+def _some_attrgetter(*items):
-+ """
-+ Return the first "truth-y" attribute or None
-+ >>> from types import SimpleNamespace
-+ >>> obj = SimpleNamespace(a=42, b=SimpleNamespace(c=13))
-+ >>> _some_attrgetter("d", "a", "b.c")(obj)
-+ 42
-+ >>> _some_attrgetter("d", "e", "b.c", "a")(obj)
-+ 13
-+ >>> _some_attrgetter("d", "e", "f")(obj) is None
-+ True
-+ """
-+ def _acessor(obj):
-+ values = (_attrgetter(i)(obj) for i in items)
-+ return next((i for i in values if i is not None), None)
-+ return _acessor
-+
-+
-+PYPROJECT_CORRESPONDENCE: Dict[str, _Correspondence] = {
-+ "readme": _long_description,
-+ "license": _license,
-+ "authors": partial(_people, kind="author"),
-+ "maintainers": partial(_people, kind="maintainer"),
-+ "urls": _project_urls,
-+ "dependencies": _dependencies,
-+ "optional_dependencies": _optional_dependencies,
-+ "requires_python": _python_requires,
-+}
-+
-+TOOL_TABLE_RENAMES = {"script_files": "scripts"}
-+TOOL_TABLE_DEPRECATIONS = {
-+ "namespace_packages": (
-+ "consider using implicit namespaces instead (PEP 420).",
-+ {"due_date": (2023, 10, 30)}, # warning introduced in May 2022
-+ )
-+}
-+
-+SETUPTOOLS_PATCHES = {"long_description_content_type", "project_urls",
-+ "provides_extras", "license_file", "license_files"}
-+
-+_PREVIOUSLY_DEFINED = {
-+ "name": _attrgetter("metadata.name"),
-+ "version": _attrgetter("metadata.version"),
-+ "description": _attrgetter("metadata.description"),
-+ "readme": _attrgetter("metadata.long_description"),
-+ "requires-python": _some_attrgetter("python_requires",
"metadata.python_requires"),
-+ "license": _attrgetter("metadata.license"),
-+ "authors": _some_attrgetter("metadata.author", "metadata.author_email"),
-+ "maintainers": _some_attrgetter("metadata.maintainer",
"metadata.maintainer_email"),
-+ "keywords": _attrgetter("metadata.keywords"),
-+ "classifiers": _attrgetter("metadata.classifiers"),
-+ "urls": _attrgetter("metadata.project_urls"),
-+ "entry-points": _get_previous_entrypoints,
-+ "dependencies": _some_attrgetter("_orig_install_requires",
"install_requires"),
-+ "optional-dependencies": _some_attrgetter("_orig_extras_require",
"extras_require"),
-+}
-+
-+
-+class _WouldIgnoreField(SetuptoolsDeprecationWarning):
-+ _SUMMARY = "`{field}` defined outside of `pyproject.toml` would be
ignored."
-+
-+ _DETAILS = """
-+
##########################################################################
-+ # configuration would be ignored/result in error due to
`pyproject.toml` #
-+
##########################################################################
-+
-+ The following seems to be defined outside of `pyproject.toml`:
-+
-+ `{field} = {value!r}`
-+
-+ According to the spec (see the link below), however, setuptools CANNOT
-+ consider this value unless `{field}` is listed as `dynamic`.
-+
-+
https://packaging.python.org/en/latest/specifications/declaring-project-metadata/
-+
-+ For the time being, `setuptools` will still consider the given value
(as a
-+ **transitional** measure), but please note that future releases of
setuptools will
-+ follow strictly the standard.
-+
-+ To prevent this warning, you can list `{field}` under `dynamic` or
alternatively
-+ remove the `[project]` table from your file and rely entirely on other
means of
-+ configuration.
-+ """
-+ _DUE_DATE = (2023, 10, 30) # Initially introduced in 27 May 2022
-diff --git
a/third_party/python/setuptools/setuptools/config/_validate_pyproject/__init__.py

b/third_party/python/setuptools/setuptools/config/_validate_pyproject/__init__.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/config/_validate_pyproject/__init__.py
-@@ -0,0 +1,34 @@
-+from functools import reduce
-+from typing import Any, Callable, Dict
-+
-+from . import formats
-+from .error_reporting import detailed_errors, ValidationError
-+from .extra_validations import EXTRA_VALIDATIONS
-+from .fastjsonschema_exceptions import JsonSchemaException,
JsonSchemaValueException
-+from .fastjsonschema_validations import validate as _validate
-+
-+__all__ = [
-+ "validate",
-+ "FORMAT_FUNCTIONS",
-+ "EXTRA_VALIDATIONS",
-+ "ValidationError",
-+ "JsonSchemaException",
-+ "JsonSchemaValueException",
-+]
-+
-+
-+FORMAT_FUNCTIONS: Dict[str, Callable[[str], bool]] = {
-+ fn.__name__.replace("_", "-"): fn
-+ for fn in formats.__dict__.values()
-+ if callable(fn) and not fn.__name__.startswith("_")
-+}
-+
-+
-+def validate(data: Any) -> bool:
-+ """Validate the given ``data`` object using JSON Schema
-+ This function raises ``ValidationError`` if ``data`` is invalid.
-+ """
-+ with detailed_errors():
-+ _validate(data, custom_formats=FORMAT_FUNCTIONS)
-+ reduce(lambda acc, fn: fn(acc), EXTRA_VALIDATIONS, data)
-+ return True
-diff --git
a/third_party/python/setuptools/setuptools/config/_validate_pyproject/error_reporting.py

b/third_party/python/setuptools/setuptools/config/_validate_pyproject/error_reporting.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/config/_validate_pyproject/error_reporting.py
-@@ -0,0 +1,318 @@
-+import io
-+import json
-+import logging
-+import os
-+import re
-+from contextlib import contextmanager
-+from textwrap import indent, wrap
-+from typing import Any, Dict, Iterator, List, Optional, Sequence, Union,
cast
-+
-+from .fastjsonschema_exceptions import JsonSchemaValueException
-+
-+_logger = logging.getLogger(__name__)
-+
-+_MESSAGE_REPLACEMENTS = {
-+ "must be named by propertyName definition": "keys must be named by",
-+ "one of contains definition": "at least one item that matches",
-+ " same as const definition:": "",
-+ "only specified items": "only items matching the definition",
-+}
-+
-+_SKIP_DETAILS = (
-+ "must not be empty",
-+ "is always invalid",
-+ "must not be there",
-+)
-+
-+_NEED_DETAILS = {"anyOf", "oneOf", "anyOf", "contains", "propertyNames",
"not", "items"}
-+
-+_CAMEL_CASE_SPLITTER = re.compile(r"\W+|([A-Z][^A-Z\W]*)")
-+_IDENTIFIER = re.compile(r"^[\w_]+$", re.I)
-+
-+_TOML_JARGON = {
-+ "object": "table",
-+ "property": "key",
-+ "properties": "keys",
-+ "property names": "keys",
-+}
-+
-+
-+class ValidationError(JsonSchemaValueException):
-+ """Report violations of a given JSON schema.
-+
-+ This class extends :exc:`~fastjsonschema.JsonSchemaValueException`
-+ by adding the following properties:
-+
-+ - ``summary``: an improved version of the ``JsonSchemaValueException``
error message
-+ with only the necessary information)
-+
-+ - ``details``: more contextual information about the error like the
failing schema
-+ itself and the value that violates the schema.
-+
-+ Depending on the level of the verbosity of the ``logging`` configuration
-+ the exception message will be only ``summary`` (default) or a
combination of
-+ ``summary`` and ``details`` (when the logging level is set to
:obj:`logging.DEBUG`).
-+ """
-+
-+ summary = ""
-+ details = ""
-+ _original_message = ""
-+
-+ @classmethod
-+ def _from_jsonschema(cls, ex: JsonSchemaValueException):
-+ formatter = _ErrorFormatting(ex)
-+ obj = cls(str(formatter), ex.value, formatter.name, ex.definition,
ex.rule)
-+ debug_code = os.getenv("JSONSCHEMA_DEBUG_CODE_GENERATION",
"false").lower()
-+ if debug_code != "false": # pragma: no cover
-+ obj.__cause__, obj.__traceback__ = ex.__cause__,
ex.__traceback__
-+ obj._original_message = ex.message
-+ obj.summary = formatter.summary
-+ obj.details = formatter.details
-+ return obj
-+
-+
-+@contextmanager
-+def detailed_errors():
-+ try:
-+ yield
-+ except JsonSchemaValueException as ex:
-+ raise ValidationError._from_jsonschema(ex) from None
-+
-+
-+class _ErrorFormatting:
-+ def __init__(self, ex: JsonSchemaValueException):
-+ self.ex = ex
-+ self.name = f"`{self._simplify_name(ex.name)}`"
-+ self._original_message = self.ex.message.replace(ex.name, self.name)
-+ self._summary = ""
-+ self._details = ""
-+
-+ def __str__(self) -> str:
-+ if _logger.getEffectiveLevel() <= logging.DEBUG and self.details:
-+ return f"{self.summary}\n\n{self.details}"
-+
-+ return self.summary
-+
-+ @property
-+ def summary(self) -> str:
-+ if not self._summary:
-+ self._summary = self._expand_summary()
-+
-+ return self._summary
-+
-+ @property
-+ def details(self) -> str:
-+ if not self._details:
-+ self._details = self._expand_details()
-+
-+ return self._details
-+
-+ def _simplify_name(self, name):
-+ x = len("data.")
-+ return name[x:] if name.startswith("data.") else name
-+
-+ def _expand_summary(self):
-+ msg = self._original_message
-+
-+ for bad, repl in _MESSAGE_REPLACEMENTS.items():
-+ msg = msg.replace(bad, repl)
-+
-+ if any(substring in msg for substring in _SKIP_DETAILS):
-+ return msg
-+
-+ schema = self.ex.rule_definition
-+ if self.ex.rule in _NEED_DETAILS and schema:
-+ summary = _SummaryWriter(_TOML_JARGON)
-+ return f"{msg}:\n\n{indent(summary(schema), ' ')}"
-+
-+ return msg
-+
-+ def _expand_details(self) -> str:
-+ optional = []
-+ desc_lines = self.ex.definition.pop("$$description", [])
-+ desc = self.ex.definition.pop("description", None) or "
".join(desc_lines)
-+ if desc:
-+ description = "\n".join(
-+ wrap(
-+ desc,
-+ width=80,
-+ initial_indent=" ",
-+ subsequent_indent=" ",
-+ break_long_words=False,
-+ )
-+ )
-+ optional.append(f"DESCRIPTION:\n{description}")
-+ schema = json.dumps(self.ex.definition, indent=4)
-+ value = json.dumps(self.ex.value, indent=4)
-+ defaults = [
-+ f"GIVEN VALUE:\n{indent(value, ' ')}",
-+ f"OFFENDING RULE: {self.ex.rule!r}",
-+ f"DEFINITION:\n{indent(schema, ' ')}",
-+ ]
-+ return "\n\n".join(optional + defaults)
-+
-+
-+class _SummaryWriter:
-+ _IGNORE = {"description", "default", "title", "examples"}
-+
-+ def __init__(self, jargon: Optional[Dict[str, str]] = None):
-+ self.jargon: Dict[str, str] = jargon or {}
-+ # Clarify confusing terms
-+ self._terms = {
-+ "anyOf": "at least one of the following",
-+ "oneOf": "exactly one of the following",
-+ "allOf": "all of the following",
-+ "not": "(*NOT* the following)",
-+ "prefixItems": f"{self._jargon('items')} (in order)",
-+ "items": "items",
-+ "contains": "contains at least one of",
-+ "propertyNames": (
-+ f"non-predefined acceptable {self._jargon('property
names')}"
-+ ),
-+ "patternProperties": f"{self._jargon('properties')} named via
pattern",
-+ "const": "predefined value",
-+ "enum": "one of",
-+ }
-+ # Attributes that indicate that the definition is easy and can be
done
-+ # inline (e.g. string and number)
-+ self._guess_inline_defs = [
-+ "enum",
-+ "const",
-+ "maxLength",
-+ "minLength",
-+ "pattern",
-+ "format",
-+ "minimum",
-+ "maximum",
-+ "exclusiveMinimum",
-+ "exclusiveMaximum",
-+ "multipleOf",
-+ ]
-+
-+ def _jargon(self, term: Union[str, List[str]]) -> Union[str, List[str]]:
-+ if isinstance(term, list):
-+ return [self.jargon.get(t, t) for t in term]
-+ return self.jargon.get(term, term)
-+
-+ def __call__(
-+ self,
-+ schema: Union[dict, List[dict]],
-+ prefix: str = "",
-+ *,
-+ _path: Sequence[str] = (),
-+ ) -> str:
-+ if isinstance(schema, list):
-+ return self._handle_list(schema, prefix, _path)
-+
-+ filtered = self._filter_unecessary(schema, _path)
-+ simple = self._handle_simple_dict(filtered, _path)
-+ if simple:
-+ return f"{prefix}{simple}"
-+
-+ child_prefix = self._child_prefix(prefix, " ")
-+ item_prefix = self._child_prefix(prefix, "- ")
-+ indent = len(prefix) * " "
-+ with io.StringIO() as buffer:
-+ for i, (key, value) in enumerate(filtered.items()):
-+ child_path = [*_path, key]
-+ line_prefix = prefix if i == 0 else indent
-+ buffer.write(f"{line_prefix}{self._label(child_path)}:")
-+ # ^ just the first item should receive the complete prefix
-+ if isinstance(value, dict):
-+ filtered = self._filter_unecessary(value, child_path)
-+ simple = self._handle_simple_dict(filtered, child_path)
-+ buffer.write(
-+ f" {simple}"
-+ if simple
-+ else f"\n{self(value, child_prefix,
_path=child_path)}"
-+ )
-+ elif isinstance(value, list) and (
-+ key != "type" or self._is_property(child_path)
-+ ):
-+ children = self._handle_list(value, item_prefix,
child_path)
-+ sep = " " if children.startswith("[") else "\n"
-+ buffer.write(f"{sep}{children}")
-+ else:
-+ buffer.write(f" {self._value(value, child_path)}\n")
-+ return buffer.getvalue()
-+
-+ def _is_unecessary(self, path: Sequence[str]) -> bool:
-+ if self._is_property(path) or not path: # empty path =>
instruction @ root
-+ return False
-+ key = path[-1]
-+ return any(key.startswith(k) for k in "$_") or key in self._IGNORE
-+
-+ def _filter_unecessary(self, schema: dict, path: Sequence[str]):
-+ return {
-+ key: value
-+ for key, value in schema.items()
-+ if not self._is_unecessary([*path, key])
-+ }
-+
-+ def _handle_simple_dict(self, value: dict, path: Sequence[str]) ->
Optional[str]:
-+ inline = any(p in value for p in self._guess_inline_defs)
-+ simple = not any(isinstance(v, (list, dict)) for v in
value.values())
-+ if inline or simple:
-+ return f"{{{', '.join(self._inline_attrs(value, path))}}}\n"
-+ return None
-+
-+ def _handle_list(
-+ self, schemas: list, prefix: str = "", path: Sequence[str] = ()
-+ ) -> str:
-+ if self._is_unecessary(path):
-+ return ""
-+
-+ repr_ = repr(schemas)
-+ if all(not isinstance(e, (dict, list)) for e in schemas) and
len(repr_) < 60:
-+ return f"{repr_}\n"
-+
-+ item_prefix = self._child_prefix(prefix, "- ")
-+ return "".join(
-+ self(v, item_prefix, _path=[*path, f"[{i}]"]) for i, v in
enumerate(schemas)
-+ )
-+
-+ def _is_property(self, path: Sequence[str]):
-+ """Check if the given path can correspond to an arbitrarily named
property"""
-+ counter = 0
-+ for key in path[-2::-1]:
-+ if key not in {"properties", "patternProperties"}:
-+ break
-+ counter += 1
-+
-+ # If the counter if even, the path correspond to a JSON Schema
keyword
-+ # otherwise it can be any arbitrary string naming a property
-+ return counter % 2 == 1
-+
-+ def _label(self, path: Sequence[str]) -> str:
-+ *parents, key = path
-+ if not self._is_property(path):
-+ norm_key = _separate_terms(key)
-+ return self._terms.get(key) or " ".join(self._jargon(norm_key))
-+
-+ if parents[-1] == "patternProperties":
-+ return f"(regex {key!r})"
-+ return repr(key) # property name
-+
-+ def _value(self, value: Any, path: Sequence[str]) -> str:
-+ if path[-1] == "type" and not self._is_property(path):
-+ type_ = self._jargon(value)
-+ return (
-+ f"[{', '.join(type_)}]" if isinstance(value, list) else
cast(str, type_)
-+ )
-+ return repr(value)
-+
-+ def _inline_attrs(self, schema: dict, path: Sequence[str]) ->
Iterator[str]:
-+ for key, value in schema.items():
-+ child_path = [*path, key]
-+ yield f"{self._label(child_path)}: {self._value(value,
child_path)}"
-+
-+ def _child_prefix(self, parent_prefix: str, child_prefix: str) -> str:
-+ return len(parent_prefix) * " " + child_prefix
-+
-+
-+def _separate_terms(word: str) -> List[str]:
-+ """
-+ >>> _separate_terms("FooBar-foo")
-+ ['foo', 'bar', 'foo']
-+ """
-+ return [w.lower() for w in _CAMEL_CASE_SPLITTER.split(word) if w]
-diff --git
a/third_party/python/setuptools/setuptools/config/_validate_pyproject/extra_validations.py

b/third_party/python/setuptools/setuptools/config/_validate_pyproject/extra_validations.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/config/_validate_pyproject/extra_validations.py
-@@ -0,0 +1,36 @@
-+"""The purpose of this module is implement PEP 621 validations that are
-+difficult to express as a JSON Schema (or that are not supported by the
current
-+JSON Schema library).
-+"""
-+
-+from typing import Mapping, TypeVar
-+
-+from .error_reporting import ValidationError
-+
-+T = TypeVar("T", bound=Mapping)
-+
-+
-+class RedefiningStaticFieldAsDynamic(ValidationError):
-+ """According to PEP 621:
-+
-+ Build back-ends MUST raise an error if the metadata specifies a field
-+ statically as well as being listed in dynamic.
-+ """
-+
-+
-+def validate_project_dynamic(pyproject: T) -> T:
-+ project_table = pyproject.get("project", {})
-+ dynamic = project_table.get("dynamic", [])
-+
-+ for field in dynamic:
-+ if field in project_table:
-+ msg = f"You cannot provide a value for `project.{field}` and "
-+ msg += "list it under `project.dynamic` at the same time"
-+ name = f"data.project.{field}"
-+ value = {field: project_table[field], "...": " # ...",
"dynamic": dynamic}
-+ raise RedefiningStaticFieldAsDynamic(msg, value, name,
rule="PEP 621")
-+
-+ return pyproject
-+
-+
-+EXTRA_VALIDATIONS = (validate_project_dynamic,)
-diff --git
a/third_party/python/setuptools/setuptools/config/_validate_pyproject/fastjsonschema_exceptions.py

b/third_party/python/setuptools/setuptools/config/_validate_pyproject/fastjsonschema_exceptions.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/config/_validate_pyproject/fastjsonschema_exceptions.py
-@@ -0,0 +1,51 @@
-+import re
-+
-+
-+SPLIT_RE = re.compile(r'[\.\[\]]+')
-+
-+
-+class JsonSchemaException(ValueError):
-+ """
-+ Base exception of ``fastjsonschema`` library.
-+ """
-+
-+
-+class JsonSchemaValueException(JsonSchemaException):
-+ """
-+ Exception raised by validation function. Available properties:
-+
-+ * ``message`` containing human-readable information what is wrong
(e.g. ``data.property[index] must be smaller than or equal to 42``),
-+ * invalid ``value`` (e.g. ``60``),
-+ * ``name`` of a path in the data structure (e.g.
``data.property[index]``),
-+ * ``path`` as an array in the data structure (e.g. ``['data',
'property', 'index']``),
-+ * the whole ``definition`` which the ``value`` has to fulfil (e.g.
``{'type': 'number', 'maximum': 42}``),
-+ * ``rule`` which the ``value`` is breaking (e.g. ``maximum``)
-+ * and ``rule_definition`` (e.g. ``42``).
-+
-+ .. versionchanged:: 2.14.0
-+ Added all extra properties.
-+ """
-+
-+ def __init__(self, message, value=None, name=None, definition=None,
rule=None):
-+ super().__init__(message)
-+ self.message = message
-+ self.value = value
-+ self.name = name
-+ self.definition = definition
-+ self.rule = rule
-+
-+ @property
-+ def path(self):
-+ return [item for item in SPLIT_RE.split(self.name) if item != '']
-+
-+ @property
-+ def rule_definition(self):
-+ if not self.rule or not self.definition:
-+ return None
-+ return self.definition.get(self.rule)
-+
-+
-+class JsonSchemaDefinitionException(JsonSchemaException):
-+ """
-+ Exception raised by generator of validation function.
-+ """
-diff --git
a/third_party/python/setuptools/setuptools/config/_validate_pyproject/fastjsonschema_validations.py

b/third_party/python/setuptools/setuptools/config/_validate_pyproject/fastjsonschema_validations.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/config/_validate_pyproject/fastjsonschema_validations.py
-@@ -0,0 +1,1052 @@
-+# noqa
-+# type: ignore
-+# flake8: noqa
-+# pylint: skip-file
-+# mypy: ignore-errors
-+# yapf: disable
-+# pylama:skip=1
-+
-+
-+# *** PLEASE DO NOT MODIFY DIRECTLY: Automatically generated code ***
-+
-+
-+VERSION = "2.16.3"
-+import re
-+from .fastjsonschema_exceptions import JsonSchemaValueException
-+
-+
-+REGEX_PATTERNS = {
-+ '^.*$': re.compile('^.*$'),
-+ '.+': re.compile('.+'),
-+ '^.+$': re.compile('^.+$'),
-+ 'idn-email_re_pattern': re.compile('^[^@]+@[^@]+\\.[^@]+\\Z')
-+}
-+
-+NoneType = type(None)
-+
-+def validate(data, custom_formats={}, name_prefix=None):
-+
validate_https___packaging_python_org_en_latest_specifications_declaring_build_dependencies(data,
custom_formats, (name_prefix or "data") + "")
-+ return data
-+
-+def
validate_https___packaging_python_org_en_latest_specifications_declaring_build_dependencies(data,
custom_formats={}, name_prefix=None):
-+ if not isinstance(data, (dict)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data") + "
must be object", value=data, name="" + (name_prefix or "data") + "",
definition={'$schema': 'http://json-schema.org/draft-07/schema', '$id':
'https://packaging.python.org/en/latest/specifications/declaring-build-dependencies/',
'title': 'Data structure for ``pyproject.toml`` files', '$$description':
['File format containing build-time configurations for the Python ecosystem.
', ':pep:`517` initially defined a build-system independent format for source
trees', 'which was complemented by :pep:`518` to provide a way of specifying
dependencies ', 'for building Python projects.', 'Please notice the
``project`` table (as initially defined in :pep:`621`) is not included', 'in
this schema and should be considered separately.'], 'type': 'object',
'additionalProperties': False, 'properties': {'build-system': {'type':
'object', 'description': 'Table used to store build-related data',
'additionalProperties': False, 'properties': {'requires': {'type': 'array',
'$$description': ['List of dependencies in the :pep:`508` format required to
execute the build', 'system. Please notice that the resulting dependency
graph', '**MUST NOT contain cycles**'], 'items': {'type': 'string'}},
'build-backend': {'type': 'string', 'description': 'Python object that will
be used to perform the build according to :pep:`517`', 'format':
'pep517-backend-reference'}, 'backend-path': {'type': 'array',
'$$description': ['List of directories to be prepended to ``sys.path`` when
loading the', 'back-end, and running its hooks'], 'items': {'type': 'string',
'$comment': 'Should be a path (TODO: enforce it with format?)'}}},
'required': ['requires']}, 'project': {'$schema':
'http://json-schema.org/draft-07/schema', '$id':
'https://packaging.python.org/en/latest/specifications/declaring-project-metadata/',
'title': 'Package metadata stored in the ``project`` table',
'$$description': ['Data structure for the **project** table inside
``pyproject.toml``', '(as initially defined in :pep:`621`)'], 'type':
'object', 'properties': {'name': {'type': 'string', 'description': 'The name
(primary identifier) of the project. MUST be statically defined.', 'format':
'pep508-identifier'}, 'version': {'type': 'string', 'description': 'The
version of the project as supported by :pep:`440`.', 'format': 'pep440'},
'description': {'type': 'string', '$$description': ['The `summary description
of the project',
'<https://packaging.python.org/specifications/core-metadata/#summary>`_']},
'readme': {'$$description': ['`Full/detailed description of the project in
the form of a README', '<https://peps.python.org/pep-0621/#readme>`_', "with
meaning similar to the one defined in `core metadata's Description",
'<https://packaging.python.org/specifications/core-metadata/#description>`_'],
'oneOf': [{'type': 'string', '$$description': ['Relative path to a text file
(UTF-8) containing the full description', 'of the project. If the file path
ends in case-insensitive ``.md`` or', '``.rst`` suffixes, then the
content-type is respectively', '``text/markdown`` or ``text/x-rst``']},
{'type': 'object', 'allOf': [{'anyOf': [{'properties': {'file': {'type':
'string', '$$description': ['Relative path to a text file containing the full
description', 'of the project.']}}, 'required': ['file']}, {'properties':
{'text': {'type': 'string', 'description': 'Full text describing the
project.'}}, 'required': ['text']}]}, {'properties': {'content-type':
{'type': 'string', '$$description': ['Content-type (:rfc:`1341`) of the full
description', '(e.g. ``text/markdown``). The ``charset`` parameter is
assumed', 'UTF-8 when not present.'], '$comment': 'TODO: add regex pattern or
format?'}}, 'required': ['content-type']}]}]}, 'requires-python': {'type':
'string', 'format': 'pep508-versionspec', '$$description': ['`The Python
version requirements of the project',
'<https://packaging.python.org/specifications/core-metadata/#requires-python>`_.']},
'license': {'description': '`Project license
<https://peps.python.org/pep-0621/#license>`_.', 'oneOf': [{'properties':
{'file': {'type': 'string', '$$description': ['Relative path to the file
(UTF-8) which contains the license for the', 'project.']}}, 'required':
['file']}, {'properties': {'text': {'type': 'string', '$$description': ['The
license of the project whose meaning is that of the', '`License field from
the core metadata',
'<https://packaging.python.org/specifications/core-metadata/#license>`_.']}},
'required': ['text']}]}, 'authors': {'type': 'array', 'items': {'$ref':
'#/definitions/author'}, '$$description': ["The people or organizations
considered to be the 'authors' of the project.", 'The exact meaning is open
to interpretation (e.g. original or primary authors,', 'current maintainers,
or owners of the package).']}, 'maintainers': {'type': 'array', 'items':
{'$ref': '#/definitions/author'}, '$$description': ["The people or
organizations considered to be the 'maintainers' of the project.", 'Similarly
to ``authors``, the exact meaning is open to interpretation.']}, 'keywords':
{'type': 'array', 'items': {'type': 'string'}, 'description': 'List of
keywords to assist searching for the distribution in a larger catalog.'},
'classifiers': {'type': 'array', 'items': {'type': 'string', 'format':
'trove-classifier', 'description': '`PyPI classifier
<https://pypi.org/classifiers/>`_.'}, '$$description': ['`Trove classifiers
<https://pypi.org/classifiers/>`_', 'which apply to the project.']}, 'urls':
{'type': 'object', 'description': 'URLs associated with the project in the
form ``label => value``.', 'additionalProperties': False,
'patternProperties': {'^.+$': {'type': 'string', 'format': 'url'}}},
'scripts': {'$ref': '#/definitions/entry-point-group', '$$description':
['Instruct the installer to create command-line wrappers for the given',
'`entry points
<https://packaging.python.org/specifications/entry-points/>`_.']},
'gui-scripts': {'$ref': '#/definitions/entry-point-group', '$$description':
['Instruct the installer to create GUI wrappers for the given', '`entry
points <https://packaging.python.org/specifications/entry-points/>`_.', 'The
difference between ``scripts`` and ``gui-scripts`` is only relevant in',
'Windows.']}, 'entry-points': {'$$description': ['Instruct the installer to
expose the given modules/functions via', '``entry-point`` discovery mechanism
(useful for plugins).', 'More information available in the `Python packaging
guide', '<https://packaging.python.org/specifications/entry-points/>`_.'],
'propertyNames': {'format': 'python-entrypoint-group'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'$ref':
'#/definitions/entry-point-group'}}}, 'dependencies': {'type': 'array',
'description': 'Project (mandatory) dependencies.', 'items': {'$ref':
'#/definitions/dependency'}}, 'optional-dependencies': {'type': 'object',
'description': 'Optional dependency for the project', 'propertyNames':
{'format': 'pep508-identifier'}, 'additionalProperties': False,
'patternProperties': {'^.+$': {'type': 'array', 'items': {'$ref':
'#/definitions/dependency'}}}}, 'dynamic': {'type': 'array', '$$description':
['Specifies which fields are intentionally unspecified and expected to be',
'dynamically provided by build tools'], 'items': {'enum': ['version',
'description', 'readme', 'requires-python', 'license', 'authors',
'maintainers', 'keywords', 'classifiers', 'urls', 'scripts', 'gui-scripts',
'entry-points', 'dependencies', 'optional-dependencies']}}}, 'required':
['name'], 'additionalProperties': False, 'if': {'not': {'required':
['dynamic'], 'properties': {'dynamic': {'contains': {'const': 'version'},
'$$description': ['version is listed in ``dynamic``']}}}, '$$comment':
['According to :pep:`621`:', ' If the core metadata specification lists a
field as "Required", then', ' the metadata MUST specify the field
statically or list it in dynamic', 'In turn, `core metadata`_ defines:', '
The required fields are: Metadata-Version, Name, Version.', ' All the
other fields are optional.', 'Since ``Metadata-Version`` is defined by the
build back-end, ``name`` and', '``version`` are the only mandatory
information in ``pyproject.toml``.', '.. _core metadata:
https://packaging.python.org/specifications/core-metadata/']}, 'then':
{'required': ['version'], '$$description': ['version should be statically
defined in the ``version`` field']}, 'definitions': {'author': {'$id':
'#/definitions/author', 'title': 'Author or Maintainer', '$comment':
'https://peps.python.org/pep-0621/#authors-maintainers', 'type': 'object',
'additionalProperties': False, 'properties': {'name': {'type': 'string',
'$$description': ['MUST be a valid email name, i.e. whatever can be put as a
name, before an', 'email, in :rfc:`822`.']}, 'email': {'type': 'string',
'format': 'idn-email', 'description': 'MUST be a valid email address'}}},
'entry-point-group': {'$id': '#/definitions/entry-point-group', 'title':
'Entry-points', 'type': 'object', '$$description': ['Entry-points are grouped
together to indicate what sort of capabilities they', 'provide.', 'See the
`packaging guides',
'<https://packaging.python.org/specifications/entry-points/>`_', 'and
`setuptools docs',
'<https://setuptools.pypa.io/en/latest/userguide/entry_point.html>`_', 'for
more information.'], 'propertyNames': {'format': 'python-entrypoint-name'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'type':
'string', '$$description': ['Reference to a Python object. It is either in
the form', '``importable.module``, or ``importable.module:object.attr``.'],
'format': 'python-entrypoint-reference', '$comment':
'https://packaging.python.org/specifications/entry-points/'}}}, 'dependency':
{'$id': '#/definitions/dependency', 'title': 'Dependency', 'type': 'string',
'description': 'Project dependency specification according to PEP 508',
'format': 'pep508'}}}, 'tool': {'type': 'object', 'properties': {'distutils':
{'$schema': 'http://json-schema.org/draft-07/schema', '$id':
'https://docs.python.org/3/install/', 'title': '``tool.distutils`` table',
'$$description': ['Originally, ``distutils`` allowed developers to configure
arguments for', '``setup.py`` scripts via `distutils configuration files',
'<https://docs.python.org/3/install/#distutils-configuration-files>`_.',
'``tool.distutils`` subtables could be used with the same purpose', '(NOT
CURRENTLY IMPLEMENTED).'], 'type': 'object', 'properties': {'global':
{'type': 'object', 'description': 'Global options applied to all
``distutils`` commands'}}, 'patternProperties': {'.+': {'type': 'object'}},
'$comment': 'TODO: Is there a practical way of making this schema more
specific?'}, 'setuptools': {'$schema':
'http://json-schema.org/draft-07/schema', '$id':
'https://setuptools.pypa.io/en/latest/references/keywords.html', 'title':
'``tool.setuptools`` table', '$$description': ['Please notice for the time
being the ``setuptools`` project does not specify', 'a way of configuring
builds via ``pyproject.toml``.', 'Therefore this schema should be taken just
as a *"thought experiment"* on how', 'this *might be done*, by following the
principles established in', '`ini2toml
<https://ini2toml.readthedocs.io/en/latest/setuptools_pep621.html>`_.', 'It
considers only ``setuptools`` `parameters',
'<https://setuptools.pypa.io/en/latest/userguide/declarative_config.html>`_',
'that can currently be configured via ``setup.cfg`` and are not covered by
:pep:`621`', 'but intentionally excludes ``dependency_links`` and
``setup_requires``.', 'NOTE: ``scripts`` was renamed to ``script-files`` to
avoid confusion with', 'entry-point based scripts (defined in :pep:`621`).'],
'type': 'object', 'additionalProperties': False, 'properties': {'platforms':
{'type': 'array', 'items': {'type': 'string'}}, 'provides': {'$$description':
['Package and virtual package names contained within this package', '**(not
supported by pip)**'], 'type': 'array', 'items': {'type': 'string', 'format':
'pep508-identifier'}}, 'obsoletes': {'$$description': ['Packages which this
package renders obsolete', '**(not supported by pip)**'], 'type': 'array',
'items': {'type': 'string', 'format': 'pep508-identifier'}}, 'zip-safe':
{'description': 'Whether the project can be safely installed and run from a
zip file.', 'type': 'boolean'}, 'script-files': {'description': 'Legacy way
of defining scripts (entry-points are preferred).', 'type': 'array', 'items':
{'type': 'string'}, '$comment': 'TODO: is this field deprecated/should be
removed?'}, 'eager-resources': {'$$description': ['Resources that should be
extracted together, if any of them is needed,', 'or if any C extensions
included in the project are imported.'], 'type': 'array', 'items': {'type':
'string'}}, 'packages': {'$$description': ['Packages that should be included
in the distribution.', 'It can be given either as a list of package
identifiers', 'or as a ``dict``-like structure with a single key ``find``',
'which corresponds to a dynamic call to',
'``setuptools.config.expand.find_packages`` function.', 'The ``find`` key is
associated with a nested ``dict``-like structure that can', 'contain
``where``, ``include``, ``exclude`` and ``namespaces`` keys,', 'mimicking the
keyword arguments of the associated function.'], 'oneOf': [{'title': 'Array
of Python package identifiers', 'type': 'array', 'items': {'$ref':
'#/definitions/package-name'}}, {'$ref': '#/definitions/find-directive'}]},
'package-dir': {'$$description': [':class:`dict`-like structure mapping from
package names to directories where their', 'code can be found.', 'The empty
string (as key) means that all packages are contained inside', 'the given
directory will be included in the distribution.'], 'type': 'object',
'additionalProperties': False, 'propertyNames': {'oneOf': [{'const': ''},
{'$ref': '#/definitions/package-name'}]}, 'patternProperties': {'^.*$':
{'type': 'string'}}}, 'package-data': {'$$description': ['Mapping from
package names to lists of glob patterns.', 'Usually this option is not needed
when using ``include-package-data = true``', 'For more information on how to
include data files, check ``setuptools`` `docs',
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_.'],
'type': 'object', 'additionalProperties': False, 'propertyNames': {'oneOf':
[{'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties':
{'^.*$': {'type': 'array', 'items': {'type': 'string'}}}},
'include-package-data': {'$$description': ['Automatically include any data
files inside the package directories', 'that are specified by
``MANIFEST.in``', 'For more information on how to include data files, check
``setuptools`` `docs',
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_.'],
'type': 'boolean'}, 'exclude-package-data': {'$$description': ['Mapping from
package names to lists of glob patterns that should be excluded', 'For more
information on how to include data files, check ``setuptools`` `docs',
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_.'],
'type': 'object', 'additionalProperties': False, 'propertyNames': {'oneOf':
[{'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties':
{'^.*$': {'type': 'array', 'items': {'type': 'string'}}}},
'namespace-packages': {'type': 'array', 'items': {'type': 'string', 'format':
'python-module-name'}, '$comment':
'https://setuptools.pypa.io/en/latest/userguide/package_discovery.html'},
'py-modules': {'description': 'Modules that setuptools will manipulate',
'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name'},
'$comment': 'TODO: clarify the relationship with ``packages``'},
'data-files': {'$$description': ['**DEPRECATED**: dict-like structure where
each key represents a directory and', 'the value is a list of glob patterns
that should be installed in them.', "Please notice this don't work with
wheels. See `data files support",
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_'], 'type':
'object', 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type':
'string'}}}}, 'cmdclass': {'$$description': ['Mapping of distutils-style
command names to ``setuptools.Command`` subclasses', 'which in turn should be
represented by strings with a qualified class name', '(i.e., "dotted" form
with module), e.g.::\n\n', ' cmdclass = {mycmd =
"pkg.subpkg.module.CommandClass"}\n\n', 'The command class should be a
directly defined at the top-level of the', 'containing module (no class
nesting).'], 'type': 'object', 'patternProperties': {'^.*$': {'type':
'string', 'format': 'python-qualified-identifier'}}}, 'license-files':
{'type': 'array', 'items': {'type': 'string'}, '$$description':
['PROVISIONAL: List of glob patterns for all license files being
distributed.', '(might become standard with PEP 639).', "By default:
``['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*']``"], '$comment': 'TODO:
revise if PEP 639 is accepted. Probably ``project.license-files``?'},
'dynamic': {'type': 'object', 'description': 'Instructions for loading
:pep:`621`-related metadata dynamically', 'additionalProperties': False,
'properties': {'version': {'$$description': ['A version dynamically loaded
via either the ``attr:`` or ``file:``', 'directives. Please make sure the
given file or attribute respects :pep:`440`.'], 'oneOf': [{'$ref':
'#/definitions/attr-directive'}, {'$ref': '#/definitions/file-directive'}]},
'classifiers': {'$ref': '#/definitions/file-directive'}, 'description':
{'$ref': '#/definitions/file-directive'}, 'dependencies': {'$ref':
'#/definitions/file-directive'}, 'entry-points': {'$ref':
'#/definitions/file-directive'}, 'optional-dependencies': {'type': 'object',
'propertyNames': {'format': 'python-identifier'}, 'additionalProperties':
False, 'patternProperties': {'.+': {'$ref':
'#/definitions/file-directive'}}}, 'readme': {'anyOf': [{'$ref':
'#/definitions/file-directive'}, {'properties': {'content-type': {'type':
'string'}}}], 'required': ['file']}}}}, 'definitions': {'package-name':
{'$id': '#/definitions/package-name', 'title': 'Valid package name',
'description': 'Valid package name (importable or PEP 561).', 'type':
'string', 'anyOf': [{'format': 'python-module-name'}, {'format':
'pep561-stub-name'}]}, 'file-directive': {'$id':
'#/definitions/file-directive', 'title': "'file:' directive", 'description':
'Value is read from a file (or list of files and then concatenated)', 'type':
'object', 'additionalProperties': False, 'properties': {'file': {'oneOf':
[{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}]}},
'required': ['file']}, 'attr-directive': {'title': "'attr:' directive",
'$id': '#/definitions/attr-directive', '$$description': ['Value is read from
a module attribute. Supports callables and iterables;', 'unsupported types
are cast via ``str()``'], 'type': 'object', 'additionalProperties': False,
'properties': {'attr': {'type': 'string'}}, 'required': ['attr']},
'find-directive': {'$id': '#/definitions/find-directive', 'title': "'find:'
directive", 'type': 'object', 'additionalProperties': False, 'properties':
{'find': {'type': 'object', '$$description': ['Dynamic `package discovery',
'<https://setuptools.pypa.io/en/latest/userguide/package_discovery.html>`_.'],
'additionalProperties': False, 'properties': {'where': {'description':
'Directories to be searched for packages (Unix-style relative path)', 'type':
'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array',
'$$description': ['Exclude packages that match the values listed in this
field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items':
{'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict
the found packages to just the ones listed in this field.', "Can container
shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}},
'namespaces': {'type': 'boolean', '$$description': ['When ``True``,
directories without a ``__init__.py`` file will also', 'be scanned for
:pep:`420`-style implicit namespaces']}}}}}}}}}}, 'project': {'$schema':
'http://json-schema.org/draft-07/schema', '$id':
'https://packaging.python.org/en/latest/specifications/declaring-project-metadata/',
'title': 'Package metadata stored in the ``project`` table',
'$$description': ['Data structure for the **project** table inside
``pyproject.toml``', '(as initially defined in :pep:`621`)'], 'type':
'object', 'properties': {'name': {'type': 'string', 'description': 'The name
(primary identifier) of the project. MUST be statically defined.', 'format':
'pep508-identifier'}, 'version': {'type': 'string', 'description': 'The
version of the project as supported by :pep:`440`.', 'format': 'pep440'},
'description': {'type': 'string', '$$description': ['The `summary description
of the project',
'<https://packaging.python.org/specifications/core-metadata/#summary>`_']},
'readme': {'$$description': ['`Full/detailed description of the project in
the form of a README', '<https://peps.python.org/pep-0621/#readme>`_', "with
meaning similar to the one defined in `core metadata's Description",
'<https://packaging.python.org/specifications/core-metadata/#description>`_'],
'oneOf': [{'type': 'string', '$$description': ['Relative path to a text file
(UTF-8) containing the full description', 'of the project. If the file path
ends in case-insensitive ``.md`` or', '``.rst`` suffixes, then the
content-type is respectively', '``text/markdown`` or ``text/x-rst``']},
{'type': 'object', 'allOf': [{'anyOf': [{'properties': {'file': {'type':
'string', '$$description': ['Relative path to a text file containing the full
description', 'of the project.']}}, 'required': ['file']}, {'properties':
{'text': {'type': 'string', 'description': 'Full text describing the
project.'}}, 'required': ['text']}]}, {'properties': {'content-type':
{'type': 'string', '$$description': ['Content-type (:rfc:`1341`) of the full
description', '(e.g. ``text/markdown``). The ``charset`` parameter is
assumed', 'UTF-8 when not present.'], '$comment': 'TODO: add regex pattern or
format?'}}, 'required': ['content-type']}]}]}, 'requires-python': {'type':
'string', 'format': 'pep508-versionspec', '$$description': ['`The Python
version requirements of the project',
'<https://packaging.python.org/specifications/core-metadata/#requires-python>`_.']},
'license': {'description': '`Project license
<https://peps.python.org/pep-0621/#license>`_.', 'oneOf': [{'properties':
{'file': {'type': 'string', '$$description': ['Relative path to the file
(UTF-8) which contains the license for the', 'project.']}}, 'required':
['file']}, {'properties': {'text': {'type': 'string', '$$description': ['The
license of the project whose meaning is that of the', '`License field from
the core metadata',
'<https://packaging.python.org/specifications/core-metadata/#license>`_.']}},
'required': ['text']}]}, 'authors': {'type': 'array', 'items': {'$ref':
'#/definitions/author'}, '$$description': ["The people or organizations
considered to be the 'authors' of the project.", 'The exact meaning is open
to interpretation (e.g. original or primary authors,', 'current maintainers,
or owners of the package).']}, 'maintainers': {'type': 'array', 'items':
{'$ref': '#/definitions/author'}, '$$description': ["The people or
organizations considered to be the 'maintainers' of the project.", 'Similarly
to ``authors``, the exact meaning is open to interpretation.']}, 'keywords':
{'type': 'array', 'items': {'type': 'string'}, 'description': 'List of
keywords to assist searching for the distribution in a larger catalog.'},
'classifiers': {'type': 'array', 'items': {'type': 'string', 'format':
'trove-classifier', 'description': '`PyPI classifier
<https://pypi.org/classifiers/>`_.'}, '$$description': ['`Trove classifiers
<https://pypi.org/classifiers/>`_', 'which apply to the project.']}, 'urls':
{'type': 'object', 'description': 'URLs associated with the project in the
form ``label => value``.', 'additionalProperties': False,
'patternProperties': {'^.+$': {'type': 'string', 'format': 'url'}}},
'scripts': {'$ref': '#/definitions/entry-point-group', '$$description':
['Instruct the installer to create command-line wrappers for the given',
'`entry points
<https://packaging.python.org/specifications/entry-points/>`_.']},
'gui-scripts': {'$ref': '#/definitions/entry-point-group', '$$description':
['Instruct the installer to create GUI wrappers for the given', '`entry
points <https://packaging.python.org/specifications/entry-points/>`_.', 'The
difference between ``scripts`` and ``gui-scripts`` is only relevant in',
'Windows.']}, 'entry-points': {'$$description': ['Instruct the installer to
expose the given modules/functions via', '``entry-point`` discovery mechanism
(useful for plugins).', 'More information available in the `Python packaging
guide', '<https://packaging.python.org/specifications/entry-points/>`_.'],
'propertyNames': {'format': 'python-entrypoint-group'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'$ref':
'#/definitions/entry-point-group'}}}, 'dependencies': {'type': 'array',
'description': 'Project (mandatory) dependencies.', 'items': {'$ref':
'#/definitions/dependency'}}, 'optional-dependencies': {'type': 'object',
'description': 'Optional dependency for the project', 'propertyNames':
{'format': 'pep508-identifier'}, 'additionalProperties': False,
'patternProperties': {'^.+$': {'type': 'array', 'items': {'$ref':
'#/definitions/dependency'}}}}, 'dynamic': {'type': 'array', '$$description':
['Specifies which fields are intentionally unspecified and expected to be',
'dynamically provided by build tools'], 'items': {'enum': ['version',
'description', 'readme', 'requires-python', 'license', 'authors',
'maintainers', 'keywords', 'classifiers', 'urls', 'scripts', 'gui-scripts',
'entry-points', 'dependencies', 'optional-dependencies']}}}, 'required':
['name'], 'additionalProperties': False, 'if': {'not': {'required':
['dynamic'], 'properties': {'dynamic': {'contains': {'const': 'version'},
'$$description': ['version is listed in ``dynamic``']}}}, '$$comment':
['According to :pep:`621`:', ' If the core metadata specification lists a
field as "Required", then', ' the metadata MUST specify the field
statically or list it in dynamic', 'In turn, `core metadata`_ defines:', '
The required fields are: Metadata-Version, Name, Version.', ' All the
other fields are optional.', 'Since ``Metadata-Version`` is defined by the
build back-end, ``name`` and', '``version`` are the only mandatory
information in ``pyproject.toml``.', '.. _core metadata:
https://packaging.python.org/specifications/core-metadata/']}, 'then':
{'required': ['version'], '$$description': ['version should be statically
defined in the ``version`` field']}, 'definitions': {'author': {'$id':
'#/definitions/author', 'title': 'Author or Maintainer', '$comment':
'https://peps.python.org/pep-0621/#authors-maintainers', 'type': 'object',
'additionalProperties': False, 'properties': {'name': {'type': 'string',
'$$description': ['MUST be a valid email name, i.e. whatever can be put as a
name, before an', 'email, in :rfc:`822`.']}, 'email': {'type': 'string',
'format': 'idn-email', 'description': 'MUST be a valid email address'}}},
'entry-point-group': {'$id': '#/definitions/entry-point-group', 'title':
'Entry-points', 'type': 'object', '$$description': ['Entry-points are grouped
together to indicate what sort of capabilities they', 'provide.', 'See the
`packaging guides',
'<https://packaging.python.org/specifications/entry-points/>`_', 'and
`setuptools docs',
'<https://setuptools.pypa.io/en/latest/userguide/entry_point.html>`_', 'for
more information.'], 'propertyNames': {'format': 'python-entrypoint-name'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'type':
'string', '$$description': ['Reference to a Python object. It is either in
the form', '``importable.module``, or ``importable.module:object.attr``.'],
'format': 'python-entrypoint-reference', '$comment':
'https://packaging.python.org/specifications/entry-points/'}}}, 'dependency':
{'$id': '#/definitions/dependency', 'title': 'Dependency', 'type': 'string',
'description': 'Project dependency specification according to PEP 508',
'format': 'pep508'}}}}, rule='type')
-+ data_is_dict = isinstance(data, dict)
-+ if data_is_dict:
-+ data_keys = set(data.keys())
-+ if "build-system" in data_keys:
-+ data_keys.remove("build-system")
-+ data__buildsystem = data["build-system"]
-+ if not isinstance(data__buildsystem, (dict)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".build-system must be object", value=data__buildsystem, name="" +
(name_prefix or "data") + ".build-system", definition={'type': 'object',
'description': 'Table used to store build-related data',
'additionalProperties': False, 'properties': {'requires': {'type': 'array',
'$$description': ['List of dependencies in the :pep:`508` format required to
execute the build', 'system. Please notice that the resulting dependency
graph', '**MUST NOT contain cycles**'], 'items': {'type': 'string'}},
'build-backend': {'type': 'string', 'description': 'Python object that will
be used to perform the build according to :pep:`517`', 'format':
'pep517-backend-reference'}, 'backend-path': {'type': 'array',
'$$description': ['List of directories to be prepended to ``sys.path`` when
loading the', 'back-end, and running its hooks'], 'items': {'type': 'string',
'$comment': 'Should be a path (TODO: enforce it with format?)'}}},
'required': ['requires']}, rule='type')
-+ data__buildsystem_is_dict = isinstance(data__buildsystem, dict)
-+ if data__buildsystem_is_dict:
-+ data__buildsystem_len = len(data__buildsystem)
-+ if not all(prop in data__buildsystem for prop in
['requires']):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".build-system must contain ['requires'] properties",
value=data__buildsystem, name="" + (name_prefix or "data") + ".build-system",
definition={'type': 'object', 'description': 'Table used to store
build-related data', 'additionalProperties': False, 'properties':
{'requires': {'type': 'array', '$$description': ['List of dependencies in the
:pep:`508` format required to execute the build', 'system. Please notice that
the resulting dependency graph', '**MUST NOT contain cycles**'], 'items':
{'type': 'string'}}, 'build-backend': {'type': 'string', 'description':
'Python object that will be used to perform the build according to
:pep:`517`', 'format': 'pep517-backend-reference'}, 'backend-path': {'type':
'array', '$$description': ['List of directories to be prepended to
``sys.path`` when loading the', 'back-end, and running its hooks'], 'items':
{'type': 'string', '$comment': 'Should be a path (TODO: enforce it with
format?)'}}}, 'required': ['requires']}, rule='required')
-+ data__buildsystem_keys = set(data__buildsystem.keys())
-+ if "requires" in data__buildsystem_keys:
-+ data__buildsystem_keys.remove("requires")
-+ data__buildsystem__requires =
data__buildsystem["requires"]
-+ if not isinstance(data__buildsystem__requires, (list,
tuple)):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".build-system.requires must be array",
value=data__buildsystem__requires, name="" + (name_prefix or "data") +
".build-system.requires", definition={'type': 'array', '$$description':
['List of dependencies in the :pep:`508` format required to execute the
build', 'system. Please notice that the resulting dependency graph', '**MUST
NOT contain cycles**'], 'items': {'type': 'string'}}, rule='type')
-+ data__buildsystem__requires_is_list =
isinstance(data__buildsystem__requires, (list, tuple))
-+ if data__buildsystem__requires_is_list:
-+ data__buildsystem__requires_len =
len(data__buildsystem__requires)
-+ for data__buildsystem__requires_x,
data__buildsystem__requires_item in enumerate(data__buildsystem__requires):
-+ if not
isinstance(data__buildsystem__requires_item, (str)):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") +
".build-system.requires[{data__buildsystem__requires_x}]".format(**locals())
+ " must be string", value=data__buildsystem__requires_item, name="" +
(name_prefix or "data") +
".build-system.requires[{data__buildsystem__requires_x}]".format(**locals())
+ "", definition={'type': 'string'}, rule='type')
-+ if "build-backend" in data__buildsystem_keys:
-+ data__buildsystem_keys.remove("build-backend")
-+ data__buildsystem__buildbackend =
data__buildsystem["build-backend"]
-+ if not isinstance(data__buildsystem__buildbackend,
(str)):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".build-system.build-backend must be string",
value=data__buildsystem__buildbackend, name="" + (name_prefix or "data") +
".build-system.build-backend", definition={'type': 'string', 'description':
'Python object that will be used to perform the build according to
:pep:`517`', 'format': 'pep517-backend-reference'}, rule='type')
-+ if isinstance(data__buildsystem__buildbackend, str):
-+ if not
custom_formats["pep517-backend-reference"](data__buildsystem__buildbackend):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".build-system.build-backend must be
pep517-backend-reference", value=data__buildsystem__buildbackend, name="" +
(name_prefix or "data") + ".build-system.build-backend", definition={'type':
'string', 'description': 'Python object that will be used to perform the
build according to :pep:`517`', 'format': 'pep517-backend-reference'},
rule='format')
-+ if "backend-path" in data__buildsystem_keys:
-+ data__buildsystem_keys.remove("backend-path")
-+ data__buildsystem__backendpath =
data__buildsystem["backend-path"]
-+ if not isinstance(data__buildsystem__backendpath,
(list, tuple)):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".build-system.backend-path must be array",
value=data__buildsystem__backendpath, name="" + (name_prefix or "data") +
".build-system.backend-path", definition={'type': 'array', '$$description':
['List of directories to be prepended to ``sys.path`` when loading the',
'back-end, and running its hooks'], 'items': {'type': 'string', '$comment':
'Should be a path (TODO: enforce it with format?)'}}, rule='type')
-+ data__buildsystem__backendpath_is_list =
isinstance(data__buildsystem__backendpath, (list, tuple))
-+ if data__buildsystem__backendpath_is_list:
-+ data__buildsystem__backendpath_len =
len(data__buildsystem__backendpath)
-+ for data__buildsystem__backendpath_x,
data__buildsystem__backendpath_item in
enumerate(data__buildsystem__backendpath):
-+ if not
isinstance(data__buildsystem__backendpath_item, (str)):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") +
".build-system.backend-path[{data__buildsystem__backendpath_x}]".format(**locals())
+ " must be string", value=data__buildsystem__backendpath_item, name="" +
(name_prefix or "data") +
".build-system.backend-path[{data__buildsystem__backendpath_x}]".format(**locals())
+ "", definition={'type': 'string', '$comment': 'Should be a path (TODO:
enforce it with format?)'}, rule='type')
-+ if data__buildsystem_keys:
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".build-system must not contain "+str(data__buildsystem_keys)+"
properties", value=data__buildsystem, name="" + (name_prefix or "data") +
".build-system", definition={'type': 'object', 'description': 'Table used to
store build-related data', 'additionalProperties': False, 'properties':
{'requires': {'type': 'array', '$$description': ['List of dependencies in the
:pep:`508` format required to execute the build', 'system. Please notice that
the resulting dependency graph', '**MUST NOT contain cycles**'], 'items':
{'type': 'string'}}, 'build-backend': {'type': 'string', 'description':
'Python object that will be used to perform the build according to
:pep:`517`', 'format': 'pep517-backend-reference'}, 'backend-path': {'type':
'array', '$$description': ['List of directories to be prepended to
``sys.path`` when loading the', 'back-end, and running its hooks'], 'items':
{'type': 'string', '$comment': 'Should be a path (TODO: enforce it with
format?)'}}}, 'required': ['requires']}, rule='additionalProperties')
-+ if "project" in data_keys:
-+ data_keys.remove("project")
-+ data__project = data["project"]
-+
validate_https___packaging_python_org_en_latest_specifications_declaring_project_metadata(data__project,
custom_formats, (name_prefix or "data") + ".project")
-+ if "tool" in data_keys:
-+ data_keys.remove("tool")
-+ data__tool = data["tool"]
-+ if not isinstance(data__tool, (dict)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".tool must be object", value=data__tool, name="" + (name_prefix or "data")
+ ".tool", definition={'type': 'object', 'properties': {'distutils':
{'$schema': 'http://json-schema.org/draft-07/schema', '$id':
'https://docs.python.org/3/install/', 'title': '``tool.distutils`` table',
'$$description': ['Originally, ``distutils`` allowed developers to configure
arguments for', '``setup.py`` scripts via `distutils configuration files',
'<https://docs.python.org/3/install/#distutils-configuration-files>`_.',
'``tool.distutils`` subtables could be used with the same purpose', '(NOT
CURRENTLY IMPLEMENTED).'], 'type': 'object', 'properties': {'global':
{'type': 'object', 'description': 'Global options applied to all
``distutils`` commands'}}, 'patternProperties': {'.+': {'type': 'object'}},
'$comment': 'TODO: Is there a practical way of making this schema more
specific?'}, 'setuptools': {'$schema':
'http://json-schema.org/draft-07/schema', '$id':
'https://setuptools.pypa.io/en/latest/references/keywords.html', 'title':
'``tool.setuptools`` table', '$$description': ['Please notice for the time
being the ``setuptools`` project does not specify', 'a way of configuring
builds via ``pyproject.toml``.', 'Therefore this schema should be taken just
as a *"thought experiment"* on how', 'this *might be done*, by following the
principles established in', '`ini2toml
<https://ini2toml.readthedocs.io/en/latest/setuptools_pep621.html>`_.', 'It
considers only ``setuptools`` `parameters',
'<https://setuptools.pypa.io/en/latest/userguide/declarative_config.html>`_',
'that can currently be configured via ``setup.cfg`` and are not covered by
:pep:`621`', 'but intentionally excludes ``dependency_links`` and
``setup_requires``.', 'NOTE: ``scripts`` was renamed to ``script-files`` to
avoid confusion with', 'entry-point based scripts (defined in :pep:`621`).'],
'type': 'object', 'additionalProperties': False, 'properties': {'platforms':
{'type': 'array', 'items': {'type': 'string'}}, 'provides': {'$$description':
['Package and virtual package names contained within this package', '**(not
supported by pip)**'], 'type': 'array', 'items': {'type': 'string', 'format':
'pep508-identifier'}}, 'obsoletes': {'$$description': ['Packages which this
package renders obsolete', '**(not supported by pip)**'], 'type': 'array',
'items': {'type': 'string', 'format': 'pep508-identifier'}}, 'zip-safe':
{'description': 'Whether the project can be safely installed and run from a
zip file.', 'type': 'boolean'}, 'script-files': {'description': 'Legacy way
of defining scripts (entry-points are preferred).', 'type': 'array', 'items':
{'type': 'string'}, '$comment': 'TODO: is this field deprecated/should be
removed?'}, 'eager-resources': {'$$description': ['Resources that should be
extracted together, if any of them is needed,', 'or if any C extensions
included in the project are imported.'], 'type': 'array', 'items': {'type':
'string'}}, 'packages': {'$$description': ['Packages that should be included
in the distribution.', 'It can be given either as a list of package
identifiers', 'or as a ``dict``-like structure with a single key ``find``',
'which corresponds to a dynamic call to',
'``setuptools.config.expand.find_packages`` function.', 'The ``find`` key is
associated with a nested ``dict``-like structure that can', 'contain
``where``, ``include``, ``exclude`` and ``namespaces`` keys,', 'mimicking the
keyword arguments of the associated function.'], 'oneOf': [{'title': 'Array
of Python package identifiers', 'type': 'array', 'items': {'$ref':
'#/definitions/package-name'}}, {'$ref': '#/definitions/find-directive'}]},
'package-dir': {'$$description': [':class:`dict`-like structure mapping from
package names to directories where their', 'code can be found.', 'The empty
string (as key) means that all packages are contained inside', 'the given
directory will be included in the distribution.'], 'type': 'object',
'additionalProperties': False, 'propertyNames': {'oneOf': [{'const': ''},
{'$ref': '#/definitions/package-name'}]}, 'patternProperties': {'^.*$':
{'type': 'string'}}}, 'package-data': {'$$description': ['Mapping from
package names to lists of glob patterns.', 'Usually this option is not needed
when using ``include-package-data = true``', 'For more information on how to
include data files, check ``setuptools`` `docs',
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_.'],
'type': 'object', 'additionalProperties': False, 'propertyNames': {'oneOf':
[{'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties':
{'^.*$': {'type': 'array', 'items': {'type': 'string'}}}},
'include-package-data': {'$$description': ['Automatically include any data
files inside the package directories', 'that are specified by
``MANIFEST.in``', 'For more information on how to include data files, check
``setuptools`` `docs',
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_.'],
'type': 'boolean'}, 'exclude-package-data': {'$$description': ['Mapping from
package names to lists of glob patterns that should be excluded', 'For more
information on how to include data files, check ``setuptools`` `docs',
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_.'],
'type': 'object', 'additionalProperties': False, 'propertyNames': {'oneOf':
[{'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties':
{'^.*$': {'type': 'array', 'items': {'type': 'string'}}}},
'namespace-packages': {'type': 'array', 'items': {'type': 'string', 'format':
'python-module-name'}, '$comment':
'https://setuptools.pypa.io/en/latest/userguide/package_discovery.html'},
'py-modules': {'description': 'Modules that setuptools will manipulate',
'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name'},
'$comment': 'TODO: clarify the relationship with ``packages``'},
'data-files': {'$$description': ['**DEPRECATED**: dict-like structure where
each key represents a directory and', 'the value is a list of glob patterns
that should be installed in them.', "Please notice this don't work with
wheels. See `data files support",
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_'], 'type':
'object', 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type':
'string'}}}}, 'cmdclass': {'$$description': ['Mapping of distutils-style
command names to ``setuptools.Command`` subclasses', 'which in turn should be
represented by strings with a qualified class name', '(i.e., "dotted" form
with module), e.g.::\n\n', ' cmdclass = {mycmd =
"pkg.subpkg.module.CommandClass"}\n\n', 'The command class should be a
directly defined at the top-level of the', 'containing module (no class
nesting).'], 'type': 'object', 'patternProperties': {'^.*$': {'type':
'string', 'format': 'python-qualified-identifier'}}}, 'license-files':
{'type': 'array', 'items': {'type': 'string'}, '$$description':
['PROVISIONAL: List of glob patterns for all license files being
distributed.', '(might become standard with PEP 639).', "By default:
``['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*']``"], '$comment': 'TODO:
revise if PEP 639 is accepted. Probably ``project.license-files``?'},
'dynamic': {'type': 'object', 'description': 'Instructions for loading
:pep:`621`-related metadata dynamically', 'additionalProperties': False,
'properties': {'version': {'$$description': ['A version dynamically loaded
via either the ``attr:`` or ``file:``', 'directives. Please make sure the
given file or attribute respects :pep:`440`.'], 'oneOf': [{'$ref':
'#/definitions/attr-directive'}, {'$ref': '#/definitions/file-directive'}]},
'classifiers': {'$ref': '#/definitions/file-directive'}, 'description':
{'$ref': '#/definitions/file-directive'}, 'dependencies': {'$ref':
'#/definitions/file-directive'}, 'entry-points': {'$ref':
'#/definitions/file-directive'}, 'optional-dependencies': {'type': 'object',
'propertyNames': {'format': 'python-identifier'}, 'additionalProperties':
False, 'patternProperties': {'.+': {'$ref':
'#/definitions/file-directive'}}}, 'readme': {'anyOf': [{'$ref':
'#/definitions/file-directive'}, {'properties': {'content-type': {'type':
'string'}}}], 'required': ['file']}}}}, 'definitions': {'package-name':
{'$id': '#/definitions/package-name', 'title': 'Valid package name',
'description': 'Valid package name (importable or PEP 561).', 'type':
'string', 'anyOf': [{'format': 'python-module-name'}, {'format':
'pep561-stub-name'}]}, 'file-directive': {'$id':
'#/definitions/file-directive', 'title': "'file:' directive", 'description':
'Value is read from a file (or list of files and then concatenated)', 'type':
'object', 'additionalProperties': False, 'properties': {'file': {'oneOf':
[{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}]}},
'required': ['file']}, 'attr-directive': {'title': "'attr:' directive",
'$id': '#/definitions/attr-directive', '$$description': ['Value is read from
a module attribute. Supports callables and iterables;', 'unsupported types
are cast via ``str()``'], 'type': 'object', 'additionalProperties': False,
'properties': {'attr': {'type': 'string'}}, 'required': ['attr']},
'find-directive': {'$id': '#/definitions/find-directive', 'title': "'find:'
directive", 'type': 'object', 'additionalProperties': False, 'properties':
{'find': {'type': 'object', '$$description': ['Dynamic `package discovery',
'<https://setuptools.pypa.io/en/latest/userguide/package_discovery.html>`_.'],
'additionalProperties': False, 'properties': {'where': {'description':
'Directories to be searched for packages (Unix-style relative path)', 'type':
'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array',
'$$description': ['Exclude packages that match the values listed in this
field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items':
{'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict
the found packages to just the ones listed in this field.', "Can container
shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}},
'namespaces': {'type': 'boolean', '$$description': ['When ``True``,
directories without a ``__init__.py`` file will also', 'be scanned for
:pep:`420`-style implicit namespaces']}}}}}}}}}, rule='type')
-+ data__tool_is_dict = isinstance(data__tool, dict)
-+ if data__tool_is_dict:
-+ data__tool_keys = set(data__tool.keys())
-+ if "distutils" in data__tool_keys:
-+ data__tool_keys.remove("distutils")
-+ data__tool__distutils = data__tool["distutils"]
-+
validate_https___docs_python_org_3_install(data__tool__distutils,
custom_formats, (name_prefix or "data") + ".tool.distutils")
-+ if "setuptools" in data__tool_keys:
-+ data__tool_keys.remove("setuptools")
-+ data__tool__setuptools = data__tool["setuptools"]
-+
validate_https___setuptools_pypa_io_en_latest_references_keywords_html(data__tool__setuptools,
custom_formats, (name_prefix or "data") + ".tool.setuptools")
-+ if data_keys:
-+ raise JsonSchemaValueException("" + (name_prefix or "data") + "
must not contain "+str(data_keys)+" properties", value=data, name="" +
(name_prefix or "data") + "", definition={'$schema':
'http://json-schema.org/draft-07/schema', '$id':
'https://packaging.python.org/en/latest/specifications/declaring-build-dependencies/',
'title': 'Data structure for ``pyproject.toml`` files', '$$description':
['File format containing build-time configurations for the Python ecosystem.
', ':pep:`517` initially defined a build-system independent format for source
trees', 'which was complemented by :pep:`518` to provide a way of specifying
dependencies ', 'for building Python projects.', 'Please notice the
``project`` table (as initially defined in :pep:`621`) is not included', 'in
this schema and should be considered separately.'], 'type': 'object',
'additionalProperties': False, 'properties': {'build-system': {'type':
'object', 'description': 'Table used to store build-related data',
'additionalProperties': False, 'properties': {'requires': {'type': 'array',
'$$description': ['List of dependencies in the :pep:`508` format required to
execute the build', 'system. Please notice that the resulting dependency
graph', '**MUST NOT contain cycles**'], 'items': {'type': 'string'}},
'build-backend': {'type': 'string', 'description': 'Python object that will
be used to perform the build according to :pep:`517`', 'format':
'pep517-backend-reference'}, 'backend-path': {'type': 'array',
'$$description': ['List of directories to be prepended to ``sys.path`` when
loading the', 'back-end, and running its hooks'], 'items': {'type': 'string',
'$comment': 'Should be a path (TODO: enforce it with format?)'}}},
'required': ['requires']}, 'project': {'$schema':
'http://json-schema.org/draft-07/schema', '$id':
'https://packaging.python.org/en/latest/specifications/declaring-project-metadata/',
'title': 'Package metadata stored in the ``project`` table',
'$$description': ['Data structure for the **project** table inside
``pyproject.toml``', '(as initially defined in :pep:`621`)'], 'type':
'object', 'properties': {'name': {'type': 'string', 'description': 'The name
(primary identifier) of the project. MUST be statically defined.', 'format':
'pep508-identifier'}, 'version': {'type': 'string', 'description': 'The
version of the project as supported by :pep:`440`.', 'format': 'pep440'},
'description': {'type': 'string', '$$description': ['The `summary description
of the project',
'<https://packaging.python.org/specifications/core-metadata/#summary>`_']},
'readme': {'$$description': ['`Full/detailed description of the project in
the form of a README', '<https://peps.python.org/pep-0621/#readme>`_', "with
meaning similar to the one defined in `core metadata's Description",
'<https://packaging.python.org/specifications/core-metadata/#description>`_'],
'oneOf': [{'type': 'string', '$$description': ['Relative path to a text file
(UTF-8) containing the full description', 'of the project. If the file path
ends in case-insensitive ``.md`` or', '``.rst`` suffixes, then the
content-type is respectively', '``text/markdown`` or ``text/x-rst``']},
{'type': 'object', 'allOf': [{'anyOf': [{'properties': {'file': {'type':
'string', '$$description': ['Relative path to a text file containing the full
description', 'of the project.']}}, 'required': ['file']}, {'properties':
{'text': {'type': 'string', 'description': 'Full text describing the
project.'}}, 'required': ['text']}]}, {'properties': {'content-type':
{'type': 'string', '$$description': ['Content-type (:rfc:`1341`) of the full
description', '(e.g. ``text/markdown``). The ``charset`` parameter is
assumed', 'UTF-8 when not present.'], '$comment': 'TODO: add regex pattern or
format?'}}, 'required': ['content-type']}]}]}, 'requires-python': {'type':
'string', 'format': 'pep508-versionspec', '$$description': ['`The Python
version requirements of the project',
'<https://packaging.python.org/specifications/core-metadata/#requires-python>`_.']},
'license': {'description': '`Project license
<https://peps.python.org/pep-0621/#license>`_.', 'oneOf': [{'properties':
{'file': {'type': 'string', '$$description': ['Relative path to the file
(UTF-8) which contains the license for the', 'project.']}}, 'required':
['file']}, {'properties': {'text': {'type': 'string', '$$description': ['The
license of the project whose meaning is that of the', '`License field from
the core metadata',
'<https://packaging.python.org/specifications/core-metadata/#license>`_.']}},
'required': ['text']}]}, 'authors': {'type': 'array', 'items': {'$ref':
'#/definitions/author'}, '$$description': ["The people or organizations
considered to be the 'authors' of the project.", 'The exact meaning is open
to interpretation (e.g. original or primary authors,', 'current maintainers,
or owners of the package).']}, 'maintainers': {'type': 'array', 'items':
{'$ref': '#/definitions/author'}, '$$description': ["The people or
organizations considered to be the 'maintainers' of the project.", 'Similarly
to ``authors``, the exact meaning is open to interpretation.']}, 'keywords':
{'type': 'array', 'items': {'type': 'string'}, 'description': 'List of
keywords to assist searching for the distribution in a larger catalog.'},
'classifiers': {'type': 'array', 'items': {'type': 'string', 'format':
'trove-classifier', 'description': '`PyPI classifier
<https://pypi.org/classifiers/>`_.'}, '$$description': ['`Trove classifiers
<https://pypi.org/classifiers/>`_', 'which apply to the project.']}, 'urls':
{'type': 'object', 'description': 'URLs associated with the project in the
form ``label => value``.', 'additionalProperties': False,
'patternProperties': {'^.+$': {'type': 'string', 'format': 'url'}}},
'scripts': {'$ref': '#/definitions/entry-point-group', '$$description':
['Instruct the installer to create command-line wrappers for the given',
'`entry points
<https://packaging.python.org/specifications/entry-points/>`_.']},
'gui-scripts': {'$ref': '#/definitions/entry-point-group', '$$description':
['Instruct the installer to create GUI wrappers for the given', '`entry
points <https://packaging.python.org/specifications/entry-points/>`_.', 'The
difference between ``scripts`` and ``gui-scripts`` is only relevant in',
'Windows.']}, 'entry-points': {'$$description': ['Instruct the installer to
expose the given modules/functions via', '``entry-point`` discovery mechanism
(useful for plugins).', 'More information available in the `Python packaging
guide', '<https://packaging.python.org/specifications/entry-points/>`_.'],
'propertyNames': {'format': 'python-entrypoint-group'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'$ref':
'#/definitions/entry-point-group'}}}, 'dependencies': {'type': 'array',
'description': 'Project (mandatory) dependencies.', 'items': {'$ref':
'#/definitions/dependency'}}, 'optional-dependencies': {'type': 'object',
'description': 'Optional dependency for the project', 'propertyNames':
{'format': 'pep508-identifier'}, 'additionalProperties': False,
'patternProperties': {'^.+$': {'type': 'array', 'items': {'$ref':
'#/definitions/dependency'}}}}, 'dynamic': {'type': 'array', '$$description':
['Specifies which fields are intentionally unspecified and expected to be',
'dynamically provided by build tools'], 'items': {'enum': ['version',
'description', 'readme', 'requires-python', 'license', 'authors',
'maintainers', 'keywords', 'classifiers', 'urls', 'scripts', 'gui-scripts',
'entry-points', 'dependencies', 'optional-dependencies']}}}, 'required':
['name'], 'additionalProperties': False, 'if': {'not': {'required':
['dynamic'], 'properties': {'dynamic': {'contains': {'const': 'version'},
'$$description': ['version is listed in ``dynamic``']}}}, '$$comment':
['According to :pep:`621`:', ' If the core metadata specification lists a
field as "Required", then', ' the metadata MUST specify the field
statically or list it in dynamic', 'In turn, `core metadata`_ defines:', '
The required fields are: Metadata-Version, Name, Version.', ' All the
other fields are optional.', 'Since ``Metadata-Version`` is defined by the
build back-end, ``name`` and', '``version`` are the only mandatory
information in ``pyproject.toml``.', '.. _core metadata:
https://packaging.python.org/specifications/core-metadata/']}, 'then':
{'required': ['version'], '$$description': ['version should be statically
defined in the ``version`` field']}, 'definitions': {'author': {'$id':
'#/definitions/author', 'title': 'Author or Maintainer', '$comment':
'https://peps.python.org/pep-0621/#authors-maintainers', 'type': 'object',
'additionalProperties': False, 'properties': {'name': {'type': 'string',
'$$description': ['MUST be a valid email name, i.e. whatever can be put as a
name, before an', 'email, in :rfc:`822`.']}, 'email': {'type': 'string',
'format': 'idn-email', 'description': 'MUST be a valid email address'}}},
'entry-point-group': {'$id': '#/definitions/entry-point-group', 'title':
'Entry-points', 'type': 'object', '$$description': ['Entry-points are grouped
together to indicate what sort of capabilities they', 'provide.', 'See the
`packaging guides',
'<https://packaging.python.org/specifications/entry-points/>`_', 'and
`setuptools docs',
'<https://setuptools.pypa.io/en/latest/userguide/entry_point.html>`_', 'for
more information.'], 'propertyNames': {'format': 'python-entrypoint-name'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'type':
'string', '$$description': ['Reference to a Python object. It is either in
the form', '``importable.module``, or ``importable.module:object.attr``.'],
'format': 'python-entrypoint-reference', '$comment':
'https://packaging.python.org/specifications/entry-points/'}}}, 'dependency':
{'$id': '#/definitions/dependency', 'title': 'Dependency', 'type': 'string',
'description': 'Project dependency specification according to PEP 508',
'format': 'pep508'}}}, 'tool': {'type': 'object', 'properties': {'distutils':
{'$schema': 'http://json-schema.org/draft-07/schema', '$id':
'https://docs.python.org/3/install/', 'title': '``tool.distutils`` table',
'$$description': ['Originally, ``distutils`` allowed developers to configure
arguments for', '``setup.py`` scripts via `distutils configuration files',
'<https://docs.python.org/3/install/#distutils-configuration-files>`_.',
'``tool.distutils`` subtables could be used with the same purpose', '(NOT
CURRENTLY IMPLEMENTED).'], 'type': 'object', 'properties': {'global':
{'type': 'object', 'description': 'Global options applied to all
``distutils`` commands'}}, 'patternProperties': {'.+': {'type': 'object'}},
'$comment': 'TODO: Is there a practical way of making this schema more
specific?'}, 'setuptools': {'$schema':
'http://json-schema.org/draft-07/schema', '$id':
'https://setuptools.pypa.io/en/latest/references/keywords.html', 'title':
'``tool.setuptools`` table', '$$description': ['Please notice for the time
being the ``setuptools`` project does not specify', 'a way of configuring
builds via ``pyproject.toml``.', 'Therefore this schema should be taken just
as a *"thought experiment"* on how', 'this *might be done*, by following the
principles established in', '`ini2toml
<https://ini2toml.readthedocs.io/en/latest/setuptools_pep621.html>`_.', 'It
considers only ``setuptools`` `parameters',
'<https://setuptools.pypa.io/en/latest/userguide/declarative_config.html>`_',
'that can currently be configured via ``setup.cfg`` and are not covered by
:pep:`621`', 'but intentionally excludes ``dependency_links`` and
``setup_requires``.', 'NOTE: ``scripts`` was renamed to ``script-files`` to
avoid confusion with', 'entry-point based scripts (defined in :pep:`621`).'],
'type': 'object', 'additionalProperties': False, 'properties': {'platforms':
{'type': 'array', 'items': {'type': 'string'}}, 'provides': {'$$description':
['Package and virtual package names contained within this package', '**(not
supported by pip)**'], 'type': 'array', 'items': {'type': 'string', 'format':
'pep508-identifier'}}, 'obsoletes': {'$$description': ['Packages which this
package renders obsolete', '**(not supported by pip)**'], 'type': 'array',
'items': {'type': 'string', 'format': 'pep508-identifier'}}, 'zip-safe':
{'description': 'Whether the project can be safely installed and run from a
zip file.', 'type': 'boolean'}, 'script-files': {'description': 'Legacy way
of defining scripts (entry-points are preferred).', 'type': 'array', 'items':
{'type': 'string'}, '$comment': 'TODO: is this field deprecated/should be
removed?'}, 'eager-resources': {'$$description': ['Resources that should be
extracted together, if any of them is needed,', 'or if any C extensions
included in the project are imported.'], 'type': 'array', 'items': {'type':
'string'}}, 'packages': {'$$description': ['Packages that should be included
in the distribution.', 'It can be given either as a list of package
identifiers', 'or as a ``dict``-like structure with a single key ``find``',
'which corresponds to a dynamic call to',
'``setuptools.config.expand.find_packages`` function.', 'The ``find`` key is
associated with a nested ``dict``-like structure that can', 'contain
``where``, ``include``, ``exclude`` and ``namespaces`` keys,', 'mimicking the
keyword arguments of the associated function.'], 'oneOf': [{'title': 'Array
of Python package identifiers', 'type': 'array', 'items': {'$ref':
'#/definitions/package-name'}}, {'$ref': '#/definitions/find-directive'}]},
'package-dir': {'$$description': [':class:`dict`-like structure mapping from
package names to directories where their', 'code can be found.', 'The empty
string (as key) means that all packages are contained inside', 'the given
directory will be included in the distribution.'], 'type': 'object',
'additionalProperties': False, 'propertyNames': {'oneOf': [{'const': ''},
{'$ref': '#/definitions/package-name'}]}, 'patternProperties': {'^.*$':
{'type': 'string'}}}, 'package-data': {'$$description': ['Mapping from
package names to lists of glob patterns.', 'Usually this option is not needed
when using ``include-package-data = true``', 'For more information on how to
include data files, check ``setuptools`` `docs',
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_.'],
'type': 'object', 'additionalProperties': False, 'propertyNames': {'oneOf':
[{'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties':
{'^.*$': {'type': 'array', 'items': {'type': 'string'}}}},
'include-package-data': {'$$description': ['Automatically include any data
files inside the package directories', 'that are specified by
``MANIFEST.in``', 'For more information on how to include data files, check
``setuptools`` `docs',
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_.'],
'type': 'boolean'}, 'exclude-package-data': {'$$description': ['Mapping from
package names to lists of glob patterns that should be excluded', 'For more
information on how to include data files, check ``setuptools`` `docs',
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_.'],
'type': 'object', 'additionalProperties': False, 'propertyNames': {'oneOf':
[{'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties':
{'^.*$': {'type': 'array', 'items': {'type': 'string'}}}},
'namespace-packages': {'type': 'array', 'items': {'type': 'string', 'format':
'python-module-name'}, '$comment':
'https://setuptools.pypa.io/en/latest/userguide/package_discovery.html'},
'py-modules': {'description': 'Modules that setuptools will manipulate',
'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name'},
'$comment': 'TODO: clarify the relationship with ``packages``'},
'data-files': {'$$description': ['**DEPRECATED**: dict-like structure where
each key represents a directory and', 'the value is a list of glob patterns
that should be installed in them.', "Please notice this don't work with
wheels. See `data files support",
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_'], 'type':
'object', 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type':
'string'}}}}, 'cmdclass': {'$$description': ['Mapping of distutils-style
command names to ``setuptools.Command`` subclasses', 'which in turn should be
represented by strings with a qualified class name', '(i.e., "dotted" form
with module), e.g.::\n\n', ' cmdclass = {mycmd =
"pkg.subpkg.module.CommandClass"}\n\n', 'The command class should be a
directly defined at the top-level of the', 'containing module (no class
nesting).'], 'type': 'object', 'patternProperties': {'^.*$': {'type':
'string', 'format': 'python-qualified-identifier'}}}, 'license-files':
{'type': 'array', 'items': {'type': 'string'}, '$$description':
['PROVISIONAL: List of glob patterns for all license files being
distributed.', '(might become standard with PEP 639).', "By default:
``['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*']``"], '$comment': 'TODO:
revise if PEP 639 is accepted. Probably ``project.license-files``?'},
'dynamic': {'type': 'object', 'description': 'Instructions for loading
:pep:`621`-related metadata dynamically', 'additionalProperties': False,
'properties': {'version': {'$$description': ['A version dynamically loaded
via either the ``attr:`` or ``file:``', 'directives. Please make sure the
given file or attribute respects :pep:`440`.'], 'oneOf': [{'$ref':
'#/definitions/attr-directive'}, {'$ref': '#/definitions/file-directive'}]},
'classifiers': {'$ref': '#/definitions/file-directive'}, 'description':
{'$ref': '#/definitions/file-directive'}, 'dependencies': {'$ref':
'#/definitions/file-directive'}, 'entry-points': {'$ref':
'#/definitions/file-directive'}, 'optional-dependencies': {'type': 'object',
'propertyNames': {'format': 'python-identifier'}, 'additionalProperties':
False, 'patternProperties': {'.+': {'$ref':
'#/definitions/file-directive'}}}, 'readme': {'anyOf': [{'$ref':
'#/definitions/file-directive'}, {'properties': {'content-type': {'type':
'string'}}}], 'required': ['file']}}}}, 'definitions': {'package-name':
{'$id': '#/definitions/package-name', 'title': 'Valid package name',
'description': 'Valid package name (importable or PEP 561).', 'type':
'string', 'anyOf': [{'format': 'python-module-name'}, {'format':
'pep561-stub-name'}]}, 'file-directive': {'$id':
'#/definitions/file-directive', 'title': "'file:' directive", 'description':
'Value is read from a file (or list of files and then concatenated)', 'type':
'object', 'additionalProperties': False, 'properties': {'file': {'oneOf':
[{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}]}},
'required': ['file']}, 'attr-directive': {'title': "'attr:' directive",
'$id': '#/definitions/attr-directive', '$$description': ['Value is read from
a module attribute. Supports callables and iterables;', 'unsupported types
are cast via ``str()``'], 'type': 'object', 'additionalProperties': False,
'properties': {'attr': {'type': 'string'}}, 'required': ['attr']},
'find-directive': {'$id': '#/definitions/find-directive', 'title': "'find:'
directive", 'type': 'object', 'additionalProperties': False, 'properties':
{'find': {'type': 'object', '$$description': ['Dynamic `package discovery',
'<https://setuptools.pypa.io/en/latest/userguide/package_discovery.html>`_.'],
'additionalProperties': False, 'properties': {'where': {'description':
'Directories to be searched for packages (Unix-style relative path)', 'type':
'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array',
'$$description': ['Exclude packages that match the values listed in this
field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items':
{'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict
the found packages to just the ones listed in this field.', "Can container
shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}},
'namespaces': {'type': 'boolean', '$$description': ['When ``True``,
directories without a ``__init__.py`` file will also', 'be scanned for
:pep:`420`-style implicit namespaces']}}}}}}}}}}, 'project': {'$schema':
'http://json-schema.org/draft-07/schema', '$id':
'https://packaging.python.org/en/latest/specifications/declaring-project-metadata/',
'title': 'Package metadata stored in the ``project`` table',
'$$description': ['Data structure for the **project** table inside
``pyproject.toml``', '(as initially defined in :pep:`621`)'], 'type':
'object', 'properties': {'name': {'type': 'string', 'description': 'The name
(primary identifier) of the project. MUST be statically defined.', 'format':
'pep508-identifier'}, 'version': {'type': 'string', 'description': 'The
version of the project as supported by :pep:`440`.', 'format': 'pep440'},
'description': {'type': 'string', '$$description': ['The `summary description
of the project',
'<https://packaging.python.org/specifications/core-metadata/#summary>`_']},
'readme': {'$$description': ['`Full/detailed description of the project in
the form of a README', '<https://peps.python.org/pep-0621/#readme>`_', "with
meaning similar to the one defined in `core metadata's Description",
'<https://packaging.python.org/specifications/core-metadata/#description>`_'],
'oneOf': [{'type': 'string', '$$description': ['Relative path to a text file
(UTF-8) containing the full description', 'of the project. If the file path
ends in case-insensitive ``.md`` or', '``.rst`` suffixes, then the
content-type is respectively', '``text/markdown`` or ``text/x-rst``']},
{'type': 'object', 'allOf': [{'anyOf': [{'properties': {'file': {'type':
'string', '$$description': ['Relative path to a text file containing the full
description', 'of the project.']}}, 'required': ['file']}, {'properties':
{'text': {'type': 'string', 'description': 'Full text describing the
project.'}}, 'required': ['text']}]}, {'properties': {'content-type':
{'type': 'string', '$$description': ['Content-type (:rfc:`1341`) of the full
description', '(e.g. ``text/markdown``). The ``charset`` parameter is
assumed', 'UTF-8 when not present.'], '$comment': 'TODO: add regex pattern or
format?'}}, 'required': ['content-type']}]}]}, 'requires-python': {'type':
'string', 'format': 'pep508-versionspec', '$$description': ['`The Python
version requirements of the project',
'<https://packaging.python.org/specifications/core-metadata/#requires-python>`_.']},
'license': {'description': '`Project license
<https://peps.python.org/pep-0621/#license>`_.', 'oneOf': [{'properties':
{'file': {'type': 'string', '$$description': ['Relative path to the file
(UTF-8) which contains the license for the', 'project.']}}, 'required':
['file']}, {'properties': {'text': {'type': 'string', '$$description': ['The
license of the project whose meaning is that of the', '`License field from
the core metadata',
'<https://packaging.python.org/specifications/core-metadata/#license>`_.']}},
'required': ['text']}]}, 'authors': {'type': 'array', 'items': {'$ref':
'#/definitions/author'}, '$$description': ["The people or organizations
considered to be the 'authors' of the project.", 'The exact meaning is open
to interpretation (e.g. original or primary authors,', 'current maintainers,
or owners of the package).']}, 'maintainers': {'type': 'array', 'items':
{'$ref': '#/definitions/author'}, '$$description': ["The people or
organizations considered to be the 'maintainers' of the project.", 'Similarly
to ``authors``, the exact meaning is open to interpretation.']}, 'keywords':
{'type': 'array', 'items': {'type': 'string'}, 'description': 'List of
keywords to assist searching for the distribution in a larger catalog.'},
'classifiers': {'type': 'array', 'items': {'type': 'string', 'format':
'trove-classifier', 'description': '`PyPI classifier
<https://pypi.org/classifiers/>`_.'}, '$$description': ['`Trove classifiers
<https://pypi.org/classifiers/>`_', 'which apply to the project.']}, 'urls':
{'type': 'object', 'description': 'URLs associated with the project in the
form ``label => value``.', 'additionalProperties': False,
'patternProperties': {'^.+$': {'type': 'string', 'format': 'url'}}},
'scripts': {'$ref': '#/definitions/entry-point-group', '$$description':
['Instruct the installer to create command-line wrappers for the given',
'`entry points
<https://packaging.python.org/specifications/entry-points/>`_.']},
'gui-scripts': {'$ref': '#/definitions/entry-point-group', '$$description':
['Instruct the installer to create GUI wrappers for the given', '`entry
points <https://packaging.python.org/specifications/entry-points/>`_.', 'The
difference between ``scripts`` and ``gui-scripts`` is only relevant in',
'Windows.']}, 'entry-points': {'$$description': ['Instruct the installer to
expose the given modules/functions via', '``entry-point`` discovery mechanism
(useful for plugins).', 'More information available in the `Python packaging
guide', '<https://packaging.python.org/specifications/entry-points/>`_.'],
'propertyNames': {'format': 'python-entrypoint-group'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'$ref':
'#/definitions/entry-point-group'}}}, 'dependencies': {'type': 'array',
'description': 'Project (mandatory) dependencies.', 'items': {'$ref':
'#/definitions/dependency'}}, 'optional-dependencies': {'type': 'object',
'description': 'Optional dependency for the project', 'propertyNames':
{'format': 'pep508-identifier'}, 'additionalProperties': False,
'patternProperties': {'^.+$': {'type': 'array', 'items': {'$ref':
'#/definitions/dependency'}}}}, 'dynamic': {'type': 'array', '$$description':
['Specifies which fields are intentionally unspecified and expected to be',
'dynamically provided by build tools'], 'items': {'enum': ['version',
'description', 'readme', 'requires-python', 'license', 'authors',
'maintainers', 'keywords', 'classifiers', 'urls', 'scripts', 'gui-scripts',
'entry-points', 'dependencies', 'optional-dependencies']}}}, 'required':
['name'], 'additionalProperties': False, 'if': {'not': {'required':
['dynamic'], 'properties': {'dynamic': {'contains': {'const': 'version'},
'$$description': ['version is listed in ``dynamic``']}}}, '$$comment':
['According to :pep:`621`:', ' If the core metadata specification lists a
field as "Required", then', ' the metadata MUST specify the field
statically or list it in dynamic', 'In turn, `core metadata`_ defines:', '
The required fields are: Metadata-Version, Name, Version.', ' All the
other fields are optional.', 'Since ``Metadata-Version`` is defined by the
build back-end, ``name`` and', '``version`` are the only mandatory
information in ``pyproject.toml``.', '.. _core metadata:
https://packaging.python.org/specifications/core-metadata/']}, 'then':
{'required': ['version'], '$$description': ['version should be statically
defined in the ``version`` field']}, 'definitions': {'author': {'$id':
'#/definitions/author', 'title': 'Author or Maintainer', '$comment':
'https://peps.python.org/pep-0621/#authors-maintainers', 'type': 'object',
'additionalProperties': False, 'properties': {'name': {'type': 'string',
'$$description': ['MUST be a valid email name, i.e. whatever can be put as a
name, before an', 'email, in :rfc:`822`.']}, 'email': {'type': 'string',
'format': 'idn-email', 'description': 'MUST be a valid email address'}}},
'entry-point-group': {'$id': '#/definitions/entry-point-group', 'title':
'Entry-points', 'type': 'object', '$$description': ['Entry-points are grouped
together to indicate what sort of capabilities they', 'provide.', 'See the
`packaging guides',
'<https://packaging.python.org/specifications/entry-points/>`_', 'and
`setuptools docs',
'<https://setuptools.pypa.io/en/latest/userguide/entry_point.html>`_', 'for
more information.'], 'propertyNames': {'format': 'python-entrypoint-name'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'type':
'string', '$$description': ['Reference to a Python object. It is either in
the form', '``importable.module``, or ``importable.module:object.attr``.'],
'format': 'python-entrypoint-reference', '$comment':
'https://packaging.python.org/specifications/entry-points/'}}}, 'dependency':
{'$id': '#/definitions/dependency', 'title': 'Dependency', 'type': 'string',
'description': 'Project dependency specification according to PEP 508',
'format': 'pep508'}}}}, rule='additionalProperties')
-+ return data
-+
-+def
validate_https___setuptools_pypa_io_en_latest_references_keywords_html(data,
custom_formats={}, name_prefix=None):
-+ if not isinstance(data, (dict)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data") + "
must be object", value=data, name="" + (name_prefix or "data") + "",
definition={'$schema': 'http://json-schema.org/draft-07/schema', '$id':
'https://setuptools.pypa.io/en/latest/references/keywords.html', 'title':
'``tool.setuptools`` table', '$$description': ['Please notice for the time
being the ``setuptools`` project does not specify', 'a way of configuring
builds via ``pyproject.toml``.', 'Therefore this schema should be taken just
as a *"thought experiment"* on how', 'this *might be done*, by following the
principles established in', '`ini2toml
<https://ini2toml.readthedocs.io/en/latest/setuptools_pep621.html>`_.', 'It
considers only ``setuptools`` `parameters',
'<https://setuptools.pypa.io/en/latest/userguide/declarative_config.html>`_',
'that can currently be configured via ``setup.cfg`` and are not covered by
:pep:`621`', 'but intentionally excludes ``dependency_links`` and
``setup_requires``.', 'NOTE: ``scripts`` was renamed to ``script-files`` to
avoid confusion with', 'entry-point based scripts (defined in :pep:`621`).'],
'type': 'object', 'additionalProperties': False, 'properties': {'platforms':
{'type': 'array', 'items': {'type': 'string'}}, 'provides': {'$$description':
['Package and virtual package names contained within this package', '**(not
supported by pip)**'], 'type': 'array', 'items': {'type': 'string', 'format':
'pep508-identifier'}}, 'obsoletes': {'$$description': ['Packages which this
package renders obsolete', '**(not supported by pip)**'], 'type': 'array',
'items': {'type': 'string', 'format': 'pep508-identifier'}}, 'zip-safe':
{'description': 'Whether the project can be safely installed and run from a
zip file.', 'type': 'boolean'}, 'script-files': {'description': 'Legacy way
of defining scripts (entry-points are preferred).', 'type': 'array', 'items':
{'type': 'string'}, '$comment': 'TODO: is this field deprecated/should be
removed?'}, 'eager-resources': {'$$description': ['Resources that should be
extracted together, if any of them is needed,', 'or if any C extensions
included in the project are imported.'], 'type': 'array', 'items': {'type':
'string'}}, 'packages': {'$$description': ['Packages that should be included
in the distribution.', 'It can be given either as a list of package
identifiers', 'or as a ``dict``-like structure with a single key ``find``',
'which corresponds to a dynamic call to',
'``setuptools.config.expand.find_packages`` function.', 'The ``find`` key is
associated with a nested ``dict``-like structure that can', 'contain
``where``, ``include``, ``exclude`` and ``namespaces`` keys,', 'mimicking the
keyword arguments of the associated function.'], 'oneOf': [{'title': 'Array
of Python package identifiers', 'type': 'array', 'items': {'$id':
'#/definitions/package-name', 'title': 'Valid package name', 'description':
'Valid package name (importable or PEP 561).', 'type': 'string', 'anyOf':
[{'format': 'python-module-name'}, {'format': 'pep561-stub-name'}]}}, {'$id':
'#/definitions/find-directive', 'title': "'find:' directive", 'type':
'object', 'additionalProperties': False, 'properties': {'find': {'type':
'object', '$$description': ['Dynamic `package discovery',
'<https://setuptools.pypa.io/en/latest/userguide/package_discovery.html>`_.'],
'additionalProperties': False, 'properties': {'where': {'description':
'Directories to be searched for packages (Unix-style relative path)', 'type':
'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array',
'$$description': ['Exclude packages that match the values listed in this
field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items':
{'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict
the found packages to just the ones listed in this field.', "Can container
shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}},
'namespaces': {'type': 'boolean', '$$description': ['When ``True``,
directories without a ``__init__.py`` file will also', 'be scanned for
:pep:`420`-style implicit namespaces']}}}}}]}, 'package-dir':
{'$$description': [':class:`dict`-like structure mapping from package names
to directories where their', 'code can be found.', 'The empty string (as key)
means that all packages are contained inside', 'the given directory will be
included in the distribution.'], 'type': 'object', 'additionalProperties':
False, 'propertyNames': {'oneOf': [{'const': ''}, {'$id':
'#/definitions/package-name', 'title': 'Valid package name', 'description':
'Valid package name (importable or PEP 561).', 'type': 'string', 'anyOf':
[{'format': 'python-module-name'}, {'format': 'pep561-stub-name'}]}]},
'patternProperties': {'^.*$': {'type': 'string'}}}, 'package-data':
{'$$description': ['Mapping from package names to lists of glob patterns.',
'Usually this option is not needed when using ``include-package-data =
true``', 'For more information on how to include data files, check
``setuptools`` `docs',
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_.'],
'type': 'object', 'additionalProperties': False, 'propertyNames': {'oneOf':
[{'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties':
{'^.*$': {'type': 'array', 'items': {'type': 'string'}}}},
'include-package-data': {'$$description': ['Automatically include any data
files inside the package directories', 'that are specified by
``MANIFEST.in``', 'For more information on how to include data files, check
``setuptools`` `docs',
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_.'],
'type': 'boolean'}, 'exclude-package-data': {'$$description': ['Mapping from
package names to lists of glob patterns that should be excluded', 'For more
information on how to include data files, check ``setuptools`` `docs',
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_.'],
'type': 'object', 'additionalProperties': False, 'propertyNames': {'oneOf':
[{'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties':
{'^.*$': {'type': 'array', 'items': {'type': 'string'}}}},
'namespace-packages': {'type': 'array', 'items': {'type': 'string', 'format':
'python-module-name'}, '$comment':
'https://setuptools.pypa.io/en/latest/userguide/package_discovery.html'},
'py-modules': {'description': 'Modules that setuptools will manipulate',
'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name'},
'$comment': 'TODO: clarify the relationship with ``packages``'},
'data-files': {'$$description': ['**DEPRECATED**: dict-like structure where
each key represents a directory and', 'the value is a list of glob patterns
that should be installed in them.', "Please notice this don't work with
wheels. See `data files support",
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_'], 'type':
'object', 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type':
'string'}}}}, 'cmdclass': {'$$description': ['Mapping of distutils-style
command names to ``setuptools.Command`` subclasses', 'which in turn should be
represented by strings with a qualified class name', '(i.e., "dotted" form
with module), e.g.::\n\n', ' cmdclass = {mycmd =
"pkg.subpkg.module.CommandClass"}\n\n', 'The command class should be a
directly defined at the top-level of the', 'containing module (no class
nesting).'], 'type': 'object', 'patternProperties': {'^.*$': {'type':
'string', 'format': 'python-qualified-identifier'}}}, 'license-files':
{'type': 'array', 'items': {'type': 'string'}, '$$description':
['PROVISIONAL: List of glob patterns for all license files being
distributed.', '(might become standard with PEP 639).', "By default:
``['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*']``"], '$comment': 'TODO:
revise if PEP 639 is accepted. Probably ``project.license-files``?'},
'dynamic': {'type': 'object', 'description': 'Instructions for loading
:pep:`621`-related metadata dynamically', 'additionalProperties': False,
'properties': {'version': {'$$description': ['A version dynamically loaded
via either the ``attr:`` or ``file:``', 'directives. Please make sure the
given file or attribute respects :pep:`440`.'], 'oneOf': [{'title': "'attr:'
directive", '$id': '#/definitions/attr-directive', '$$description': ['Value
is read from a module attribute. Supports callables and iterables;',
'unsupported types are cast via ``str()``'], 'type': 'object',
'additionalProperties': False, 'properties': {'attr': {'type': 'string'}},
'required': ['attr']}, {'$id': '#/definitions/file-directive', 'title':
"'file:' directive", 'description': 'Value is read from a file (or list of
files and then concatenated)', 'type': 'object', 'additionalProperties':
False, 'properties': {'file': {'oneOf': [{'type': 'string'}, {'type':
'array', 'items': {'type': 'string'}}]}}, 'required': ['file']}]},
'classifiers': {'$id': '#/definitions/file-directive', 'title': "'file:'
directive", 'description': 'Value is read from a file (or list of files and
then concatenated)', 'type': 'object', 'additionalProperties': False,
'properties': {'file': {'oneOf': [{'type': 'string'}, {'type': 'array',
'items': {'type': 'string'}}]}}, 'required': ['file']}, 'description':
{'$id': '#/definitions/file-directive', 'title': "'file:' directive",
'description': 'Value is read from a file (or list of files and then
concatenated)', 'type': 'object', 'additionalProperties': False,
'properties': {'file': {'oneOf': [{'type': 'string'}, {'type': 'array',
'items': {'type': 'string'}}]}}, 'required': ['file']}, 'dependencies':
{'$id': '#/definitions/file-directive', 'title': "'file:' directive",
'description': 'Value is read from a file (or list of files and then
concatenated)', 'type': 'object', 'additionalProperties': False,
'properties': {'file': {'oneOf': [{'type': 'string'}, {'type': 'array',
'items': {'type': 'string'}}]}}, 'required': ['file']}, 'entry-points':
{'$id': '#/definitions/file-directive', 'title': "'file:' directive",
'description': 'Value is read from a file (or list of files and then
concatenated)', 'type': 'object', 'additionalProperties': False,
'properties': {'file': {'oneOf': [{'type': 'string'}, {'type': 'array',
'items': {'type': 'string'}}]}}, 'required': ['file']},
'optional-dependencies': {'type': 'object', 'propertyNames': {'format':
'python-identifier'}, 'additionalProperties': False, 'patternProperties':
{'.+': {'$id': '#/definitions/file-directive', 'title': "'file:' directive",
'description': 'Value is read from a file (or list of files and then
concatenated)', 'type': 'object', 'additionalProperties': False,
'properties': {'file': {'oneOf': [{'type': 'string'}, {'type': 'array',
'items': {'type': 'string'}}]}}, 'required': ['file']}}}, 'readme': {'anyOf':
[{'$id': '#/definitions/file-directive', 'title': "'file:' directive",
'description': 'Value is read from a file (or list of files and then
concatenated)', 'type': 'object', 'additionalProperties': False,
'properties': {'file': {'oneOf': [{'type': 'string'}, {'type': 'array',
'items': {'type': 'string'}}]}}, 'required': ['file']}, {'properties':
{'content-type': {'type': 'string'}}}], 'required': ['file']}}}},
'definitions': {'package-name': {'$id': '#/definitions/package-name',
'title': 'Valid package name', 'description': 'Valid package name (importable
or PEP 561).', 'type': 'string', 'anyOf': [{'format': 'python-module-name'},
{'format': 'pep561-stub-name'}]}, 'file-directive': {'$id':
'#/definitions/file-directive', 'title': "'file:' directive", 'description':
'Value is read from a file (or list of files and then concatenated)', 'type':
'object', 'additionalProperties': False, 'properties': {'file': {'oneOf':
[{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}]}},
'required': ['file']}, 'attr-directive': {'title': "'attr:' directive",
'$id': '#/definitions/attr-directive', '$$description': ['Value is read from
a module attribute. Supports callables and iterables;', 'unsupported types
are cast via ``str()``'], 'type': 'object', 'additionalProperties': False,
'properties': {'attr': {'type': 'string'}}, 'required': ['attr']},
'find-directive': {'$id': '#/definitions/find-directive', 'title': "'find:'
directive", 'type': 'object', 'additionalProperties': False, 'properties':
{'find': {'type': 'object', '$$description': ['Dynamic `package discovery',
'<https://setuptools.pypa.io/en/latest/userguide/package_discovery.html>`_.'],
'additionalProperties': False, 'properties': {'where': {'description':
'Directories to be searched for packages (Unix-style relative path)', 'type':
'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array',
'$$description': ['Exclude packages that match the values listed in this
field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items':
{'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict
the found packages to just the ones listed in this field.', "Can container
shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}},
'namespaces': {'type': 'boolean', '$$description': ['When ``True``,
directories without a ``__init__.py`` file will also', 'be scanned for
:pep:`420`-style implicit namespaces']}}}}}}}, rule='type')
-+ data_is_dict = isinstance(data, dict)
-+ if data_is_dict:
-+ data_keys = set(data.keys())
-+ if "platforms" in data_keys:
-+ data_keys.remove("platforms")
-+ data__platforms = data["platforms"]
-+ if not isinstance(data__platforms, (list, tuple)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".platforms must be array", value=data__platforms, name="" + (name_prefix
or "data") + ".platforms", definition={'type': 'array', 'items': {'type':
'string'}}, rule='type')
-+ data__platforms_is_list = isinstance(data__platforms, (list,
tuple))
-+ if data__platforms_is_list:
-+ data__platforms_len = len(data__platforms)
-+ for data__platforms_x, data__platforms_item in
enumerate(data__platforms):
-+ if not isinstance(data__platforms_item, (str)):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".platforms[{data__platforms_x}]".format(**locals()) + " must be
string", value=data__platforms_item, name="" + (name_prefix or "data") +
".platforms[{data__platforms_x}]".format(**locals()) + "",
definition={'type': 'string'}, rule='type')
-+ if "provides" in data_keys:
-+ data_keys.remove("provides")
-+ data__provides = data["provides"]
-+ if not isinstance(data__provides, (list, tuple)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".provides must be array", value=data__provides, name="" + (name_prefix or
"data") + ".provides", definition={'$$description': ['Package and virtual
package names contained within this package', '**(not supported by pip)**'],
'type': 'array', 'items': {'type': 'string', 'format': 'pep508-identifier'}},
rule='type')
-+ data__provides_is_list = isinstance(data__provides, (list,
tuple))
-+ if data__provides_is_list:
-+ data__provides_len = len(data__provides)
-+ for data__provides_x, data__provides_item in
enumerate(data__provides):
-+ if not isinstance(data__provides_item, (str)):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".provides[{data__provides_x}]".format(**locals()) + " must be
string", value=data__provides_item, name="" + (name_prefix or "data") +
".provides[{data__provides_x}]".format(**locals()) + "", definition={'type':
'string', 'format': 'pep508-identifier'}, rule='type')
-+ if isinstance(data__provides_item, str):
-+ if not
custom_formats["pep508-identifier"](data__provides_item):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".provides[{data__provides_x}]".format(**locals())
+ " must be pep508-identifier", value=data__provides_item, name="" +
(name_prefix or "data") + ".provides[{data__provides_x}]".format(**locals())
+ "", definition={'type': 'string', 'format': 'pep508-identifier'},
rule='format')
-+ if "obsoletes" in data_keys:
-+ data_keys.remove("obsoletes")
-+ data__obsoletes = data["obsoletes"]
-+ if not isinstance(data__obsoletes, (list, tuple)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".obsoletes must be array", value=data__obsoletes, name="" + (name_prefix
or "data") + ".obsoletes", definition={'$$description': ['Packages which this
package renders obsolete', '**(not supported by pip)**'], 'type': 'array',
'items': {'type': 'string', 'format': 'pep508-identifier'}}, rule='type')
-+ data__obsoletes_is_list = isinstance(data__obsoletes, (list,
tuple))
-+ if data__obsoletes_is_list:
-+ data__obsoletes_len = len(data__obsoletes)
-+ for data__obsoletes_x, data__obsoletes_item in
enumerate(data__obsoletes):
-+ if not isinstance(data__obsoletes_item, (str)):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".obsoletes[{data__obsoletes_x}]".format(**locals()) + " must be
string", value=data__obsoletes_item, name="" + (name_prefix or "data") +
".obsoletes[{data__obsoletes_x}]".format(**locals()) + "",
definition={'type': 'string', 'format': 'pep508-identifier'}, rule='type')
-+ if isinstance(data__obsoletes_item, str):
-+ if not
custom_formats["pep508-identifier"](data__obsoletes_item):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") +
".obsoletes[{data__obsoletes_x}]".format(**locals()) + " must be
pep508-identifier", value=data__obsoletes_item, name="" + (name_prefix or
"data") + ".obsoletes[{data__obsoletes_x}]".format(**locals()) + "",
definition={'type': 'string', 'format': 'pep508-identifier'}, rule='format')
-+ if "zip-safe" in data_keys:
-+ data_keys.remove("zip-safe")
-+ data__zipsafe = data["zip-safe"]
-+ if not isinstance(data__zipsafe, (bool)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".zip-safe must be boolean", value=data__zipsafe, name="" + (name_prefix or
"data") + ".zip-safe", definition={'description': 'Whether the project can be
safely installed and run from a zip file.', 'type': 'boolean'}, rule='type')
-+ if "script-files" in data_keys:
-+ data_keys.remove("script-files")
-+ data__scriptfiles = data["script-files"]
-+ if not isinstance(data__scriptfiles, (list, tuple)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".script-files must be array", value=data__scriptfiles, name="" +
(name_prefix or "data") + ".script-files", definition={'description': 'Legacy
way of defining scripts (entry-points are preferred).', 'type': 'array',
'items': {'type': 'string'}, '$comment': 'TODO: is this field
deprecated/should be removed?'}, rule='type')
-+ data__scriptfiles_is_list = isinstance(data__scriptfiles,
(list, tuple))
-+ if data__scriptfiles_is_list:
-+ data__scriptfiles_len = len(data__scriptfiles)
-+ for data__scriptfiles_x, data__scriptfiles_item in
enumerate(data__scriptfiles):
-+ if not isinstance(data__scriptfiles_item, (str)):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".script-files[{data__scriptfiles_x}]".format(**locals()) + " must
be string", value=data__scriptfiles_item, name="" + (name_prefix or "data") +
".script-files[{data__scriptfiles_x}]".format(**locals()) + "",
definition={'type': 'string'}, rule='type')
-+ if "eager-resources" in data_keys:
-+ data_keys.remove("eager-resources")
-+ data__eagerresources = data["eager-resources"]
-+ if not isinstance(data__eagerresources, (list, tuple)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".eager-resources must be array", value=data__eagerresources, name="" +
(name_prefix or "data") + ".eager-resources", definition={'$$description':
['Resources that should be extracted together, if any of them is needed,',
'or if any C extensions included in the project are imported.'], 'type':
'array', 'items': {'type': 'string'}}, rule='type')
-+ data__eagerresources_is_list = isinstance(data__eagerresources,
(list, tuple))
-+ if data__eagerresources_is_list:
-+ data__eagerresources_len = len(data__eagerresources)
-+ for data__eagerresources_x, data__eagerresources_item in
enumerate(data__eagerresources):
-+ if not isinstance(data__eagerresources_item, (str)):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".eager-resources[{data__eagerresources_x}]".format(**locals()) + "
must be string", value=data__eagerresources_item, name="" + (name_prefix or
"data") + ".eager-resources[{data__eagerresources_x}]".format(**locals()) +
"", definition={'type': 'string'}, rule='type')
-+ if "packages" in data_keys:
-+ data_keys.remove("packages")
-+ data__packages = data["packages"]
-+ data__packages_one_of_count1 = 0
-+ if data__packages_one_of_count1 < 2:
-+ try:
-+ if not isinstance(data__packages, (list, tuple)):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".packages must be array", value=data__packages, name="" +
(name_prefix or "data") + ".packages", definition={'title': 'Array of Python
package identifiers', 'type': 'array', 'items': {'$id':
'#/definitions/package-name', 'title': 'Valid package name', 'description':
'Valid package name (importable or PEP 561).', 'type': 'string', 'anyOf':
[{'format': 'python-module-name'}, {'format': 'pep561-stub-name'}]}},
rule='type')
-+ data__packages_is_list = isinstance(data__packages,
(list, tuple))
-+ if data__packages_is_list:
-+ data__packages_len = len(data__packages)
-+ for data__packages_x, data__packages_item in
enumerate(data__packages):
-+
validate_https___setuptools_pypa_io_en_latest_references_keywords_html__definitions_package_name(data__packages_item,
custom_formats, (name_prefix or "data") +
".packages[{data__packages_x}]".format(**locals()))
-+ data__packages_one_of_count1 += 1
-+ except JsonSchemaValueException: pass
-+ if data__packages_one_of_count1 < 2:
-+ try:
-+
validate_https___setuptools_pypa_io_en_latest_references_keywords_html__definitions_find_directive(data__packages,
custom_formats, (name_prefix or "data") + ".packages")
-+ data__packages_one_of_count1 += 1
-+ except JsonSchemaValueException: pass
-+ if data__packages_one_of_count1 != 1:
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".packages must be valid exactly by one definition" + (" (" +
str(data__packages_one_of_count1) + " matches found)"), value=data__packages,
name="" + (name_prefix or "data") + ".packages", definition={'$$description':
['Packages that should be included in the distribution.', 'It can be given
either as a list of package identifiers', 'or as a ``dict``-like structure
with a single key ``find``', 'which corresponds to a dynamic call to',
'``setuptools.config.expand.find_packages`` function.', 'The ``find`` key is
associated with a nested ``dict``-like structure that can', 'contain
``where``, ``include``, ``exclude`` and ``namespaces`` keys,', 'mimicking the
keyword arguments of the associated function.'], 'oneOf': [{'title': 'Array
of Python package identifiers', 'type': 'array', 'items': {'$id':
'#/definitions/package-name', 'title': 'Valid package name', 'description':
'Valid package name (importable or PEP 561).', 'type': 'string', 'anyOf':
[{'format': 'python-module-name'}, {'format': 'pep561-stub-name'}]}}, {'$id':
'#/definitions/find-directive', 'title': "'find:' directive", 'type':
'object', 'additionalProperties': False, 'properties': {'find': {'type':
'object', '$$description': ['Dynamic `package discovery',
'<https://setuptools.pypa.io/en/latest/userguide/package_discovery.html>`_.'],
'additionalProperties': False, 'properties': {'where': {'description':
'Directories to be searched for packages (Unix-style relative path)', 'type':
'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array',
'$$description': ['Exclude packages that match the values listed in this
field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items':
{'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict
the found packages to just the ones listed in this field.', "Can container
shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}},
'namespaces': {'type': 'boolean', '$$description': ['When ``True``,
directories without a ``__init__.py`` file will also', 'be scanned for
:pep:`420`-style implicit namespaces']}}}}}]}, rule='oneOf')
-+ if "package-dir" in data_keys:
-+ data_keys.remove("package-dir")
-+ data__packagedir = data["package-dir"]
-+ if not isinstance(data__packagedir, (dict)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".package-dir must be object", value=data__packagedir, name="" +
(name_prefix or "data") + ".package-dir", definition={'$$description':
[':class:`dict`-like structure mapping from package names to directories
where their', 'code can be found.', 'The empty string (as key) means that all
packages are contained inside', 'the given directory will be included in the
distribution.'], 'type': 'object', 'additionalProperties': False,
'propertyNames': {'oneOf': [{'const': ''}, {'$id':
'#/definitions/package-name', 'title': 'Valid package name', 'description':
'Valid package name (importable or PEP 561).', 'type': 'string', 'anyOf':
[{'format': 'python-module-name'}, {'format': 'pep561-stub-name'}]}]},
'patternProperties': {'^.*$': {'type': 'string'}}}, rule='type')
-+ data__packagedir_is_dict = isinstance(data__packagedir, dict)
-+ if data__packagedir_is_dict:
-+ data__packagedir_keys = set(data__packagedir.keys())
-+ for data__packagedir_key, data__packagedir_val in
data__packagedir.items():
-+ if REGEX_PATTERNS['^.*$'].search(data__packagedir_key):
-+ if data__packagedir_key in data__packagedir_keys:
-+
data__packagedir_keys.remove(data__packagedir_key)
-+ if not isinstance(data__packagedir_val, (str)):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") +
".package-dir.{data__packagedir_key}".format(**locals()) + " must be string",
value=data__packagedir_val, name="" + (name_prefix or "data") +
".package-dir.{data__packagedir_key}".format(**locals()) + "",
definition={'type': 'string'}, rule='type')
-+ if data__packagedir_keys:
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".package-dir must not contain "+str(data__packagedir_keys)+"
properties", value=data__packagedir, name="" + (name_prefix or "data") +
".package-dir", definition={'$$description': [':class:`dict`-like structure
mapping from package names to directories where their', 'code can be found.',
'The empty string (as key) means that all packages are contained inside',
'the given directory will be included in the distribution.'], 'type':
'object', 'additionalProperties': False, 'propertyNames': {'oneOf':
[{'const': ''}, {'$id': '#/definitions/package-name', 'title': 'Valid package
name', 'description': 'Valid package name (importable or PEP 561).', 'type':
'string', 'anyOf': [{'format': 'python-module-name'}, {'format':
'pep561-stub-name'}]}]}, 'patternProperties': {'^.*$': {'type': 'string'}}},
rule='additionalProperties')
-+ data__packagedir_len = len(data__packagedir)
-+ if data__packagedir_len != 0:
-+ data__packagedir_property_names = True
-+ for data__packagedir_key in data__packagedir:
-+ try:
-+ data__packagedir_key_one_of_count2 = 0
-+ if data__packagedir_key_one_of_count2 < 2:
-+ try:
-+ if data__packagedir_key != "":
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".package-dir must be same as const definition: ",
value=data__packagedir_key, name="" + (name_prefix or "data") +
".package-dir", definition={'const': ''}, rule='const')
-+ data__packagedir_key_one_of_count2 += 1
-+ except JsonSchemaValueException: pass
-+ if data__packagedir_key_one_of_count2 < 2:
-+ try:
-+
validate_https___setuptools_pypa_io_en_latest_references_keywords_html__definitions_package_name(data__packagedir_key,
custom_formats, (name_prefix or "data") + ".package-dir")
-+ data__packagedir_key_one_of_count2 += 1
-+ except JsonSchemaValueException: pass
-+ if data__packagedir_key_one_of_count2 != 1:
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".package-dir must be valid exactly by one
definition" + (" (" + str(data__packagedir_key_one_of_count2) + " matches
found)"), value=data__packagedir_key, name="" + (name_prefix or "data") +
".package-dir", definition={'oneOf': [{'const': ''}, {'$id':
'#/definitions/package-name', 'title': 'Valid package name', 'description':
'Valid package name (importable or PEP 561).', 'type': 'string', 'anyOf':
[{'format': 'python-module-name'}, {'format': 'pep561-stub-name'}]}]},
rule='oneOf')
-+ except JsonSchemaValueException:
-+ data__packagedir_property_names = False
-+ if not data__packagedir_property_names:
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".package-dir must be named by propertyName definition",
value=data__packagedir, name="" + (name_prefix or "data") + ".package-dir",
definition={'$$description': [':class:`dict`-like structure mapping from
package names to directories where their', 'code can be found.', 'The empty
string (as key) means that all packages are contained inside', 'the given
directory will be included in the distribution.'], 'type': 'object',
'additionalProperties': False, 'propertyNames': {'oneOf': [{'const': ''},
{'$id': '#/definitions/package-name', 'title': 'Valid package name',
'description': 'Valid package name (importable or PEP 561).', 'type':
'string', 'anyOf': [{'format': 'python-module-name'}, {'format':
'pep561-stub-name'}]}]}, 'patternProperties': {'^.*$': {'type': 'string'}}},
rule='propertyNames')
-+ if "package-data" in data_keys:
-+ data_keys.remove("package-data")
-+ data__packagedata = data["package-data"]
-+ if not isinstance(data__packagedata, (dict)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".package-data must be object", value=data__packagedata, name="" +
(name_prefix or "data") + ".package-data", definition={'$$description':
['Mapping from package names to lists of glob patterns.', 'Usually this
option is not needed when using ``include-package-data = true``', 'For more
information on how to include data files, check ``setuptools`` `docs',
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_.'],
'type': 'object', 'additionalProperties': False, 'propertyNames': {'oneOf':
[{'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties':
{'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, rule='type')
-+ data__packagedata_is_dict = isinstance(data__packagedata, dict)
-+ if data__packagedata_is_dict:
-+ data__packagedata_keys = set(data__packagedata.keys())
-+ for data__packagedata_key, data__packagedata_val in
data__packagedata.items():
-+ if REGEX_PATTERNS['^.*$'].search(data__packagedata_key):
-+ if data__packagedata_key in data__packagedata_keys:
-+
data__packagedata_keys.remove(data__packagedata_key)
-+ if not isinstance(data__packagedata_val, (list,
tuple)):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") +
".package-data.{data__packagedata_key}".format(**locals()) + " must be
array", value=data__packagedata_val, name="" + (name_prefix or "data") +
".package-data.{data__packagedata_key}".format(**locals()) + "",
definition={'type': 'array', 'items': {'type': 'string'}}, rule='type')
-+ data__packagedata_val_is_list =
isinstance(data__packagedata_val, (list, tuple))
-+ if data__packagedata_val_is_list:
-+ data__packagedata_val_len =
len(data__packagedata_val)
-+ for data__packagedata_val_x,
data__packagedata_val_item in enumerate(data__packagedata_val):
-+ if not
isinstance(data__packagedata_val_item, (str)):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") +
".package-data.{data__packagedata_key}[{data__packagedata_val_x}]".format(**locals())
+ " must be string", value=data__packagedata_val_item, name="" +
(name_prefix or "data") +
".package-data.{data__packagedata_key}[{data__packagedata_val_x}]".format(**locals())
+ "", definition={'type': 'string'}, rule='type')
-+ if data__packagedata_keys:
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".package-data must not contain "+str(data__packagedata_keys)+"
properties", value=data__packagedata, name="" + (name_prefix or "data") +
".package-data", definition={'$$description': ['Mapping from package names to
lists of glob patterns.', 'Usually this option is not needed when using
``include-package-data = true``', 'For more information on how to include
data files, check ``setuptools`` `docs',
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_.'],
'type': 'object', 'additionalProperties': False, 'propertyNames': {'oneOf':
[{'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties':
{'^.*$': {'type': 'array', 'items': {'type': 'string'}}}},
rule='additionalProperties')
-+ data__packagedata_len = len(data__packagedata)
-+ if data__packagedata_len != 0:
-+ data__packagedata_property_names = True
-+ for data__packagedata_key in data__packagedata:
-+ try:
-+ data__packagedata_key_one_of_count3 = 0
-+ if data__packagedata_key_one_of_count3 < 2:
-+ try:
-+ if isinstance(data__packagedata_key,
str):
-+ if not
custom_formats["python-module-name"](data__packagedata_key):
-+ raise
JsonSchemaValueException("" + (name_prefix or "data") + ".package-data must
be python-module-name", value=data__packagedata_key, name="" + (name_prefix
or "data") + ".package-data", definition={'format': 'python-module-name'},
rule='format')
-+ data__packagedata_key_one_of_count3 += 1
-+ except JsonSchemaValueException: pass
-+ if data__packagedata_key_one_of_count3 < 2:
-+ try:
-+ if data__packagedata_key != "*":
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".package-data must be same as const definition:
*", value=data__packagedata_key, name="" + (name_prefix or "data") +
".package-data", definition={'const': '*'}, rule='const')
-+ data__packagedata_key_one_of_count3 += 1
-+ except JsonSchemaValueException: pass
-+ if data__packagedata_key_one_of_count3 != 1:
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".package-data must be valid exactly by one
definition" + (" (" + str(data__packagedata_key_one_of_count3) + " matches
found)"), value=data__packagedata_key, name="" + (name_prefix or "data") +
".package-data", definition={'oneOf': [{'format': 'python-module-name'},
{'const': '*'}]}, rule='oneOf')
-+ except JsonSchemaValueException:
-+ data__packagedata_property_names = False
-+ if not data__packagedata_property_names:
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".package-data must be named by propertyName definition",
value=data__packagedata, name="" + (name_prefix or "data") + ".package-data",
definition={'$$description': ['Mapping from package names to lists of glob
patterns.', 'Usually this option is not needed when using
``include-package-data = true``', 'For more information on how to include
data files, check ``setuptools`` `docs',
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_.'],
'type': 'object', 'additionalProperties': False, 'propertyNames': {'oneOf':
[{'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties':
{'^.*$': {'type': 'array', 'items': {'type': 'string'}}}},
rule='propertyNames')
-+ if "include-package-data" in data_keys:
-+ data_keys.remove("include-package-data")
-+ data__includepackagedata = data["include-package-data"]
-+ if not isinstance(data__includepackagedata, (bool)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".include-package-data must be boolean", value=data__includepackagedata,
name="" + (name_prefix or "data") + ".include-package-data",
definition={'$$description': ['Automatically include any data files inside
the package directories', 'that are specified by ``MANIFEST.in``', 'For more
information on how to include data files, check ``setuptools`` `docs',
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_.'],
'type': 'boolean'}, rule='type')
-+ if "exclude-package-data" in data_keys:
-+ data_keys.remove("exclude-package-data")
-+ data__excludepackagedata = data["exclude-package-data"]
-+ if not isinstance(data__excludepackagedata, (dict)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".exclude-package-data must be object", value=data__excludepackagedata,
name="" + (name_prefix or "data") + ".exclude-package-data",
definition={'$$description': ['Mapping from package names to lists of glob
patterns that should be excluded', 'For more information on how to include
data files, check ``setuptools`` `docs',
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_.'],
'type': 'object', 'additionalProperties': False, 'propertyNames': {'oneOf':
[{'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties':
{'^.*$': {'type': 'array', 'items': {'type': 'string'}}}}, rule='type')
-+ data__excludepackagedata_is_dict =
isinstance(data__excludepackagedata, dict)
-+ if data__excludepackagedata_is_dict:
-+ data__excludepackagedata_keys =
set(data__excludepackagedata.keys())
-+ for data__excludepackagedata_key,
data__excludepackagedata_val in data__excludepackagedata.items():
-+ if
REGEX_PATTERNS['^.*$'].search(data__excludepackagedata_key):
-+ if data__excludepackagedata_key in
data__excludepackagedata_keys:
-+
data__excludepackagedata_keys.remove(data__excludepackagedata_key)
-+ if not isinstance(data__excludepackagedata_val,
(list, tuple)):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") +
".exclude-package-data.{data__excludepackagedata_key}".format(**locals()) + "
must be array", value=data__excludepackagedata_val, name="" + (name_prefix or
"data") +
".exclude-package-data.{data__excludepackagedata_key}".format(**locals()) +
"", definition={'type': 'array', 'items': {'type': 'string'}}, rule='type')
-+ data__excludepackagedata_val_is_list =
isinstance(data__excludepackagedata_val, (list, tuple))
-+ if data__excludepackagedata_val_is_list:
-+ data__excludepackagedata_val_len =
len(data__excludepackagedata_val)
-+ for data__excludepackagedata_val_x,
data__excludepackagedata_val_item in enumerate(data__excludepackagedata_val):
-+ if not
isinstance(data__excludepackagedata_val_item, (str)):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") +
".exclude-package-data.{data__excludepackagedata_key}[{data__excludepackagedata_val_x}]".format(**locals())
+ " must be string", value=data__excludepackagedata_val_item, name="" +
(name_prefix or "data") +
".exclude-package-data.{data__excludepackagedata_key}[{data__excludepackagedata_val_x}]".format(**locals())
+ "", definition={'type': 'string'}, rule='type')
-+ if data__excludepackagedata_keys:
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".exclude-package-data must not contain
"+str(data__excludepackagedata_keys)+" properties",
value=data__excludepackagedata, name="" + (name_prefix or "data") +
".exclude-package-data", definition={'$$description': ['Mapping from package
names to lists of glob patterns that should be excluded', 'For more
information on how to include data files, check ``setuptools`` `docs',
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_.'],
'type': 'object', 'additionalProperties': False, 'propertyNames': {'oneOf':
[{'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties':
{'^.*$': {'type': 'array', 'items': {'type': 'string'}}}},
rule='additionalProperties')
-+ data__excludepackagedata_len = len(data__excludepackagedata)
-+ if data__excludepackagedata_len != 0:
-+ data__excludepackagedata_property_names = True
-+ for data__excludepackagedata_key in
data__excludepackagedata:
-+ try:
-+ data__excludepackagedata_key_one_of_count4 = 0
-+ if data__excludepackagedata_key_one_of_count4 <
2:
-+ try:
-+ if
isinstance(data__excludepackagedata_key, str):
-+ if not
custom_formats["python-module-name"](data__excludepackagedata_key):
-+ raise
JsonSchemaValueException("" + (name_prefix or "data") +
".exclude-package-data must be python-module-name",
value=data__excludepackagedata_key, name="" + (name_prefix or "data") +
".exclude-package-data", definition={'format': 'python-module-name'},
rule='format')
-+
data__excludepackagedata_key_one_of_count4 += 1
-+ except JsonSchemaValueException: pass
-+ if data__excludepackagedata_key_one_of_count4 <
2:
-+ try:
-+ if data__excludepackagedata_key != "*":
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".exclude-package-data must be same as const
definition: *", value=data__excludepackagedata_key, name="" + (name_prefix or
"data") + ".exclude-package-data", definition={'const': '*'}, rule='const')
-+
data__excludepackagedata_key_one_of_count4 += 1
-+ except JsonSchemaValueException: pass
-+ if data__excludepackagedata_key_one_of_count4
!= 1:
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".exclude-package-data must be valid exactly by one
definition" + (" (" + str(data__excludepackagedata_key_one_of_count4) + "
matches found)"), value=data__excludepackagedata_key, name="" + (name_prefix
or "data") + ".exclude-package-data", definition={'oneOf': [{'format':
'python-module-name'}, {'const': '*'}]}, rule='oneOf')
-+ except JsonSchemaValueException:
-+ data__excludepackagedata_property_names = False
-+ if not data__excludepackagedata_property_names:
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".exclude-package-data must be named by propertyName definition",
value=data__excludepackagedata, name="" + (name_prefix or "data") +
".exclude-package-data", definition={'$$description': ['Mapping from package
names to lists of glob patterns that should be excluded', 'For more
information on how to include data files, check ``setuptools`` `docs',
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_.'],
'type': 'object', 'additionalProperties': False, 'propertyNames': {'oneOf':
[{'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties':
{'^.*$': {'type': 'array', 'items': {'type': 'string'}}}},
rule='propertyNames')
-+ if "namespace-packages" in data_keys:
-+ data_keys.remove("namespace-packages")
-+ data__namespacepackages = data["namespace-packages"]
-+ if not isinstance(data__namespacepackages, (list, tuple)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".namespace-packages must be array", value=data__namespacepackages, name=""
+ (name_prefix or "data") + ".namespace-packages", definition={'type':
'array', 'items': {'type': 'string', 'format': 'python-module-name'},
'$comment':
'https://setuptools.pypa.io/en/latest/userguide/package_discovery.html'},
rule='type')
-+ data__namespacepackages_is_list =
isinstance(data__namespacepackages, (list, tuple))
-+ if data__namespacepackages_is_list:
-+ data__namespacepackages_len = len(data__namespacepackages)
-+ for data__namespacepackages_x, data__namespacepackages_item
in enumerate(data__namespacepackages):
-+ if not isinstance(data__namespacepackages_item, (str)):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") +
".namespace-packages[{data__namespacepackages_x}]".format(**locals()) + "
must be string", value=data__namespacepackages_item, name="" + (name_prefix
or "data") +
".namespace-packages[{data__namespacepackages_x}]".format(**locals()) + "",
definition={'type': 'string', 'format': 'python-module-name'}, rule='type')
-+ if isinstance(data__namespacepackages_item, str):
-+ if not
custom_formats["python-module-name"](data__namespacepackages_item):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") +
".namespace-packages[{data__namespacepackages_x}]".format(**locals()) + "
must be python-module-name", value=data__namespacepackages_item, name="" +
(name_prefix or "data") +
".namespace-packages[{data__namespacepackages_x}]".format(**locals()) + "",
definition={'type': 'string', 'format': 'python-module-name'}, rule='format')
-+ if "py-modules" in data_keys:
-+ data_keys.remove("py-modules")
-+ data__pymodules = data["py-modules"]
-+ if not isinstance(data__pymodules, (list, tuple)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".py-modules must be array", value=data__pymodules, name="" + (name_prefix
or "data") + ".py-modules", definition={'description': 'Modules that
setuptools will manipulate', 'type': 'array', 'items': {'type': 'string',
'format': 'python-module-name'}, '$comment': 'TODO: clarify the relationship
with ``packages``'}, rule='type')
-+ data__pymodules_is_list = isinstance(data__pymodules, (list,
tuple))
-+ if data__pymodules_is_list:
-+ data__pymodules_len = len(data__pymodules)
-+ for data__pymodules_x, data__pymodules_item in
enumerate(data__pymodules):
-+ if not isinstance(data__pymodules_item, (str)):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".py-modules[{data__pymodules_x}]".format(**locals()) + " must be
string", value=data__pymodules_item, name="" + (name_prefix or "data") +
".py-modules[{data__pymodules_x}]".format(**locals()) + "",
definition={'type': 'string', 'format': 'python-module-name'}, rule='type')
-+ if isinstance(data__pymodules_item, str):
-+ if not
custom_formats["python-module-name"](data__pymodules_item):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") +
".py-modules[{data__pymodules_x}]".format(**locals()) + " must be
python-module-name", value=data__pymodules_item, name="" + (name_prefix or
"data") + ".py-modules[{data__pymodules_x}]".format(**locals()) + "",
definition={'type': 'string', 'format': 'python-module-name'}, rule='format')
-+ if "data-files" in data_keys:
-+ data_keys.remove("data-files")
-+ data__datafiles = data["data-files"]
-+ if not isinstance(data__datafiles, (dict)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".data-files must be object", value=data__datafiles, name="" + (name_prefix
or "data") + ".data-files", definition={'$$description': ['**DEPRECATED**:
dict-like structure where each key represents a directory and', 'the value is
a list of glob patterns that should be installed in them.', "Please notice
this don't work with wheels. See `data files support",
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_'], 'type':
'object', 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type':
'string'}}}}, rule='type')
-+ data__datafiles_is_dict = isinstance(data__datafiles, dict)
-+ if data__datafiles_is_dict:
-+ data__datafiles_keys = set(data__datafiles.keys())
-+ for data__datafiles_key, data__datafiles_val in
data__datafiles.items():
-+ if REGEX_PATTERNS['^.*$'].search(data__datafiles_key):
-+ if data__datafiles_key in data__datafiles_keys:
-+ data__datafiles_keys.remove(data__datafiles_key)
-+ if not isinstance(data__datafiles_val, (list,
tuple)):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") +
".data-files.{data__datafiles_key}".format(**locals()) + " must be array",
value=data__datafiles_val, name="" + (name_prefix or "data") +
".data-files.{data__datafiles_key}".format(**locals()) + "",
definition={'type': 'array', 'items': {'type': 'string'}}, rule='type')
-+ data__datafiles_val_is_list =
isinstance(data__datafiles_val, (list, tuple))
-+ if data__datafiles_val_is_list:
-+ data__datafiles_val_len =
len(data__datafiles_val)
-+ for data__datafiles_val_x,
data__datafiles_val_item in enumerate(data__datafiles_val):
-+ if not isinstance(data__datafiles_val_item,
(str)):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") +
".data-files.{data__datafiles_key}[{data__datafiles_val_x}]".format(**locals())
+ " must be string", value=data__datafiles_val_item, name="" + (name_prefix
or "data") +
".data-files.{data__datafiles_key}[{data__datafiles_val_x}]".format(**locals())
+ "", definition={'type': 'string'}, rule='type')
-+ if "cmdclass" in data_keys:
-+ data_keys.remove("cmdclass")
-+ data__cmdclass = data["cmdclass"]
-+ if not isinstance(data__cmdclass, (dict)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".cmdclass must be object", value=data__cmdclass, name="" + (name_prefix or
"data") + ".cmdclass", definition={'$$description': ['Mapping of
distutils-style command names to ``setuptools.Command`` subclasses', 'which
in turn should be represented by strings with a qualified class name',
'(i.e., "dotted" form with module), e.g.::\n\n', ' cmdclass = {mycmd =
"pkg.subpkg.module.CommandClass"}\n\n', 'The command class should be a
directly defined at the top-level of the', 'containing module (no class
nesting).'], 'type': 'object', 'patternProperties': {'^.*$': {'type':
'string', 'format': 'python-qualified-identifier'}}}, rule='type')
-+ data__cmdclass_is_dict = isinstance(data__cmdclass, dict)
-+ if data__cmdclass_is_dict:
-+ data__cmdclass_keys = set(data__cmdclass.keys())
-+ for data__cmdclass_key, data__cmdclass_val in
data__cmdclass.items():
-+ if REGEX_PATTERNS['^.*$'].search(data__cmdclass_key):
-+ if data__cmdclass_key in data__cmdclass_keys:
-+ data__cmdclass_keys.remove(data__cmdclass_key)
-+ if not isinstance(data__cmdclass_val, (str)):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".cmdclass.{data__cmdclass_key}".format(**locals())
+ " must be string", value=data__cmdclass_val, name="" + (name_prefix or
"data") + ".cmdclass.{data__cmdclass_key}".format(**locals()) + "",
definition={'type': 'string', 'format': 'python-qualified-identifier'},
rule='type')
-+ if isinstance(data__cmdclass_val, str):
-+ if not
custom_formats["python-qualified-identifier"](data__cmdclass_val):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".cmdclass.{data__cmdclass_key}".format(**locals())
+ " must be python-qualified-identifier", value=data__cmdclass_val, name="" +
(name_prefix or "data") + ".cmdclass.{data__cmdclass_key}".format(**locals())
+ "", definition={'type': 'string', 'format': 'python-qualified-identifier'},
rule='format')
-+ if "license-files" in data_keys:
-+ data_keys.remove("license-files")
-+ data__licensefiles = data["license-files"]
-+ if not isinstance(data__licensefiles, (list, tuple)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".license-files must be array", value=data__licensefiles, name="" +
(name_prefix or "data") + ".license-files", definition={'type': 'array',
'items': {'type': 'string'}, '$$description': ['PROVISIONAL: List of glob
patterns for all license files being distributed.', '(might become standard
with PEP 639).', "By default: ``['LICEN[CS]E*', 'COPYING*', 'NOTICE*',
'AUTHORS*']``"], '$comment': 'TODO: revise if PEP 639 is accepted. Probably
``project.license-files``?'}, rule='type')
-+ data__licensefiles_is_list = isinstance(data__licensefiles,
(list, tuple))
-+ if data__licensefiles_is_list:
-+ data__licensefiles_len = len(data__licensefiles)
-+ for data__licensefiles_x, data__licensefiles_item in
enumerate(data__licensefiles):
-+ if not isinstance(data__licensefiles_item, (str)):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".license-files[{data__licensefiles_x}]".format(**locals()) + "
must be string", value=data__licensefiles_item, name="" + (name_prefix or
"data") + ".license-files[{data__licensefiles_x}]".format(**locals()) + "",
definition={'type': 'string'}, rule='type')
-+ if "dynamic" in data_keys:
-+ data_keys.remove("dynamic")
-+ data__dynamic = data["dynamic"]
-+ if not isinstance(data__dynamic, (dict)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".dynamic must be object", value=data__dynamic, name="" + (name_prefix or
"data") + ".dynamic", definition={'type': 'object', 'description':
'Instructions for loading :pep:`621`-related metadata dynamically',
'additionalProperties': False, 'properties': {'version': {'$$description':
['A version dynamically loaded via either the ``attr:`` or ``file:``',
'directives. Please make sure the given file or attribute respects
:pep:`440`.'], 'oneOf': [{'title': "'attr:' directive", '$id':
'#/definitions/attr-directive', '$$description': ['Value is read from a
module attribute. Supports callables and iterables;', 'unsupported types are
cast via ``str()``'], 'type': 'object', 'additionalProperties': False,
'properties': {'attr': {'type': 'string'}}, 'required': ['attr']}, {'$id':
'#/definitions/file-directive', 'title': "'file:' directive", 'description':
'Value is read from a file (or list of files and then concatenated)', 'type':
'object', 'additionalProperties': False, 'properties': {'file': {'oneOf':
[{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}]}},
'required': ['file']}]}, 'classifiers': {'$id':
'#/definitions/file-directive', 'title': "'file:' directive", 'description':
'Value is read from a file (or list of files and then concatenated)', 'type':
'object', 'additionalProperties': False, 'properties': {'file': {'oneOf':
[{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}]}},
'required': ['file']}, 'description': {'$id': '#/definitions/file-directive',
'title': "'file:' directive", 'description': 'Value is read from a file (or
list of files and then concatenated)', 'type': 'object',
'additionalProperties': False, 'properties': {'file': {'oneOf': [{'type':
'string'}, {'type': 'array', 'items': {'type': 'string'}}]}}, 'required':
['file']}, 'dependencies': {'$id': '#/definitions/file-directive', 'title':
"'file:' directive", 'description': 'Value is read from a file (or list of
files and then concatenated)', 'type': 'object', 'additionalProperties':
False, 'properties': {'file': {'oneOf': [{'type': 'string'}, {'type':
'array', 'items': {'type': 'string'}}]}}, 'required': ['file']},
'entry-points': {'$id': '#/definitions/file-directive', 'title': "'file:'
directive", 'description': 'Value is read from a file (or list of files and
then concatenated)', 'type': 'object', 'additionalProperties': False,
'properties': {'file': {'oneOf': [{'type': 'string'}, {'type': 'array',
'items': {'type': 'string'}}]}}, 'required': ['file']},
'optional-dependencies': {'type': 'object', 'propertyNames': {'format':
'python-identifier'}, 'additionalProperties': False, 'patternProperties':
{'.+': {'$id': '#/definitions/file-directive', 'title': "'file:' directive",
'description': 'Value is read from a file (or list of files and then
concatenated)', 'type': 'object', 'additionalProperties': False,
'properties': {'file': {'oneOf': [{'type': 'string'}, {'type': 'array',
'items': {'type': 'string'}}]}}, 'required': ['file']}}}, 'readme': {'anyOf':
[{'$id': '#/definitions/file-directive', 'title': "'file:' directive",
'description': 'Value is read from a file (or list of files and then
concatenated)', 'type': 'object', 'additionalProperties': False,
'properties': {'file': {'oneOf': [{'type': 'string'}, {'type': 'array',
'items': {'type': 'string'}}]}}, 'required': ['file']}, {'properties':
{'content-type': {'type': 'string'}}}], 'required': ['file']}}}, rule='type')
-+ data__dynamic_is_dict = isinstance(data__dynamic, dict)
-+ if data__dynamic_is_dict:
-+ data__dynamic_keys = set(data__dynamic.keys())
-+ if "version" in data__dynamic_keys:
-+ data__dynamic_keys.remove("version")
-+ data__dynamic__version = data__dynamic["version"]
-+ data__dynamic__version_one_of_count5 = 0
-+ if data__dynamic__version_one_of_count5 < 2:
-+ try:
-+
validate_https___setuptools_pypa_io_en_latest_references_keywords_html__definitions_attr_directive(data__dynamic__version,
custom_formats, (name_prefix or "data") + ".dynamic.version")
-+ data__dynamic__version_one_of_count5 += 1
-+ except JsonSchemaValueException: pass
-+ if data__dynamic__version_one_of_count5 < 2:
-+ try:
-+
validate_https___setuptools_pypa_io_en_latest_references_keywords_html__definitions_file_directive(data__dynamic__version,
custom_formats, (name_prefix or "data") + ".dynamic.version")
-+ data__dynamic__version_one_of_count5 += 1
-+ except JsonSchemaValueException: pass
-+ if data__dynamic__version_one_of_count5 != 1:
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".dynamic.version must be valid exactly by one definition" + (" ("
+ str(data__dynamic__version_one_of_count5) + " matches found)"),
value=data__dynamic__version, name="" + (name_prefix or "data") +
".dynamic.version", definition={'$$description': ['A version dynamically
loaded via either the ``attr:`` or ``file:``', 'directives. Please make sure
the given file or attribute respects :pep:`440`.'], 'oneOf': [{'title':
"'attr:' directive", '$id': '#/definitions/attr-directive', '$$description':
['Value is read from a module attribute. Supports callables and iterables;',
'unsupported types are cast via ``str()``'], 'type': 'object',
'additionalProperties': False, 'properties': {'attr': {'type': 'string'}},
'required': ['attr']}, {'$id': '#/definitions/file-directive', 'title':
"'file:' directive", 'description': 'Value is read from a file (or list of
files and then concatenated)', 'type': 'object', 'additionalProperties':
False, 'properties': {'file': {'oneOf': [{'type': 'string'}, {'type':
'array', 'items': {'type': 'string'}}]}}, 'required': ['file']}]},
rule='oneOf')
-+ if "classifiers" in data__dynamic_keys:
-+ data__dynamic_keys.remove("classifiers")
-+ data__dynamic__classifiers =
data__dynamic["classifiers"]
-+
validate_https___setuptools_pypa_io_en_latest_references_keywords_html__definitions_file_directive(data__dynamic__classifiers,
custom_formats, (name_prefix or "data") + ".dynamic.classifiers")
-+ if "description" in data__dynamic_keys:
-+ data__dynamic_keys.remove("description")
-+ data__dynamic__description =
data__dynamic["description"]
-+
validate_https___setuptools_pypa_io_en_latest_references_keywords_html__definitions_file_directive(data__dynamic__description,
custom_formats, (name_prefix or "data") + ".dynamic.description")
-+ if "dependencies" in data__dynamic_keys:
-+ data__dynamic_keys.remove("dependencies")
-+ data__dynamic__dependencies =
data__dynamic["dependencies"]
-+
validate_https___setuptools_pypa_io_en_latest_references_keywords_html__definitions_file_directive(data__dynamic__dependencies,
custom_formats, (name_prefix or "data") + ".dynamic.dependencies")
-+ if "entry-points" in data__dynamic_keys:
-+ data__dynamic_keys.remove("entry-points")
-+ data__dynamic__entrypoints =
data__dynamic["entry-points"]
-+
validate_https___setuptools_pypa_io_en_latest_references_keywords_html__definitions_file_directive(data__dynamic__entrypoints,
custom_formats, (name_prefix or "data") + ".dynamic.entry-points")
-+ if "optional-dependencies" in data__dynamic_keys:
-+ data__dynamic_keys.remove("optional-dependencies")
-+ data__dynamic__optionaldependencies =
data__dynamic["optional-dependencies"]
-+ if not isinstance(data__dynamic__optionaldependencies,
(dict)):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".dynamic.optional-dependencies must be object",
value=data__dynamic__optionaldependencies, name="" + (name_prefix or "data")
+ ".dynamic.optional-dependencies", definition={'type': 'object',
'propertyNames': {'format': 'python-identifier'}, 'additionalProperties':
False, 'patternProperties': {'.+': {'$id': '#/definitions/file-directive',
'title': "'file:' directive", 'description': 'Value is read from a file (or
list of files and then concatenated)', 'type': 'object',
'additionalProperties': False, 'properties': {'file': {'oneOf': [{'type':
'string'}, {'type': 'array', 'items': {'type': 'string'}}]}}, 'required':
['file']}}}, rule='type')
-+ data__dynamic__optionaldependencies_is_dict =
isinstance(data__dynamic__optionaldependencies, dict)
-+ if data__dynamic__optionaldependencies_is_dict:
-+ data__dynamic__optionaldependencies_keys =
set(data__dynamic__optionaldependencies.keys())
-+ for data__dynamic__optionaldependencies_key,
data__dynamic__optionaldependencies_val in
data__dynamic__optionaldependencies.items():
-+ if
REGEX_PATTERNS['.+'].search(data__dynamic__optionaldependencies_key):
-+ if data__dynamic__optionaldependencies_key
in data__dynamic__optionaldependencies_keys:
-+
data__dynamic__optionaldependencies_keys.remove(data__dynamic__optionaldependencies_key)
-+
validate_https___setuptools_pypa_io_en_latest_references_keywords_html__definitions_file_directive(data__dynamic__optionaldependencies_val,
custom_formats, (name_prefix or "data") +
".dynamic.optional-dependencies.{data__dynamic__optionaldependencies_key}".format(**locals()))
-+ if data__dynamic__optionaldependencies_keys:
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".dynamic.optional-dependencies must not contain
"+str(data__dynamic__optionaldependencies_keys)+" properties",
value=data__dynamic__optionaldependencies, name="" + (name_prefix or "data")
+ ".dynamic.optional-dependencies", definition={'type': 'object',
'propertyNames': {'format': 'python-identifier'}, 'additionalProperties':
False, 'patternProperties': {'.+': {'$id': '#/definitions/file-directive',
'title': "'file:' directive", 'description': 'Value is read from a file (or
list of files and then concatenated)', 'type': 'object',
'additionalProperties': False, 'properties': {'file': {'oneOf': [{'type':
'string'}, {'type': 'array', 'items': {'type': 'string'}}]}}, 'required':
['file']}}}, rule='additionalProperties')
-+ data__dynamic__optionaldependencies_len =
len(data__dynamic__optionaldependencies)
-+ if data__dynamic__optionaldependencies_len != 0:
-+
data__dynamic__optionaldependencies_property_names = True
-+ for data__dynamic__optionaldependencies_key in
data__dynamic__optionaldependencies:
-+ try:
-+ if
isinstance(data__dynamic__optionaldependencies_key, str):
-+ if not
custom_formats["python-identifier"](data__dynamic__optionaldependencies_key):
-+ raise
JsonSchemaValueException("" + (name_prefix or "data") +
".dynamic.optional-dependencies must be python-identifier",
value=data__dynamic__optionaldependencies_key, name="" + (name_prefix or
"data") + ".dynamic.optional-dependencies", definition={'format':
'python-identifier'}, rule='format')
-+ except JsonSchemaValueException:
-+
data__dynamic__optionaldependencies_property_names = False
-+ if not
data__dynamic__optionaldependencies_property_names:
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".dynamic.optional-dependencies must be named by
propertyName definition", value=data__dynamic__optionaldependencies, name=""
+ (name_prefix or "data") + ".dynamic.optional-dependencies",
definition={'type': 'object', 'propertyNames': {'format':
'python-identifier'}, 'additionalProperties': False, 'patternProperties':
{'.+': {'$id': '#/definitions/file-directive', 'title': "'file:' directive",
'description': 'Value is read from a file (or list of files and then
concatenated)', 'type': 'object', 'additionalProperties': False,
'properties': {'file': {'oneOf': [{'type': 'string'}, {'type': 'array',
'items': {'type': 'string'}}]}}, 'required': ['file']}}},
rule='propertyNames')
-+ if "readme" in data__dynamic_keys:
-+ data__dynamic_keys.remove("readme")
-+ data__dynamic__readme = data__dynamic["readme"]
-+ data__dynamic__readme_any_of_count6 = 0
-+ if not data__dynamic__readme_any_of_count6:
-+ try:
-+
validate_https___setuptools_pypa_io_en_latest_references_keywords_html__definitions_file_directive(data__dynamic__readme,
custom_formats, (name_prefix or "data") + ".dynamic.readme")
-+ data__dynamic__readme_any_of_count6 += 1
-+ except JsonSchemaValueException: pass
-+ if not data__dynamic__readme_any_of_count6:
-+ try:
-+ data__dynamic__readme_is_dict =
isinstance(data__dynamic__readme, dict)
-+ if data__dynamic__readme_is_dict:
-+ data__dynamic__readme_keys =
set(data__dynamic__readme.keys())
-+ if "content-type" in
data__dynamic__readme_keys:
-+
data__dynamic__readme_keys.remove("content-type")
-+ data__dynamic__readme__contenttype =
data__dynamic__readme["content-type"]
-+ if not
isinstance(data__dynamic__readme__contenttype, (str)):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".dynamic.readme.content-type must be string",
value=data__dynamic__readme__contenttype, name="" + (name_prefix or "data") +
".dynamic.readme.content-type", definition={'type': 'string'}, rule='type')
-+ data__dynamic__readme_any_of_count6 += 1
-+ except JsonSchemaValueException: pass
-+ if not data__dynamic__readme_any_of_count6:
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".dynamic.readme cannot be validated by any definition",
value=data__dynamic__readme, name="" + (name_prefix or "data") +
".dynamic.readme", definition={'anyOf': [{'$id':
'#/definitions/file-directive', 'title': "'file:' directive", 'description':
'Value is read from a file (or list of files and then concatenated)', 'type':
'object', 'additionalProperties': False, 'properties': {'file': {'oneOf':
[{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}]}},
'required': ['file']}, {'properties': {'content-type': {'type': 'string'}}}],
'required': ['file']}, rule='anyOf')
-+ data__dynamic__readme_is_dict =
isinstance(data__dynamic__readme, dict)
-+ if data__dynamic__readme_is_dict:
-+ data__dynamic__readme_len =
len(data__dynamic__readme)
-+ if not all(prop in data__dynamic__readme for prop
in ['file']):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".dynamic.readme must contain ['file'] properties",
value=data__dynamic__readme, name="" + (name_prefix or "data") +
".dynamic.readme", definition={'anyOf': [{'$id':
'#/definitions/file-directive', 'title': "'file:' directive", 'description':
'Value is read from a file (or list of files and then concatenated)', 'type':
'object', 'additionalProperties': False, 'properties': {'file': {'oneOf':
[{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}]}},
'required': ['file']}, {'properties': {'content-type': {'type': 'string'}}}],
'required': ['file']}, rule='required')
-+ if data__dynamic_keys:
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".dynamic must not contain "+str(data__dynamic_keys)+" properties",
value=data__dynamic, name="" + (name_prefix or "data") + ".dynamic",
definition={'type': 'object', 'description': 'Instructions for loading
:pep:`621`-related metadata dynamically', 'additionalProperties': False,
'properties': {'version': {'$$description': ['A version dynamically loaded
via either the ``attr:`` or ``file:``', 'directives. Please make sure the
given file or attribute respects :pep:`440`.'], 'oneOf': [{'title': "'attr:'
directive", '$id': '#/definitions/attr-directive', '$$description': ['Value
is read from a module attribute. Supports callables and iterables;',
'unsupported types are cast via ``str()``'], 'type': 'object',
'additionalProperties': False, 'properties': {'attr': {'type': 'string'}},
'required': ['attr']}, {'$id': '#/definitions/file-directive', 'title':
"'file:' directive", 'description': 'Value is read from a file (or list of
files and then concatenated)', 'type': 'object', 'additionalProperties':
False, 'properties': {'file': {'oneOf': [{'type': 'string'}, {'type':
'array', 'items': {'type': 'string'}}]}}, 'required': ['file']}]},
'classifiers': {'$id': '#/definitions/file-directive', 'title': "'file:'
directive", 'description': 'Value is read from a file (or list of files and
then concatenated)', 'type': 'object', 'additionalProperties': False,
'properties': {'file': {'oneOf': [{'type': 'string'}, {'type': 'array',
'items': {'type': 'string'}}]}}, 'required': ['file']}, 'description':
{'$id': '#/definitions/file-directive', 'title': "'file:' directive",
'description': 'Value is read from a file (or list of files and then
concatenated)', 'type': 'object', 'additionalProperties': False,
'properties': {'file': {'oneOf': [{'type': 'string'}, {'type': 'array',
'items': {'type': 'string'}}]}}, 'required': ['file']}, 'dependencies':
{'$id': '#/definitions/file-directive', 'title': "'file:' directive",
'description': 'Value is read from a file (or list of files and then
concatenated)', 'type': 'object', 'additionalProperties': False,
'properties': {'file': {'oneOf': [{'type': 'string'}, {'type': 'array',
'items': {'type': 'string'}}]}}, 'required': ['file']}, 'entry-points':
{'$id': '#/definitions/file-directive', 'title': "'file:' directive",
'description': 'Value is read from a file (or list of files and then
concatenated)', 'type': 'object', 'additionalProperties': False,
'properties': {'file': {'oneOf': [{'type': 'string'}, {'type': 'array',
'items': {'type': 'string'}}]}}, 'required': ['file']},
'optional-dependencies': {'type': 'object', 'propertyNames': {'format':
'python-identifier'}, 'additionalProperties': False, 'patternProperties':
{'.+': {'$id': '#/definitions/file-directive', 'title': "'file:' directive",
'description': 'Value is read from a file (or list of files and then
concatenated)', 'type': 'object', 'additionalProperties': False,
'properties': {'file': {'oneOf': [{'type': 'string'}, {'type': 'array',
'items': {'type': 'string'}}]}}, 'required': ['file']}}}, 'readme': {'anyOf':
[{'$id': '#/definitions/file-directive', 'title': "'file:' directive",
'description': 'Value is read from a file (or list of files and then
concatenated)', 'type': 'object', 'additionalProperties': False,
'properties': {'file': {'oneOf': [{'type': 'string'}, {'type': 'array',
'items': {'type': 'string'}}]}}, 'required': ['file']}, {'properties':
{'content-type': {'type': 'string'}}}], 'required': ['file']}}},
rule='additionalProperties')
-+ if data_keys:
-+ raise JsonSchemaValueException("" + (name_prefix or "data") + "
must not contain "+str(data_keys)+" properties", value=data, name="" +
(name_prefix or "data") + "", definition={'$schema':
'http://json-schema.org/draft-07/schema', '$id':
'https://setuptools.pypa.io/en/latest/references/keywords.html', 'title':
'``tool.setuptools`` table', '$$description': ['Please notice for the time
being the ``setuptools`` project does not specify', 'a way of configuring
builds via ``pyproject.toml``.', 'Therefore this schema should be taken just
as a *"thought experiment"* on how', 'this *might be done*, by following the
principles established in', '`ini2toml
<https://ini2toml.readthedocs.io/en/latest/setuptools_pep621.html>`_.', 'It
considers only ``setuptools`` `parameters',
'<https://setuptools.pypa.io/en/latest/userguide/declarative_config.html>`_',
'that can currently be configured via ``setup.cfg`` and are not covered by
:pep:`621`', 'but intentionally excludes ``dependency_links`` and
``setup_requires``.', 'NOTE: ``scripts`` was renamed to ``script-files`` to
avoid confusion with', 'entry-point based scripts (defined in :pep:`621`).'],
'type': 'object', 'additionalProperties': False, 'properties': {'platforms':
{'type': 'array', 'items': {'type': 'string'}}, 'provides': {'$$description':
['Package and virtual package names contained within this package', '**(not
supported by pip)**'], 'type': 'array', 'items': {'type': 'string', 'format':
'pep508-identifier'}}, 'obsoletes': {'$$description': ['Packages which this
package renders obsolete', '**(not supported by pip)**'], 'type': 'array',
'items': {'type': 'string', 'format': 'pep508-identifier'}}, 'zip-safe':
{'description': 'Whether the project can be safely installed and run from a
zip file.', 'type': 'boolean'}, 'script-files': {'description': 'Legacy way
of defining scripts (entry-points are preferred).', 'type': 'array', 'items':
{'type': 'string'}, '$comment': 'TODO: is this field deprecated/should be
removed?'}, 'eager-resources': {'$$description': ['Resources that should be
extracted together, if any of them is needed,', 'or if any C extensions
included in the project are imported.'], 'type': 'array', 'items': {'type':
'string'}}, 'packages': {'$$description': ['Packages that should be included
in the distribution.', 'It can be given either as a list of package
identifiers', 'or as a ``dict``-like structure with a single key ``find``',
'which corresponds to a dynamic call to',
'``setuptools.config.expand.find_packages`` function.', 'The ``find`` key is
associated with a nested ``dict``-like structure that can', 'contain
``where``, ``include``, ``exclude`` and ``namespaces`` keys,', 'mimicking the
keyword arguments of the associated function.'], 'oneOf': [{'title': 'Array
of Python package identifiers', 'type': 'array', 'items': {'$id':
'#/definitions/package-name', 'title': 'Valid package name', 'description':
'Valid package name (importable or PEP 561).', 'type': 'string', 'anyOf':
[{'format': 'python-module-name'}, {'format': 'pep561-stub-name'}]}}, {'$id':
'#/definitions/find-directive', 'title': "'find:' directive", 'type':
'object', 'additionalProperties': False, 'properties': {'find': {'type':
'object', '$$description': ['Dynamic `package discovery',
'<https://setuptools.pypa.io/en/latest/userguide/package_discovery.html>`_.'],
'additionalProperties': False, 'properties': {'where': {'description':
'Directories to be searched for packages (Unix-style relative path)', 'type':
'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array',
'$$description': ['Exclude packages that match the values listed in this
field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items':
{'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict
the found packages to just the ones listed in this field.', "Can container
shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}},
'namespaces': {'type': 'boolean', '$$description': ['When ``True``,
directories without a ``__init__.py`` file will also', 'be scanned for
:pep:`420`-style implicit namespaces']}}}}}]}, 'package-dir':
{'$$description': [':class:`dict`-like structure mapping from package names
to directories where their', 'code can be found.', 'The empty string (as key)
means that all packages are contained inside', 'the given directory will be
included in the distribution.'], 'type': 'object', 'additionalProperties':
False, 'propertyNames': {'oneOf': [{'const': ''}, {'$id':
'#/definitions/package-name', 'title': 'Valid package name', 'description':
'Valid package name (importable or PEP 561).', 'type': 'string', 'anyOf':
[{'format': 'python-module-name'}, {'format': 'pep561-stub-name'}]}]},
'patternProperties': {'^.*$': {'type': 'string'}}}, 'package-data':
{'$$description': ['Mapping from package names to lists of glob patterns.',
'Usually this option is not needed when using ``include-package-data =
true``', 'For more information on how to include data files, check
``setuptools`` `docs',
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_.'],
'type': 'object', 'additionalProperties': False, 'propertyNames': {'oneOf':
[{'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties':
{'^.*$': {'type': 'array', 'items': {'type': 'string'}}}},
'include-package-data': {'$$description': ['Automatically include any data
files inside the package directories', 'that are specified by
``MANIFEST.in``', 'For more information on how to include data files, check
``setuptools`` `docs',
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_.'],
'type': 'boolean'}, 'exclude-package-data': {'$$description': ['Mapping from
package names to lists of glob patterns that should be excluded', 'For more
information on how to include data files, check ``setuptools`` `docs',
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_.'],
'type': 'object', 'additionalProperties': False, 'propertyNames': {'oneOf':
[{'format': 'python-module-name'}, {'const': '*'}]}, 'patternProperties':
{'^.*$': {'type': 'array', 'items': {'type': 'string'}}}},
'namespace-packages': {'type': 'array', 'items': {'type': 'string', 'format':
'python-module-name'}, '$comment':
'https://setuptools.pypa.io/en/latest/userguide/package_discovery.html'},
'py-modules': {'description': 'Modules that setuptools will manipulate',
'type': 'array', 'items': {'type': 'string', 'format': 'python-module-name'},
'$comment': 'TODO: clarify the relationship with ``packages``'},
'data-files': {'$$description': ['**DEPRECATED**: dict-like structure where
each key represents a directory and', 'the value is a list of glob patterns
that should be installed in them.', "Please notice this don't work with
wheels. See `data files support",
'<https://setuptools.pypa.io/en/latest/userguide/datafiles.html>`_'], 'type':
'object', 'patternProperties': {'^.*$': {'type': 'array', 'items': {'type':
'string'}}}}, 'cmdclass': {'$$description': ['Mapping of distutils-style
command names to ``setuptools.Command`` subclasses', 'which in turn should be
represented by strings with a qualified class name', '(i.e., "dotted" form
with module), e.g.::\n\n', ' cmdclass = {mycmd =
"pkg.subpkg.module.CommandClass"}\n\n', 'The command class should be a
directly defined at the top-level of the', 'containing module (no class
nesting).'], 'type': 'object', 'patternProperties': {'^.*$': {'type':
'string', 'format': 'python-qualified-identifier'}}}, 'license-files':
{'type': 'array', 'items': {'type': 'string'}, '$$description':
['PROVISIONAL: List of glob patterns for all license files being
distributed.', '(might become standard with PEP 639).', "By default:
``['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*']``"], '$comment': 'TODO:
revise if PEP 639 is accepted. Probably ``project.license-files``?'},
'dynamic': {'type': 'object', 'description': 'Instructions for loading
:pep:`621`-related metadata dynamically', 'additionalProperties': False,
'properties': {'version': {'$$description': ['A version dynamically loaded
via either the ``attr:`` or ``file:``', 'directives. Please make sure the
given file or attribute respects :pep:`440`.'], 'oneOf': [{'title': "'attr:'
directive", '$id': '#/definitions/attr-directive', '$$description': ['Value
is read from a module attribute. Supports callables and iterables;',
'unsupported types are cast via ``str()``'], 'type': 'object',
'additionalProperties': False, 'properties': {'attr': {'type': 'string'}},
'required': ['attr']}, {'$id': '#/definitions/file-directive', 'title':
"'file:' directive", 'description': 'Value is read from a file (or list of
files and then concatenated)', 'type': 'object', 'additionalProperties':
False, 'properties': {'file': {'oneOf': [{'type': 'string'}, {'type':
'array', 'items': {'type': 'string'}}]}}, 'required': ['file']}]},
'classifiers': {'$id': '#/definitions/file-directive', 'title': "'file:'
directive", 'description': 'Value is read from a file (or list of files and
then concatenated)', 'type': 'object', 'additionalProperties': False,
'properties': {'file': {'oneOf': [{'type': 'string'}, {'type': 'array',
'items': {'type': 'string'}}]}}, 'required': ['file']}, 'description':
{'$id': '#/definitions/file-directive', 'title': "'file:' directive",
'description': 'Value is read from a file (or list of files and then
concatenated)', 'type': 'object', 'additionalProperties': False,
'properties': {'file': {'oneOf': [{'type': 'string'}, {'type': 'array',
'items': {'type': 'string'}}]}}, 'required': ['file']}, 'dependencies':
{'$id': '#/definitions/file-directive', 'title': "'file:' directive",
'description': 'Value is read from a file (or list of files and then
concatenated)', 'type': 'object', 'additionalProperties': False,
'properties': {'file': {'oneOf': [{'type': 'string'}, {'type': 'array',
'items': {'type': 'string'}}]}}, 'required': ['file']}, 'entry-points':
{'$id': '#/definitions/file-directive', 'title': "'file:' directive",
'description': 'Value is read from a file (or list of files and then
concatenated)', 'type': 'object', 'additionalProperties': False,
'properties': {'file': {'oneOf': [{'type': 'string'}, {'type': 'array',
'items': {'type': 'string'}}]}}, 'required': ['file']},
'optional-dependencies': {'type': 'object', 'propertyNames': {'format':
'python-identifier'}, 'additionalProperties': False, 'patternProperties':
{'.+': {'$id': '#/definitions/file-directive', 'title': "'file:' directive",
'description': 'Value is read from a file (or list of files and then
concatenated)', 'type': 'object', 'additionalProperties': False,
'properties': {'file': {'oneOf': [{'type': 'string'}, {'type': 'array',
'items': {'type': 'string'}}]}}, 'required': ['file']}}}, 'readme': {'anyOf':
[{'$id': '#/definitions/file-directive', 'title': "'file:' directive",
'description': 'Value is read from a file (or list of files and then
concatenated)', 'type': 'object', 'additionalProperties': False,
'properties': {'file': {'oneOf': [{'type': 'string'}, {'type': 'array',
'items': {'type': 'string'}}]}}, 'required': ['file']}, {'properties':
{'content-type': {'type': 'string'}}}], 'required': ['file']}}}},
'definitions': {'package-name': {'$id': '#/definitions/package-name',
'title': 'Valid package name', 'description': 'Valid package name (importable
or PEP 561).', 'type': 'string', 'anyOf': [{'format': 'python-module-name'},
{'format': 'pep561-stub-name'}]}, 'file-directive': {'$id':
'#/definitions/file-directive', 'title': "'file:' directive", 'description':
'Value is read from a file (or list of files and then concatenated)', 'type':
'object', 'additionalProperties': False, 'properties': {'file': {'oneOf':
[{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}]}},
'required': ['file']}, 'attr-directive': {'title': "'attr:' directive",
'$id': '#/definitions/attr-directive', '$$description': ['Value is read from
a module attribute. Supports callables and iterables;', 'unsupported types
are cast via ``str()``'], 'type': 'object', 'additionalProperties': False,
'properties': {'attr': {'type': 'string'}}, 'required': ['attr']},
'find-directive': {'$id': '#/definitions/find-directive', 'title': "'find:'
directive", 'type': 'object', 'additionalProperties': False, 'properties':
{'find': {'type': 'object', '$$description': ['Dynamic `package discovery',
'<https://setuptools.pypa.io/en/latest/userguide/package_discovery.html>`_.'],
'additionalProperties': False, 'properties': {'where': {'description':
'Directories to be searched for packages (Unix-style relative path)', 'type':
'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array',
'$$description': ['Exclude packages that match the values listed in this
field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items':
{'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict
the found packages to just the ones listed in this field.', "Can container
shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}},
'namespaces': {'type': 'boolean', '$$description': ['When ``True``,
directories without a ``__init__.py`` file will also', 'be scanned for
:pep:`420`-style implicit namespaces']}}}}}}}, rule='additionalProperties')
-+ return data
-+
-+def
validate_https___setuptools_pypa_io_en_latest_references_keywords_html__definitions_file_directive(data,
custom_formats={}, name_prefix=None):
-+ if not isinstance(data, (dict)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data") + "
must be object", value=data, name="" + (name_prefix or "data") + "",
definition={'$id': '#/definitions/file-directive', 'title': "'file:'
directive", 'description': 'Value is read from a file (or list of files and
then concatenated)', 'type': 'object', 'additionalProperties': False,
'properties': {'file': {'oneOf': [{'type': 'string'}, {'type': 'array',
'items': {'type': 'string'}}]}}, 'required': ['file']}, rule='type')
-+ data_is_dict = isinstance(data, dict)
-+ if data_is_dict:
-+ data_len = len(data)
-+ if not all(prop in data for prop in ['file']):
-+ raise JsonSchemaValueException("" + (name_prefix or "data") + "
must contain ['file'] properties", value=data, name="" + (name_prefix or
"data") + "", definition={'$id': '#/definitions/file-directive', 'title':
"'file:' directive", 'description': 'Value is read from a file (or list of
files and then concatenated)', 'type': 'object', 'additionalProperties':
False, 'properties': {'file': {'oneOf': [{'type': 'string'}, {'type':
'array', 'items': {'type': 'string'}}]}}, 'required': ['file']},
rule='required')
-+ data_keys = set(data.keys())
-+ if "file" in data_keys:
-+ data_keys.remove("file")
-+ data__file = data["file"]
-+ data__file_one_of_count7 = 0
-+ if data__file_one_of_count7 < 2:
-+ try:
-+ if not isinstance(data__file, (str)):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".file must be string", value=data__file, name="" + (name_prefix or
"data") + ".file", definition={'type': 'string'}, rule='type')
-+ data__file_one_of_count7 += 1
-+ except JsonSchemaValueException: pass
-+ if data__file_one_of_count7 < 2:
-+ try:
-+ if not isinstance(data__file, (list, tuple)):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".file must be array", value=data__file, name="" + (name_prefix or
"data") + ".file", definition={'type': 'array', 'items': {'type': 'string'}},
rule='type')
-+ data__file_is_list = isinstance(data__file, (list,
tuple))
-+ if data__file_is_list:
-+ data__file_len = len(data__file)
-+ for data__file_x, data__file_item in
enumerate(data__file):
-+ if not isinstance(data__file_item, (str)):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".file[{data__file_x}]".format(**locals()) + " must
be string", value=data__file_item, name="" + (name_prefix or "data") +
".file[{data__file_x}]".format(**locals()) + "", definition={'type':
'string'}, rule='type')
-+ data__file_one_of_count7 += 1
-+ except JsonSchemaValueException: pass
-+ if data__file_one_of_count7 != 1:
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".file must be valid exactly by one definition" + (" (" +
str(data__file_one_of_count7) + " matches found)"), value=data__file, name=""
+ (name_prefix or "data") + ".file", definition={'oneOf': [{'type':
'string'}, {'type': 'array', 'items': {'type': 'string'}}]}, rule='oneOf')
-+ if data_keys:
-+ raise JsonSchemaValueException("" + (name_prefix or "data") + "
must not contain "+str(data_keys)+" properties", value=data, name="" +
(name_prefix or "data") + "", definition={'$id':
'#/definitions/file-directive', 'title': "'file:' directive", 'description':
'Value is read from a file (or list of files and then concatenated)', 'type':
'object', 'additionalProperties': False, 'properties': {'file': {'oneOf':
[{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}]}},
'required': ['file']}, rule='additionalProperties')
-+ return data
-+
-+def
validate_https___setuptools_pypa_io_en_latest_references_keywords_html__definitions_attr_directive(data,
custom_formats={}, name_prefix=None):
-+ if not isinstance(data, (dict)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data") + "
must be object", value=data, name="" + (name_prefix or "data") + "",
definition={'title': "'attr:' directive", '$id':
'#/definitions/attr-directive', '$$description': ['Value is read from a
module attribute. Supports callables and iterables;', 'unsupported types are
cast via ``str()``'], 'type': 'object', 'additionalProperties': False,
'properties': {'attr': {'type': 'string'}}, 'required': ['attr']},
rule='type')
-+ data_is_dict = isinstance(data, dict)
-+ if data_is_dict:
-+ data_len = len(data)
-+ if not all(prop in data for prop in ['attr']):
-+ raise JsonSchemaValueException("" + (name_prefix or "data") + "
must contain ['attr'] properties", value=data, name="" + (name_prefix or
"data") + "", definition={'title': "'attr:' directive", '$id':
'#/definitions/attr-directive', '$$description': ['Value is read from a
module attribute. Supports callables and iterables;', 'unsupported types are
cast via ``str()``'], 'type': 'object', 'additionalProperties': False,
'properties': {'attr': {'type': 'string'}}, 'required': ['attr']},
rule='required')
-+ data_keys = set(data.keys())
-+ if "attr" in data_keys:
-+ data_keys.remove("attr")
-+ data__attr = data["attr"]
-+ if not isinstance(data__attr, (str)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".attr must be string", value=data__attr, name="" + (name_prefix or "data")
+ ".attr", definition={'type': 'string'}, rule='type')
-+ if data_keys:
-+ raise JsonSchemaValueException("" + (name_prefix or "data") + "
must not contain "+str(data_keys)+" properties", value=data, name="" +
(name_prefix or "data") + "", definition={'title': "'attr:' directive",
'$id': '#/definitions/attr-directive', '$$description': ['Value is read from
a module attribute. Supports callables and iterables;', 'unsupported types
are cast via ``str()``'], 'type': 'object', 'additionalProperties': False,
'properties': {'attr': {'type': 'string'}}, 'required': ['attr']},
rule='additionalProperties')
-+ return data
-+
-+def
validate_https___setuptools_pypa_io_en_latest_references_keywords_html__definitions_find_directive(data,
custom_formats={}, name_prefix=None):
-+ if not isinstance(data, (dict)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data") + "
must be object", value=data, name="" + (name_prefix or "data") + "",
definition={'$id': '#/definitions/find-directive', 'title': "'find:'
directive", 'type': 'object', 'additionalProperties': False, 'properties':
{'find': {'type': 'object', '$$description': ['Dynamic `package discovery',
'<https://setuptools.pypa.io/en/latest/userguide/package_discovery.html>`_.'],
'additionalProperties': False, 'properties': {'where': {'description':
'Directories to be searched for packages (Unix-style relative path)', 'type':
'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array',
'$$description': ['Exclude packages that match the values listed in this
field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items':
{'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict
the found packages to just the ones listed in this field.', "Can container
shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}},
'namespaces': {'type': 'boolean', '$$description': ['When ``True``,
directories without a ``__init__.py`` file will also', 'be scanned for
:pep:`420`-style implicit namespaces']}}}}}, rule='type')
-+ data_is_dict = isinstance(data, dict)
-+ if data_is_dict:
-+ data_keys = set(data.keys())
-+ if "find" in data_keys:
-+ data_keys.remove("find")
-+ data__find = data["find"]
-+ if not isinstance(data__find, (dict)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".find must be object", value=data__find, name="" + (name_prefix or "data")
+ ".find", definition={'type': 'object', '$$description': ['Dynamic `package
discovery',
'<https://setuptools.pypa.io/en/latest/userguide/package_discovery.html>`_.'],
'additionalProperties': False, 'properties': {'where': {'description':
'Directories to be searched for packages (Unix-style relative path)', 'type':
'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array',
'$$description': ['Exclude packages that match the values listed in this
field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items':
{'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict
the found packages to just the ones listed in this field.', "Can container
shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}},
'namespaces': {'type': 'boolean', '$$description': ['When ``True``,
directories without a ``__init__.py`` file will also', 'be scanned for
:pep:`420`-style implicit namespaces']}}}, rule='type')
-+ data__find_is_dict = isinstance(data__find, dict)
-+ if data__find_is_dict:
-+ data__find_keys = set(data__find.keys())
-+ if "where" in data__find_keys:
-+ data__find_keys.remove("where")
-+ data__find__where = data__find["where"]
-+ if not isinstance(data__find__where, (list, tuple)):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".find.where must be array", value=data__find__where, name="" +
(name_prefix or "data") + ".find.where", definition={'description':
'Directories to be searched for packages (Unix-style relative path)', 'type':
'array', 'items': {'type': 'string'}}, rule='type')
-+ data__find__where_is_list =
isinstance(data__find__where, (list, tuple))
-+ if data__find__where_is_list:
-+ data__find__where_len = len(data__find__where)
-+ for data__find__where_x, data__find__where_item in
enumerate(data__find__where):
-+ if not isinstance(data__find__where_item,
(str)):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") +
".find.where[{data__find__where_x}]".format(**locals()) + " must be string",
value=data__find__where_item, name="" + (name_prefix or "data") +
".find.where[{data__find__where_x}]".format(**locals()) + "",
definition={'type': 'string'}, rule='type')
-+ if "exclude" in data__find_keys:
-+ data__find_keys.remove("exclude")
-+ data__find__exclude = data__find["exclude"]
-+ if not isinstance(data__find__exclude, (list, tuple)):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".find.exclude must be array", value=data__find__exclude, name="" +
(name_prefix or "data") + ".find.exclude", definition={'type': 'array',
'$$description': ['Exclude packages that match the values listed in this
field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items':
{'type': 'string'}}, rule='type')
-+ data__find__exclude_is_list =
isinstance(data__find__exclude, (list, tuple))
-+ if data__find__exclude_is_list:
-+ data__find__exclude_len = len(data__find__exclude)
-+ for data__find__exclude_x, data__find__exclude_item
in enumerate(data__find__exclude):
-+ if not isinstance(data__find__exclude_item,
(str)):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") +
".find.exclude[{data__find__exclude_x}]".format(**locals()) + " must be
string", value=data__find__exclude_item, name="" + (name_prefix or "data") +
".find.exclude[{data__find__exclude_x}]".format(**locals()) + "",
definition={'type': 'string'}, rule='type')
-+ if "include" in data__find_keys:
-+ data__find_keys.remove("include")
-+ data__find__include = data__find["include"]
-+ if not isinstance(data__find__include, (list, tuple)):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".find.include must be array", value=data__find__include, name="" +
(name_prefix or "data") + ".find.include", definition={'type': 'array',
'$$description': ['Restrict the found packages to just the ones listed in
this field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"],
'items': {'type': 'string'}}, rule='type')
-+ data__find__include_is_list =
isinstance(data__find__include, (list, tuple))
-+ if data__find__include_is_list:
-+ data__find__include_len = len(data__find__include)
-+ for data__find__include_x, data__find__include_item
in enumerate(data__find__include):
-+ if not isinstance(data__find__include_item,
(str)):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") +
".find.include[{data__find__include_x}]".format(**locals()) + " must be
string", value=data__find__include_item, name="" + (name_prefix or "data") +
".find.include[{data__find__include_x}]".format(**locals()) + "",
definition={'type': 'string'}, rule='type')
-+ if "namespaces" in data__find_keys:
-+ data__find_keys.remove("namespaces")
-+ data__find__namespaces = data__find["namespaces"]
-+ if not isinstance(data__find__namespaces, (bool)):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".find.namespaces must be boolean", value=data__find__namespaces,
name="" + (name_prefix or "data") + ".find.namespaces", definition={'type':
'boolean', '$$description': ['When ``True``, directories without a
``__init__.py`` file will also', 'be scanned for :pep:`420`-style implicit
namespaces']}, rule='type')
-+ if data__find_keys:
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".find must not contain "+str(data__find_keys)+" properties",
value=data__find, name="" + (name_prefix or "data") + ".find",
definition={'type': 'object', '$$description': ['Dynamic `package discovery',
'<https://setuptools.pypa.io/en/latest/userguide/package_discovery.html>`_.'],
'additionalProperties': False, 'properties': {'where': {'description':
'Directories to be searched for packages (Unix-style relative path)', 'type':
'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array',
'$$description': ['Exclude packages that match the values listed in this
field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items':
{'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict
the found packages to just the ones listed in this field.', "Can container
shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}},
'namespaces': {'type': 'boolean', '$$description': ['When ``True``,
directories without a ``__init__.py`` file will also', 'be scanned for
:pep:`420`-style implicit namespaces']}}}, rule='additionalProperties')
-+ if data_keys:
-+ raise JsonSchemaValueException("" + (name_prefix or "data") + "
must not contain "+str(data_keys)+" properties", value=data, name="" +
(name_prefix or "data") + "", definition={'$id':
'#/definitions/find-directive', 'title': "'find:' directive", 'type':
'object', 'additionalProperties': False, 'properties': {'find': {'type':
'object', '$$description': ['Dynamic `package discovery',
'<https://setuptools.pypa.io/en/latest/userguide/package_discovery.html>`_.'],
'additionalProperties': False, 'properties': {'where': {'description':
'Directories to be searched for packages (Unix-style relative path)', 'type':
'array', 'items': {'type': 'string'}}, 'exclude': {'type': 'array',
'$$description': ['Exclude packages that match the values listed in this
field.', "Can container shell-style wildcards (e.g. ``'pkg.*'``)"], 'items':
{'type': 'string'}}, 'include': {'type': 'array', '$$description': ['Restrict
the found packages to just the ones listed in this field.', "Can container
shell-style wildcards (e.g. ``'pkg.*'``)"], 'items': {'type': 'string'}},
'namespaces': {'type': 'boolean', '$$description': ['When ``True``,
directories without a ``__init__.py`` file will also', 'be scanned for
:pep:`420`-style implicit namespaces']}}}}}, rule='additionalProperties')
-+ return data
-+
-+def
validate_https___setuptools_pypa_io_en_latest_references_keywords_html__definitions_package_name(data,
custom_formats={}, name_prefix=None):
-+ if not isinstance(data, (str)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data") + "
must be string", value=data, name="" + (name_prefix or "data") + "",
definition={'$id': '#/definitions/package-name', 'title': 'Valid package
name', 'description': 'Valid package name (importable or PEP 561).', 'type':
'string', 'anyOf': [{'format': 'python-module-name'}, {'format':
'pep561-stub-name'}]}, rule='type')
-+ data_any_of_count8 = 0
-+ if not data_any_of_count8:
-+ try:
-+ if isinstance(data, str):
-+ if not custom_formats["python-module-name"](data):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + " must be python-module-name", value=data, name="" + (name_prefix
or "data") + "", definition={'format': 'python-module-name'}, rule='format')
-+ data_any_of_count8 += 1
-+ except JsonSchemaValueException: pass
-+ if not data_any_of_count8:
-+ try:
-+ if isinstance(data, str):
-+ if not custom_formats["pep561-stub-name"](data):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + " must be pep561-stub-name", value=data, name="" + (name_prefix or
"data") + "", definition={'format': 'pep561-stub-name'}, rule='format')
-+ data_any_of_count8 += 1
-+ except JsonSchemaValueException: pass
-+ if not data_any_of_count8:
-+ raise JsonSchemaValueException("" + (name_prefix or "data") + "
cannot be validated by any definition", value=data, name="" + (name_prefix or
"data") + "", definition={'$id': '#/definitions/package-name', 'title':
'Valid package name', 'description': 'Valid package name (importable or PEP
561).', 'type': 'string', 'anyOf': [{'format': 'python-module-name'},
{'format': 'pep561-stub-name'}]}, rule='anyOf')
-+ return data
-+
-+def validate_https___docs_python_org_3_install(data, custom_formats={},
name_prefix=None):
-+ if not isinstance(data, (dict)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data") + "
must be object", value=data, name="" + (name_prefix or "data") + "",
definition={'$schema': 'http://json-schema.org/draft-07/schema', '$id':
'https://docs.python.org/3/install/', 'title': '``tool.distutils`` table',
'$$description': ['Originally, ``distutils`` allowed developers to configure
arguments for', '``setup.py`` scripts via `distutils configuration files',
'<https://docs.python.org/3/install/#distutils-configuration-files>`_.',
'``tool.distutils`` subtables could be used with the same purpose', '(NOT
CURRENTLY IMPLEMENTED).'], 'type': 'object', 'properties': {'global':
{'type': 'object', 'description': 'Global options applied to all
``distutils`` commands'}}, 'patternProperties': {'.+': {'type': 'object'}},
'$comment': 'TODO: Is there a practical way of making this schema more
specific?'}, rule='type')
-+ data_is_dict = isinstance(data, dict)
-+ if data_is_dict:
-+ data_keys = set(data.keys())
-+ if "global" in data_keys:
-+ data_keys.remove("global")
-+ data__global = data["global"]
-+ if not isinstance(data__global, (dict)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".global must be object", value=data__global, name="" + (name_prefix or
"data") + ".global", definition={'type': 'object', 'description': 'Global
options applied to all ``distutils`` commands'}, rule='type')
-+ for data_key, data_val in data.items():
-+ if REGEX_PATTERNS['.+'].search(data_key):
-+ if data_key in data_keys:
-+ data_keys.remove(data_key)
-+ if not isinstance(data_val, (dict)):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".{data_key}".format(**locals()) + " must be object",
value=data_val, name="" + (name_prefix or "data") +
".{data_key}".format(**locals()) + "", definition={'type': 'object'},
rule='type')
-+ return data
-+
-+def
validate_https___packaging_python_org_en_latest_specifications_declaring_project_metadata(data,
custom_formats={}, name_prefix=None):
-+ if not isinstance(data, (dict)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data") + "
must be object", value=data, name="" + (name_prefix or "data") + "",
definition={'$schema': 'http://json-schema.org/draft-07/schema', '$id':
'https://packaging.python.org/en/latest/specifications/declaring-project-metadata/',
'title': 'Package metadata stored in the ``project`` table',
'$$description': ['Data structure for the **project** table inside
``pyproject.toml``', '(as initially defined in :pep:`621`)'], 'type':
'object', 'properties': {'name': {'type': 'string', 'description': 'The name
(primary identifier) of the project. MUST be statically defined.', 'format':
'pep508-identifier'}, 'version': {'type': 'string', 'description': 'The
version of the project as supported by :pep:`440`.', 'format': 'pep440'},
'description': {'type': 'string', '$$description': ['The `summary description
of the project',
'<https://packaging.python.org/specifications/core-metadata/#summary>`_']},
'readme': {'$$description': ['`Full/detailed description of the project in
the form of a README', '<https://peps.python.org/pep-0621/#readme>`_', "with
meaning similar to the one defined in `core metadata's Description",
'<https://packaging.python.org/specifications/core-metadata/#description>`_'],
'oneOf': [{'type': 'string', '$$description': ['Relative path to a text file
(UTF-8) containing the full description', 'of the project. If the file path
ends in case-insensitive ``.md`` or', '``.rst`` suffixes, then the
content-type is respectively', '``text/markdown`` or ``text/x-rst``']},
{'type': 'object', 'allOf': [{'anyOf': [{'properties': {'file': {'type':
'string', '$$description': ['Relative path to a text file containing the full
description', 'of the project.']}}, 'required': ['file']}, {'properties':
{'text': {'type': 'string', 'description': 'Full text describing the
project.'}}, 'required': ['text']}]}, {'properties': {'content-type':
{'type': 'string', '$$description': ['Content-type (:rfc:`1341`) of the full
description', '(e.g. ``text/markdown``). The ``charset`` parameter is
assumed', 'UTF-8 when not present.'], '$comment': 'TODO: add regex pattern or
format?'}}, 'required': ['content-type']}]}]}, 'requires-python': {'type':
'string', 'format': 'pep508-versionspec', '$$description': ['`The Python
version requirements of the project',
'<https://packaging.python.org/specifications/core-metadata/#requires-python>`_.']},
'license': {'description': '`Project license
<https://peps.python.org/pep-0621/#license>`_.', 'oneOf': [{'properties':
{'file': {'type': 'string', '$$description': ['Relative path to the file
(UTF-8) which contains the license for the', 'project.']}}, 'required':
['file']}, {'properties': {'text': {'type': 'string', '$$description': ['The
license of the project whose meaning is that of the', '`License field from
the core metadata',
'<https://packaging.python.org/specifications/core-metadata/#license>`_.']}},
'required': ['text']}]}, 'authors': {'type': 'array', 'items': {'$id':
'#/definitions/author', 'title': 'Author or Maintainer', '$comment':
'https://peps.python.org/pep-0621/#authors-maintainers', 'type': 'object',
'additionalProperties': False, 'properties': {'name': {'type': 'string',
'$$description': ['MUST be a valid email name, i.e. whatever can be put as a
name, before an', 'email, in :rfc:`822`.']}, 'email': {'type': 'string',
'format': 'idn-email', 'description': 'MUST be a valid email address'}}},
'$$description': ["The people or organizations considered to be the 'authors'
of the project.", 'The exact meaning is open to interpretation (e.g. original
or primary authors,', 'current maintainers, or owners of the package).']},
'maintainers': {'type': 'array', 'items': {'$id': '#/definitions/author',
'title': 'Author or Maintainer', '$comment':
'https://peps.python.org/pep-0621/#authors-maintainers', 'type': 'object',
'additionalProperties': False, 'properties': {'name': {'type': 'string',
'$$description': ['MUST be a valid email name, i.e. whatever can be put as a
name, before an', 'email, in :rfc:`822`.']}, 'email': {'type': 'string',
'format': 'idn-email', 'description': 'MUST be a valid email address'}}},
'$$description': ["The people or organizations considered to be the
'maintainers' of the project.", 'Similarly to ``authors``, the exact meaning
is open to interpretation.']}, 'keywords': {'type': 'array', 'items':
{'type': 'string'}, 'description': 'List of keywords to assist searching for
the distribution in a larger catalog.'}, 'classifiers': {'type': 'array',
'items': {'type': 'string', 'format': 'trove-classifier', 'description':
'`PyPI classifier <https://pypi.org/classifiers/>`_.'}, '$$description':
['`Trove classifiers <https://pypi.org/classifiers/>`_', 'which apply to the
project.']}, 'urls': {'type': 'object', 'description': 'URLs associated with
the project in the form ``label => value``.', 'additionalProperties': False,
'patternProperties': {'^.+$': {'type': 'string', 'format': 'url'}}},
'scripts': {'$id': '#/definitions/entry-point-group', 'title':
'Entry-points', 'type': 'object', '$$description': ['Entry-points are grouped
together to indicate what sort of capabilities they', 'provide.', 'See the
`packaging guides',
'<https://packaging.python.org/specifications/entry-points/>`_', 'and
`setuptools docs',
'<https://setuptools.pypa.io/en/latest/userguide/entry_point.html>`_', 'for
more information.'], 'propertyNames': {'format': 'python-entrypoint-name'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'type':
'string', '$$description': ['Reference to a Python object. It is either in
the form', '``importable.module``, or ``importable.module:object.attr``.'],
'format': 'python-entrypoint-reference', '$comment':
'https://packaging.python.org/specifications/entry-points/'}}},
'gui-scripts': {'$id': '#/definitions/entry-point-group', 'title':
'Entry-points', 'type': 'object', '$$description': ['Entry-points are grouped
together to indicate what sort of capabilities they', 'provide.', 'See the
`packaging guides',
'<https://packaging.python.org/specifications/entry-points/>`_', 'and
`setuptools docs',
'<https://setuptools.pypa.io/en/latest/userguide/entry_point.html>`_', 'for
more information.'], 'propertyNames': {'format': 'python-entrypoint-name'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'type':
'string', '$$description': ['Reference to a Python object. It is either in
the form', '``importable.module``, or ``importable.module:object.attr``.'],
'format': 'python-entrypoint-reference', '$comment':
'https://packaging.python.org/specifications/entry-points/'}}},
'entry-points': {'$$description': ['Instruct the installer to expose the
given modules/functions via', '``entry-point`` discovery mechanism (useful
for plugins).', 'More information available in the `Python packaging guide',
'<https://packaging.python.org/specifications/entry-points/>`_.'],
'propertyNames': {'format': 'python-entrypoint-group'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'$id':
'#/definitions/entry-point-group', 'title': 'Entry-points', 'type': 'object',
'$$description': ['Entry-points are grouped together to indicate what sort of
capabilities they', 'provide.', 'See the `packaging guides',
'<https://packaging.python.org/specifications/entry-points/>`_', 'and
`setuptools docs',
'<https://setuptools.pypa.io/en/latest/userguide/entry_point.html>`_', 'for
more information.'], 'propertyNames': {'format': 'python-entrypoint-name'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'type':
'string', '$$description': ['Reference to a Python object. It is either in
the form', '``importable.module``, or ``importable.module:object.attr``.'],
'format': 'python-entrypoint-reference', '$comment':
'https://packaging.python.org/specifications/entry-points/'}}}}},
'dependencies': {'type': 'array', 'description': 'Project (mandatory)
dependencies.', 'items': {'$id': '#/definitions/dependency', 'title':
'Dependency', 'type': 'string', 'description': 'Project dependency
specification according to PEP 508', 'format': 'pep508'}},
'optional-dependencies': {'type': 'object', 'description': 'Optional
dependency for the project', 'propertyNames': {'format':
'pep508-identifier'}, 'additionalProperties': False, 'patternProperties':
{'^.+$': {'type': 'array', 'items': {'$id': '#/definitions/dependency',
'title': 'Dependency', 'type': 'string', 'description': 'Project dependency
specification according to PEP 508', 'format': 'pep508'}}}}, 'dynamic':
{'type': 'array', '$$description': ['Specifies which fields are intentionally
unspecified and expected to be', 'dynamically provided by build tools'],
'items': {'enum': ['version', 'description', 'readme', 'requires-python',
'license', 'authors', 'maintainers', 'keywords', 'classifiers', 'urls',
'scripts', 'gui-scripts', 'entry-points', 'dependencies',
'optional-dependencies']}}}, 'required': ['name'], 'additionalProperties':
False, 'if': {'not': {'required': ['dynamic'], 'properties': {'dynamic':
{'contains': {'const': 'version'}, '$$description': ['version is listed in
``dynamic``']}}}, '$$comment': ['According to :pep:`621`:', ' If the core
metadata specification lists a field as "Required", then', ' the metadata
MUST specify the field statically or list it in dynamic', 'In turn, `core
metadata`_ defines:', ' The required fields are: Metadata-Version, Name,
Version.', ' All the other fields are optional.', 'Since
``Metadata-Version`` is defined by the build back-end, ``name`` and',
'``version`` are the only mandatory information in ``pyproject.toml``.', '..
_core metadata:
https://packaging.python.org/specifications/core-metadata/']}, 'then':
{'required': ['version'], '$$description': ['version should be statically
defined in the ``version`` field']}, 'definitions': {'author': {'$id':
'#/definitions/author', 'title': 'Author or Maintainer', '$comment':
'https://peps.python.org/pep-0621/#authors-maintainers', 'type': 'object',
'additionalProperties': False, 'properties': {'name': {'type': 'string',
'$$description': ['MUST be a valid email name, i.e. whatever can be put as a
name, before an', 'email, in :rfc:`822`.']}, 'email': {'type': 'string',
'format': 'idn-email', 'description': 'MUST be a valid email address'}}},
'entry-point-group': {'$id': '#/definitions/entry-point-group', 'title':
'Entry-points', 'type': 'object', '$$description': ['Entry-points are grouped
together to indicate what sort of capabilities they', 'provide.', 'See the
`packaging guides',
'<https://packaging.python.org/specifications/entry-points/>`_', 'and
`setuptools docs',
'<https://setuptools.pypa.io/en/latest/userguide/entry_point.html>`_', 'for
more information.'], 'propertyNames': {'format': 'python-entrypoint-name'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'type':
'string', '$$description': ['Reference to a Python object. It is either in
the form', '``importable.module``, or ``importable.module:object.attr``.'],
'format': 'python-entrypoint-reference', '$comment':
'https://packaging.python.org/specifications/entry-points/'}}}, 'dependency':
{'$id': '#/definitions/dependency', 'title': 'Dependency', 'type': 'string',
'description': 'Project dependency specification according to PEP 508',
'format': 'pep508'}}}, rule='type')
-+ data_is_dict = isinstance(data, dict)
-+ if data_is_dict:
-+ data_len = len(data)
-+ if not all(prop in data for prop in ['name']):
-+ raise JsonSchemaValueException("" + (name_prefix or "data") + "
must contain ['name'] properties", value=data, name="" + (name_prefix or
"data") + "", definition={'$schema':
'http://json-schema.org/draft-07/schema', '$id':
'https://packaging.python.org/en/latest/specifications/declaring-project-metadata/',
'title': 'Package metadata stored in the ``project`` table',
'$$description': ['Data structure for the **project** table inside
``pyproject.toml``', '(as initially defined in :pep:`621`)'], 'type':
'object', 'properties': {'name': {'type': 'string', 'description': 'The name
(primary identifier) of the project. MUST be statically defined.', 'format':
'pep508-identifier'}, 'version': {'type': 'string', 'description': 'The
version of the project as supported by :pep:`440`.', 'format': 'pep440'},
'description': {'type': 'string', '$$description': ['The `summary description
of the project',
'<https://packaging.python.org/specifications/core-metadata/#summary>`_']},
'readme': {'$$description': ['`Full/detailed description of the project in
the form of a README', '<https://peps.python.org/pep-0621/#readme>`_', "with
meaning similar to the one defined in `core metadata's Description",
'<https://packaging.python.org/specifications/core-metadata/#description>`_'],
'oneOf': [{'type': 'string', '$$description': ['Relative path to a text file
(UTF-8) containing the full description', 'of the project. If the file path
ends in case-insensitive ``.md`` or', '``.rst`` suffixes, then the
content-type is respectively', '``text/markdown`` or ``text/x-rst``']},
{'type': 'object', 'allOf': [{'anyOf': [{'properties': {'file': {'type':
'string', '$$description': ['Relative path to a text file containing the full
description', 'of the project.']}}, 'required': ['file']}, {'properties':
{'text': {'type': 'string', 'description': 'Full text describing the
project.'}}, 'required': ['text']}]}, {'properties': {'content-type':
{'type': 'string', '$$description': ['Content-type (:rfc:`1341`) of the full
description', '(e.g. ``text/markdown``). The ``charset`` parameter is
assumed', 'UTF-8 when not present.'], '$comment': 'TODO: add regex pattern or
format?'}}, 'required': ['content-type']}]}]}, 'requires-python': {'type':
'string', 'format': 'pep508-versionspec', '$$description': ['`The Python
version requirements of the project',
'<https://packaging.python.org/specifications/core-metadata/#requires-python>`_.']},
'license': {'description': '`Project license
<https://peps.python.org/pep-0621/#license>`_.', 'oneOf': [{'properties':
{'file': {'type': 'string', '$$description': ['Relative path to the file
(UTF-8) which contains the license for the', 'project.']}}, 'required':
['file']}, {'properties': {'text': {'type': 'string', '$$description': ['The
license of the project whose meaning is that of the', '`License field from
the core metadata',
'<https://packaging.python.org/specifications/core-metadata/#license>`_.']}},
'required': ['text']}]}, 'authors': {'type': 'array', 'items': {'$id':
'#/definitions/author', 'title': 'Author or Maintainer', '$comment':
'https://peps.python.org/pep-0621/#authors-maintainers', 'type': 'object',
'additionalProperties': False, 'properties': {'name': {'type': 'string',
'$$description': ['MUST be a valid email name, i.e. whatever can be put as a
name, before an', 'email, in :rfc:`822`.']}, 'email': {'type': 'string',
'format': 'idn-email', 'description': 'MUST be a valid email address'}}},
'$$description': ["The people or organizations considered to be the 'authors'
of the project.", 'The exact meaning is open to interpretation (e.g. original
or primary authors,', 'current maintainers, or owners of the package).']},
'maintainers': {'type': 'array', 'items': {'$id': '#/definitions/author',
'title': 'Author or Maintainer', '$comment':
'https://peps.python.org/pep-0621/#authors-maintainers', 'type': 'object',
'additionalProperties': False, 'properties': {'name': {'type': 'string',
'$$description': ['MUST be a valid email name, i.e. whatever can be put as a
name, before an', 'email, in :rfc:`822`.']}, 'email': {'type': 'string',
'format': 'idn-email', 'description': 'MUST be a valid email address'}}},
'$$description': ["The people or organizations considered to be the
'maintainers' of the project.", 'Similarly to ``authors``, the exact meaning
is open to interpretation.']}, 'keywords': {'type': 'array', 'items':
{'type': 'string'}, 'description': 'List of keywords to assist searching for
the distribution in a larger catalog.'}, 'classifiers': {'type': 'array',
'items': {'type': 'string', 'format': 'trove-classifier', 'description':
'`PyPI classifier <https://pypi.org/classifiers/>`_.'}, '$$description':
['`Trove classifiers <https://pypi.org/classifiers/>`_', 'which apply to the
project.']}, 'urls': {'type': 'object', 'description': 'URLs associated with
the project in the form ``label => value``.', 'additionalProperties': False,
'patternProperties': {'^.+$': {'type': 'string', 'format': 'url'}}},
'scripts': {'$id': '#/definitions/entry-point-group', 'title':
'Entry-points', 'type': 'object', '$$description': ['Entry-points are grouped
together to indicate what sort of capabilities they', 'provide.', 'See the
`packaging guides',
'<https://packaging.python.org/specifications/entry-points/>`_', 'and
`setuptools docs',
'<https://setuptools.pypa.io/en/latest/userguide/entry_point.html>`_', 'for
more information.'], 'propertyNames': {'format': 'python-entrypoint-name'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'type':
'string', '$$description': ['Reference to a Python object. It is either in
the form', '``importable.module``, or ``importable.module:object.attr``.'],
'format': 'python-entrypoint-reference', '$comment':
'https://packaging.python.org/specifications/entry-points/'}}},
'gui-scripts': {'$id': '#/definitions/entry-point-group', 'title':
'Entry-points', 'type': 'object', '$$description': ['Entry-points are grouped
together to indicate what sort of capabilities they', 'provide.', 'See the
`packaging guides',
'<https://packaging.python.org/specifications/entry-points/>`_', 'and
`setuptools docs',
'<https://setuptools.pypa.io/en/latest/userguide/entry_point.html>`_', 'for
more information.'], 'propertyNames': {'format': 'python-entrypoint-name'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'type':
'string', '$$description': ['Reference to a Python object. It is either in
the form', '``importable.module``, or ``importable.module:object.attr``.'],
'format': 'python-entrypoint-reference', '$comment':
'https://packaging.python.org/specifications/entry-points/'}}},
'entry-points': {'$$description': ['Instruct the installer to expose the
given modules/functions via', '``entry-point`` discovery mechanism (useful
for plugins).', 'More information available in the `Python packaging guide',
'<https://packaging.python.org/specifications/entry-points/>`_.'],
'propertyNames': {'format': 'python-entrypoint-group'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'$id':
'#/definitions/entry-point-group', 'title': 'Entry-points', 'type': 'object',
'$$description': ['Entry-points are grouped together to indicate what sort of
capabilities they', 'provide.', 'See the `packaging guides',
'<https://packaging.python.org/specifications/entry-points/>`_', 'and
`setuptools docs',
'<https://setuptools.pypa.io/en/latest/userguide/entry_point.html>`_', 'for
more information.'], 'propertyNames': {'format': 'python-entrypoint-name'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'type':
'string', '$$description': ['Reference to a Python object. It is either in
the form', '``importable.module``, or ``importable.module:object.attr``.'],
'format': 'python-entrypoint-reference', '$comment':
'https://packaging.python.org/specifications/entry-points/'}}}}},
'dependencies': {'type': 'array', 'description': 'Project (mandatory)
dependencies.', 'items': {'$id': '#/definitions/dependency', 'title':
'Dependency', 'type': 'string', 'description': 'Project dependency
specification according to PEP 508', 'format': 'pep508'}},
'optional-dependencies': {'type': 'object', 'description': 'Optional
dependency for the project', 'propertyNames': {'format':
'pep508-identifier'}, 'additionalProperties': False, 'patternProperties':
{'^.+$': {'type': 'array', 'items': {'$id': '#/definitions/dependency',
'title': 'Dependency', 'type': 'string', 'description': 'Project dependency
specification according to PEP 508', 'format': 'pep508'}}}}, 'dynamic':
{'type': 'array', '$$description': ['Specifies which fields are intentionally
unspecified and expected to be', 'dynamically provided by build tools'],
'items': {'enum': ['version', 'description', 'readme', 'requires-python',
'license', 'authors', 'maintainers', 'keywords', 'classifiers', 'urls',
'scripts', 'gui-scripts', 'entry-points', 'dependencies',
'optional-dependencies']}}}, 'required': ['name'], 'additionalProperties':
False, 'if': {'not': {'required': ['dynamic'], 'properties': {'dynamic':
{'contains': {'const': 'version'}, '$$description': ['version is listed in
``dynamic``']}}}, '$$comment': ['According to :pep:`621`:', ' If the core
metadata specification lists a field as "Required", then', ' the metadata
MUST specify the field statically or list it in dynamic', 'In turn, `core
metadata`_ defines:', ' The required fields are: Metadata-Version, Name,
Version.', ' All the other fields are optional.', 'Since
``Metadata-Version`` is defined by the build back-end, ``name`` and',
'``version`` are the only mandatory information in ``pyproject.toml``.', '..
_core metadata:
https://packaging.python.org/specifications/core-metadata/']}, 'then':
{'required': ['version'], '$$description': ['version should be statically
defined in the ``version`` field']}, 'definitions': {'author': {'$id':
'#/definitions/author', 'title': 'Author or Maintainer', '$comment':
'https://peps.python.org/pep-0621/#authors-maintainers', 'type': 'object',
'additionalProperties': False, 'properties': {'name': {'type': 'string',
'$$description': ['MUST be a valid email name, i.e. whatever can be put as a
name, before an', 'email, in :rfc:`822`.']}, 'email': {'type': 'string',
'format': 'idn-email', 'description': 'MUST be a valid email address'}}},
'entry-point-group': {'$id': '#/definitions/entry-point-group', 'title':
'Entry-points', 'type': 'object', '$$description': ['Entry-points are grouped
together to indicate what sort of capabilities they', 'provide.', 'See the
`packaging guides',
'<https://packaging.python.org/specifications/entry-points/>`_', 'and
`setuptools docs',
'<https://setuptools.pypa.io/en/latest/userguide/entry_point.html>`_', 'for
more information.'], 'propertyNames': {'format': 'python-entrypoint-name'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'type':
'string', '$$description': ['Reference to a Python object. It is either in
the form', '``importable.module``, or ``importable.module:object.attr``.'],
'format': 'python-entrypoint-reference', '$comment':
'https://packaging.python.org/specifications/entry-points/'}}}, 'dependency':
{'$id': '#/definitions/dependency', 'title': 'Dependency', 'type': 'string',
'description': 'Project dependency specification according to PEP 508',
'format': 'pep508'}}}, rule='required')
-+ data_keys = set(data.keys())
-+ if "name" in data_keys:
-+ data_keys.remove("name")
-+ data__name = data["name"]
-+ if not isinstance(data__name, (str)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".name must be string", value=data__name, name="" + (name_prefix or "data")
+ ".name", definition={'type': 'string', 'description': 'The name (primary
identifier) of the project. MUST be statically defined.', 'format':
'pep508-identifier'}, rule='type')
-+ if isinstance(data__name, str):
-+ if not custom_formats["pep508-identifier"](data__name):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".name must be pep508-identifier", value=data__name, name="" +
(name_prefix or "data") + ".name", definition={'type': 'string',
'description': 'The name (primary identifier) of the project. MUST be
statically defined.', 'format': 'pep508-identifier'}, rule='format')
-+ if "version" in data_keys:
-+ data_keys.remove("version")
-+ data__version = data["version"]
-+ if not isinstance(data__version, (str)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".version must be string", value=data__version, name="" + (name_prefix or
"data") + ".version", definition={'type': 'string', 'description': 'The
version of the project as supported by :pep:`440`.', 'format': 'pep440'},
rule='type')
-+ if isinstance(data__version, str):
-+ if not custom_formats["pep440"](data__version):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".version must be pep440", value=data__version, name="" +
(name_prefix or "data") + ".version", definition={'type': 'string',
'description': 'The version of the project as supported by :pep:`440`.',
'format': 'pep440'}, rule='format')
-+ if "description" in data_keys:
-+ data_keys.remove("description")
-+ data__description = data["description"]
-+ if not isinstance(data__description, (str)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".description must be string", value=data__description, name="" +
(name_prefix or "data") + ".description", definition={'type': 'string',
'$$description': ['The `summary description of the project',
'<https://packaging.python.org/specifications/core-metadata/#summary>`_']},
rule='type')
-+ if "readme" in data_keys:
-+ data_keys.remove("readme")
-+ data__readme = data["readme"]
-+ data__readme_one_of_count9 = 0
-+ if data__readme_one_of_count9 < 2:
-+ try:
-+ if not isinstance(data__readme, (str)):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".readme must be string", value=data__readme, name="" +
(name_prefix or "data") + ".readme", definition={'type': 'string',
'$$description': ['Relative path to a text file (UTF-8) containing the full
description', 'of the project. If the file path ends in case-insensitive
``.md`` or', '``.rst`` suffixes, then the content-type is respectively',
'``text/markdown`` or ``text/x-rst``']}, rule='type')
-+ data__readme_one_of_count9 += 1
-+ except JsonSchemaValueException: pass
-+ if data__readme_one_of_count9 < 2:
-+ try:
-+ if not isinstance(data__readme, (dict)):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".readme must be object", value=data__readme, name="" +
(name_prefix or "data") + ".readme", definition={'type': 'object', 'allOf':
[{'anyOf': [{'properties': {'file': {'type': 'string', '$$description':
['Relative path to a text file containing the full description', 'of the
project.']}}, 'required': ['file']}, {'properties': {'text': {'type':
'string', 'description': 'Full text describing the project.'}}, 'required':
['text']}]}, {'properties': {'content-type': {'type': 'string',
'$$description': ['Content-type (:rfc:`1341`) of the full description',
'(e.g. ``text/markdown``). The ``charset`` parameter is assumed', 'UTF-8 when
not present.'], '$comment': 'TODO: add regex pattern or format?'}},
'required': ['content-type']}]}, rule='type')
-+ data__readme_any_of_count10 = 0
-+ if not data__readme_any_of_count10:
-+ try:
-+ data__readme_is_dict = isinstance(data__readme,
dict)
-+ if data__readme_is_dict:
-+ data__readme_len = len(data__readme)
-+ if not all(prop in data__readme for prop in
['file']):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".readme must contain ['file'] properties",
value=data__readme, name="" + (name_prefix or "data") + ".readme",
definition={'properties': {'file': {'type': 'string', '$$description':
['Relative path to a text file containing the full description', 'of the
project.']}}, 'required': ['file']}, rule='required')
-+ data__readme_keys = set(data__readme.keys())
-+ if "file" in data__readme_keys:
-+ data__readme_keys.remove("file")
-+ data__readme__file =
data__readme["file"]
-+ if not isinstance(data__readme__file,
(str)):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".readme.file must be string",
value=data__readme__file, name="" + (name_prefix or "data") + ".readme.file",
definition={'type': 'string', '$$description': ['Relative path to a text file
containing the full description', 'of the project.']}, rule='type')
-+ data__readme_any_of_count10 += 1
-+ except JsonSchemaValueException: pass
-+ if not data__readme_any_of_count10:
-+ try:
-+ data__readme_is_dict = isinstance(data__readme,
dict)
-+ if data__readme_is_dict:
-+ data__readme_len = len(data__readme)
-+ if not all(prop in data__readme for prop in
['text']):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".readme must contain ['text'] properties",
value=data__readme, name="" + (name_prefix or "data") + ".readme",
definition={'properties': {'text': {'type': 'string', 'description': 'Full
text describing the project.'}}, 'required': ['text']}, rule='required')
-+ data__readme_keys = set(data__readme.keys())
-+ if "text" in data__readme_keys:
-+ data__readme_keys.remove("text")
-+ data__readme__text =
data__readme["text"]
-+ if not isinstance(data__readme__text,
(str)):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".readme.text must be string",
value=data__readme__text, name="" + (name_prefix or "data") + ".readme.text",
definition={'type': 'string', 'description': 'Full text describing the
project.'}, rule='type')
-+ data__readme_any_of_count10 += 1
-+ except JsonSchemaValueException: pass
-+ if not data__readme_any_of_count10:
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".readme cannot be validated by any definition",
value=data__readme, name="" + (name_prefix or "data") + ".readme",
definition={'anyOf': [{'properties': {'file': {'type': 'string',
'$$description': ['Relative path to a text file containing the full
description', 'of the project.']}}, 'required': ['file']}, {'properties':
{'text': {'type': 'string', 'description': 'Full text describing the
project.'}}, 'required': ['text']}]}, rule='anyOf')
-+ data__readme_is_dict = isinstance(data__readme, dict)
-+ if data__readme_is_dict:
-+ data__readme_len = len(data__readme)
-+ if not all(prop in data__readme for prop in
['content-type']):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".readme must contain ['content-type'] properties",
value=data__readme, name="" + (name_prefix or "data") + ".readme",
definition={'properties': {'content-type': {'type': 'string',
'$$description': ['Content-type (:rfc:`1341`) of the full description',
'(e.g. ``text/markdown``). The ``charset`` parameter is assumed', 'UTF-8 when
not present.'], '$comment': 'TODO: add regex pattern or format?'}},
'required': ['content-type']}, rule='required')
-+ data__readme_keys = set(data__readme.keys())
-+ if "content-type" in data__readme_keys:
-+ data__readme_keys.remove("content-type")
-+ data__readme__contenttype =
data__readme["content-type"]
-+ if not isinstance(data__readme__contenttype,
(str)):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".readme.content-type must be string",
value=data__readme__contenttype, name="" + (name_prefix or "data") +
".readme.content-type", definition={'type': 'string', '$$description':
['Content-type (:rfc:`1341`) of the full description', '(e.g.
``text/markdown``). The ``charset`` parameter is assumed', 'UTF-8 when not
present.'], '$comment': 'TODO: add regex pattern or format?'}, rule='type')
-+ data__readme_one_of_count9 += 1
-+ except JsonSchemaValueException: pass
-+ if data__readme_one_of_count9 != 1:
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".readme must be valid exactly by one definition" + (" (" +
str(data__readme_one_of_count9) + " matches found)"), value=data__readme,
name="" + (name_prefix or "data") + ".readme", definition={'$$description':
['`Full/detailed description of the project in the form of a README',
'<https://peps.python.org/pep-0621/#readme>`_', "with meaning similar to the
one defined in `core metadata's Description",
'<https://packaging.python.org/specifications/core-metadata/#description>`_'],
'oneOf': [{'type': 'string', '$$description': ['Relative path to a text file
(UTF-8) containing the full description', 'of the project. If the file path
ends in case-insensitive ``.md`` or', '``.rst`` suffixes, then the
content-type is respectively', '``text/markdown`` or ``text/x-rst``']},
{'type': 'object', 'allOf': [{'anyOf': [{'properties': {'file': {'type':
'string', '$$description': ['Relative path to a text file containing the full
description', 'of the project.']}}, 'required': ['file']}, {'properties':
{'text': {'type': 'string', 'description': 'Full text describing the
project.'}}, 'required': ['text']}]}, {'properties': {'content-type':
{'type': 'string', '$$description': ['Content-type (:rfc:`1341`) of the full
description', '(e.g. ``text/markdown``). The ``charset`` parameter is
assumed', 'UTF-8 when not present.'], '$comment': 'TODO: add regex pattern or
format?'}}, 'required': ['content-type']}]}]}, rule='oneOf')
-+ if "requires-python" in data_keys:
-+ data_keys.remove("requires-python")
-+ data__requirespython = data["requires-python"]
-+ if not isinstance(data__requirespython, (str)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".requires-python must be string", value=data__requirespython, name="" +
(name_prefix or "data") + ".requires-python", definition={'type': 'string',
'format': 'pep508-versionspec', '$$description': ['`The Python version
requirements of the project',
'<https://packaging.python.org/specifications/core-metadata/#requires-python>`_.']},
rule='type')
-+ if isinstance(data__requirespython, str):
-+ if not
custom_formats["pep508-versionspec"](data__requirespython):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".requires-python must be pep508-versionspec",
value=data__requirespython, name="" + (name_prefix or "data") +
".requires-python", definition={'type': 'string', 'format':
'pep508-versionspec', '$$description': ['`The Python version requirements of
the project',
'<https://packaging.python.org/specifications/core-metadata/#requires-python>`_.']},
rule='format')
-+ if "license" in data_keys:
-+ data_keys.remove("license")
-+ data__license = data["license"]
-+ data__license_one_of_count11 = 0
-+ if data__license_one_of_count11 < 2:
-+ try:
-+ data__license_is_dict = isinstance(data__license, dict)
-+ if data__license_is_dict:
-+ data__license_len = len(data__license)
-+ if not all(prop in data__license for prop in
['file']):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".license must contain ['file'] properties",
value=data__license, name="" + (name_prefix or "data") + ".license",
definition={'properties': {'file': {'type': 'string', '$$description':
['Relative path to the file (UTF-8) which contains the license for the',
'project.']}}, 'required': ['file']}, rule='required')
-+ data__license_keys = set(data__license.keys())
-+ if "file" in data__license_keys:
-+ data__license_keys.remove("file")
-+ data__license__file = data__license["file"]
-+ if not isinstance(data__license__file, (str)):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".license.file must be string",
value=data__license__file, name="" + (name_prefix or "data") +
".license.file", definition={'type': 'string', '$$description': ['Relative
path to the file (UTF-8) which contains the license for the', 'project.']},
rule='type')
-+ data__license_one_of_count11 += 1
-+ except JsonSchemaValueException: pass
-+ if data__license_one_of_count11 < 2:
-+ try:
-+ data__license_is_dict = isinstance(data__license, dict)
-+ if data__license_is_dict:
-+ data__license_len = len(data__license)
-+ if not all(prop in data__license for prop in
['text']):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".license must contain ['text'] properties",
value=data__license, name="" + (name_prefix or "data") + ".license",
definition={'properties': {'text': {'type': 'string', '$$description': ['The
license of the project whose meaning is that of the', '`License field from
the core metadata',
'<https://packaging.python.org/specifications/core-metadata/#license>`_.']}},
'required': ['text']}, rule='required')
-+ data__license_keys = set(data__license.keys())
-+ if "text" in data__license_keys:
-+ data__license_keys.remove("text")
-+ data__license__text = data__license["text"]
-+ if not isinstance(data__license__text, (str)):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".license.text must be string",
value=data__license__text, name="" + (name_prefix or "data") +
".license.text", definition={'type': 'string', '$$description': ['The license
of the project whose meaning is that of the', '`License field from the core
metadata',
'<https://packaging.python.org/specifications/core-metadata/#license>`_.']},
rule='type')
-+ data__license_one_of_count11 += 1
-+ except JsonSchemaValueException: pass
-+ if data__license_one_of_count11 != 1:
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".license must be valid exactly by one definition" + (" (" +
str(data__license_one_of_count11) + " matches found)"), value=data__license,
name="" + (name_prefix or "data") + ".license", definition={'description':
'`Project license <https://peps.python.org/pep-0621/#license>`_.', 'oneOf':
[{'properties': {'file': {'type': 'string', '$$description': ['Relative path
to the file (UTF-8) which contains the license for the', 'project.']}},
'required': ['file']}, {'properties': {'text': {'type': 'string',
'$$description': ['The license of the project whose meaning is that of the',
'`License field from the core metadata',
'<https://packaging.python.org/specifications/core-metadata/#license>`_.']}},
'required': ['text']}]}, rule='oneOf')
-+ if "authors" in data_keys:
-+ data_keys.remove("authors")
-+ data__authors = data["authors"]
-+ if not isinstance(data__authors, (list, tuple)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".authors must be array", value=data__authors, name="" + (name_prefix or
"data") + ".authors", definition={'type': 'array', 'items': {'$id':
'#/definitions/author', 'title': 'Author or Maintainer', '$comment':
'https://peps.python.org/pep-0621/#authors-maintainers', 'type': 'object',
'additionalProperties': False, 'properties': {'name': {'type': 'string',
'$$description': ['MUST be a valid email name, i.e. whatever can be put as a
name, before an', 'email, in :rfc:`822`.']}, 'email': {'type': 'string',
'format': 'idn-email', 'description': 'MUST be a valid email address'}}},
'$$description': ["The people or organizations considered to be the 'authors'
of the project.", 'The exact meaning is open to interpretation (e.g. original
or primary authors,', 'current maintainers, or owners of the package).']},
rule='type')
-+ data__authors_is_list = isinstance(data__authors, (list, tuple))
-+ if data__authors_is_list:
-+ data__authors_len = len(data__authors)
-+ for data__authors_x, data__authors_item in
enumerate(data__authors):
-+
validate_https___packaging_python_org_en_latest_specifications_declaring_project_metadata___definitions_author(data__authors_item,
custom_formats, (name_prefix or "data") +
".authors[{data__authors_x}]".format(**locals()))
-+ if "maintainers" in data_keys:
-+ data_keys.remove("maintainers")
-+ data__maintainers = data["maintainers"]
-+ if not isinstance(data__maintainers, (list, tuple)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".maintainers must be array", value=data__maintainers, name="" +
(name_prefix or "data") + ".maintainers", definition={'type': 'array',
'items': {'$id': '#/definitions/author', 'title': 'Author or Maintainer',
'$comment': 'https://peps.python.org/pep-0621/#authors-maintainers', 'type':
'object', 'additionalProperties': False, 'properties': {'name': {'type':
'string', '$$description': ['MUST be a valid email name, i.e. whatever can be
put as a name, before an', 'email, in :rfc:`822`.']}, 'email': {'type':
'string', 'format': 'idn-email', 'description': 'MUST be a valid email
address'}}}, '$$description': ["The people or organizations considered to be
the 'maintainers' of the project.", 'Similarly to ``authors``, the exact
meaning is open to interpretation.']}, rule='type')
-+ data__maintainers_is_list = isinstance(data__maintainers,
(list, tuple))
-+ if data__maintainers_is_list:
-+ data__maintainers_len = len(data__maintainers)
-+ for data__maintainers_x, data__maintainers_item in
enumerate(data__maintainers):
-+
validate_https___packaging_python_org_en_latest_specifications_declaring_project_metadata___definitions_author(data__maintainers_item,
custom_formats, (name_prefix or "data") +
".maintainers[{data__maintainers_x}]".format(**locals()))
-+ if "keywords" in data_keys:
-+ data_keys.remove("keywords")
-+ data__keywords = data["keywords"]
-+ if not isinstance(data__keywords, (list, tuple)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".keywords must be array", value=data__keywords, name="" + (name_prefix or
"data") + ".keywords", definition={'type': 'array', 'items': {'type':
'string'}, 'description': 'List of keywords to assist searching for the
distribution in a larger catalog.'}, rule='type')
-+ data__keywords_is_list = isinstance(data__keywords, (list,
tuple))
-+ if data__keywords_is_list:
-+ data__keywords_len = len(data__keywords)
-+ for data__keywords_x, data__keywords_item in
enumerate(data__keywords):
-+ if not isinstance(data__keywords_item, (str)):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".keywords[{data__keywords_x}]".format(**locals()) + " must be
string", value=data__keywords_item, name="" + (name_prefix or "data") +
".keywords[{data__keywords_x}]".format(**locals()) + "", definition={'type':
'string'}, rule='type')
-+ if "classifiers" in data_keys:
-+ data_keys.remove("classifiers")
-+ data__classifiers = data["classifiers"]
-+ if not isinstance(data__classifiers, (list, tuple)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".classifiers must be array", value=data__classifiers, name="" +
(name_prefix or "data") + ".classifiers", definition={'type': 'array',
'items': {'type': 'string', 'format': 'trove-classifier', 'description':
'`PyPI classifier <https://pypi.org/classifiers/>`_.'}, '$$description':
['`Trove classifiers <https://pypi.org/classifiers/>`_', 'which apply to the
project.']}, rule='type')
-+ data__classifiers_is_list = isinstance(data__classifiers,
(list, tuple))
-+ if data__classifiers_is_list:
-+ data__classifiers_len = len(data__classifiers)
-+ for data__classifiers_x, data__classifiers_item in
enumerate(data__classifiers):
-+ if not isinstance(data__classifiers_item, (str)):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".classifiers[{data__classifiers_x}]".format(**locals()) + " must
be string", value=data__classifiers_item, name="" + (name_prefix or "data") +
".classifiers[{data__classifiers_x}]".format(**locals()) + "",
definition={'type': 'string', 'format': 'trove-classifier', 'description':
'`PyPI classifier <https://pypi.org/classifiers/>`_.'}, rule='type')
-+ if isinstance(data__classifiers_item, str):
-+ if not
custom_formats["trove-classifier"](data__classifiers_item):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") +
".classifiers[{data__classifiers_x}]".format(**locals()) + " must be
trove-classifier", value=data__classifiers_item, name="" + (name_prefix or
"data") + ".classifiers[{data__classifiers_x}]".format(**locals()) + "",
definition={'type': 'string', 'format': 'trove-classifier', 'description':
'`PyPI classifier <https://pypi.org/classifiers/>`_.'}, rule='format')
-+ if "urls" in data_keys:
-+ data_keys.remove("urls")
-+ data__urls = data["urls"]
-+ if not isinstance(data__urls, (dict)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".urls must be object", value=data__urls, name="" + (name_prefix or "data")
+ ".urls", definition={'type': 'object', 'description': 'URLs associated with
the project in the form ``label => value``.', 'additionalProperties': False,
'patternProperties': {'^.+$': {'type': 'string', 'format': 'url'}}},
rule='type')
-+ data__urls_is_dict = isinstance(data__urls, dict)
-+ if data__urls_is_dict:
-+ data__urls_keys = set(data__urls.keys())
-+ for data__urls_key, data__urls_val in data__urls.items():
-+ if REGEX_PATTERNS['^.+$'].search(data__urls_key):
-+ if data__urls_key in data__urls_keys:
-+ data__urls_keys.remove(data__urls_key)
-+ if not isinstance(data__urls_val, (str)):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".urls.{data__urls_key}".format(**locals()) + "
must be string", value=data__urls_val, name="" + (name_prefix or "data") +
".urls.{data__urls_key}".format(**locals()) + "", definition={'type':
'string', 'format': 'url'}, rule='type')
-+ if isinstance(data__urls_val, str):
-+ if not custom_formats["url"](data__urls_val):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".urls.{data__urls_key}".format(**locals()) + "
must be url", value=data__urls_val, name="" + (name_prefix or "data") +
".urls.{data__urls_key}".format(**locals()) + "", definition={'type':
'string', 'format': 'url'}, rule='format')
-+ if data__urls_keys:
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".urls must not contain "+str(data__urls_keys)+" properties",
value=data__urls, name="" + (name_prefix or "data") + ".urls",
definition={'type': 'object', 'description': 'URLs associated with the
project in the form ``label => value``.', 'additionalProperties': False,
'patternProperties': {'^.+$': {'type': 'string', 'format': 'url'}}},
rule='additionalProperties')
-+ if "scripts" in data_keys:
-+ data_keys.remove("scripts")
-+ data__scripts = data["scripts"]
-+
validate_https___packaging_python_org_en_latest_specifications_declaring_project_metadata___definitions_entry_point_group(data__scripts,
custom_formats, (name_prefix or "data") + ".scripts")
-+ if "gui-scripts" in data_keys:
-+ data_keys.remove("gui-scripts")
-+ data__guiscripts = data["gui-scripts"]
-+
validate_https___packaging_python_org_en_latest_specifications_declaring_project_metadata___definitions_entry_point_group(data__guiscripts,
custom_formats, (name_prefix or "data") + ".gui-scripts")
-+ if "entry-points" in data_keys:
-+ data_keys.remove("entry-points")
-+ data__entrypoints = data["entry-points"]
-+ data__entrypoints_is_dict = isinstance(data__entrypoints, dict)
-+ if data__entrypoints_is_dict:
-+ data__entrypoints_keys = set(data__entrypoints.keys())
-+ for data__entrypoints_key, data__entrypoints_val in
data__entrypoints.items():
-+ if REGEX_PATTERNS['^.+$'].search(data__entrypoints_key):
-+ if data__entrypoints_key in data__entrypoints_keys:
-+
data__entrypoints_keys.remove(data__entrypoints_key)
-+
validate_https___packaging_python_org_en_latest_specifications_declaring_project_metadata___definitions_entry_point_group(data__entrypoints_val,
custom_formats, (name_prefix or "data") +
".entry-points.{data__entrypoints_key}".format(**locals()))
-+ if data__entrypoints_keys:
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".entry-points must not contain "+str(data__entrypoints_keys)+"
properties", value=data__entrypoints, name="" + (name_prefix or "data") +
".entry-points", definition={'$$description': ['Instruct the installer to
expose the given modules/functions via', '``entry-point`` discovery mechanism
(useful for plugins).', 'More information available in the `Python packaging
guide', '<https://packaging.python.org/specifications/entry-points/>`_.'],
'propertyNames': {'format': 'python-entrypoint-group'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'$id':
'#/definitions/entry-point-group', 'title': 'Entry-points', 'type': 'object',
'$$description': ['Entry-points are grouped together to indicate what sort of
capabilities they', 'provide.', 'See the `packaging guides',
'<https://packaging.python.org/specifications/entry-points/>`_', 'and
`setuptools docs',
'<https://setuptools.pypa.io/en/latest/userguide/entry_point.html>`_', 'for
more information.'], 'propertyNames': {'format': 'python-entrypoint-name'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'type':
'string', '$$description': ['Reference to a Python object. It is either in
the form', '``importable.module``, or ``importable.module:object.attr``.'],
'format': 'python-entrypoint-reference', '$comment':
'https://packaging.python.org/specifications/entry-points/'}}}}},
rule='additionalProperties')
-+ data__entrypoints_len = len(data__entrypoints)
-+ if data__entrypoints_len != 0:
-+ data__entrypoints_property_names = True
-+ for data__entrypoints_key in data__entrypoints:
-+ try:
-+ if isinstance(data__entrypoints_key, str):
-+ if not
custom_formats["python-entrypoint-group"](data__entrypoints_key):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".entry-points must be python-entrypoint-group",
value=data__entrypoints_key, name="" + (name_prefix or "data") +
".entry-points", definition={'format': 'python-entrypoint-group'},
rule='format')
-+ except JsonSchemaValueException:
-+ data__entrypoints_property_names = False
-+ if not data__entrypoints_property_names:
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".entry-points must be named by propertyName definition",
value=data__entrypoints, name="" + (name_prefix or "data") + ".entry-points",
definition={'$$description': ['Instruct the installer to expose the given
modules/functions via', '``entry-point`` discovery mechanism (useful for
plugins).', 'More information available in the `Python packaging guide',
'<https://packaging.python.org/specifications/entry-points/>`_.'],
'propertyNames': {'format': 'python-entrypoint-group'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'$id':
'#/definitions/entry-point-group', 'title': 'Entry-points', 'type': 'object',
'$$description': ['Entry-points are grouped together to indicate what sort of
capabilities they', 'provide.', 'See the `packaging guides',
'<https://packaging.python.org/specifications/entry-points/>`_', 'and
`setuptools docs',
'<https://setuptools.pypa.io/en/latest/userguide/entry_point.html>`_', 'for
more information.'], 'propertyNames': {'format': 'python-entrypoint-name'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'type':
'string', '$$description': ['Reference to a Python object. It is either in
the form', '``importable.module``, or ``importable.module:object.attr``.'],
'format': 'python-entrypoint-reference', '$comment':
'https://packaging.python.org/specifications/entry-points/'}}}}},
rule='propertyNames')
-+ if "dependencies" in data_keys:
-+ data_keys.remove("dependencies")
-+ data__dependencies = data["dependencies"]
-+ if not isinstance(data__dependencies, (list, tuple)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".dependencies must be array", value=data__dependencies, name="" +
(name_prefix or "data") + ".dependencies", definition={'type': 'array',
'description': 'Project (mandatory) dependencies.', 'items': {'$id':
'#/definitions/dependency', 'title': 'Dependency', 'type': 'string',
'description': 'Project dependency specification according to PEP 508',
'format': 'pep508'}}, rule='type')
-+ data__dependencies_is_list = isinstance(data__dependencies,
(list, tuple))
-+ if data__dependencies_is_list:
-+ data__dependencies_len = len(data__dependencies)
-+ for data__dependencies_x, data__dependencies_item in
enumerate(data__dependencies):
-+
validate_https___packaging_python_org_en_latest_specifications_declaring_project_metadata___definitions_dependency(data__dependencies_item,
custom_formats, (name_prefix or "data") +
".dependencies[{data__dependencies_x}]".format(**locals()))
-+ if "optional-dependencies" in data_keys:
-+ data_keys.remove("optional-dependencies")
-+ data__optionaldependencies = data["optional-dependencies"]
-+ if not isinstance(data__optionaldependencies, (dict)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".optional-dependencies must be object", value=data__optionaldependencies,
name="" + (name_prefix or "data") + ".optional-dependencies",
definition={'type': 'object', 'description': 'Optional dependency for the
project', 'propertyNames': {'format': 'pep508-identifier'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'type':
'array', 'items': {'$id': '#/definitions/dependency', 'title': 'Dependency',
'type': 'string', 'description': 'Project dependency specification according
to PEP 508', 'format': 'pep508'}}}}, rule='type')
-+ data__optionaldependencies_is_dict =
isinstance(data__optionaldependencies, dict)
-+ if data__optionaldependencies_is_dict:
-+ data__optionaldependencies_keys =
set(data__optionaldependencies.keys())
-+ for data__optionaldependencies_key,
data__optionaldependencies_val in data__optionaldependencies.items():
-+ if
REGEX_PATTERNS['^.+$'].search(data__optionaldependencies_key):
-+ if data__optionaldependencies_key in
data__optionaldependencies_keys:
-+
data__optionaldependencies_keys.remove(data__optionaldependencies_key)
-+ if not isinstance(data__optionaldependencies_val,
(list, tuple)):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") +
".optional-dependencies.{data__optionaldependencies_key}".format(**locals())
+ " must be array", value=data__optionaldependencies_val, name="" +
(name_prefix or "data") +
".optional-dependencies.{data__optionaldependencies_key}".format(**locals())
+ "", definition={'type': 'array', 'items': {'$id':
'#/definitions/dependency', 'title': 'Dependency', 'type': 'string',
'description': 'Project dependency specification according to PEP 508',
'format': 'pep508'}}, rule='type')
-+ data__optionaldependencies_val_is_list =
isinstance(data__optionaldependencies_val, (list, tuple))
-+ if data__optionaldependencies_val_is_list:
-+ data__optionaldependencies_val_len =
len(data__optionaldependencies_val)
-+ for data__optionaldependencies_val_x,
data__optionaldependencies_val_item in
enumerate(data__optionaldependencies_val):
-+
validate_https___packaging_python_org_en_latest_specifications_declaring_project_metadata___definitions_dependency(data__optionaldependencies_val_item,
custom_formats, (name_prefix or "data") +
".optional-dependencies.{data__optionaldependencies_key}[{data__optionaldependencies_val_x}]".format(**locals()))
-+ if data__optionaldependencies_keys:
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".optional-dependencies must not contain
"+str(data__optionaldependencies_keys)+" properties",
value=data__optionaldependencies, name="" + (name_prefix or "data") +
".optional-dependencies", definition={'type': 'object', 'description':
'Optional dependency for the project', 'propertyNames': {'format':
'pep508-identifier'}, 'additionalProperties': False, 'patternProperties':
{'^.+$': {'type': 'array', 'items': {'$id': '#/definitions/dependency',
'title': 'Dependency', 'type': 'string', 'description': 'Project dependency
specification according to PEP 508', 'format': 'pep508'}}}},
rule='additionalProperties')
-+ data__optionaldependencies_len =
len(data__optionaldependencies)
-+ if data__optionaldependencies_len != 0:
-+ data__optionaldependencies_property_names = True
-+ for data__optionaldependencies_key in
data__optionaldependencies:
-+ try:
-+ if isinstance(data__optionaldependencies_key,
str):
-+ if not
custom_formats["pep508-identifier"](data__optionaldependencies_key):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".optional-dependencies must be pep508-identifier",
value=data__optionaldependencies_key, name="" + (name_prefix or "data") +
".optional-dependencies", definition={'format': 'pep508-identifier'},
rule='format')
-+ except JsonSchemaValueException:
-+ data__optionaldependencies_property_names =
False
-+ if not data__optionaldependencies_property_names:
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".optional-dependencies must be named by propertyName definition",
value=data__optionaldependencies, name="" + (name_prefix or "data") +
".optional-dependencies", definition={'type': 'object', 'description':
'Optional dependency for the project', 'propertyNames': {'format':
'pep508-identifier'}, 'additionalProperties': False, 'patternProperties':
{'^.+$': {'type': 'array', 'items': {'$id': '#/definitions/dependency',
'title': 'Dependency', 'type': 'string', 'description': 'Project dependency
specification according to PEP 508', 'format': 'pep508'}}}},
rule='propertyNames')
-+ if "dynamic" in data_keys:
-+ data_keys.remove("dynamic")
-+ data__dynamic = data["dynamic"]
-+ if not isinstance(data__dynamic, (list, tuple)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".dynamic must be array", value=data__dynamic, name="" + (name_prefix or
"data") + ".dynamic", definition={'type': 'array', '$$description':
['Specifies which fields are intentionally unspecified and expected to be',
'dynamically provided by build tools'], 'items': {'enum': ['version',
'description', 'readme', 'requires-python', 'license', 'authors',
'maintainers', 'keywords', 'classifiers', 'urls', 'scripts', 'gui-scripts',
'entry-points', 'dependencies', 'optional-dependencies']}}, rule='type')
-+ data__dynamic_is_list = isinstance(data__dynamic, (list, tuple))
-+ if data__dynamic_is_list:
-+ data__dynamic_len = len(data__dynamic)
-+ for data__dynamic_x, data__dynamic_item in
enumerate(data__dynamic):
-+ if data__dynamic_item not in ['version', 'description',
'readme', 'requires-python', 'license', 'authors', 'maintainers', 'keywords',
'classifiers', 'urls', 'scripts', 'gui-scripts', 'entry-points',
'dependencies', 'optional-dependencies']:
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".dynamic[{data__dynamic_x}]".format(**locals()) + " must be one of
['version', 'description', 'readme', 'requires-python', 'license', 'authors',
'maintainers', 'keywords', 'classifiers', 'urls', 'scripts', 'gui-scripts',
'entry-points', 'dependencies', 'optional-dependencies']",
value=data__dynamic_item, name="" + (name_prefix or "data") +
".dynamic[{data__dynamic_x}]".format(**locals()) + "", definition={'enum':
['version', 'description', 'readme', 'requires-python', 'license', 'authors',
'maintainers', 'keywords', 'classifiers', 'urls', 'scripts', 'gui-scripts',
'entry-points', 'dependencies', 'optional-dependencies']}, rule='enum')
-+ if data_keys:
-+ raise JsonSchemaValueException("" + (name_prefix or "data") + "
must not contain "+str(data_keys)+" properties", value=data, name="" +
(name_prefix or "data") + "", definition={'$schema':
'http://json-schema.org/draft-07/schema', '$id':
'https://packaging.python.org/en/latest/specifications/declaring-project-metadata/',
'title': 'Package metadata stored in the ``project`` table',
'$$description': ['Data structure for the **project** table inside
``pyproject.toml``', '(as initially defined in :pep:`621`)'], 'type':
'object', 'properties': {'name': {'type': 'string', 'description': 'The name
(primary identifier) of the project. MUST be statically defined.', 'format':
'pep508-identifier'}, 'version': {'type': 'string', 'description': 'The
version of the project as supported by :pep:`440`.', 'format': 'pep440'},
'description': {'type': 'string', '$$description': ['The `summary description
of the project',
'<https://packaging.python.org/specifications/core-metadata/#summary>`_']},
'readme': {'$$description': ['`Full/detailed description of the project in
the form of a README', '<https://peps.python.org/pep-0621/#readme>`_', "with
meaning similar to the one defined in `core metadata's Description",
'<https://packaging.python.org/specifications/core-metadata/#description>`_'],
'oneOf': [{'type': 'string', '$$description': ['Relative path to a text file
(UTF-8) containing the full description', 'of the project. If the file path
ends in case-insensitive ``.md`` or', '``.rst`` suffixes, then the
content-type is respectively', '``text/markdown`` or ``text/x-rst``']},
{'type': 'object', 'allOf': [{'anyOf': [{'properties': {'file': {'type':
'string', '$$description': ['Relative path to a text file containing the full
description', 'of the project.']}}, 'required': ['file']}, {'properties':
{'text': {'type': 'string', 'description': 'Full text describing the
project.'}}, 'required': ['text']}]}, {'properties': {'content-type':
{'type': 'string', '$$description': ['Content-type (:rfc:`1341`) of the full
description', '(e.g. ``text/markdown``). The ``charset`` parameter is
assumed', 'UTF-8 when not present.'], '$comment': 'TODO: add regex pattern or
format?'}}, 'required': ['content-type']}]}]}, 'requires-python': {'type':
'string', 'format': 'pep508-versionspec', '$$description': ['`The Python
version requirements of the project',
'<https://packaging.python.org/specifications/core-metadata/#requires-python>`_.']},
'license': {'description': '`Project license
<https://peps.python.org/pep-0621/#license>`_.', 'oneOf': [{'properties':
{'file': {'type': 'string', '$$description': ['Relative path to the file
(UTF-8) which contains the license for the', 'project.']}}, 'required':
['file']}, {'properties': {'text': {'type': 'string', '$$description': ['The
license of the project whose meaning is that of the', '`License field from
the core metadata',
'<https://packaging.python.org/specifications/core-metadata/#license>`_.']}},
'required': ['text']}]}, 'authors': {'type': 'array', 'items': {'$id':
'#/definitions/author', 'title': 'Author or Maintainer', '$comment':
'https://peps.python.org/pep-0621/#authors-maintainers', 'type': 'object',
'additionalProperties': False, 'properties': {'name': {'type': 'string',
'$$description': ['MUST be a valid email name, i.e. whatever can be put as a
name, before an', 'email, in :rfc:`822`.']}, 'email': {'type': 'string',
'format': 'idn-email', 'description': 'MUST be a valid email address'}}},
'$$description': ["The people or organizations considered to be the 'authors'
of the project.", 'The exact meaning is open to interpretation (e.g. original
or primary authors,', 'current maintainers, or owners of the package).']},
'maintainers': {'type': 'array', 'items': {'$id': '#/definitions/author',
'title': 'Author or Maintainer', '$comment':
'https://peps.python.org/pep-0621/#authors-maintainers', 'type': 'object',
'additionalProperties': False, 'properties': {'name': {'type': 'string',
'$$description': ['MUST be a valid email name, i.e. whatever can be put as a
name, before an', 'email, in :rfc:`822`.']}, 'email': {'type': 'string',
'format': 'idn-email', 'description': 'MUST be a valid email address'}}},
'$$description': ["The people or organizations considered to be the
'maintainers' of the project.", 'Similarly to ``authors``, the exact meaning
is open to interpretation.']}, 'keywords': {'type': 'array', 'items':
{'type': 'string'}, 'description': 'List of keywords to assist searching for
the distribution in a larger catalog.'}, 'classifiers': {'type': 'array',
'items': {'type': 'string', 'format': 'trove-classifier', 'description':
'`PyPI classifier <https://pypi.org/classifiers/>`_.'}, '$$description':
['`Trove classifiers <https://pypi.org/classifiers/>`_', 'which apply to the
project.']}, 'urls': {'type': 'object', 'description': 'URLs associated with
the project in the form ``label => value``.', 'additionalProperties': False,
'patternProperties': {'^.+$': {'type': 'string', 'format': 'url'}}},
'scripts': {'$id': '#/definitions/entry-point-group', 'title':
'Entry-points', 'type': 'object', '$$description': ['Entry-points are grouped
together to indicate what sort of capabilities they', 'provide.', 'See the
`packaging guides',
'<https://packaging.python.org/specifications/entry-points/>`_', 'and
`setuptools docs',
'<https://setuptools.pypa.io/en/latest/userguide/entry_point.html>`_', 'for
more information.'], 'propertyNames': {'format': 'python-entrypoint-name'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'type':
'string', '$$description': ['Reference to a Python object. It is either in
the form', '``importable.module``, or ``importable.module:object.attr``.'],
'format': 'python-entrypoint-reference', '$comment':
'https://packaging.python.org/specifications/entry-points/'}}},
'gui-scripts': {'$id': '#/definitions/entry-point-group', 'title':
'Entry-points', 'type': 'object', '$$description': ['Entry-points are grouped
together to indicate what sort of capabilities they', 'provide.', 'See the
`packaging guides',
'<https://packaging.python.org/specifications/entry-points/>`_', 'and
`setuptools docs',
'<https://setuptools.pypa.io/en/latest/userguide/entry_point.html>`_', 'for
more information.'], 'propertyNames': {'format': 'python-entrypoint-name'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'type':
'string', '$$description': ['Reference to a Python object. It is either in
the form', '``importable.module``, or ``importable.module:object.attr``.'],
'format': 'python-entrypoint-reference', '$comment':
'https://packaging.python.org/specifications/entry-points/'}}},
'entry-points': {'$$description': ['Instruct the installer to expose the
given modules/functions via', '``entry-point`` discovery mechanism (useful
for plugins).', 'More information available in the `Python packaging guide',
'<https://packaging.python.org/specifications/entry-points/>`_.'],
'propertyNames': {'format': 'python-entrypoint-group'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'$id':
'#/definitions/entry-point-group', 'title': 'Entry-points', 'type': 'object',
'$$description': ['Entry-points are grouped together to indicate what sort of
capabilities they', 'provide.', 'See the `packaging guides',
'<https://packaging.python.org/specifications/entry-points/>`_', 'and
`setuptools docs',
'<https://setuptools.pypa.io/en/latest/userguide/entry_point.html>`_', 'for
more information.'], 'propertyNames': {'format': 'python-entrypoint-name'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'type':
'string', '$$description': ['Reference to a Python object. It is either in
the form', '``importable.module``, or ``importable.module:object.attr``.'],
'format': 'python-entrypoint-reference', '$comment':
'https://packaging.python.org/specifications/entry-points/'}}}}},
'dependencies': {'type': 'array', 'description': 'Project (mandatory)
dependencies.', 'items': {'$id': '#/definitions/dependency', 'title':
'Dependency', 'type': 'string', 'description': 'Project dependency
specification according to PEP 508', 'format': 'pep508'}},
'optional-dependencies': {'type': 'object', 'description': 'Optional
dependency for the project', 'propertyNames': {'format':
'pep508-identifier'}, 'additionalProperties': False, 'patternProperties':
{'^.+$': {'type': 'array', 'items': {'$id': '#/definitions/dependency',
'title': 'Dependency', 'type': 'string', 'description': 'Project dependency
specification according to PEP 508', 'format': 'pep508'}}}}, 'dynamic':
{'type': 'array', '$$description': ['Specifies which fields are intentionally
unspecified and expected to be', 'dynamically provided by build tools'],
'items': {'enum': ['version', 'description', 'readme', 'requires-python',
'license', 'authors', 'maintainers', 'keywords', 'classifiers', 'urls',
'scripts', 'gui-scripts', 'entry-points', 'dependencies',
'optional-dependencies']}}}, 'required': ['name'], 'additionalProperties':
False, 'if': {'not': {'required': ['dynamic'], 'properties': {'dynamic':
{'contains': {'const': 'version'}, '$$description': ['version is listed in
``dynamic``']}}}, '$$comment': ['According to :pep:`621`:', ' If the core
metadata specification lists a field as "Required", then', ' the metadata
MUST specify the field statically or list it in dynamic', 'In turn, `core
metadata`_ defines:', ' The required fields are: Metadata-Version, Name,
Version.', ' All the other fields are optional.', 'Since
``Metadata-Version`` is defined by the build back-end, ``name`` and',
'``version`` are the only mandatory information in ``pyproject.toml``.', '..
_core metadata:
https://packaging.python.org/specifications/core-metadata/']}, 'then':
{'required': ['version'], '$$description': ['version should be statically
defined in the ``version`` field']}, 'definitions': {'author': {'$id':
'#/definitions/author', 'title': 'Author or Maintainer', '$comment':
'https://peps.python.org/pep-0621/#authors-maintainers', 'type': 'object',
'additionalProperties': False, 'properties': {'name': {'type': 'string',
'$$description': ['MUST be a valid email name, i.e. whatever can be put as a
name, before an', 'email, in :rfc:`822`.']}, 'email': {'type': 'string',
'format': 'idn-email', 'description': 'MUST be a valid email address'}}},
'entry-point-group': {'$id': '#/definitions/entry-point-group', 'title':
'Entry-points', 'type': 'object', '$$description': ['Entry-points are grouped
together to indicate what sort of capabilities they', 'provide.', 'See the
`packaging guides',
'<https://packaging.python.org/specifications/entry-points/>`_', 'and
`setuptools docs',
'<https://setuptools.pypa.io/en/latest/userguide/entry_point.html>`_', 'for
more information.'], 'propertyNames': {'format': 'python-entrypoint-name'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'type':
'string', '$$description': ['Reference to a Python object. It is either in
the form', '``importable.module``, or ``importable.module:object.attr``.'],
'format': 'python-entrypoint-reference', '$comment':
'https://packaging.python.org/specifications/entry-points/'}}}, 'dependency':
{'$id': '#/definitions/dependency', 'title': 'Dependency', 'type': 'string',
'description': 'Project dependency specification according to PEP 508',
'format': 'pep508'}}}, rule='additionalProperties')
-+ try:
-+ try:
-+ data_is_dict = isinstance(data, dict)
-+ if data_is_dict:
-+ data_len = len(data)
-+ if not all(prop in data for prop in ['dynamic']):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + " must contain ['dynamic'] properties", value=data, name="" +
(name_prefix or "data") + "", definition={'required': ['dynamic'],
'properties': {'dynamic': {'contains': {'const': 'version'}, '$$description':
['version is listed in ``dynamic``']}}}, rule='required')
-+ data_keys = set(data.keys())
-+ if "dynamic" in data_keys:
-+ data_keys.remove("dynamic")
-+ data__dynamic = data["dynamic"]
-+ data__dynamic_is_list = isinstance(data__dynamic,
(list, tuple))
-+ if data__dynamic_is_list:
-+ data__dynamic_contains = False
-+ for data__dynamic_key in data__dynamic:
-+ try:
-+ if data__dynamic_key != "version":
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".dynamic must be same as const definition:
version", value=data__dynamic_key, name="" + (name_prefix or "data") +
".dynamic", definition={'const': 'version'}, rule='const')
-+ data__dynamic_contains = True
-+ break
-+ except JsonSchemaValueException: pass
-+ if not data__dynamic_contains:
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + ".dynamic must contain one of contains definition",
value=data__dynamic, name="" + (name_prefix or "data") + ".dynamic",
definition={'contains': {'const': 'version'}, '$$description': ['version is
listed in ``dynamic``']}, rule='contains')
-+ except JsonSchemaValueException: pass
-+ else:
-+ raise JsonSchemaValueException("" + (name_prefix or "data") + "
must NOT match a disallowed definition", value=data, name="" + (name_prefix
or "data") + "", definition={'not': {'required': ['dynamic'], 'properties':
{'dynamic': {'contains': {'const': 'version'}, '$$description': ['version is
listed in ``dynamic``']}}}, '$$comment': ['According to :pep:`621`:', ' If
the core metadata specification lists a field as "Required", then', ' the
metadata MUST specify the field statically or list it in dynamic', 'In turn,
`core metadata`_ defines:', ' The required fields are: Metadata-Version,
Name, Version.', ' All the other fields are optional.', 'Since
``Metadata-Version`` is defined by the build back-end, ``name`` and',
'``version`` are the only mandatory information in ``pyproject.toml``.', '..
_core metadata:
https://packaging.python.org/specifications/core-metadata/']}, rule='not')
-+ except JsonSchemaValueException:
-+ pass
-+ else:
-+ data_is_dict = isinstance(data, dict)
-+ if data_is_dict:
-+ data_len = len(data)
-+ if not all(prop in data for prop in ['version']):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ " must contain ['version'] properties", value=data, name="" + (name_prefix
or "data") + "", definition={'required': ['version'], '$$description':
['version should be statically defined in the ``version`` field']},
rule='required')
-+ return data
-+
-+def
validate_https___packaging_python_org_en_latest_specifications_declaring_project_metadata___definitions_dependency(data,
custom_formats={}, name_prefix=None):
-+ if not isinstance(data, (str)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data") + "
must be string", value=data, name="" + (name_prefix or "data") + "",
definition={'$id': '#/definitions/dependency', 'title': 'Dependency', 'type':
'string', 'description': 'Project dependency specification according to PEP
508', 'format': 'pep508'}, rule='type')
-+ if isinstance(data, str):
-+ if not custom_formats["pep508"](data):
-+ raise JsonSchemaValueException("" + (name_prefix or "data") + "
must be pep508", value=data, name="" + (name_prefix or "data") + "",
definition={'$id': '#/definitions/dependency', 'title': 'Dependency', 'type':
'string', 'description': 'Project dependency specification according to PEP
508', 'format': 'pep508'}, rule='format')
-+ return data
-+
-+def
validate_https___packaging_python_org_en_latest_specifications_declaring_project_metadata___definitions_entry_point_group(data,
custom_formats={}, name_prefix=None):
-+ if not isinstance(data, (dict)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data") + "
must be object", value=data, name="" + (name_prefix or "data") + "",
definition={'$id': '#/definitions/entry-point-group', 'title':
'Entry-points', 'type': 'object', '$$description': ['Entry-points are grouped
together to indicate what sort of capabilities they', 'provide.', 'See the
`packaging guides',
'<https://packaging.python.org/specifications/entry-points/>`_', 'and
`setuptools docs',
'<https://setuptools.pypa.io/en/latest/userguide/entry_point.html>`_', 'for
more information.'], 'propertyNames': {'format': 'python-entrypoint-name'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'type':
'string', '$$description': ['Reference to a Python object. It is either in
the form', '``importable.module``, or ``importable.module:object.attr``.'],
'format': 'python-entrypoint-reference', '$comment':
'https://packaging.python.org/specifications/entry-points/'}}}, rule='type')
-+ data_is_dict = isinstance(data, dict)
-+ if data_is_dict:
-+ data_keys = set(data.keys())
-+ for data_key, data_val in data.items():
-+ if REGEX_PATTERNS['^.+$'].search(data_key):
-+ if data_key in data_keys:
-+ data_keys.remove(data_key)
-+ if not isinstance(data_val, (str)):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".{data_key}".format(**locals()) + " must be string",
value=data_val, name="" + (name_prefix or "data") +
".{data_key}".format(**locals()) + "", definition={'type': 'string',
'$$description': ['Reference to a Python object. It is either in the form',
'``importable.module``, or ``importable.module:object.attr``.'], 'format':
'python-entrypoint-reference', '$comment':
'https://packaging.python.org/specifications/entry-points/'}, rule='type')
-+ if isinstance(data_val, str):
-+ if not
custom_formats["python-entrypoint-reference"](data_val):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".{data_key}".format(**locals()) + " must be
python-entrypoint-reference", value=data_val, name="" + (name_prefix or
"data") + ".{data_key}".format(**locals()) + "", definition={'type':
'string', '$$description': ['Reference to a Python object. It is either in
the form', '``importable.module``, or ``importable.module:object.attr``.'],
'format': 'python-entrypoint-reference', '$comment':
'https://packaging.python.org/specifications/entry-points/'}, rule='format')
-+ if data_keys:
-+ raise JsonSchemaValueException("" + (name_prefix or "data") + "
must not contain "+str(data_keys)+" properties", value=data, name="" +
(name_prefix or "data") + "", definition={'$id':
'#/definitions/entry-point-group', 'title': 'Entry-points', 'type': 'object',
'$$description': ['Entry-points are grouped together to indicate what sort of
capabilities they', 'provide.', 'See the `packaging guides',
'<https://packaging.python.org/specifications/entry-points/>`_', 'and
`setuptools docs',
'<https://setuptools.pypa.io/en/latest/userguide/entry_point.html>`_', 'for
more information.'], 'propertyNames': {'format': 'python-entrypoint-name'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'type':
'string', '$$description': ['Reference to a Python object. It is either in
the form', '``importable.module``, or ``importable.module:object.attr``.'],
'format': 'python-entrypoint-reference', '$comment':
'https://packaging.python.org/specifications/entry-points/'}}},
rule='additionalProperties')
-+ data_len = len(data)
-+ if data_len != 0:
-+ data_property_names = True
-+ for data_key in data:
-+ try:
-+ if isinstance(data_key, str):
-+ if not
custom_formats["python-entrypoint-name"](data_key):
-+ raise JsonSchemaValueException("" +
(name_prefix or "data") + " must be python-entrypoint-name", value=data_key,
name="" + (name_prefix or "data") + "", definition={'format':
'python-entrypoint-name'}, rule='format')
-+ except JsonSchemaValueException:
-+ data_property_names = False
-+ if not data_property_names:
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ " must be named by propertyName definition", value=data, name="" +
(name_prefix or "data") + "", definition={'$id':
'#/definitions/entry-point-group', 'title': 'Entry-points', 'type': 'object',
'$$description': ['Entry-points are grouped together to indicate what sort of
capabilities they', 'provide.', 'See the `packaging guides',
'<https://packaging.python.org/specifications/entry-points/>`_', 'and
`setuptools docs',
'<https://setuptools.pypa.io/en/latest/userguide/entry_point.html>`_', 'for
more information.'], 'propertyNames': {'format': 'python-entrypoint-name'},
'additionalProperties': False, 'patternProperties': {'^.+$': {'type':
'string', '$$description': ['Reference to a Python object. It is either in
the form', '``importable.module``, or ``importable.module:object.attr``.'],
'format': 'python-entrypoint-reference', '$comment':
'https://packaging.python.org/specifications/entry-points/'}}},
rule='propertyNames')
-+ return data
-+
-+def
validate_https___packaging_python_org_en_latest_specifications_declaring_project_metadata___definitions_author(data,
custom_formats={}, name_prefix=None):
-+ if not isinstance(data, (dict)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data") + "
must be object", value=data, name="" + (name_prefix or "data") + "",
definition={'$id': '#/definitions/author', 'title': 'Author or Maintainer',
'$comment': 'https://peps.python.org/pep-0621/#authors-maintainers', 'type':
'object', 'additionalProperties': False, 'properties': {'name': {'type':
'string', '$$description': ['MUST be a valid email name, i.e. whatever can be
put as a name, before an', 'email, in :rfc:`822`.']}, 'email': {'type':
'string', 'format': 'idn-email', 'description': 'MUST be a valid email
address'}}}, rule='type')
-+ data_is_dict = isinstance(data, dict)
-+ if data_is_dict:
-+ data_keys = set(data.keys())
-+ if "name" in data_keys:
-+ data_keys.remove("name")
-+ data__name = data["name"]
-+ if not isinstance(data__name, (str)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".name must be string", value=data__name, name="" + (name_prefix or "data")
+ ".name", definition={'type': 'string', '$$description': ['MUST be a valid
email name, i.e. whatever can be put as a name, before an', 'email, in
:rfc:`822`.']}, rule='type')
-+ if "email" in data_keys:
-+ data_keys.remove("email")
-+ data__email = data["email"]
-+ if not isinstance(data__email, (str)):
-+ raise JsonSchemaValueException("" + (name_prefix or "data")
+ ".email must be string", value=data__email, name="" + (name_prefix or
"data") + ".email", definition={'type': 'string', 'format': 'idn-email',
'description': 'MUST be a valid email address'}, rule='type')
-+ if isinstance(data__email, str):
-+ if not
REGEX_PATTERNS["idn-email_re_pattern"].match(data__email):
-+ raise JsonSchemaValueException("" + (name_prefix or
"data") + ".email must be idn-email", value=data__email, name="" +
(name_prefix or "data") + ".email", definition={'type': 'string', 'format':
'idn-email', 'description': 'MUST be a valid email address'}, rule='format')
-+ if data_keys:
-+ raise JsonSchemaValueException("" + (name_prefix or "data") + "
must not contain "+str(data_keys)+" properties", value=data, name="" +
(name_prefix or "data") + "", definition={'$id': '#/definitions/author',
'title': 'Author or Maintainer', '$comment':
'https://peps.python.org/pep-0621/#authors-maintainers', 'type': 'object',
'additionalProperties': False, 'properties': {'name': {'type': 'string',
'$$description': ['MUST be a valid email name, i.e. whatever can be put as a
name, before an', 'email, in :rfc:`822`.']}, 'email': {'type': 'string',
'format': 'idn-email', 'description': 'MUST be a valid email address'}}},
rule='additionalProperties')
-+ return data
-\ No newline at end of file
-diff --git
a/third_party/python/setuptools/setuptools/config/_validate_pyproject/formats.py

b/third_party/python/setuptools/setuptools/config/_validate_pyproject/formats.py
-new file mode 100644
---- /dev/null
-+++
b/third_party/python/setuptools/setuptools/config/_validate_pyproject/formats.py
-@@ -0,0 +1,275 @@
-+import logging
-+import os
-+import re
-+import string
-+import typing
-+from itertools import chain as _chain
-+
-+if typing.TYPE_CHECKING:
-+ from typing_extensions import Literal
-+
-+_logger = logging.getLogger(__name__)
-+
-+#
-------------------------------------------------------------------------------------
-+# PEP 440
-+
-+VERSION_PATTERN = r"""
-+ v?
-+ (?:
-+ (?:(?P<epoch>[0-9]+)!)? # epoch
-+ (?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
-+ (?P<pre> # pre-release
-+ [-_\.]?
-+ (?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
-+ [-_\.]?
-+ (?P<pre_n>[0-9]+)?
-+ )?
-+ (?P<post> # post release
-+ (?:-(?P<post_n1>[0-9]+))
-+ |
-+ (?:
-+ [-_\.]?
-+ (?P<post_l>post|rev|r)
-+ [-_\.]?
-+ (?P<post_n2>[0-9]+)?
-+ )
-+ )?
-+ (?P<dev> # dev release
-+ [-_\.]?
-+ (?P<dev_l>dev)
-+ [-_\.]?
-+ (?P<dev_n>[0-9]+)?
-+ )?
-+ )
-+ (?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
-+"""
-+
-+VERSION_REGEX = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.X | re.I)
-+
-+
-+def pep440(version: str) -> bool:
-+ return VERSION_REGEX.match(version) is not None
-+
-+
-+#
-------------------------------------------------------------------------------------
-+# PEP 508
-+
-+PEP508_IDENTIFIER_PATTERN = r"([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])"
-+PEP508_IDENTIFIER_REGEX = re.compile(f"^{PEP508_IDENTIFIER_PATTERN}$", re.I)
-+
-+
-+def pep508_identifier(name: str) -> bool:
-+ return PEP508_IDENTIFIER_REGEX.match(name) is not None
-+
-+
-+try:
-+ try:
-+ from packaging import requirements as _req
-+ except ImportError: # pragma: no cover
-+ # let's try setuptools vendored version
-+ from setuptools._vendor.packaging import requirements as _req #
type: ignore
-+
-+ def pep508(value: str) -> bool:
-+ try:
-+ _req.Requirement(value)
-+ return True
-+ except _req.InvalidRequirement:
-+ return False
-+
-+except ImportError: # pragma: no cover
-+ _logger.warning(
-+ "Could not find an installation of `packaging`. Requirements,
dependencies and "
-+ "versions might not be validated. "
-+ "To enforce validation, please install `packaging`."
-+ )
-+
-+ def pep508(value: str) -> bool:
-+ return True
-+
-+
-+def pep508_versionspec(value: str) -> bool:
-+ """Expression that can be used to specify/lock versions (including
ranges)"""
-+ if any(c in value for c in (";", "]", "@")):
-+ # In PEP 508:
-+ # conditional markers, extras and URL specs are not included in the
-+ # versionspec
-+ return False
-+ # Let's pretend we have a dependency called `requirement` with the given
-+ # version spec, then we can re-use the pep508 function for validation:
-+ return pep508(f"requirement{value}")
-+
-+
-+#
-------------------------------------------------------------------------------------
-+# PEP 517
-+
-+
-+def pep517_backend_reference(value: str) -> bool:
-+ module, _, obj = value.partition(":")
-+ identifiers = (i.strip() for i in _chain(module.split("."),
obj.split(".")))
-+ return all(python_identifier(i) for i in identifiers if i)
-+
-+
-+#
-------------------------------------------------------------------------------------
-+# Classifiers - PEP 301
-+
-+
-+def _download_classifiers() -> str:
-+ import ssl
-+ from email.message import Message
-+ from urllib.request import urlopen
-+
-+ url = "https://pypi.org/pypi?:action=list_classifiers";
-+ context = ssl.create_default_context()
-+ with urlopen(url, context=context) as response:
-+ headers = Message()
-+ headers["content_type"] = response.getheader("content-type",
"text/plain")
-+ return response.read().decode(headers.get_param("charset", "utf-8"))
-+
-+
-+class _TroveClassifier:
-+ """The ``trove_classifiers`` package is the official way of validating
classifiers,
-+ however this package might not be always available.
-+ As a workaround we can still download a list from PyPI.
-+ We also don't want to be over strict about it, so simply skipping
silently is an
-+ option (classifiers will be validated anyway during the upload to PyPI).
-+ """
-+
-+ downloaded: typing.Union[None, "Literal[False]", typing.Set[str]]
-+
-+ def __init__(self):
-+ self.downloaded = None
-+ self._skip_download = False
-+ # None => not cached yet
-+ # False => cache not available
-+ self.__name__ = "trove_classifier" # Emulate a public function
-+
-+ def _disable_download(self):
-+ # This is a private API. Only setuptools has the consent of using
it.
-+ self._skip_download = True
-+
-+ def __call__(self, value: str) -> bool:
-+ if self.downloaded is False or self._skip_download is True:
-+ return True
-+
-+ if os.getenv("NO_NETWORK") or
os.getenv("VALIDATE_PYPROJECT_NO_NETWORK"):
-+ self.downloaded = False
-+ msg = (
-+ "Install ``trove-classifiers`` to ensure proper validation.
"
-+ "Skipping download of classifiers list from PyPI
(NO_NETWORK)."
-+ )
-+ _logger.debug(msg)
-+ return True
-+
-+ if self.downloaded is None:
-+ msg = (
-+ "Install ``trove-classifiers`` to ensure proper validation.
"
-+ "Meanwhile a list of classifiers will be downloaded from
PyPI."
-+ )
-+ _logger.debug(msg)
-+ try:
-+ self.downloaded = set(_download_classifiers().splitlines())
-+ except Exception:
-+ self.downloaded = False
-+ _logger.debug("Problem with download, skipping validation")
-+ return True
-+
-+ return value in self.downloaded or
value.lower().startswith("private ::")
-+
-+
-+try:
-+ from trove_classifiers import classifiers as _trove_classifiers
-+
-+ def trove_classifier(value: str) -> bool:
-+ return value in _trove_classifiers or
value.lower().startswith("private ::")
-+
-+except ImportError: # pragma: no cover
-+ trove_classifier = _TroveClassifier()
-+
-+
-+#
-------------------------------------------------------------------------------------
-+# Stub packages - PEP 561
-+
-+
-+def pep561_stub_name(value: str) -> bool:
-+ top, *children = value.split(".")
-+ if not top.endswith("-stubs"):
-+ return False
-+ return python_module_name(".".join([top[: -len("-stubs")], *children]))
-+
-+
-+#
-------------------------------------------------------------------------------------
-+# Non-PEP related
-+
-+
-+def url(value: str) -> bool:
-+ from urllib.parse import urlparse
-+
-+ try:
-+ parts = urlparse(value)
-+ if not parts.scheme:
-+ _logger.warning(
-+ "For maximum compatibility please make sure to include a "
-+ "`scheme` prefix in your URL (e.g. 'http://'). "
-+ f"Given value: {value}"
-+ )
-+ if not (value.startswith("/") or value.startswith("\\") or "@"
in value):
-+ parts = urlparse(f"http://{value}";)
-+
-+ return bool(parts.scheme and parts.netloc)
-+ except Exception:
-+ return False
-+
-+
-+# https://packaging.python.org/specifications/entry-points/
-+ENTRYPOINT_PATTERN = r"[^\[\s=]([^=]*[^\s=])?"
-+ENTRYPOINT_REGEX = re.compile(f"^{ENTRYPOINT_PATTERN}$", re.I)
-+RECOMMEDED_ENTRYPOINT_PATTERN = r"[\w.-]+"
-+RECOMMEDED_ENTRYPOINT_REGEX =
re.compile(f"^{RECOMMEDED_ENTRYPOINT_PATTERN}$", re.I)
-+ENTRYPOINT_GROUP_PATTERN = r"\w+(\.\w+)*"
-+ENTRYPOINT_GROUP_REGEX = re.compile(f"^{ENTRYPOINT_GROUP_PATTERN}$", re.I)
-+
-+
-+def python_identifier(value: str) -> bool:
-+ return value.isidentifier()
-+
-+
-+def python_qualified_identifier(value: str) -> bool:
-+ if value.startswith(".") or value.endswith("."):
-+ return False
-+ return all(python_identifier(m) for m in value.split("."))
-+
-+
-+def python_module_name(value: str) -> bool:
-+ return python_qualified_identifier(value)
-+
-+
-+def python_entrypoint_group(value: str) -> bool:
-+ return ENTRYPOINT_GROUP_REGEX.match(value) is not None
-+
-+
-+def python_entrypoint_name(value: str) -> bool:
-+ if not ENTRYPOINT_REGEX.match(value):
-+ return False
-+ if not RECOMMEDED_ENTRYPOINT_REGEX.match(value):
-+ msg = f"Entry point `{value}` does not follow recommended pattern: "
-+ msg += RECOMMEDED_ENTRYPOINT_PATTERN
-+ _logger.warning(msg)
-+ return True
-+
-+
-+def python_entrypoint_reference(value: str) -> bool:
-+ module, _, rest = value.partition(":")
-+ if "[" in rest:
-+ obj, _, extras_ = rest.partition("[")
-+ if extras_.strip()[-1] != "]":
-+ return False
-+ extras = (x.strip() for x in extras_.strip(string.whitespace +
"[]").split(","))
-+ if not all(pep508_identifier(e) for e in extras):
-+ return False
-+ _logger.warning(f"`{value}` - using extras for entry points is not
recommended")
-+ else:
-+ obj = rest
-+
-+ module_parts = module.split(".")
-+ identifiers = _chain(module_parts, obj.split(".")) if rest else
module_parts
-+ return all(python_identifier(i.strip()) for i in identifiers)
-diff --git a/third_party/python/setuptools/setuptools/config/expand.py
b/third_party/python/setuptools/setuptools/config/expand.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/config/expand.py
-@@ -0,0 +1,462 @@
-+"""Utility functions to expand configuration directives or special values
-+(such glob patterns).
-+
-+We can split the process of interpreting configuration files into 2 steps:
-+
-+1. The parsing the file contents from strings to value objects
-+ that can be understand by Python (for example a string with a comma
-+ separated list of keywords into an actual Python list of strings).
-+
-+2. The expansion (or post-processing) of these values according to the
-+ semantics ``setuptools`` assign to them (for example a configuration
field
-+ with the ``file:`` directive should be expanded from a list of file
paths to
-+ a single string with the contents of those files concatenated)
-+
-+This module focus on the second step, and therefore allow sharing the
expansion
-+functions among several configuration file formats.
-+
-+**PRIVATE MODULE**: API reserved for setuptools internal usage only.
-+"""
-+import ast
-+import importlib
-+import io
-+import os
-+import pathlib
-+import sys
-+from glob import iglob
-+from configparser import ConfigParser
-+from importlib.machinery import ModuleSpec
-+from itertools import chain
-+from typing import (
-+ TYPE_CHECKING,
-+ Callable,
-+ Dict,
-+ Iterable,
-+ Iterator,
-+ List,
-+ Mapping,
-+ Optional,
-+ Tuple,
-+ TypeVar,
-+ Union,
-+ cast
-+)
-+from pathlib import Path
-+from types import ModuleType
-+
-+from distutils.errors import DistutilsOptionError
-+
-+from .._path import same_path as _same_path
-+from ..warnings import SetuptoolsWarning
-+
-+if TYPE_CHECKING:
-+ from setuptools.dist import Distribution # noqa
-+ from setuptools.discovery import ConfigDiscovery # noqa
-+ from distutils.dist import DistributionMetadata # noqa
-+
-+chain_iter = chain.from_iterable
-+_Path = Union[str, os.PathLike]
-+_K = TypeVar("_K")
-+_V = TypeVar("_V", covariant=True)
-+
-+
-+class StaticModule:
-+ """Proxy to a module object that avoids executing arbitrary code."""
-+
-+ def __init__(self, name: str, spec: ModuleSpec):
-+ module = ast.parse(pathlib.Path(spec.origin).read_bytes())
-+ vars(self).update(locals())
-+ del self.self
-+
-+ def _find_assignments(self) -> Iterator[Tuple[ast.AST, ast.AST]]:
-+ for statement in self.module.body:
-+ if isinstance(statement, ast.Assign):
-+ yield from ((target, statement.value) for target in
statement.targets)
-+ elif isinstance(statement, ast.AnnAssign) and statement.value:
-+ yield (statement.target, statement.value)
-+
-+ def __getattr__(self, attr):
-+ """Attempt to load an attribute "statically", via
:func:`ast.literal_eval`."""
-+ try:
-+ return next(
-+ ast.literal_eval(value)
-+ for target, value in self._find_assignments()
-+ if isinstance(target, ast.Name) and target.id == attr
-+ )
-+ except Exception as e:
-+ raise AttributeError(f"{self.name} has no attribute {attr}")
from e
-+
-+
-+def glob_relative(
-+ patterns: Iterable[str], root_dir: Optional[_Path] = None
-+) -> List[str]:
-+ """Expand the list of glob patterns, but preserving relative paths.
-+
-+ :param list[str] patterns: List of glob patterns
-+ :param str root_dir: Path to which globs should be relative
-+ (current directory by default)
-+ :rtype: list
-+ """
-+ glob_characters = {'*', '?', '[', ']', '{', '}'}
-+ expanded_values = []
-+ root_dir = root_dir or os.getcwd()
-+ for value in patterns:
-+
-+ # Has globby characters?
-+ if any(char in value for char in glob_characters):
-+ # then expand the glob pattern while keeping paths *relative*:
-+ glob_path = os.path.abspath(os.path.join(root_dir, value))
-+ expanded_values.extend(sorted(
-+ os.path.relpath(path, root_dir).replace(os.sep, "/")
-+ for path in iglob(glob_path, recursive=True)))
-+
-+ else:
-+ # take the value as-is
-+ path = os.path.relpath(value, root_dir).replace(os.sep, "/")
-+ expanded_values.append(path)
-+
-+ return expanded_values
-+
-+
-+def read_files(filepaths: Union[str, bytes, Iterable[_Path]],
root_dir=None) -> str:
-+ """Return the content of the files concatenated using ``\n`` as str
-+
-+ This function is sandboxed and won't reach anything outside ``root_dir``
-+
-+ (By default ``root_dir`` is the current directory).
-+ """
-+ from setuptools.extern.more_itertools import always_iterable
-+
-+ root_dir = os.path.abspath(root_dir or os.getcwd())
-+ _filepaths = (os.path.join(root_dir, path) for path in
always_iterable(filepaths))
-+ return '\n'.join(
-+ _read_file(path)
-+ for path in _filter_existing_files(_filepaths)
-+ if _assert_local(path, root_dir)
-+ )
-+
-+
-+def _filter_existing_files(filepaths: Iterable[_Path]) -> Iterator[_Path]:
-+ for path in filepaths:
-+ if os.path.isfile(path):
-+ yield path
-+ else:
-+ SetuptoolsWarning.emit(f"File {path!r} cannot be found")
-+
-+
-+def _read_file(filepath: Union[bytes, _Path]) -> str:
-+ with io.open(filepath, encoding='utf-8') as f:
-+ return f.read()
-+
-+
-+def _assert_local(filepath: _Path, root_dir: str):
-+ if Path(os.path.abspath(root_dir)) not in
Path(os.path.abspath(filepath)).parents:
-+ msg = f"Cannot access {filepath!r} (or anything outside
{root_dir!r})"
-+ raise DistutilsOptionError(msg)
-+
-+ return True
-+
-+
-+def read_attr(
-+ attr_desc: str,
-+ package_dir: Optional[Mapping[str, str]] = None,
-+ root_dir: Optional[_Path] = None
-+):
-+ """Reads the value of an attribute from a module.
-+
-+ This function will try to read the attributed statically first
-+ (via :func:`ast.literal_eval`), and only evaluate the module if it
fails.
-+
-+ Examples:
-+ read_attr("package.attr")
-+ read_attr("package.module.attr")
-+
-+ :param str attr_desc: Dot-separated string describing how to reach the
-+ attribute (see examples above)
-+ :param dict[str, str] package_dir: Mapping of package names to their
-+ location in disk (represented by paths relative to ``root_dir``).
-+ :param str root_dir: Path to directory containing all the packages in
-+ ``package_dir`` (current directory by default).
-+ :rtype: str
-+ """
-+ root_dir = root_dir or os.getcwd()
-+ attrs_path = attr_desc.strip().split('.')
-+ attr_name = attrs_path.pop()
-+ module_name = '.'.join(attrs_path)
-+ module_name = module_name or '__init__'
-+ _parent_path, path, module_name = _find_module(module_name,
package_dir, root_dir)
-+ spec = _find_spec(module_name, path)
-+
-+ try:
-+ return getattr(StaticModule(module_name, spec), attr_name)
-+ except Exception:
-+ # fallback to evaluate module
-+ module = _load_spec(spec, module_name)
-+ return getattr(module, attr_name)
-+
-+
-+def _find_spec(module_name: str, module_path: Optional[_Path]) ->
ModuleSpec:
-+ spec = importlib.util.spec_from_file_location(module_name, module_path)
-+ spec = spec or importlib.util.find_spec(module_name)
-+
-+ if spec is None:
-+ raise ModuleNotFoundError(module_name)
-+
-+ return spec
-+
-+
-+def _load_spec(spec: ModuleSpec, module_name: str) -> ModuleType:
-+ name = getattr(spec, "__name__", module_name)
-+ if name in sys.modules:
-+ return sys.modules[name]
-+ module = importlib.util.module_from_spec(spec)
-+ sys.modules[name] = module # cache (it also ensures `==` works on
loaded items)
-+ spec.loader.exec_module(module) # type: ignore
-+ return module
-+
-+
-+def _find_module(
-+ module_name: str, package_dir: Optional[Mapping[str, str]], root_dir:
_Path
-+) -> Tuple[_Path, Optional[str], str]:
-+ """Given a module (that could normally be imported by ``module_name``
-+ after the build is complete), find the path to the parent directory
where
-+ it is contained and the canonical name that could be used to import it
-+ considering the ``package_dir`` in the build configuration and
``root_dir``
-+ """
-+ parent_path = root_dir
-+ module_parts = module_name.split('.')
-+ if package_dir:
-+ if module_parts[0] in package_dir:
-+ # A custom path was specified for the module we want to import
-+ custom_path = package_dir[module_parts[0]]
-+ parts = custom_path.rsplit('/', 1)
-+ if len(parts) > 1:
-+ parent_path = os.path.join(root_dir, parts[0])
-+ parent_module = parts[1]
-+ else:
-+ parent_module = custom_path
-+ module_name = ".".join([parent_module, *module_parts[1:]])
-+ elif '' in package_dir:
-+ # A custom parent directory was specified for all root modules
-+ parent_path = os.path.join(root_dir, package_dir[''])
-+
-+ path_start = os.path.join(parent_path, *module_name.split("."))
-+ candidates = chain(
-+ (f"{path_start}.py", os.path.join(path_start, "__init__.py")),
-+ iglob(f"{path_start}.*")
-+ )
-+ module_path = next((x for x in candidates if os.path.isfile(x)), None)
-+ return parent_path, module_path, module_name
-+
-+
-+def resolve_class(
-+ qualified_class_name: str,
-+ package_dir: Optional[Mapping[str, str]] = None,
-+ root_dir: Optional[_Path] = None
-+) -> Callable:
-+ """Given a qualified class name, return the associated class object"""
-+ root_dir = root_dir or os.getcwd()
-+ idx = qualified_class_name.rfind('.')
-+ class_name = qualified_class_name[idx + 1 :]
-+ pkg_name = qualified_class_name[:idx]
-+
-+ _parent_path, path, module_name = _find_module(pkg_name, package_dir,
root_dir)
-+ module = _load_spec(_find_spec(module_name, path), module_name)
-+ return getattr(module, class_name)
-+
-+
-+def cmdclass(
-+ values: Dict[str, str],
-+ package_dir: Optional[Mapping[str, str]] = None,
-+ root_dir: Optional[_Path] = None
-+) -> Dict[str, Callable]:
-+ """Given a dictionary mapping command names to strings for qualified
class
-+ names, apply :func:`resolve_class` to the dict values.
-+ """
-+ return {k: resolve_class(v, package_dir, root_dir) for k, v in
values.items()}
-+
-+
-+def find_packages(
-+ *,
-+ namespaces=True,
-+ fill_package_dir: Optional[Dict[str, str]] = None,
-+ root_dir: Optional[_Path] = None,
-+ **kwargs
-+) -> List[str]:
-+ """Works similarly to :func:`setuptools.find_packages`, but with all
-+ arguments given as keyword arguments. Moreover, ``where`` can be given
-+ as a list (the results will be simply concatenated).
-+
-+ When the additional keyword argument ``namespaces`` is ``True``, it will
-+ behave like :func:`setuptools.find_namespace_packages`` (i.e. include
-+ implicit namespaces as per :pep:`420`).
-+
-+ The ``where`` argument will be considered relative to ``root_dir`` (or
the current
-+ working directory when ``root_dir`` is not given).
-+
-+ If the ``fill_package_dir`` argument is passed, this function will
consider it as a
-+ similar data structure to the ``package_dir`` configuration parameter
add fill-in
-+ any missing package location.
-+
-+ :rtype: list
-+ """
-+ from setuptools.discovery import construct_package_dir
-+ from setuptools.extern.more_itertools import unique_everseen,
always_iterable
-+
-+ if namespaces:
-+ from setuptools.discovery import PEP420PackageFinder as
PackageFinder
-+ else:
-+ from setuptools.discovery import PackageFinder # type: ignore
-+
-+ root_dir = root_dir or os.curdir
-+ where = kwargs.pop('where', ['.'])
-+ packages: List[str] = []
-+ fill_package_dir = {} if fill_package_dir is None else fill_package_dir
-+ search = list(unique_everseen(always_iterable(where)))
-+
-+ if len(search) == 1 and all(not _same_path(search[0], x) for x in (".",
root_dir)):
-+ fill_package_dir.setdefault("", search[0])
-+
-+ for path in search:
-+ package_path = _nest_path(root_dir, path)
-+ pkgs = PackageFinder.find(package_path, **kwargs)
-+ packages.extend(pkgs)
-+ if pkgs and not (
-+ fill_package_dir.get("") == path
-+ or os.path.samefile(package_path, root_dir)
-+ ):
-+ fill_package_dir.update(construct_package_dir(pkgs, path))
-+
-+ return packages
-+
-+
-+def _nest_path(parent: _Path, path: _Path) -> str:
-+ path = parent if path in {".", ""} else os.path.join(parent, path)
-+ return os.path.normpath(path)
-+
-+
-+def version(value: Union[Callable, Iterable[Union[str, int]], str]) -> str:
-+ """When getting the version directly from an attribute,
-+ it should be normalised to string.
-+ """
-+ if callable(value):
-+ value = value()
-+
-+ value = cast(Iterable[Union[str, int]], value)
-+
-+ if not isinstance(value, str):
-+ if hasattr(value, '__iter__'):
-+ value = '.'.join(map(str, value))
-+ else:
-+ value = '%s' % value
-+
-+ return value
-+
-+
-+def canonic_package_data(package_data: dict) -> dict:
-+ if "*" in package_data:
-+ package_data[""] = package_data.pop("*")
-+ return package_data
-+
-+
-+def canonic_data_files(
-+ data_files: Union[list, dict], root_dir: Optional[_Path] = None
-+) -> List[Tuple[str, List[str]]]:
-+ """For compatibility with ``setup.py``, ``data_files`` should be a list
-+ of pairs instead of a dict.
-+
-+ This function also expands glob patterns.
-+ """
-+ if isinstance(data_files, list):
-+ return data_files
-+
-+ return [
-+ (dest, glob_relative(patterns, root_dir))
-+ for dest, patterns in data_files.items()
-+ ]
-+
-+
-+def entry_points(text: str, text_source="entry-points") -> Dict[str, dict]:
-+ """Given the contents of entry-points file,
-+ process it into a 2-level dictionary (``dict[str, dict[str, str]]``).
-+ The first level keys are entry-point groups, the second level keys are
-+ entry-point names, and the second level values are references to objects
-+ (that correspond to the entry-point value).
-+ """
-+ parser = ConfigParser(default_section=None, delimiters=("=",)) # type:
ignore
-+ parser.optionxform = str # case sensitive
-+ parser.read_string(text, text_source)
-+ groups = {k: dict(v.items()) for k, v in parser.items()}
-+ groups.pop(parser.default_section, None)
-+ return groups
-+
-+
-+class EnsurePackagesDiscovered:
-+ """Some expand functions require all the packages to already be
discovered before
-+ they run, e.g. :func:`read_attr`, :func:`resolve_class`,
:func:`cmdclass`.
-+
-+ Therefore in some cases we will need to run autodiscovery during the
evaluation of
-+ the configuration. However, it is better to postpone calling package
discovery as
-+ much as possible, because some parameters can influence it (e.g.
``package_dir``),
-+ and those might not have been processed yet.
-+ """
-+
-+ def __init__(self, distribution: "Distribution"):
-+ self._dist = distribution
-+ self._called = False
-+
-+ def __call__(self):
-+ """Trigger the automatic package discovery, if it is still
necessary."""
-+ if not self._called:
-+ self._called = True
-+ self._dist.set_defaults(name=False) # Skip name, we can still
be parsing
-+
-+ def __enter__(self):
-+ return self
-+
-+ def __exit__(self, _exc_type, _exc_value, _traceback):
-+ if self._called:
-+ self._dist.set_defaults.analyse_name() # Now we can set a
default name
-+
-+ def _get_package_dir(self) -> Mapping[str, str]:
-+ self()
-+ pkg_dir = self._dist.package_dir
-+ return {} if pkg_dir is None else pkg_dir
-+
-+ @property
-+ def package_dir(self) -> Mapping[str, str]:
-+ """Proxy to ``package_dir`` that may trigger auto-discovery when
used."""
-+ return LazyMappingProxy(self._get_package_dir)
-+
-+
-+class LazyMappingProxy(Mapping[_K, _V]):
-+ """Mapping proxy that delays resolving the target object, until really
needed.
-+
-+ >>> def obtain_mapping():
-+ ... print("Running expensive function!")
-+ ... return {"key": "value", "other key": "other value"}
-+ >>> mapping = LazyMappingProxy(obtain_mapping)
-+ >>> mapping["key"]
-+ Running expensive function!
-+ 'value'
-+ >>> mapping["other key"]
-+ 'other value'
-+ """
-+
-+ def __init__(self, obtain_mapping_value: Callable[[], Mapping[_K, _V]]):
-+ self._obtain = obtain_mapping_value
-+ self._value: Optional[Mapping[_K, _V]] = None
-+
-+ def _target(self) -> Mapping[_K, _V]:
-+ if self._value is None:
-+ self._value = self._obtain()
-+ return self._value
-+
-+ def __getitem__(self, key: _K) -> _V:
-+ return self._target()[key]
-+
-+ def __len__(self) -> int:
-+ return len(self._target())
-+
-+ def __iter__(self) -> Iterator[_K]:
-+ return iter(self._target())
-diff --git
a/third_party/python/setuptools/setuptools/config/pyprojecttoml.py
b/third_party/python/setuptools/setuptools/config/pyprojecttoml.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/config/pyprojecttoml.py
-@@ -0,0 +1,437 @@
-+"""
-+Load setuptools configuration from ``pyproject.toml`` files.
-+
-+**PRIVATE MODULE**: API reserved for setuptools internal usage only.
-+
-+To read project metadata, consider using
-+``build.util.project_wheel_metadata`` (https://pypi.org/project/build/).
-+For simple scenarios, you can also try parsing the file directly
-+with the help of ``tomllib`` or ``tomli``.
-+"""
-+import logging
-+import os
-+from contextlib import contextmanager
-+from functools import partial
-+from typing import TYPE_CHECKING, Callable, Dict, Mapping, Optional, Set,
Union
-+
-+from ..errors import FileError, OptionError
-+from ..warnings import SetuptoolsWarning
-+from . import expand as _expand
-+from ._apply_pyprojecttoml import _PREVIOUSLY_DEFINED, _WouldIgnoreField
-+from ._apply_pyprojecttoml import apply as _apply
-+
-+if TYPE_CHECKING:
-+ from setuptools.dist import Distribution # noqa
-+
-+_Path = Union[str, os.PathLike]
-+_logger = logging.getLogger(__name__)
-+
-+
-+def load_file(filepath: _Path) -> dict:
-+ from setuptools.extern import tomli # type: ignore
-+
-+ with open(filepath, "rb") as file:
-+ return tomli.load(file)
-+
-+
-+def validate(config: dict, filepath: _Path) -> bool:
-+ from . import _validate_pyproject as validator
-+
-+ trove_classifier = validator.FORMAT_FUNCTIONS.get("trove-classifier")
-+ if hasattr(trove_classifier, "_disable_download"):
-+ # Improve reproducibility by default. See issue 31 for
validate-pyproject.
-+ trove_classifier._disable_download() # type: ignore
-+
-+ try:
-+ return validator.validate(config)
-+ except validator.ValidationError as ex:
-+ summary = f"configuration error: {ex.summary}"
-+ if ex.name.strip("`") != "project":
-+ # Probably it is just a field missing/misnamed, not worthy the
verbosity...
-+ _logger.debug(summary)
-+ _logger.debug(ex.details)
-+
-+ error = f"invalid pyproject.toml config: {ex.name}."
-+ raise ValueError(f"{error}\n{summary}") from None
-+
-+
-+def apply_configuration(
-+ dist: "Distribution",
-+ filepath: _Path,
-+ ignore_option_errors=False,
-+) -> "Distribution":
-+ """Apply the configuration from a ``pyproject.toml`` file into an
existing
-+ distribution object.
-+ """
-+ config = read_configuration(filepath, True, ignore_option_errors, dist)
-+ return _apply(dist, config, filepath)
-+
-+
-+def read_configuration(
-+ filepath: _Path,
-+ expand=True,
-+ ignore_option_errors=False,
-+ dist: Optional["Distribution"] = None,
-+):
-+ """Read given configuration file and returns options from it as a dict.
-+
-+ :param str|unicode filepath: Path to configuration file in the
``pyproject.toml``
-+ format.
-+
-+ :param bool expand: Whether to expand directives and other computed
values
-+ (i.e. post-process the given configuration)
-+
-+ :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.
-+
-+ :param Distribution|None: Distribution object to which the
configuration refers.
-+ If not given a dummy object will be created and discarded after the
-+ configuration is read. This is used for auto-discovery of packages
and in the
-+ case a dynamic configuration (e.g. ``attr`` or ``cmdclass``) is
expanded.
-+ When ``expand=False`` this object is simply ignored.
-+
-+ :rtype: dict
-+ """
-+ filepath = os.path.abspath(filepath)
-+
-+ if not os.path.isfile(filepath):
-+ raise FileError(f"Configuration file {filepath!r} does not exist.")
-+
-+ asdict = load_file(filepath) or {}
-+ project_table = asdict.get("project", {})
-+ tool_table = asdict.get("tool", {})
-+ setuptools_table = tool_table.get("setuptools", {})
-+ if not asdict or not (project_table or setuptools_table):
-+ return {} # User is not using pyproject to configure setuptools
-+
-+ if setuptools_table:
-+ # TODO: Remove the following once the feature stabilizes:
-+ _BetaConfiguration.emit()
-+
-+ # There is an overall sense in the community that making
include_package_data=True
-+ # the default would be an improvement.
-+ # `ini2toml` backfills include_package_data=False when nothing is
explicitly given,
-+ # therefore setting a default here is backwards compatible.
-+ if dist and getattr(dist, "include_package_data", None) is not None:
-+ setuptools_table.setdefault("include-package-data",
dist.include_package_data)
-+ else:
-+ setuptools_table.setdefault("include-package-data", True)
-+ # Persist changes:
-+ asdict["tool"] = tool_table
-+ tool_table["setuptools"] = setuptools_table
-+
-+ with _ignore_errors(ignore_option_errors):
-+ # Don't complain about unrelated errors (e.g. tools not using the
"tool" table)
-+ subset = {"project": project_table, "tool": {"setuptools":
setuptools_table}}
-+ validate(subset, filepath)
-+
-+ if expand:
-+ root_dir = os.path.dirname(filepath)
-+ return expand_configuration(asdict, root_dir, ignore_option_errors,
dist)
-+
-+ return asdict
-+
-+
-+def expand_configuration(
-+ config: dict,
-+ root_dir: Optional[_Path] = None,
-+ ignore_option_errors: bool = False,
-+ dist: Optional["Distribution"] = None,
-+) -> dict:
-+ """Given a configuration with unresolved fields (e.g. dynamic,
cmdclass, ...)
-+ find their final values.
-+
-+ :param dict config: Dict containing the configuration for the
distribution
-+ :param str root_dir: Top-level directory for the distribution/project
-+ (the same directory where ``pyproject.toml`` is place)
-+ :param bool ignore_option_errors: see :func:`read_configuration`
-+ :param Distribution|None: Distribution object to which the
configuration refers.
-+ If not given a dummy object will be created and discarded after the
-+ configuration is read. Used in the case a dynamic configuration
-+ (e.g. ``attr`` or ``cmdclass``).
-+
-+ :rtype: dict
-+ """
-+ return _ConfigExpander(config, root_dir, ignore_option_errors,
dist).expand()
-+
-+
-+class _ConfigExpander:
-+ def __init__(
-+ self,
-+ config: dict,
-+ root_dir: Optional[_Path] = None,
-+ ignore_option_errors: bool = False,
-+ dist: Optional["Distribution"] = None,
-+ ):
-+ self.config = config
-+ self.root_dir = root_dir or os.getcwd()
-+ self.project_cfg = config.get("project", {})
-+ self.dynamic = self.project_cfg.get("dynamic", [])
-+ self.setuptools_cfg = config.get("tool", {}).get("setuptools", {})
-+ self.dynamic_cfg = self.setuptools_cfg.get("dynamic", {})
-+ self.ignore_option_errors = ignore_option_errors
-+ self._dist = dist
-+ self._referenced_files: Set[str] = set()
-+
-+ def _ensure_dist(self) -> "Distribution":
-+ from setuptools.dist import Distribution
-+
-+ attrs = {"src_root": self.root_dir, "name":
self.project_cfg.get("name", None)}
-+ return self._dist or Distribution(attrs)
-+
-+ def _process_field(self, container: dict, field: str, fn: Callable):
-+ if field in container:
-+ with _ignore_errors(self.ignore_option_errors):
-+ container[field] = fn(container[field])
-+
-+ def _canonic_package_data(self, field="package-data"):
-+ package_data = self.setuptools_cfg.get(field, {})
-+ return _expand.canonic_package_data(package_data)
-+
-+ def expand(self):
-+ self._expand_packages()
-+ self._canonic_package_data()
-+ self._canonic_package_data("exclude-package-data")
-+
-+ # A distribution object is required for discovering the correct
package_dir
-+ dist = self._ensure_dist()
-+ ctx = _EnsurePackagesDiscovered(dist, self.project_cfg,
self.setuptools_cfg)
-+ with ctx as ensure_discovered:
-+ package_dir = ensure_discovered.package_dir
-+ self._expand_data_files()
-+ self._expand_cmdclass(package_dir)
-+ self._expand_all_dynamic(dist, package_dir)
-+
-+ dist._referenced_files.update(self._referenced_files)
-+ return self.config
-+
-+ def _expand_packages(self):
-+ packages = self.setuptools_cfg.get("packages")
-+ if packages is None or isinstance(packages, (list, tuple)):
-+ return
-+
-+ find = packages.get("find")
-+ if isinstance(find, dict):
-+ find["root_dir"] = self.root_dir
-+ find["fill_package_dir"] =
self.setuptools_cfg.setdefault("package-dir", {})
-+ with _ignore_errors(self.ignore_option_errors):
-+ self.setuptools_cfg["packages"] =
_expand.find_packages(**find)
-+
-+ def _expand_data_files(self):
-+ data_files = partial(_expand.canonic_data_files,
root_dir=self.root_dir)
-+ self._process_field(self.setuptools_cfg, "data-files", data_files)
-+
-+ def _expand_cmdclass(self, package_dir: Mapping[str, str]):
-+ root_dir = self.root_dir
-+ cmdclass = partial(_expand.cmdclass, package_dir=package_dir,
root_dir=root_dir)
-+ self._process_field(self.setuptools_cfg, "cmdclass", cmdclass)
-+
-+ def _expand_all_dynamic(self, dist: "Distribution", package_dir:
Mapping[str, str]):
-+ special = ( # need special handling
-+ "version",
-+ "readme",
-+ "entry-points",
-+ "scripts",
-+ "gui-scripts",
-+ "classifiers",
-+ "dependencies",
-+ "optional-dependencies",
-+ )
-+ # `_obtain` functions are assumed to raise appropriate
exceptions/warnings.
-+ obtained_dynamic = {
-+ field: self._obtain(dist, field, package_dir)
-+ for field in self.dynamic
-+ if field not in special
-+ }
-+ obtained_dynamic.update(
-+ self._obtain_entry_points(dist, package_dir) or {},
-+ version=self._obtain_version(dist, package_dir),
-+ readme=self._obtain_readme(dist),
-+ classifiers=self._obtain_classifiers(dist),
-+ dependencies=self._obtain_dependencies(dist),
-+ optional_dependencies=self._obtain_optional_dependencies(dist),
-+ )
-+ # `None` indicates there is nothing in `tool.setuptools.dynamic`
but the value
-+ # might have already been set by setup.py/extensions, so avoid
overwriting.
-+ updates = {k: v for k, v in obtained_dynamic.items() if v is not
None}
-+ self.project_cfg.update(updates)
-+
-+ def _ensure_previously_set(self, dist: "Distribution", field: str):
-+ previous = _PREVIOUSLY_DEFINED[field](dist)
-+ if previous is None and not self.ignore_option_errors:
-+ msg = (
-+ f"No configuration found for dynamic {field!r}.\n"
-+ "Some dynamic fields need to be specified via
`tool.setuptools.dynamic`"
-+ "\nothers must be specified via the equivalent attribute in
`setup.py`."
-+ )
-+ raise OptionError(msg)
-+
-+ def _expand_directive(
-+ self, specifier: str, directive, package_dir: Mapping[str, str]
-+ ):
-+ from setuptools.extern.more_itertools import always_iterable #
type: ignore
-+
-+ with _ignore_errors(self.ignore_option_errors):
-+ root_dir = self.root_dir
-+ if "file" in directive:
-+
self._referenced_files.update(always_iterable(directive["file"]))
-+ return _expand.read_files(directive["file"], root_dir)
-+ if "attr" in directive:
-+ return _expand.read_attr(directive["attr"], package_dir,
root_dir)
-+ raise ValueError(f"invalid `{specifier}`: {directive!r}")
-+ return None
-+
-+ def _obtain(self, dist: "Distribution", field: str, package_dir:
Mapping[str, str]):
-+ if field in self.dynamic_cfg:
-+ return self._expand_directive(
-+ f"tool.setuptools.dynamic.{field}",
-+ self.dynamic_cfg[field],
-+ package_dir,
-+ )
-+ self._ensure_previously_set(dist, field)
-+ return None
-+
-+ def _obtain_version(self, dist: "Distribution", package_dir:
Mapping[str, str]):
-+ # Since plugins can set version, let's silently skip if it cannot
be obtained
-+ if "version" in self.dynamic and "version" in self.dynamic_cfg:
-+ return _expand.version(self._obtain(dist, "version",
package_dir))
-+ return None
-+
-+ def _obtain_readme(self, dist: "Distribution") -> Optional[Dict[str,
str]]:
-+ if "readme" not in self.dynamic:
-+ return None
-+
-+ dynamic_cfg = self.dynamic_cfg
-+ if "readme" in dynamic_cfg:
-+ return {
-+ "text": self._obtain(dist, "readme", {}),
-+ "content-type": dynamic_cfg["readme"].get("content-type",
"text/x-rst"),
-+ }
-+
-+ self._ensure_previously_set(dist, "readme")
-+ return None
-+
-+ def _obtain_entry_points(
-+ self, dist: "Distribution", package_dir: Mapping[str, str]
-+ ) -> Optional[Dict[str, dict]]:
-+ fields = ("entry-points", "scripts", "gui-scripts")
-+ if not any(field in self.dynamic for field in fields):
-+ return None
-+
-+ text = self._obtain(dist, "entry-points", package_dir)
-+ if text is None:
-+ return None
-+
-+ groups = _expand.entry_points(text)
-+ expanded = {"entry-points": groups}
-+
-+ def _set_scripts(field: str, group: str):
-+ if group in groups:
-+ value = groups.pop(group)
-+ if field not in self.dynamic:
-+ _WouldIgnoreField.emit(field=field, value=value)
-+ # TODO: Don't set field when support for pyproject.toml
stabilizes
-+ # instead raise an error as specified in PEP 621
-+ expanded[field] = value
-+
-+ _set_scripts("scripts", "console_scripts")
-+ _set_scripts("gui-scripts", "gui_scripts")
-+
-+ return expanded
-+
-+ def _obtain_classifiers(self, dist: "Distribution"):
-+ if "classifiers" in self.dynamic:
-+ value = self._obtain(dist, "classifiers", {})
-+ if value:
-+ return value.splitlines()
-+ return None
-+
-+ def _obtain_dependencies(self, dist: "Distribution"):
-+ if "dependencies" in self.dynamic:
-+ value = self._obtain(dist, "dependencies", {})
-+ if value:
-+ return _parse_requirements_list(value)
-+ return None
-+
-+ def _obtain_optional_dependencies(self, dist: "Distribution"):
-+ if "optional-dependencies" not in self.dynamic:
-+ return None
-+ if "optional-dependencies" in self.dynamic_cfg:
-+ optional_dependencies_map =
self.dynamic_cfg["optional-dependencies"]
-+ assert isinstance(optional_dependencies_map, dict)
-+ return {
-+ group: _parse_requirements_list(self._expand_directive(
-+
f"tool.setuptools.dynamic.optional-dependencies.{group}",
-+ directive,
-+ {},
-+ ))
-+ for group, directive in optional_dependencies_map.items()
-+ }
-+ self._ensure_previously_set(dist, "optional-dependencies")
-+ return None
-+
-+
-+def _parse_requirements_list(value):
-+ return [
-+ line
-+ for line in value.splitlines()
-+ if line.strip() and not line.strip().startswith("#")
-+ ]
-+
-+
-+@contextmanager
-+def _ignore_errors(ignore_option_errors: bool):
-+ if not ignore_option_errors:
-+ yield
-+ return
-+
-+ try:
-+ yield
-+ except Exception as ex:
-+ _logger.debug(f"ignored error: {ex.__class__.__name__} - {ex}")
-+
-+
-+class _EnsurePackagesDiscovered(_expand.EnsurePackagesDiscovered):
-+ def __init__(
-+ self, distribution: "Distribution", project_cfg: dict,
setuptools_cfg: dict
-+ ):
-+ super().__init__(distribution)
-+ self._project_cfg = project_cfg
-+ self._setuptools_cfg = setuptools_cfg
-+
-+ def __enter__(self):
-+ """When entering the context, the values of ``packages``,
``py_modules`` and
-+ ``package_dir`` that are missing in ``dist`` are copied from
``setuptools_cfg``.
-+ """
-+ dist, cfg = self._dist, self._setuptools_cfg
-+ package_dir: Dict[str, str] = cfg.setdefault("package-dir", {})
-+ package_dir.update(dist.package_dir or {})
-+ dist.package_dir = package_dir # needs to be the same object
-+
-+ dist.set_defaults._ignore_ext_modules() # pyproject.toml-specific
behaviour
-+
-+ # Set `name`, `py_modules` and `packages` in dist to short-circuit
-+ # auto-discovery, but avoid overwriting empty lists purposefully
set by users.
-+ if dist.metadata.name is None:
-+ dist.metadata.name = self._project_cfg.get("name")
-+ if dist.py_modules is None:
-+ dist.py_modules = cfg.get("py-modules")
-+ if dist.packages is None:
-+ dist.packages = cfg.get("packages")
-+
-+ return super().__enter__()
-+
-+ def __exit__(self, exc_type, exc_value, traceback):
-+ """When exiting the context, if values of ``packages``,
``py_modules`` and
-+ ``package_dir`` are missing in ``setuptools_cfg``, copy from
``dist``.
-+ """
-+ # If anything was discovered set them back, so they count in the
final config.
-+ self._setuptools_cfg.setdefault("packages", self._dist.packages)
-+ self._setuptools_cfg.setdefault("py-modules", self._dist.py_modules)
-+ return super().__exit__(exc_type, exc_value, traceback)
-+
-+
-+class _BetaConfiguration(SetuptoolsWarning):
-+ _SUMMARY = "Support for `[tool.setuptools]` in `pyproject.toml` is
still *beta*."
-diff --git a/third_party/python/setuptools/setuptools/config/setupcfg.py
b/third_party/python/setuptools/setuptools/config/setupcfg.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/config/setupcfg.py
-@@ -0,0 +1,789 @@
-+"""
-+Load setuptools configuration from ``setup.cfg`` files.
-+
-+**API will be made private in the future**
-+
-+To read project metadata, consider using
-+``build.util.project_wheel_metadata`` (https://pypi.org/project/build/).
-+For simple scenarios, you can also try parsing the file directly
-+with the help of ``configparser``.
-+"""
-+import contextlib
-+import functools
-+import os
-+from collections import defaultdict
-+from functools import partial
-+from functools import wraps
-+from typing import (
-+ TYPE_CHECKING,
-+ Callable,
-+ Any,
-+ Dict,
-+ Generic,
-+ Iterable,
-+ List,
-+ Optional,
-+ Set,
-+ Tuple,
-+ TypeVar,
-+ Union,
-+)
-+
-+from ..errors import FileError, OptionError
-+from ..extern.packaging.markers import default_environment as marker_env
-+from ..extern.packaging.requirements import InvalidRequirement, Requirement
-+from ..extern.packaging.specifiers import SpecifierSet
-+from ..extern.packaging.version import InvalidVersion, Version
-+from ..warnings import SetuptoolsDeprecationWarning
-+from . import expand
-+
-+if TYPE_CHECKING:
-+ from distutils.dist import DistributionMetadata # noqa
-+
-+ from setuptools.dist import Distribution # noqa
-+
-+_Path = Union[str, os.PathLike]
-+SingleCommandOptions = Dict["str", Tuple["str", Any]]
-+"""Dict that associate the name of the options of a particular command to a
-+tuple. The first element of the tuple indicates the origin of the option
value
-+(e.g. the name of the configuration file where it was read from),
-+while the second element of the tuple is the option value itself
-+"""
-+AllCommandOptions = Dict["str", SingleCommandOptions] # cmd name => its
options
-+Target = TypeVar("Target", bound=Union["Distribution",
"DistributionMetadata"])
-+
-+
-+def read_configuration(
-+ filepath: _Path, find_others=False, ignore_option_errors=False
-+) -> dict:
-+ """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
-+
-+ dist = Distribution()
-+ filenames = dist.find_config_files() if find_others else []
-+ handlers = _apply(dist, filepath, filenames, ignore_option_errors)
-+ return configuration_to_dict(handlers)
-+
-+
-+def apply_configuration(dist: "Distribution", filepath: _Path) ->
"Distribution":
-+ """Apply the configuration from a ``setup.cfg`` file into an existing
-+ distribution object.
-+ """
-+ _apply(dist, filepath)
-+ dist._finalize_requires()
-+ return dist
-+
-+
-+def _apply(
-+ dist: "Distribution",
-+ filepath: _Path,
-+ other_files: Iterable[_Path] = (),
-+ ignore_option_errors: bool = False,
-+) -> Tuple["ConfigHandler", ...]:
-+ """Read configuration from ``filepath`` and applies to the ``dist``
object."""
-+ from setuptools.dist import _Distribution
-+
-+ filepath = os.path.abspath(filepath)
-+
-+ if not os.path.isfile(filepath):
-+ raise FileError(f'Configuration file {filepath} does not exist.')
-+
-+ current_directory = os.getcwd()
-+ os.chdir(os.path.dirname(filepath))
-+ filenames = [*other_files, filepath]
-+
-+ try:
-+ _Distribution.parse_config_files(dist, filenames=filenames)
-+ handlers = parse_configuration(
-+ dist, dist.command_options,
ignore_option_errors=ignore_option_errors
-+ )
-+ dist._finalize_license_files()
-+ finally:
-+ os.chdir(current_directory)
-+
-+ return handlers
-+
-+
-+def _get_option(target_obj: Target, key: str):
-+ """
-+ 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 = f'get_{key}'
-+ by_attribute = functools.partial(getattr, target_obj, key)
-+ getter = getattr(target_obj, getter_name, by_attribute)
-+ return getter()
-+
-+
-+def configuration_to_dict(handlers: Tuple["ConfigHandler", ...]) -> dict:
-+ """Returns configuration data gathered by given handlers as a dict.
-+
-+ :param list[ConfigHandler] handlers: Handlers list,
-+ usually from parse_configuration()
-+
-+ :rtype: dict
-+ """
-+ config_dict: 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: "Distribution",
-+ command_options: AllCommandOptions,
-+ ignore_option_errors=False,
-+) -> Tuple["ConfigMetadataHandler", "ConfigOptionsHandler"]:
-+ """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
-+ """
-+ with expand.EnsurePackagesDiscovered(distribution) as ensure_discovered:
-+ options = ConfigOptionsHandler(
-+ distribution,
-+ command_options,
-+ ignore_option_errors,
-+ ensure_discovered,
-+ )
-+
-+ options.parse()
-+ if not distribution.package_dir:
-+ distribution.package_dir = options.package_dir # Filled by
`find_packages`
-+
-+ meta = ConfigMetadataHandler(
-+ distribution.metadata,
-+ command_options,
-+ ignore_option_errors,
-+ ensure_discovered,
-+ distribution.package_dir,
-+ distribution.src_root,
-+ )
-+ meta.parse()
-+ distribution._referenced_files.update(
-+ options._referenced_files, meta._referenced_files
-+ )
-+
-+ return meta, options
-+
-+
-+def _warn_accidental_env_marker_misconfig(label: str, orig_value: str,
parsed: list):
-+ """Because users sometimes misinterpret this configuration:
-+
-+ [options.extras_require]
-+ foo = bar;python_version<"4"
-+
-+ It looks like one requirement with an environment marker
-+ but because there is no newline, it's parsed as two requirements
-+ with a semicolon as separator.
-+
-+ Therefore, if:
-+ * input string does not contain a newline AND
-+ * parsed result contains two requirements AND
-+ * parsing of the two parts from the result ("<first>;<second>")
-+ leads in a valid Requirement with a valid marker
-+ a UserWarning is shown to inform the user about the possible problem.
-+ """
-+ if "\n" in orig_value or len(parsed) != 2:
-+ return
-+
-+ markers = marker_env().keys()
-+
-+ try:
-+ req = Requirement(parsed[1])
-+ if req.name in markers:
-+ _AmbiguousMarker.emit(field=label, req=parsed[1])
-+ except InvalidRequirement as ex:
-+ if any(parsed[1].startswith(marker) for marker in markers):
-+ msg = _AmbiguousMarker.message(field=label, req=parsed[1])
-+ raise InvalidRequirement(msg) from ex
-+
-+
-+class ConfigHandler(Generic[Target]):
-+ """Handles metadata supplied in configuration files."""
-+
-+ section_prefix: str
-+ """Prefix for config sections handled by this handler.
-+ Must be provided by class heirs.
-+
-+ """
-+
-+ aliases: Dict[str, str] = {}
-+ """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: Target,
-+ options: AllCommandOptions,
-+ ignore_option_errors,
-+ ensure_discovered: expand.EnsurePackagesDiscovered,
-+ ):
-+ self.ignore_option_errors = ignore_option_errors
-+ self.target_obj = target_obj
-+ self.sections = dict(self._section_options(options))
-+ self.set_options: List[str] = []
-+ self.ensure_discovered = ensure_discovered
-+ self._referenced_files: Set[str] = set()
-+ """After parsing configurations, this property will enumerate
-+ all files referenced by the "file:" directive. Private API for
setuptools only.
-+ """
-+
-+ @classmethod
-+ def _section_options(cls, options: AllCommandOptions):
-+ for full_name, value in options.items():
-+ pre, sep, name = full_name.partition(cls.section_prefix)
-+ if pre:
-+ continue
-+ yield name.lstrip('.'), value
-+
-+ @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):
-+ target_obj = self.target_obj
-+
-+ # Translate alias into real name.
-+ option_name = self.aliases.get(option_name, option_name)
-+
-+ try:
-+ current_value = getattr(target_obj, option_name)
-+ except AttributeError:
-+ raise KeyError(option_name)
-+
-+ if current_value:
-+ # Already inhabited. Skipping.
-+ return
-+
-+ try:
-+ parsed = self.parsers.get(option_name, lambda x: x)(value)
-+ except (Exception,) * self.ignore_option_errors:
-+ return
-+
-+ simple_setter = functools.partial(target_obj.__setattr__,
option_name)
-+ setter = getattr(target_obj, 'set_%s' % option_name, simple_setter)
-+ setter(parsed)
-+
-+ 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_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 OptionError(f"Unable to parse option value to dict:
{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
-+
-+ def _parse_file(self, value, root_dir: _Path):
-+ """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 = [path.strip() for path in spec.split(',')]
-+ self._referenced_files.update(filepaths)
-+ return expand.read_files(filepaths, root_dir)
-+
-+ def _parse_attr(self, value, package_dir, root_dir: _Path):
-+ """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
-+
-+ attr_desc = value.replace(attr_directive, '')
-+
-+ # Make sure package_dir is populated correctly, so `attr:`
directives can work
-+ package_dir.update(self.ensure_discovered.package_dir)
-+ return expand.read_attr(attr_desc, package_dir, root_dir)
-+
-+ @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_with_key(cls, section_options,
values_parser):
-+ """Parses section options into a dictionary.
-+
-+ Applies a given parser to each option in a section.
-+
-+ :param dict section_options:
-+ :param callable values_parser: function with 2 args corresponding
to key, value
-+ :rtype: dict
-+ """
-+ value = {}
-+ for key, (_, val) in section_options.items():
-+ value[key] = values_parser(key, val)
-+ return value
-+
-+ @classmethod
-+ def _parse_section_to_dict(cls, section_options, values_parser=None):
-+ """Parses section options into a dictionary.
-+
-+ Optionally applies a given parser to each value.
-+
-+ :param dict section_options:
-+ :param callable values_parser: function with 1 arg corresponding to
option value
-+ :rtype: dict
-+ """
-+ parser = (lambda _, v: values_parser(v)) if values_parser else
(lambda _, v: v)
-+ return cls._parse_section_to_dict_with_key(section_options, parser)
-+
-+ def parse_section(self, section_options):
-+ """Parses configuration file section.
-+
-+ :param dict section_options:
-+ """
-+ for name, (_, value) in section_options.items():
-+ with contextlib.suppress(KeyError):
-+ # Keep silent for a new option may appear anytime.
-+ self[name] = value
-+
-+ 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: Optional[Callable] = getattr(
-+ self,
-+ # Dots in section names are translated into dunderscores.
-+ ('parse_section%s' % method_postfix).replace('.', '__'),
-+ None,
-+ )
-+
-+ if section_parser_method is None:
-+ raise OptionError(
-+ "Unsupported distribution option section: "
-+ f"[{self.section_prefix}.{section_name}]"
-+ )
-+
-+ section_parser_method(section_options)
-+
-+ def _deprecated_config_handler(self, func, msg, **kw):
-+ """this function will wrap around parameters that are deprecated
-+
-+ :param msg: deprecation message
-+ :param func: function to be wrapped around
-+ """
-+
-+ @wraps(func)
-+ def config_handler(*args, **kwargs):
-+ kw.setdefault("stacklevel", 2)
-+ _DeprecatedConfig.emit("Deprecated config in `setup.cfg`", msg,
**kw)
-+ return func(*args, **kwargs)
-+
-+ return config_handler
-+
-+
-+class ConfigMetadataHandler(ConfigHandler["DistributionMetadata"]):
-+ 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: "DistributionMetadata",
-+ options: AllCommandOptions,
-+ ignore_option_errors: bool,
-+ ensure_discovered: expand.EnsurePackagesDiscovered,
-+ package_dir: Optional[dict] = None,
-+ root_dir: _Path = os.curdir,
-+ ):
-+ super().__init__(target_obj, options, ignore_option_errors,
ensure_discovered)
-+ self.package_dir = package_dir
-+ self.root_dir = root_dir
-+
-+ @property
-+ def parsers(self):
-+ """Metadata item name to parser function mapping."""
-+ parse_list = self._parse_list
-+ parse_file = partial(self._parse_file, root_dir=self.root_dir)
-+ 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.",
-+ due_date=(2023, 10, 30),
-+ # Warning introduced in 27 Oct 2018
-+ ),
-+ '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.",
-+ due_date=(2023, 10, 30),
-+ # Warning introduced in 23 May 2021
-+ ),
-+ '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, self.root_dir)
-+
-+ 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:
-+ raise OptionError(
-+ f'Version loaded from {value} does not '
-+ f'comply with PEP 440: {version}'
-+ )
-+
-+ return version
-+
-+ return expand.version(self._parse_attr(value, self.package_dir,
self.root_dir))
-+
-+
-+class ConfigOptionsHandler(ConfigHandler["Distribution"]):
-+ section_prefix = 'options'
-+
-+ def __init__(
-+ self,
-+ target_obj: "Distribution",
-+ options: AllCommandOptions,
-+ ignore_option_errors: bool,
-+ ensure_discovered: expand.EnsurePackagesDiscovered,
-+ ):
-+ super().__init__(target_obj, options, ignore_option_errors,
ensure_discovered)
-+ self.root_dir = target_obj.src_root
-+ self.package_dir: Dict[str, str] = {} # To be filled by
`find_packages`
-+
-+ @classmethod
-+ def _parse_list_semicolon(cls, value):
-+ return cls._parse_list(value, separator=';')
-+
-+ def _parse_file_in_root(self, value):
-+ return self._parse_file(value, root_dir=self.root_dir)
-+
-+ def _parse_requirements_list(self, label: str, value: str):
-+ # Parse a requirements list, either by reading in a `file:`, or a
list.
-+ parsed = self._parse_list_semicolon(self._parse_file_in_root(value))
-+ _warn_accidental_env_marker_misconfig(label, value, parsed)
-+ # Filter it to only include lines that are not comments.
`parse_list`
-+ # will have stripped each line and filtered out empties.
-+ return [line for line in parsed if not line.startswith("#")]
-+
-+ @property
-+ def parsers(self):
-+ """Metadata item name to parser function mapping."""
-+ parse_list = self._parse_list
-+ 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': self._deprecated_config_handler(
-+ parse_list,
-+ "The namespace_packages parameter is deprecated, "
-+ "consider using implicit namespaces instead (PEP 420).",
-+ # TODO: define due date, see setuptools.dist:check_nsp.
-+ ),
-+ 'install_requires': partial(
-+ self._parse_requirements_list, "install_requires"
-+ ),
-+ 'setup_requires': self._parse_list_semicolon,
-+ 'tests_require': self._parse_list_semicolon,
-+ 'packages': self._parse_packages,
-+ 'entry_points': self._parse_file_in_root,
-+ 'py_modules': parse_list,
-+ 'python_requires': SpecifierSet,
-+ 'cmdclass': parse_cmdclass,
-+ }
-+
-+ def _parse_cmdclass(self, value):
-+ package_dir = self.ensure_discovered.package_dir
-+ return expand.cmdclass(self._parse_dict(value), package_dir,
self.root_dir)
-+
-+ 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)
-+
-+ # Read function arguments from a dedicated section.
-+ find_kwargs = self.parse_section_packages__find(
-+ self.sections.get('packages.find', {})
-+ )
-+
-+ find_kwargs.update(
-+ namespaces=(trimmed_value == find_directives[1]),
-+ root_dir=self.root_dir,
-+ fill_package_dir=self.package_dir,
-+ )
-+
-+ return expand.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):
-+ package_data = self._parse_section_to_dict(section_options,
self._parse_list)
-+ return expand.canonic_package_data(package_data)
-+
-+ 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:
-+ """
-+ parsed = self._parse_section_to_dict_with_key(
-+ section_options,
-+ lambda k, v:
self._parse_requirements_list(f"extras_require[{k}]", v),
-+ )
-+
-+ self['extras_require'] = parsed
-+
-+ 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)
-+ self['data_files'] = expand.canonic_data_files(parsed,
self.root_dir)
-+
-+
-+class _AmbiguousMarker(SetuptoolsDeprecationWarning):
-+ _SUMMARY = "Ambiguous requirement marker."
-+ _DETAILS = """
-+ One of the parsed requirements in `{field}` looks like a valid
environment marker:
-+
-+ {req!r}
-+
-+ Please make sure that the configuration file is correct.
-+ You can use dangling lines to avoid this problem.
-+ """
-+ _SEE_DOCS = "userguide/declarative_config.html#opt-2"
-+ # TODO: should we include due_date here? Initially introduced in 6 Aug
2022.
-+ # Does this make sense with latest version of packaging?
-+
-+ @classmethod
-+ def message(cls, **kw):
-+ docs = f"https://setuptools.pypa.io/en/latest/{cls._SEE_DOCS}";
-+ return cls._format(cls._SUMMARY, cls._DETAILS, see_url=docs,
format_args=kw)
-+
-+
-+class _DeprecatedConfig(SetuptoolsDeprecationWarning):
-+ _SEE_DOCS = "userguide/declarative_config.html"
-diff --git a/third_party/python/setuptools/setuptools/depends.py
b/third_party/python/setuptools/setuptools/depends.py
---- a/third_party/python/setuptools/setuptools/depends.py
-+++ b/third_party/python/setuptools/setuptools/depends.py
-@@ -1,13 +1,14 @@
- import sys
- import marshal
- import contextlib
- import dis
--from distutils.version import StrictVersion
-+
-+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'
- ]
-@@ -16,17 +17,17 @@ from . import _imp
- 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 = StrictVersion
-+ 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
-@@ -35,17 +36,17 @@ class Require:
- """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 version >= self.requested_version
-+ 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
-@@ -73,17 +74,17 @@ class Require:
- """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(version)
-+ return self.version_ok(str(version))
-
-
- def maybe_close(f):
- @contextlib.contextmanager
- def empty():
- yield
- return
- if not f:
-diff --git a/third_party/python/setuptools/setuptools/discovery.py
b/third_party/python/setuptools/setuptools/discovery.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/discovery.py
-@@ -0,0 +1,611 @@
-+"""Automatic discovery of Python modules and packages (for inclusion in the
-+distribution) and other config values.
-+
-+For the purposes of this module, the following nomenclature is used:
-+
-+- "src-layout": a directory representing a Python project that contains a
"src"
-+ folder. Everything under the "src" folder is meant to be included in the
-+ distribution when packaging the project. Example::
-+
-+ .
-+ ├── tox.ini
-+ ├── pyproject.toml
-+ └── src/
-+ └── mypkg/
-+ ├── __init__.py
-+ ├── mymodule.py
-+ └── my_data_file.txt
-+
-+- "flat-layout": a Python project that does not use "src-layout" but instead
-+ have a directory under the project root for each package::
-+
-+ .
-+ ├── tox.ini
-+ ├── pyproject.toml
-+ └── mypkg/
-+ ├── __init__.py
-+ ├── mymodule.py
-+ └── my_data_file.txt
-+
-+- "single-module": a project that contains a single Python script direct
under
-+ the project root (no directory used)::
-+
-+ .
-+ ├── tox.ini
-+ ├── pyproject.toml
-+ └── mymodule.py
-+
-+"""
-+
-+import itertools
-+import os
-+from fnmatch import fnmatchcase
-+from glob import glob
-+from pathlib import Path
-+from typing import (
-+ TYPE_CHECKING,
-+ Dict,
-+ Iterable,
-+ Iterator,
-+ List,
-+ Mapping,
-+ Optional,
-+ Tuple,
-+ Union
-+)
-+
-+import _distutils_hack.override # noqa: F401
-+
-+from distutils import log
-+from distutils.util import convert_path
-+
-+_Path = Union[str, os.PathLike]
-+StrIter = Iterator[str]
-+
-+chain_iter = itertools.chain.from_iterable
-+
-+if TYPE_CHECKING:
-+ from setuptools import Distribution # noqa
-+
-+
-+def _valid_name(path: _Path) -> bool:
-+ # Ignore invalid names that cannot be imported directly
-+ return os.path.basename(path).isidentifier()
-+
-+
-+class _Filter:
-+ """
-+ Given a list of patterns, create a callable that will be true only if
-+ the input matches at least one of the patterns.
-+ """
-+
-+ def __init__(self, *patterns: str):
-+ self._patterns = dict.fromkeys(patterns)
-+
-+ def __call__(self, item: str) -> bool:
-+ return any(fnmatchcase(item, pat) for pat in self._patterns)
-+
-+ def __contains__(self, item: str) -> bool:
-+ return item in self._patterns
-+
-+
-+class _Finder:
-+ """Base class that exposes functionality for module/package finders"""
-+
-+ ALWAYS_EXCLUDE: Tuple[str, ...] = ()
-+ DEFAULT_EXCLUDE: Tuple[str, ...] = ()
-+
-+ @classmethod
-+ def find(
-+ cls,
-+ where: _Path = '.',
-+ exclude: Iterable[str] = (),
-+ include: Iterable[str] = ('*',)
-+ ) -> List[str]:
-+ """Return a list of all Python items (packages or modules,
depending on
-+ the finder implementation) found within directory 'where'.
-+
-+ 'where' is the root directory which will be searched.
-+ 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 names to exclude; '*' can be used
-+ as a wildcard in the names.
-+ When finding packages, 'foo.*' will exclude all subpackages of 'foo'
-+ (but not 'foo' itself).
-+
-+ 'include' is a sequence of names to include.
-+ If it's specified, only the named items will be included.
-+ If it's not specified, all found items will be included.
-+ 'include' can contain shell style wildcard patterns just like
-+ 'exclude'.
-+ """
-+
-+ exclude = exclude or cls.DEFAULT_EXCLUDE
-+ return list(
-+ cls._find_iter(
-+ convert_path(str(where)),
-+ _Filter(*cls.ALWAYS_EXCLUDE, *exclude),
-+ _Filter(*include),
-+ )
-+ )
-+
-+ @classmethod
-+ def _find_iter(cls, where: _Path, exclude: _Filter, include: _Filter)
-> StrIter:
-+ raise NotImplementedError
-+
-+
-+class PackageFinder(_Finder):
-+ """
-+ Generate a list of all Python packages found within a directory
-+ """
-+
-+ ALWAYS_EXCLUDE = ("ez_setup", "*__pycache__")
-+
-+ @classmethod
-+ def _find_iter(cls, where: _Path, exclude: _Filter, include: _Filter)
-> StrIter:
-+ """
-+ All the packages found in 'where' that pass the 'include' filter,
but
-+ not the 'exclude' filter.
-+ """
-+ for root, dirs, files in os.walk(str(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,
package):
-+ continue
-+
-+ # Should this package be included?
-+ if include(package) and not exclude(package):
-+ yield package
-+
-+ # Early pruning if there is nothing else to be scanned
-+ if f"{package}*" in exclude or f"{package}.*" in exclude:
-+ continue
-+
-+ # 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: _Path, _package_name: str) -> bool:
-+ """Does a directory look like a package?"""
-+ return os.path.isfile(os.path.join(path, '__init__.py'))
-+
-+
-+class PEP420PackageFinder(PackageFinder):
-+ @staticmethod
-+ def _looks_like_package(_path: _Path, _package_name: str) -> bool:
-+ return True
-+
-+
-+class ModuleFinder(_Finder):
-+ """Find isolated Python modules.
-+ This function will **not** recurse subdirectories.
-+ """
-+
-+ @classmethod
-+ def _find_iter(cls, where: _Path, exclude: _Filter, include: _Filter)
-> StrIter:
-+ for file in glob(os.path.join(where, "*.py")):
-+ module, _ext = os.path.splitext(os.path.basename(file))
-+
-+ if not cls._looks_like_module(module):
-+ continue
-+
-+ if include(module) and not exclude(module):
-+ yield module
-+
-+ _looks_like_module = staticmethod(_valid_name)
-+
-+
-+# We have to be extra careful in the case of flat layout to not include
files
-+# and directories not meant for distribution (e.g. tool-related)
-+
-+
-+class FlatLayoutPackageFinder(PEP420PackageFinder):
-+ _EXCLUDE = (
-+ "ci",
-+ "bin",
-+ "doc",
-+ "docs",
-+ "documentation",
-+ "manpages",
-+ "news",
-+ "changelog",
-+ "test",
-+ "tests",
-+ "unit_test",
-+ "unit_tests",
-+ "example",
-+ "examples",
-+ "scripts",
-+ "tools",
-+ "util",
-+ "utils",
-+ "python",
-+ "build",
-+ "dist",
-+ "venv",
-+ "env",
-+ "requirements",
-+ # ---- Task runners / Build tools ----
-+ "tasks", # invoke
-+ "fabfile", # fabric
-+ "site_scons", # SCons
-+ # ---- Other tools ----
-+ "benchmark",
-+ "benchmarks",
-+ "exercise",
-+ "exercises",
-+ "htmlcov", # Coverage.py
-+ # ---- Hidden directories/Private packages ----
-+ "[._]*",
-+ )
-+
-+ DEFAULT_EXCLUDE = tuple(chain_iter((p, f"{p}.*") for p in _EXCLUDE))
-+ """Reserved package names"""
-+
-+ @staticmethod
-+ def _looks_like_package(_path: _Path, package_name: str) -> bool:
-+ names = package_name.split('.')
-+ # Consider PEP 561
-+ root_pkg_is_valid = names[0].isidentifier() or
names[0].endswith("-stubs")
-+ return root_pkg_is_valid and all(name.isidentifier() for name in
names[1:])
-+
-+
-+class FlatLayoutModuleFinder(ModuleFinder):
-+ DEFAULT_EXCLUDE = (
-+ "setup",
-+ "conftest",
-+ "test",
-+ "tests",
-+ "example",
-+ "examples",
-+ "build",
-+ # ---- Task runners ----
-+ "toxfile",
-+ "noxfile",
-+ "pavement",
-+ "dodo",
-+ "tasks",
-+ "fabfile",
-+ # ---- Other tools ----
-+ "[Ss][Cc]onstruct", # SCons
-+ "conanfile", # Connan: C/C++ build tool
-+ "manage", # Django
-+ "benchmark",
-+ "benchmarks",
-+ "exercise",
-+ "exercises",
-+ # ---- Hidden files/Private modules ----
-+ "[._]*",
-+ )
-+ """Reserved top-level module names"""
-+
-+
-+def _find_packages_within(root_pkg: str, pkg_dir: _Path) -> List[str]:
-+ nested = PEP420PackageFinder.find(pkg_dir)
-+ return [root_pkg] + [".".join((root_pkg, n)) for n in nested]
-+
-+
-+class ConfigDiscovery:
-+ """Fill-in metadata and options that can be automatically derived
-+ (from other metadata/options, the file system or conventions)
-+ """
-+
-+ def __init__(self, distribution: "Distribution"):
-+ self.dist = distribution
-+ self._called = False
-+ self._disabled = False
-+ self._skip_ext_modules = False
-+
-+ def _disable(self):
-+ """Internal API to disable automatic discovery"""
-+ self._disabled = True
-+
-+ def _ignore_ext_modules(self):
-+ """Internal API to disregard ext_modules.
-+
-+ Normally auto-discovery would not be triggered if ``ext_modules``
are set
-+ (this is done for backward compatibility with existing packages
relying on
-+ ``setup.py`` or ``setup.cfg``). However, ``setuptools`` can call
this function
-+ to ignore given ``ext_modules`` and proceed with the auto-discovery
if
-+ ``packages`` and ``py_modules`` are not given (e.g. when using
pyproject.toml
-+ metadata).
-+ """
-+ self._skip_ext_modules = True
-+
-+ @property
-+ def _root_dir(self) -> _Path:
-+ # The best is to wait until `src_root` is set in dist, before using
_root_dir.
-+ return self.dist.src_root or os.curdir
-+
-+ @property
-+ def _package_dir(self) -> Dict[str, str]:
-+ if self.dist.package_dir is None:
-+ return {}
-+ return self.dist.package_dir
-+
-+ def __call__(self, force=False, name=True, ignore_ext_modules=False):
-+ """Automatically discover missing configuration fields
-+ and modifies the given ``distribution`` object in-place.
-+
-+ Note that by default this will only have an effect the first time
the
-+ ``ConfigDiscovery`` object is called.
-+
-+ To repeatedly invoke automatic discovery (e.g. when the project
-+ directory changes), please use ``force=True`` (or create a new
-+ ``ConfigDiscovery`` instance).
-+ """
-+ if force is False and (self._called or self._disabled):
-+ # Avoid overhead of multiple calls
-+ return
-+
-+ self._analyse_package_layout(ignore_ext_modules)
-+ if name:
-+ self.analyse_name() # depends on ``packages`` and
``py_modules``
-+
-+ self._called = True
-+
-+ def _explicitly_specified(self, ignore_ext_modules: bool) -> bool:
-+ """``True`` if the user has specified some form of package/module
listing"""
-+ ignore_ext_modules = ignore_ext_modules or self._skip_ext_modules
-+ ext_modules = not (self.dist.ext_modules is None or
ignore_ext_modules)
-+ return (
-+ self.dist.packages is not None
-+ or self.dist.py_modules is not None
-+ or ext_modules
-+ or hasattr(self.dist, "configuration") and
self.dist.configuration
-+ # ^ Some projects use numpy.distutils.misc_util.Configuration
-+ )
-+
-+ def _analyse_package_layout(self, ignore_ext_modules: bool) -> bool:
-+ if self._explicitly_specified(ignore_ext_modules):
-+ # For backward compatibility, just try to find modules/packages
-+ # when nothing is given
-+ return True
-+
-+ log.debug(
-+ "No `packages` or `py_modules` configuration, performing "
-+ "automatic discovery."
-+ )
-+
-+ return (
-+ self._analyse_explicit_layout()
-+ or self._analyse_src_layout()
-+ # flat-layout is the trickiest for discovery so it should be
last
-+ or self._analyse_flat_layout()
-+ )
-+
-+ def _analyse_explicit_layout(self) -> bool:
-+ """The user can explicitly give a package layout via
``package_dir``"""
-+ package_dir = self._package_dir.copy() # don't modify directly
-+ package_dir.pop("", None) # This falls under the "src-layout"
umbrella
-+ root_dir = self._root_dir
-+
-+ if not package_dir:
-+ return False
-+
-+ log.debug(f"`explicit-layout` detected -- analysing {package_dir}")
-+ pkgs = chain_iter(
-+ _find_packages_within(pkg, os.path.join(root_dir, parent_dir))
-+ for pkg, parent_dir in package_dir.items()
-+ )
-+ self.dist.packages = list(pkgs)
-+ log.debug(f"discovered packages -- {self.dist.packages}")
-+ return True
-+
-+ def _analyse_src_layout(self) -> bool:
-+ """Try to find all packages or modules under the ``src`` directory
-+ (or anything pointed by ``package_dir[""]``).
-+
-+ The "src-layout" is relatively safe for automatic discovery.
-+ We assume that everything within is meant to be included in the
-+ distribution.
-+
-+ If ``package_dir[""]`` is not given, but the ``src`` directory
exists,
-+ this function will set ``package_dir[""] = "src"``.
-+ """
-+ package_dir = self._package_dir
-+ src_dir = os.path.join(self._root_dir, package_dir.get("", "src"))
-+ if not os.path.isdir(src_dir):
-+ return False
-+
-+ log.debug(f"`src-layout` detected -- analysing {src_dir}")
-+ package_dir.setdefault("", os.path.basename(src_dir))
-+ self.dist.package_dir = package_dir # persist eventual
modifications
-+ self.dist.packages = PEP420PackageFinder.find(src_dir)
-+ self.dist.py_modules = ModuleFinder.find(src_dir)
-+ log.debug(f"discovered packages -- {self.dist.packages}")
-+ log.debug(f"discovered py_modules -- {self.dist.py_modules}")
-+ return True
-+
-+ def _analyse_flat_layout(self) -> bool:
-+ """Try to find all packages and modules under the project root.
-+
-+ Since the ``flat-layout`` is more dangerous in terms of
accidentally including
-+ extra files/directories, this function is more conservative and
will raise an
-+ error if multiple packages or modules are found.
-+
-+ This assumes that multi-package dists are uncommon and refuse to
support that
-+ use case in order to be able to prevent unintended errors.
-+ """
-+ log.debug(f"`flat-layout` detected -- analysing {self._root_dir}")
-+ return self._analyse_flat_packages() or self._analyse_flat_modules()
-+
-+ def _analyse_flat_packages(self) -> bool:
-+ self.dist.packages = FlatLayoutPackageFinder.find(self._root_dir)
-+ top_level = remove_nested_packages(remove_stubs(self.dist.packages))
-+ log.debug(f"discovered packages -- {self.dist.packages}")
-+ self._ensure_no_accidental_inclusion(top_level, "packages")
-+ return bool(top_level)
-+
-+ def _analyse_flat_modules(self) -> bool:
-+ self.dist.py_modules = FlatLayoutModuleFinder.find(self._root_dir)
-+ log.debug(f"discovered py_modules -- {self.dist.py_modules}")
-+ self._ensure_no_accidental_inclusion(self.dist.py_modules,
"modules")
-+ return bool(self.dist.py_modules)
-+
-+ def _ensure_no_accidental_inclusion(self, detected: List[str], kind:
str):
-+ if len(detected) > 1:
-+ from inspect import cleandoc
-+
-+ from setuptools.errors import PackageDiscoveryError
-+
-+ msg = f"""Multiple top-level {kind} discovered in a
flat-layout: {detected}.
-+
-+ To avoid accidental inclusion of unwanted files or directories,
-+ setuptools will not proceed with this build.
-+
-+ If you are trying to create a single distribution with multiple
{kind}
-+ on purpose, you should not rely on automatic discovery.
-+ Instead, consider the following options:
-+
-+ 1. set up custom discovery (`find` directive with `include` or
`exclude`)
-+ 2. use a `src-layout`
-+ 3. explicitly set `py_modules` or `packages` with a list of
names
-+
-+ To find more information, look for "package discovery" on
setuptools docs.
-+ """
-+ raise PackageDiscoveryError(cleandoc(msg))
-+
-+ def analyse_name(self):
-+ """The packages/modules are the essential contribution of the
author.
-+ Therefore the name of the distribution can be derived from them.
-+ """
-+ if self.dist.metadata.name or self.dist.name:
-+ # get_name() is not reliable (can return "UNKNOWN")
-+ return None
-+
-+ log.debug("No `name` configuration, performing automatic discovery")
-+
-+ name = (
-+ self._find_name_single_package_or_module()
-+ or self._find_name_from_packages()
-+ )
-+ if name:
-+ self.dist.metadata.name = name
-+
-+ def _find_name_single_package_or_module(self) -> Optional[str]:
-+ """Exactly one module or package"""
-+ for field in ('packages', 'py_modules'):
-+ items = getattr(self.dist, field, None) or []
-+ if items and len(items) == 1:
-+ log.debug(f"Single module/package detected, name:
{items[0]}")
-+ return items[0]
-+
-+ return None
-+
-+ def _find_name_from_packages(self) -> Optional[str]:
-+ """Try to find the root package that is not a PEP 420 namespace"""
-+ if not self.dist.packages:
-+ return None
-+
-+ packages = remove_stubs(sorted(self.dist.packages, key=len))
-+ package_dir = self.dist.package_dir or {}
-+
-+ parent_pkg = find_parent_package(packages, package_dir,
self._root_dir)
-+ if parent_pkg:
-+ log.debug(f"Common parent package detected, name: {parent_pkg}")
-+ return parent_pkg
-+
-+ log.warn("No parent package detected, impossible to derive `name`")
-+ return None
-+
-+
-+def remove_nested_packages(packages: List[str]) -> List[str]:
-+ """Remove nested packages from a list of packages.
-+
-+ >>> remove_nested_packages(["a", "a.b1", "a.b2", "a.b1.c1"])
-+ ['a']
-+ >>> remove_nested_packages(["a", "b", "c.d", "c.d.e.f", "g.h", "a.a1"])
-+ ['a', 'b', 'c.d', 'g.h']
-+ """
-+ pkgs = sorted(packages, key=len)
-+ top_level = pkgs[:]
-+ size = len(pkgs)
-+ for i, name in enumerate(reversed(pkgs)):
-+ if any(name.startswith(f"{other}.") for other in top_level):
-+ top_level.pop(size - i - 1)
-+
-+ return top_level
-+
-+
-+def remove_stubs(packages: List[str]) -> List[str]:
-+ """Remove type stubs (:pep:`561`) from a list of packages.
-+
-+ >>> remove_stubs(["a", "a.b", "a-stubs", "a-stubs.b.c", "b", "c-stubs"])
-+ ['a', 'a.b', 'b']
-+ """
-+ return [pkg for pkg in packages if not
pkg.split(".")[0].endswith("-stubs")]
-+
-+
-+def find_parent_package(
-+ packages: List[str], package_dir: Mapping[str, str], root_dir: _Path
-+) -> Optional[str]:
-+ """Find the parent package that is not a namespace."""
-+ packages = sorted(packages, key=len)
-+ common_ancestors = []
-+ for i, name in enumerate(packages):
-+ if not all(n.startswith(f"{name}.") for n in packages[i+1:]):
-+ # Since packages are sorted by length, this condition is able
-+ # to find a list of all common ancestors.
-+ # When there is divergence (e.g. multiple root packages)
-+ # the list will be empty
-+ break
-+ common_ancestors.append(name)
-+
-+ for name in common_ancestors:
-+ pkg_path = find_package_path(name, package_dir, root_dir)
-+ init = os.path.join(pkg_path, "__init__.py")
-+ if os.path.isfile(init):
-+ return name
-+
-+ return None
-+
-+
-+def find_package_path(
-+ name: str, package_dir: Mapping[str, str], root_dir: _Path
-+) -> str:
-+ """Given a package name, return the path where it should be found on
-+ disk, considering the ``package_dir`` option.
-+
-+ >>> path = find_package_path("my.pkg", {"": "root/is/nested"}, ".")
-+ >>> path.replace(os.sep, "/")
-+ './root/is/nested/my/pkg'
-+
-+ >>> path = find_package_path("my.pkg", {"my": "root/is/nested"}, ".")
-+ >>> path.replace(os.sep, "/")
-+ './root/is/nested/pkg'
-+
-+ >>> path = find_package_path("my.pkg", {"my.pkg": "root/is/nested"},
".")
-+ >>> path.replace(os.sep, "/")
-+ './root/is/nested'
-+
-+ >>> path = find_package_path("other.pkg", {"my.pkg": "root/is/nested"},
".")
-+ >>> path.replace(os.sep, "/")
-+ './other/pkg'
-+ """
-+ parts = name.split(".")
-+ for i in range(len(parts), 0, -1):
-+ # Look backwards, the most specific package_dir first
-+ partial_name = ".".join(parts[:i])
-+ if partial_name in package_dir:
-+ parent = package_dir[partial_name]
-+ return os.path.join(root_dir, parent, *parts[i:])
-+
-+ parent = package_dir.get("") or ""
-+ return os.path.join(root_dir, *parent.split("/"), *parts)
-+
-+
-+def construct_package_dir(packages: List[str], package_path: _Path) ->
Dict[str, str]:
-+ parent_pkgs = remove_nested_packages(packages)
-+ prefix = Path(package_path).parts
-+ return {pkg: "/".join([*prefix, *pkg.split(".")]) for pkg in
parent_pkgs}
-diff --git a/third_party/python/setuptools/setuptools/dist.py
b/third_party/python/setuptools/setuptools/dist.py
---- a/third_party/python/setuptools/setuptools/dist.py
-+++ b/third_party/python/setuptools/setuptools/dist.py
-@@ -1,212 +1,248 @@
--# -*- 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 contextlib import suppress
-+from typing import List, Optional, Set, TYPE_CHECKING
-+from pathlib import Path
-
- from collections import defaultdict
- from email import message_from_file
-
- from distutils.errors import DistutilsOptionError, DistutilsSetupError
- from distutils.util import rfc822_escape
--from distutils.version import StrictVersion
-
- from setuptools.extern import packaging
- from setuptools.extern import ordered_set
--
--from . import SetuptoolsDeprecationWarning
-+from setuptools.extern.more_itertools import unique_everseen, partition
-
- 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.config import setupcfg, pyprojecttoml
-+from setuptools.discovery import ConfigDiscovery
-+
-+from setuptools.extern.packaging import version
-+from . import _reqs
-+from . import _entry_points
-+from . import _normalization
-+from ._importlib import metadata
-+from .warnings import InformationOnly, SetuptoolsDeprecationWarning
-+
-+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
-
-- if mv is None:
-- if self.long_description_content_type or self.provides_extras:
-- mv = StrictVersion('2.1')
-- elif (self.maintainer is not None or
-- self.maintainer_email is not None or
-- getattr(self, 'python_requires', None) is not None or
-- self.project_urls):
-- mv = StrictVersion('1.2')
-- elif (self.provides or self.requires or self.obsoletes or
-- self.classifiers or self.download_url):
-- mv = StrictVersion('1.1')
-- else:
-- mv = StrictVersion('1.0')
-+
-+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
-+
-
-- self.metadata_version = mv
-+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)
-+
-
-- return mv
-+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' or not value:
-+ return None
-+ return value
-
-
- 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
-+ 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.download_url = _read_field_from_msg(msg, 'download-url')
-+ self.license = _read_field_unescaped_from_msg(msg, 'license')
-
-- self.metadata_version = StrictVersion(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')
-+ 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('keywords').split(',')
-+ self.keywords = _read_field_from_msg(msg, 'keywords').split(',')
-
-- self.platforms = _read_list('platform')
-- self.classifiers = _read_list('classifier')
-+ 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 == StrictVersion('1.1'):
-- self.requires = _read_list('requires')
-- self.provides = _read_list('provides')
-- self.obsoletes = _read_list('obsoletes')
-+ 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.
-+ msg = "newlines are not allowed in `summary` and will break in the
future"
-+ SetuptoolsDeprecationWarning.emit("Invalid config.", msg)
-+ # due_date is undefined. Controversial change, there was a lot of
push back.
-+ val = val.strip().split('\n')[0]
-+ return val
-+
-
- # Based on Python 3.5 version
--def write_pkg_file(self, file):
-- """Write the PKG-INFO format data to a file object.
-- """
-+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', self.get_description())
-- write_field('Home-page', self.get_url())
-+
-+ summary = self.get_description()
-+ if summary:
-+ write_field('Summary', single_line(summary))
-
-- if version < StrictVersion('1.2'):
-- write_field('Author', self.get_contact())
-- write_field('Author-email', self.get_contact_email())
-- else:
-- optional_fields = (
-- ('Author', 'author'),
-- ('Author-email', 'author_email'),
-- ('Maintainer', 'maintainer'),
-- ('Maintainer-email', 'maintainer_email'),
-- )
-+ optional_fields = (
-+ ('Home-page', 'url'),
-+ ('Download-URL', 'download_url'),
-+ ('Author', 'author'),
-+ ('Author-email', 'author_email'),
-+ ('Maintainer', 'maintainer'),
-+ ('Maintainer-email', 'maintainer_email'),
-+ )
-
-- for field, attr in optional_fields:
-- attr_val = getattr(self, attr)
-+ for field, attr in optional_fields:
-+ attr_val = getattr(self, attr, None)
-+ if attr_val is not None:
-+ write_field(field, attr_val)
-
-- if attr_val is not None:
-- write_field(field, attr_val)
-+ license = self.get_license()
-+ if license:
-+ write_field('License', rfc822_escape(license))
-
-- write_field('License', self.get_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)
-
-- long_desc = rfc822_escape(self.get_long_description())
-- write_field('Description', long_desc)
--
- keywords = ','.join(self.get_keywords())
- if keywords:
- write_field('Keywords', keywords)
-
-- if version >= StrictVersion('1.2'):
-- for platform in self.get_platforms():
-- write_field('Platform', platform)
-- else:
-- self._write_list(file, 'Platform', self.get_platforms())
-+ platforms = self.get_platforms() or []
-+ for platform in 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
-- )
-+ write_field('Description-Content-Type',
self.long_description_content_type)
- if self.provides_extras:
- for extra in self.provides_extras:
- write_field('Provides-Extra', extra)
-
-+ self._write_list(file, 'License-File', self.license_files or [])
-+
-+ long_description = self.get_long_description()
-+ if long_description:
-+ file.write("\n%s" % long_description)
-+ if not long_description.endswith("\n"):
-+ file.write("\n")
-+
-
- sequence = tuple, list
-
-
- def check_importable(dist, attr, value):
- try:
-- ep = pkg_resources.EntryPoint.parse('x=' + value)
-+ ep = metadata.EntryPoint(value=value, name=None, group=None)
- assert not ep.extras
- except (TypeError, ValueError, AttributeError, AssertionError) as e:
- raise DistutilsSetupError(
-- "%r must be importable 'module:attrs' string (got %r)"
-- % (attr, value)
-+ "%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
-@@ -221,117 +257,142 @@ def assert_string_list(dist, attr, value
-
- 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
-+ "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
-+ " is not: please correct this in setup.py",
-+ nsp,
-+ parent,
- )
-+ SetuptoolsDeprecationWarning.emit(
-+ "The namespace_packages parameter is deprecated.",
-+ "Please replace its usage with implicit namespaces (PEP 420).",
-+ see_docs="references/keywords.html#keyword-namespace-packages"
-+ # TODO: define due_date, it may break old packages that are no
longer
-+ # maintained (e.g. sphinxcontrib extensions) when installed
from source.
-+ # Warning officially introduced in May 2022, however the
deprecation
-+ # was mentioned much earlier in the docs (May 2020, see #2149).
-+ )
-
-
- 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))
-+ try:
-+ _check_marker(marker)
-+ except packaging.markers.InvalidMarker:
-+ msg = f"Invalid environment marker: {marker} ({extra!r})"
-+ raise DistutilsSetupError(msg) from None
-+ list(_reqs.parse(reqs))
-+
-+
-+def _check_marker(marker):
-+ if not marker:
-+ return
-+ m = packaging.markers.Marker(marker)
-+ m.evaluate()
-
-
- 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:
-+ DistDeprecationWarning.emit(f"{attr} is ignored.")
-+ # TODO: should there be a `due_date` here?
-+ 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))
-+ list(_reqs.parse(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
-+ 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 as error:
-+ except (packaging.specifiers.InvalidSpecifier, AttributeError) as error:
- tmpl = (
-- "{attr!r} must be a string "
-- "containing valid version specifiers; {error}"
-+ "{attr!r} must be a string " "containing valid version
specifiers; {error}"
- )
-- raise DistutilsSetupError(
-- tmpl.format(attr=attr, error=error)
-- ) from 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:
-+ _entry_points.load(value)
-+ except Exception 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))
-+ "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)
-+ "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
-+ ".-separated package names in setup.py",
-+ pkgname,
- )
-
-
- _Distribution = get_unpatched(distutils.core.Distribution)
-
-
- class Distribution(_Distribution):
- """Distribution with support for tests and package data
-@@ -381,134 +442,178 @@ class Distribution(_Distribution):
- 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': None,
-+ 'long_description_content_type': lambda: None,
- 'project_urls': dict,
- 'provides_extras': ordered_set.OrderedSet,
-- 'license_files': 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
-+ name = _normalization.safe_name(str(attrs['name'])).lower()
-+ with suppress(metadata.PackageNotFoundError):
-+ dist = metadata.distribution(name)
-+ if dist is not None and not dist.read_text('PKG-INFO'):
-+ dist._version =
_normalization.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'):
-+ for ep in metadata.entry_points(group='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
-- })
-+ _Distribution.__init__(
-+ self,
-+ {
-+ k: v
-+ for k, v in attrs.items()
-+ if k not in self._DISTUTILS_UNSUPPORTED_METADATA
-+ },
-+ )
-
-- # Fill-in missing metadata fields not supported by distutils.
-- # Note some fields may have been set by other tools (e.g. pbr)
-- # above; they are taken preferrentially to setup() arguments
-- for option, default in self._DISTUTILS_UNSUPPORTED_METADATA.items():
-- for source in self.metadata.__dict__, attrs:
-- if option in source:
-- value = source[option]
-- break
-- else:
-- value = default() if default else None
-- setattr(self.metadata, option, value)
-+ # Private API (setuptools-use only, not restricted to Distribution)
-+ # Stores files that are referenced by the configuration and need to
be in the
-+ # sdist (e.g. `version = file: VERSION.txt`)
-+ self._referenced_files: Set[str] = set()
-+
-+ # Save the original dependencies before they are processed into the
egg format
-+ self._orig_extras_require = {}
-+ self._orig_install_requires = []
-+ self._tmp_extras_require = defaultdict(ordered_set.OrderedSet)
-+
-+ self.set_defaults = ConfigDiscovery(self)
-+
-+ self._set_metadata_defaults(attrs)
-
- self.metadata.version = self._normalize_version(
-- self._validate_version(self.metadata.version))
-+ self._validate_version(self.metadata.version)
-+ )
- self._finalize_requires()
-
-+ def _validate_metadata(self):
-+ required = {"name"}
-+ provided = {
-+ key
-+ for key in vars(self.metadata)
-+ if getattr(self.metadata, key, None) is not None
-+ }
-+ missing = required - provided
-+
-+ if missing:
-+ msg = f"Required package metadata is missing: {missing}"
-+ raise DistutilsSetupError(msg)
-+
-+ 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()))
-+ InformationOnly.emit(f"Normalizing '{version}' to
'{normalized}'")
- 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
-+ SetuptoolsDeprecationWarning.emit(
-+ f"Invalid version: {version!r}.",
-+ """
-+ The version specified is not a valid version according
to PEP 440.
-+ This may not work as expected with newer versions of
-+ setuptools, pip, and PyPI.
-+ """,
-+ see_url="https://peps.python.org/pep-0440/";,
-+ due_date=(2023, 9, 26),
-+ # Warning initially introduced in 26 Sept 2014
-+ # pypa/packaging already removed legacy versions.
- )
- 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):
-+ # Save original before it is messed by
_convert_extras_requirements
-+ self._orig_extras_require = self._orig_extras_require or
self.extras_require
- 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)
-
-+ if getattr(self, 'install_requires', None) and not
self._orig_install_requires:
-+ # Save original before it is messed by
_move_install_requirements_markers
-+ self._orig_install_requires = self.install_requires
-+
- 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)
-+ tmp = defaultdict(ordered_set.OrderedSet)
-+ self._tmp_extras_require = getattr(self, '_tmp_extras_require', tmp)
- 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):
-+ for r in _reqs.parse(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.
-@@ -524,98 +629,208 @@ class Distribution(_Distribution):
- # 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))
-+ inst_reqs = list(_reqs.parse(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)])
-+ # list(dict.fromkeys(...)) ensures a list of unique strings
-+ (k, list(dict.fromkeys(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 _parse_config_files(self, filenames=None):
-+ 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
-- 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 = (
-+ []
-+ 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__' and opt not in ignore_options:
-- val = parser.get(section, opt)
-- opt = opt.replace('-', '_')
-- opt_dict[opt] = (filename, val)
-+ 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.
-
-- 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 e:
-- raise DistutilsOptionError(e) from e
-+ 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 = list(itertools.chain(
-+ distutils.command.__all__,
-+ self._setuptools_commands(),
-+ ))
-+ if (
-+ not section.startswith('options')
-+ and section != 'metadata'
-+ and section not in commands
-+ ):
-+ return underscore_opt
-
-- def _set_command_options(self, command_obj, option_dict=None):
-+ if '-' in opt:
-+ SetuptoolsDeprecationWarning.emit(
-+ "Invalid dash-separated options",
-+ f"""
-+ Usage of dash-separated {opt!r} will not be supported in
future
-+ versions. Please use the underscore name {underscore_opt!r}
instead.
-+ """,
-+ see_docs="userguide/declarative_config.html",
-+ due_date=(2023, 9, 26),
-+ # Warning initially introduced in 3 Mar 2021
-+ )
-+ return underscore_opt
-+
-+ def _setuptools_commands(self):
-+ try:
-+ return metadata.distribution('setuptools').entry_points.names
-+ except metadata.PackageNotFoundError:
-+ # 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()
-+ SetuptoolsDeprecationWarning.emit(
-+ "Invalid uppercase configuration",
-+ f"""
-+ Usage of uppercase key {opt!r} in {section!r} will not be
supported in
-+ future versions. Please use lowercase {lowercase_opt!r} instead.
-+ """,
-+ see_docs="userguide/declarative_config.html",
-+ due_date=(2023, 9, 26),
-+ # Warning initially introduced in 6 Mar 2021
-+ )
-+ 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').
-@@ -625,21 +840,19 @@ class Distribution(_Distribution):
- 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))
-+ self.announce(" %s = %s (from %s)" % (option, value,
source))
- try:
-- bool_opts = [translate_longopt(o)
-- for o in command_obj.boolean_options]
-+ 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:
-@@ -648,120 +861,142 @@ class Distribution(_Distribution):
- 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))
-+ % (source, command_name, option)
-+ )
- except ValueError as e:
- raise DistutilsOptionError(e) from e
-
-+ def _get_project_config_files(self, filenames):
-+ """Add default file and split between INI and TOML"""
-+ tomlfiles = []
-+ standard_project_metadata = Path(self.src_root or os.curdir,
"pyproject.toml")
-+ if filenames is not None:
-+ parts = partition(lambda f: Path(f).suffix == ".toml",
filenames)
-+ filenames = list(parts[0]) # 1st element => predicate is False
-+ tomlfiles = list(parts[1]) # 2nd element => predicate is True
-+ elif standard_project_metadata.exists():
-+ tomlfiles = [standard_project_metadata]
-+ return filenames, tomlfiles
-+
- def parse_config_files(self, filenames=None,
ignore_option_errors=False):
- """Parses configuration files from various levels
- and loads configuration.
-+ """
-+ inifiles, tomlfiles = self._get_project_config_files(filenames)
-
-- """
-- self._parse_config_files(filenames=filenames)
-+ self._parse_config_files(filenames=inifiles)
-
-- parse_configuration(self, self.command_options,
-- ignore_option_errors=ignore_option_errors)
-+ setupcfg.parse_configuration(
-+ self, self.command_options,
ignore_option_errors=ignore_option_errors
-+ )
-+ for filename in tomlfiles:
-+ pyprojecttoml.apply_configuration(self, filename,
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
-+ from setuptools.installer import _fetch_build_eggs
-+
-+ return _fetch_build_eggs(self, requires)
-
- 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)
-- eps = map(lambda e: e.load(),
pkg_resources.iter_entry_points(group))
-- for ep in sorted(eps, key=by_order):
-+
-+ defined = metadata.entry_points(group=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'):
-+ for ep in metadata.entry_points(group='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 _finalize_2to3_doctests(self):
-- if getattr(self, 'convert_2to3_doctests', None):
-- # XXX may convert to set here when we can rely on set being
builtin
-- self.convert_2to3_doctests = [
-- os.path.abspath(p)
-- for p in self.convert_2to3_doctests
-- ]
-- else:
-- self.convert_2to3_doctests = []
--
- 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(
-+ '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)
-+ eps = metadata.entry_points(group='distutils.commands',
name=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'):
-+ for ep in metadata.entry_points(group='distutils.commands'):
- if ep.name not in self.cmdclass:
-- # don't require extras as the commands won't be invoked
-- cmdclass = ep.resolve()
-+ cmdclass = ep.load()
- 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'):
-+ for ep in metadata.entry_points(group='distutils.commands'):
- if ep.name not in self.cmdclass:
-- # don't require extras as the commands won't be invoked
-- cmdclass = ep.resolve()
-+ cmdclass = ep.load()
- 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
-@@ -783,29 +1018,28 @@ class Distribution(_Distribution):
- 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)
-+ 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)
-+ 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
-+ 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 + '.'
-
-@@ -817,39 +1051,33 @@ class Distribution(_Distribution):
- """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
-+ 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)
-- )
-+ 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
-+ 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]
-@@ -892,16 +1120,17 @@ class Distribution(_Distribution):
-
- # 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):
-@@ -986,24 +1215,25 @@ class Distribution(_Distribution):
-
- # 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)
-+ sys.stdout.reconfigure(encoding='utf-8')
- try:
- return _Distribution.handle_display_options(self, option_order)
- finally:
-- sys.stdout = io.TextIOWrapper(
-- sys.stdout.detach(), encoding, errors, newline,
line_buffering)
-+ sys.stdout.reconfigure(encoding=encoding)
-+
-+ def run_command(self, command):
-+ self.set_defaults()
-+ # Postpone defaults until all explicit configuration is considered
-+ # (setup() args, config files, command line and plugins)
-+
-+ super().run_command(command)
-
-
- class DistDeprecationWarning(SetuptoolsDeprecationWarning):
- """Class for warning about deprecations in dist in
- setuptools. Not ignored by default, unlike DeprecationWarning."""
-diff --git a/third_party/python/setuptools/setuptools/errors.py
b/third_party/python/setuptools/setuptools/errors.py
---- a/third_party/python/setuptools/setuptools/errors.py
-+++ b/third_party/python/setuptools/setuptools/errors.py
-@@ -1,16 +1,58 @@
- """setuptools.errors
-
- Provides exceptions used by setuptools modules.
- """
-
--from distutils.errors import DistutilsError
-+from distutils import errors as _distutils_errors
-
-
--class RemovedCommandError(DistutilsError, RuntimeError):
-+# 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
-+
-+
-+class RemovedCommandError(BaseError, 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``.
- """
-+
-+
-+class PackageDiscoveryError(BaseError, RuntimeError):
-+ """Impossible to perform automatic discovery of packages and/or modules.
-+
-+ The current project layout or given discovery options can lead to
problems when
-+ scanning the project directory.
-+
-+ Setuptools might also refuse to complete auto-discovery if an error
prone condition
-+ is detected (e.g. when a project is organised as a flat-layout but
contains
-+ multiple directories that can be taken as top-level packages inside a
single
-+ distribution [*]_). In these situations the users are encouraged to be
explicit
-+ about which packages to include or to make the discovery parameters
more specific.
-+
-+ .. [*] Since multi-package distributions are uncommon it is very likely
that the
-+ developers did not intend for all the directories to be packaged,
and are just
-+ leaving auxiliary code in the repository top-level, such as
maintenance-related
-+ scripts.
-+ """
-diff --git a/third_party/python/setuptools/setuptools/extension.py
b/third_party/python/setuptools/setuptools/extension.py
---- a/third_party/python/setuptools/setuptools/extension.py
-+++ b/third_party/python/setuptools/setuptools/extension.py
-@@ -23,23 +23,116 @@ def _have_cython():
-
- # 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"""
-+ """
-+ Describes a single extension module.
-+
-+ This means that all source files will be compiled into a single binary
file
-+ ``<module path>.<suffix>`` (with ``<module path>`` derived from
``name`` and
-+ ``<suffix>`` defined by one of the values in
-+ ``importlib.machinery.EXTENSION_SUFFIXES``).
-+
-+ In the case ``.pyx`` files are passed as ``sources and`` ``Cython`` is
**not**
-+ installed in the build environment, ``setuptools`` may also try to look
for the
-+ equivalent ``.cpp`` or ``.c`` files.
-+
-+ :arg str name:
-+ the full name of the extension, including any packages -- ie.
-+ *not* a filename or pathname, but Python dotted name
-+
-+ :arg list[str] sources:
-+ 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.
-+
-+ :keyword list[str] include_dirs:
-+ list of directories to search for C/C++ header files (in Unix
-+ form for portability)
-+
-+ :keyword list[tuple[str, str|None]] define_macros:
-+ list of macros to define; each macro is defined using a 2-tuple:
-+ the first item corresponding to the name of the macro and the second
-+ item either a string with its value or None to
-+ define it without a particular value (equivalent of "#define
-+ FOO" in source or -DFOO on Unix C compiler command line)
-+
-+ :keyword list[str] undef_macros:
-+ list of macros to undefine explicitly
-+
-+ :keyword list[str] library_dirs:
-+ list of directories to search for C/C++ libraries at link time
-+
-+ :keyword list[str] libraries:
-+ list of library names (not filenames or paths) to link against
-+
-+ :keyword list[str] runtime_library_dirs:
-+ list of directories to search for C/C++ libraries at run time
-+ (for shared extensions, this is when the extension is loaded).
-+ Setting this will cause an exception during build on Windows
-+ platforms.
-+
-+ :keyword list[str] extra_objects:
-+ 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.)
-+
-+ :keyword list[str] extra_compile_args:
-+ 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.
-+
-+ :keyword list[str] extra_link_args:
-+ 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'.
-+
-+ :keyword list[str] export_symbols:
-+ 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.
-+
-+ :keyword list[str] swig_opts:
-+ any extra options to pass to SWIG if a source file has the .i
-+ extension.
-+
-+ :keyword list[str] depends:
-+ list of files that the extension depends on
-+
-+ :keyword str language:
-+ extension language (i.e. "c", "c++", "objc"). Will be detected
-+ from the source extensions if not provided.
-+
-+ :keyword bool optional:
-+ specifies that a build failure in the extension should not abort the
-+ build process, but simply not install the failing extension.
-+
-+ :keyword bool py_limited_api:
-+ opt-in flag for the usage of :doc:`Python's limited API
<python:c-api/stable>`.
-+
-+ :raises setuptools.errors.PlatformError: if 'runtime_library_dirs' is
-+ specified on Windows. (since v63)
-+ """
-
- 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)
-+ super().__init__(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():
-diff --git a/third_party/python/setuptools/setuptools/extern/__init__.py
b/third_party/python/setuptools/setuptools/extern/__init__.py
---- a/third_party/python/setuptools/setuptools/extern/__init__.py
-+++ b/third_party/python/setuptools/setuptools/extern/__init__.py
-@@ -1,8 +1,9 @@
-+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.
- """
-@@ -15,27 +16,20 @@ class VendorImporter:
- @property
- def search_path(self):
- """
- Search first the vendor package then as a natural package.
- """
- yield self.vendor_pkg + '.'
- yield ''
-
-- def find_module(self, fullname, path=None):
-- """
-- Return self when fullname starts with root_name and the
-- target module is one vendored through this importer.
-- """
-+ def _module_matches_namespace(self, fullname):
-+ """Figure out if the target module is vendored."""
- root, base, target = fullname.partition(self.root_name + '.')
-- if root:
-- return
-- if not any(map(target.startswith, self.vendored_names)):
-- return
-- return self
-+ 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:
-@@ -49,18 +43,41 @@ class VendorImporter:
- 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',
-+names = (
-+ 'packaging',
-+ 'ordered_set',
-+ 'more_itertools',
-+ 'importlib_metadata',
-+ 'zipp',
-+ 'importlib_resources',
-+ 'jaraco',
-+ 'typing_extensions',
-+ 'tomli',
-+)
- VendorImporter(__name__, names, 'setuptools._vendor').install()
-diff --git a/third_party/python/setuptools/setuptools/glob.py
b/third_party/python/setuptools/setuptools/glob.py
---- a/third_party/python/setuptools/setuptools/glob.py
-+++ b/third_party/python/setuptools/setuptools/glob.py
-@@ -42,46 +42,39 @@ def iglob(pathname, recursive=False):
- 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:
-- if recursive and _isrecursive(basename):
-- for x in glob2(dirname, basename):
-- yield x
-- else:
-- for x in glob1(dirname, basename):
-- yield x
-+ 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 has_magic(basename):
-- if recursive and _isrecursive(basename):
-- glob_in_dir = glob2
-- else:
-- glob_in_dir = glob1
-- else:
-+ 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`
-diff --git a/third_party/python/setuptools/setuptools/installer.py
b/third_party/python/setuptools/setuptools/installer.py
---- a/third_party/python/setuptools/setuptools/installer.py
-+++ b/third_party/python/setuptools/setuptools/installer.py
-@@ -1,104 +1,78 @@
- import glob
- import os
- import subprocess
- import sys
- import tempfile
- from distutils import log
- from distutils.errors import DistutilsError
-+from functools import partial
-
--import pkg_resources
--from setuptools.command.easy_install import easy_install
--from setuptools.wheel import Wheel
-+from . import _reqs
-+from .wheel import Wheel
-+from .warnings import SetuptoolsDeprecationWarning
-
-
- def _fixup_find_links(find_links):
- """Ensure find-links option end-up being a list of strings."""
- if isinstance(find_links, str):
- return find_links.split()
- assert isinstance(find_links, (tuple, list))
- return find_links
-
-
--def _legacy_fetch_build_egg(dist, req):
-- """Fetch an egg needed for building.
--
-- Legacy path using EasyInstall.
-- """
-- tmp_dist = dist.__class__({'script_args': ['easy_install']})
-- opts = tmp_dist.get_option_dict('easy_install')
-- opts.clear()
-- opts.update(
-- (k, v)
-- for k, v in dist.get_option_dict('easy_install').items()
-- if k in (
-- # don't use any other settings
-- 'find_links', 'site_dirs', 'index_url',
-- 'optimize', 'site_dirs', 'allow_hosts',
-- ))
-- if dist.dependency_links:
-- links = dist.dependency_links[:]
-- if 'find_links' in opts:
-- links = _fixup_find_links(opts['find_links'][1]) + links
-- opts['find_links'] = ('setup', links)
-- install_dir = dist.get_egg_cache_dir()
-- cmd = easy_install(
-- tmp_dist, args=["x"], install_dir=install_dir,
-- exclude_scripts=True,
-- always_copy=False, build_directory=None, editable=False,
-- upgrade=False, multi_version=True, no_report=True, user=False
-- )
-- cmd.ensure_finalized()
-- return cmd.easy_install(req)
--
--
- def fetch_build_egg(dist, req):
- """Fetch an egg needed for building.
-
- Use pip/wheel to fetch/build a wheel."""
-- # Check pip is available.
-- try:
-- pkg_resources.get_distribution('pip')
-- except pkg_resources.DistributionNotFound:
-- dist.announce(
-- 'WARNING: The pip package is not available, falling back '
-- 'to EasyInstall for handling setup_requires/test_requires; '
-- 'this is deprecated and will be removed in a future version.',
-- log.WARN
-- )
-- return _legacy_fetch_build_egg(dist, req)
-- # Warn if wheel is not.
-- try:
-- pkg_resources.get_distribution('wheel')
-- except pkg_resources.DistributionNotFound:
-- dist.announce('WARNING: The wheel package is not available.',
log.WARN)
-+ _DeprecatedInstaller.emit()
-+ _warn_wheel_not_available(dist)
-+ return _fetch_build_egg_no_warn(dist, req)
-+
-+
-+def _fetch_build_eggs(dist, requires):
-+ import pkg_resources # Delay import to avoid unnecessary side-effects
-+
-+ _DeprecatedInstaller.emit(stacklevel=3)
-+ _warn_wheel_not_available(dist)
-+
-+ resolved_dists = pkg_resources.working_set.resolve(
-+ _reqs.parse(requires, pkg_resources.Requirement), # required for
compatibility
-+ installer=partial(_fetch_build_egg_no_warn, dist), # avoid warning
twice
-+ replace_conflicting=True,
-+ )
-+ for dist in resolved_dists:
-+ pkg_resources.working_set.add(dist, replace=True)
-+ return resolved_dists
-+
-+
-+def _fetch_build_egg_no_warn(dist, req): # noqa: C901 # is too complex
(16) # FIXME
-+ import pkg_resources # Delay import to avoid unnecessary side-effects
-+
- # Ignore environment markers; if supplied, it is required.
- req = strip_marker(req)
- # Take easy_install options into account, but do not override relevant
- # pip environment variables (like PIP_INDEX_URL or PIP_QUIET); they'll
- # take precedence.
- opts = dist.get_option_dict('easy_install')
- if 'allow_hosts' in opts:
- raise DistutilsError('the `allow-hosts` option is not supported '
- 'when using pip to install requirements.')
-- if 'PIP_QUIET' in os.environ or 'PIP_VERBOSE' in os.environ:
-- quiet = False
-- else:
-- quiet = True
-+ quiet = 'PIP_QUIET' not in os.environ and 'PIP_VERBOSE' not in
os.environ
- if 'PIP_INDEX_URL' in os.environ:
- index_url = None
- elif 'index_url' in opts:
- index_url = opts['index_url'][1]
- else:
- index_url = None
-- if 'find_links' in opts:
-- find_links = _fixup_find_links(opts['find_links'][1])[:]
-- else:
-- find_links = []
-+ find_links = (
-+ _fixup_find_links(opts['find_links'][1])[:] if 'find_links' in opts
-+ else []
-+ )
- if dist.dependency_links:
- find_links.extend(dist.dependency_links)
- eggs_dir = os.path.realpath(dist.get_egg_cache_dir())
- environment = pkg_resources.Environment()
- for egg_dist in pkg_resources.find_distributions(eggs_dir):
- if egg_dist in req and environment.can_add(egg_dist):
- return egg_dist
- with tempfile.TemporaryDirectory() as tmpdir:
-@@ -107,26 +81,22 @@ def fetch_build_egg(dist, req):
- '--disable-pip-version-check',
- 'wheel', '--no-deps',
- '-w', tmpdir,
- ]
- if quiet:
- cmd.append('--quiet')
- if index_url is not None:
- cmd.extend(('--index-url', index_url))
-- if find_links is not None:
-- for link in find_links:
-- cmd.extend(('--find-links', link))
-+ for link in find_links or []:
-+ cmd.extend(('--find-links', link))
- # If requirement is a PEP 508 direct URL, directly pass
- # the URL to pip, as `req @ url` does not work on the
- # command line.
-- if req.url:
-- cmd.append(req.url)
-- else:
-- cmd.append(str(req))
-+ cmd.append(req.url or str(req))
- try:
- subprocess.check_call(cmd)
- except subprocess.CalledProcessError as e:
- raise DistutilsError(str(e)) from e
- wheel = Wheel(glob.glob(os.path.join(tmpdir, '*.whl'))[0])
- dist_location = os.path.join(eggs_dir, wheel.egg_name())
- wheel.install_as_egg(dist_location)
- dist_metadata = pkg_resources.PathMetadata(
-@@ -137,12 +107,32 @@ def fetch_build_egg(dist, req):
-
-
- def strip_marker(req):
- """
- Return a new requirement without the environment marker to avoid
- calling pip with something like `babel; extra == "i18n"`, which
- would always be ignored.
- """
-+ import pkg_resources # Delay import to avoid unnecessary side-effects
-+
- # create a copy to avoid mutating the input
- req = pkg_resources.Requirement.parse(str(req))
- req.marker = None
- return req
-+
-+
-+def _warn_wheel_not_available(dist):
-+ import pkg_resources # Delay import to avoid unnecessary side-effects
-+
-+ try:
-+ pkg_resources.get_distribution('wheel')
-+ except pkg_resources.DistributionNotFound:
-+ dist.announce('WARNING: The wheel package is not available.',
log.WARN)
-+
-+
-+class _DeprecatedInstaller(SetuptoolsDeprecationWarning):
-+ _SUMMARY = "setuptools.installer and fetch_build_eggs are deprecated."
-+ _DETAILS = """
-+ Requirements should be satisfied by a PEP 517 installer.
-+ If you are using pip, you can try `pip install --use-pep517`.
-+ """
-+ # _DUE_DATE not decided yet
-diff --git a/third_party/python/setuptools/setuptools/lib2to3_ex.py
b/third_party/python/setuptools/setuptools/lib2to3_ex.py
-deleted file mode 100644
---- a/third_party/python/setuptools/setuptools/lib2to3_ex.py
-+++ /dev/null
-@@ -1,68 +0,0 @@
--"""
--Customized Mixin2to3 support:
--
-- - adds support for converting doctests
--"""
--
--import warnings
--from distutils.util import Mixin2to3 as _Mixin2to3
--from distutils import log
--from lib2to3.refactor import RefactoringTool, get_fixers_from_package
--
--import setuptools
--from ._deprecation_warning import SetuptoolsDeprecationWarning
--
--
--class DistutilsRefactoringTool(RefactoringTool):
-- def log_error(self, msg, *args, **kw):
-- log.error(msg, *args)
--
-- def log_message(self, msg, *args):
-- log.info(msg, *args)
--
-- def log_debug(self, msg, *args):
-- log.debug(msg, *args)
--
--
--class Mixin2to3(_Mixin2to3):
-- def run_2to3(self, files, doctests=False):
-- # See of the distribution option has been set, otherwise check the
-- # setuptools default.
-- if self.distribution.use_2to3 is not True:
-- return
-- if not files:
-- return
--
-- warnings.warn(
-- "2to3 support is deprecated. If the project still "
-- "requires Python 2 support, please migrate to "
-- "a single-codebase solution or employ an "
-- "independent conversion process.",
-- SetuptoolsDeprecationWarning)
-- log.info("Fixing " + " ".join(files))
-- self.__build_fixer_names()
-- self.__exclude_fixers()
-- if doctests:
-- if setuptools.run_2to3_on_doctests:
-- r = DistutilsRefactoringTool(self.fixer_names)
-- r.refactor(files, write=True, doctests_only=True)
-- else:
-- _Mixin2to3.run_2to3(self, files)
--
-- def __build_fixer_names(self):
-- if self.fixer_names:
-- return
-- self.fixer_names = []
-- for p in setuptools.lib2to3_fixer_packages:
-- self.fixer_names.extend(get_fixers_from_package(p))
-- if self.distribution.use_2to3_fixers is not None:
-- for p in self.distribution.use_2to3_fixers:
-- self.fixer_names.extend(get_fixers_from_package(p))
--
-- def __exclude_fixers(self):
-- excluded_fixers = getattr(self, 'exclude_fixers', [])
-- if self.distribution.use_2to3_exclude_fixers is not None:
--
excluded_fixers.extend(self.distribution.use_2to3_exclude_fixers)
-- for fixer_name in excluded_fixers:
-- if fixer_name in self.fixer_names:
-- self.fixer_names.remove(fixer_name)
-diff --git a/third_party/python/setuptools/setuptools/logging.py
b/third_party/python/setuptools/setuptools/logging.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/logging.py
-@@ -0,0 +1,37 @@
-+import sys
-+import inspect
-+import logging
-+import distutils.log
-+from . import monkey
-+
-+
-+def _not_warning(record):
-+ return record.levelno < logging.WARNING
-+
-+
-+def configure():
-+ """
-+ Configure logging to emit warning and above to stderr
-+ and everything else to stdout. This behavior is provided
-+ for compatibility with distutils.log but may change in
-+ the future.
-+ """
-+ err_handler = logging.StreamHandler()
-+ err_handler.setLevel(logging.WARNING)
-+ out_handler = logging.StreamHandler(sys.stdout)
-+ out_handler.addFilter(_not_warning)
-+ handlers = err_handler, out_handler
-+ logging.basicConfig(
-+ format="{message}", style='{', handlers=handlers,
level=logging.DEBUG)
-+ if inspect.ismodule(distutils.dist.log):
-+ monkey.patch_func(set_threshold, distutils.log, 'set_threshold')
-+ # For some reason `distutils.log` module is getting cached in
`distutils.dist`
-+ # and then loaded again when patched,
-+ # implying: id(distutils.log) != id(distutils.dist.log).
-+ # Make sure the same module object is used everywhere:
-+ distutils.dist.log = distutils.log
-+
-+
-+def set_threshold(level):
-+ logging.root.setLevel(level*10)
-+ return set_threshold.unpatched(level)
-diff --git a/third_party/python/setuptools/setuptools/monkey.py
b/third_party/python/setuptools/setuptools/monkey.py
---- a/third_party/python/setuptools/setuptools/monkey.py
-+++ b/third_party/python/setuptools/setuptools/monkey.py
-@@ -66,18 +66,16 @@ def patch_all():
-
- has_issue_12885 = sys.version_info <= (3, 5, 3)
-
- if has_issue_12885:
- # fix findall bug in distutils (http://bugs.python.org/issue12885)
- distutils.filelist.findall = setuptools.findall
-
- needs_warehouse = (
-- sys.version_info < (2, 7, 13)
-- or
- (3, 4) < sys.version_info < (3, 4, 6)
- or
- (3, 5) < sys.version_info <= (3, 5, 3)
- )
-
- if needs_warehouse:
- warehouse = 'https://upload.pypi.org/legacy/'
- distutils.config.PyPIRCCommand.DEFAULT_REPOSITORY = warehouse
-@@ -138,40 +136,24 @@ def patch_for_msvc_specialized_compiler(
- if platform.system() != 'Windows':
- # Compilers only available on Microsoft Windows
- return
-
- def patch_params(mod_name, func_name):
- """
- Prepare the parameters for patch_func to patch indicated function.
- """
-- repl_prefix = 'msvc9_' if 'msvc9' in mod_name else 'msvc14_'
-+ repl_prefix = 'msvc14_'
- repl_name = repl_prefix + func_name.lstrip('_')
- repl = getattr(msvc, repl_name)
- mod = import_module(mod_name)
- if not hasattr(mod, func_name):
- raise ImportError(func_name)
- return repl, mod, func_name
-
-- # Python 2.7 to 3.4
-- msvc9 = functools.partial(patch_params, 'distutils.msvc9compiler')
--
- # Python 3.5+
- msvc14 = functools.partial(patch_params, 'distutils._msvccompiler')
-
- try:
-- # Patch distutils.msvc9compiler
-- patch_func(*msvc9('find_vcvarsall'))
-- patch_func(*msvc9('query_vcvarsall'))
-- except ImportError:
-- pass
--
-- try:
- # Patch distutils._msvccompiler._get_vc_env
- patch_func(*msvc14('_get_vc_env'))
- except ImportError:
- pass
--
-- try:
-- # Patch distutils._msvccompiler.gen_lib_options for Numpy
-- patch_func(*msvc14('gen_lib_options'))
-- except ImportError:
-- pass
-diff --git a/third_party/python/setuptools/setuptools/msvc.py
b/third_party/python/setuptools/setuptools/msvc.py
---- a/third_party/python/setuptools/setuptools/msvc.py
-+++ b/third_party/python/setuptools/setuptools/msvc.py
-@@ -1,150 +1,47 @@
- """
- Improved support for Microsoft Visual C++ compilers.
-
- Known supported compilers:
- --------------------------
--Microsoft Visual C++ 9.0:
-- Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64)
-- Microsoft Windows SDK 6.1 (x86, x64, ia64)
-- Microsoft Windows SDK 7.0 (x86, x64, ia64)
--
--Microsoft Visual C++ 10.0:
-- Microsoft Windows SDK 7.1 (x86, x64, ia64)
--
- Microsoft Visual C++ 14.X:
- Microsoft Visual C++ Build Tools 2015 (x86, x64, arm)
- Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64)
- Microsoft Visual Studio Build Tools 2019 (x86, x64, arm, arm64)
-
- This may also support compilers shipped with compatible Visual Studio
versions.
- """
-
- import json
- from io import open
- from os import listdir, pathsep
- from os.path import join, isfile, isdir, dirname
--import sys
-+from subprocess import CalledProcessError
-+import contextlib
- import platform
- import itertools
- import subprocess
- import distutils.errors
--from setuptools.extern.packaging.version import LegacyVersion
--
--from .monkey import get_unpatched
-+from setuptools.extern.more_itertools import unique_everseen
-
- if platform.system() == 'Windows':
- import winreg
- from os import environ
- else:
- # Mock winreg and environ so the module can be imported on this
platform.
-
- class winreg:
- HKEY_USERS = None
- HKEY_CURRENT_USER = None
- HKEY_LOCAL_MACHINE = None
- HKEY_CLASSES_ROOT = None
-
- environ = dict()
-
--_msvc9_suppress_errors = (
-- # msvc9compiler isn't available on some platforms
-- ImportError,
--
-- # msvc9compiler raises DistutilsPlatformError in some
-- # environments. See #1118.
-- distutils.errors.DistutilsPlatformError,
--)
--
--try:
-- from distutils.msvc9compiler import Reg
--except _msvc9_suppress_errors:
-- pass
--
--
--def msvc9_find_vcvarsall(version):
-- """
-- Patched "distutils.msvc9compiler.find_vcvarsall" to use the standalone
-- compiler build for Python
-- (VCForPython / Microsoft Visual C++ Compiler for Python 2.7).
--
-- Fall back to original behavior when the standalone compiler is not
-- available.
--
-- Redirect the path of "vcvarsall.bat".
--
-- Parameters
-- ----------
-- version: float
-- Required Microsoft Visual C++ version.
--
-- Return
-- ------
-- str
-- vcvarsall.bat path
-- """
-- vc_base = r'Software\%sMicrosoft\DevDiv\VCForPython\%0.1f'
-- key = vc_base % ('', version)
-- try:
-- # Per-user installs register the compiler path here
-- productdir = Reg.get_value(key, "installdir")
-- except KeyError:
-- try:
-- # All-user installs on a 64-bit system register here
-- key = vc_base % ('Wow6432Node\\', version)
-- productdir = Reg.get_value(key, "installdir")
-- except KeyError:
-- productdir = None
--
-- if productdir:
-- vcvarsall = join(productdir, "vcvarsall.bat")
-- if isfile(vcvarsall):
-- return vcvarsall
--
-- return get_unpatched(msvc9_find_vcvarsall)(version)
--
--
--def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs):
-- """
-- Patched "distutils.msvc9compiler.query_vcvarsall" for support extra
-- Microsoft Visual C++ 9.0 and 10.0 compilers.
--
-- Set environment without use of "vcvarsall.bat".
--
-- Parameters
-- ----------
-- ver: float
-- Required Microsoft Visual C++ version.
-- arch: str
-- Target architecture.
--
-- Return
-- ------
-- dict
-- environment
-- """
-- # Try to get environment from vcvarsall.bat (Classical way)
-- try:
-- orig = get_unpatched(msvc9_query_vcvarsall)
-- return orig(ver, arch, *args, **kwargs)
-- except distutils.errors.DistutilsPlatformError:
-- # Pass error if Vcvarsall.bat is missing
-- pass
-- except ValueError:
-- # Pass error if environment not set after executing vcvarsall.bat
-- pass
--
-- # If error, try to set environment directly
-- try:
-- return EnvironmentInfo(arch, ver).return_env()
-- except distutils.errors.DistutilsPlatformError as exc:
-- _augment_exception(exc, ver, arch)
-- raise
--
-
- def _msvc14_find_vc2015():
- """Python 3.8 "distutils/_msvccompiler.py" backport"""
- try:
- key = winreg.OpenKey(
- winreg.HKEY_LOCAL_MACHINE,
- r"Software\Microsoft\VisualStudio\SxS\VC7",
- 0,
-@@ -182,33 +79,38 @@ def _msvc14_find_vc2017():
-
- 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",
-- "-requires",
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
-- "-property", "installationPath",
-- "-products", "*",
-- ]).decode(encoding="mbcs", errors="strict").strip()
-- except (subprocess.CalledProcessError, OSError, UnicodeDecodeError):
-- return None, None
-+ suitable_components = (
-+ "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
-+ "Microsoft.VisualStudio.Workload.WDExpress",
-+ )
-
-- path = join(path, "VC", "Auxiliary", "Build")
-- if isdir(path):
-- return 15, path
-+ for component in suitable_components:
-+ # Workaround for `-requiresAny` (only available on VS 2017 > 15.6)
-+ with contextlib.suppress(CalledProcessError, OSError,
UnicodeDecodeError):
-+ path = subprocess.check_output([
-+ join(root, "Microsoft Visual Studio", "Installer",
"vswhere.exe"),
-+ "-latest",
-+ "-prerelease",
-+ "-requires", component,
-+ "-property", "installationPath",
-+ "-products", "*",
-+ ]).decode(encoding="mbcs", errors="strict").strip()
-
-- return None, None
-+ path = join(path, "VC", "Auxiliary", "Build")
-+ if isdir(path):
-+ return 15, path
-+
-+ return None, None # no suitable component found
-
-
- PLAT_SPEC_TO_RUNTIME = {
- 'x86': 'x86',
- 'x86_amd64': 'x64',
- 'x86_arm': 'arm',
- 'x86_arm64': 'arm64'
- }
-@@ -310,29 +212,16 @@ def msvc14_get_vc_env(plat_spec):
- # 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]
-
-@@ -719,38 +608,33 @@ class SystemInfo:
- 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 in self.ri.HKEYS:
-- for key in 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):
-- try:
-- ver = float(winreg.EnumValue(bkey, i)[0])
-- if ver not in vs_vers:
-- vs_vers.append(ver)
-- except ValueError:
-- pass
-- for i in range(subkeys):
-- try:
-- ver = float(winreg.EnumKey(bkey, i))
-- if ver not in vs_vers:
-- vs_vers.append(ver)
-- except ValueError:
-- pass
-+ 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
-@@ -920,18 +804,18 @@ class SystemInfo:
-
- Return
- ------
- str
- version
- """
- return self._use_last_dir_name(join(self.WindowsSdkDir, 'lib'))
-
-- @property
-- def WindowsSdkDir(self):
-+ @property # noqa: C901
-+ def WindowsSdkDir(self): # noqa: C901 # is too complex (12) # FIXME
- """
- Microsoft Windows SDK directory.
-
- Return
- ------
- str
- path
- """
-@@ -1797,34 +1681,10 @@ class EnvironmentInfo:
- # 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 = self._unique_everseen(extant_paths)
-+ unique_paths = unique_everseen(extant_paths)
- return pathsep.join(unique_paths)
--
-- # from Python docs
-- @staticmethod
-- def _unique_everseen(iterable, key=None):
-- """
-- List unique elements, preserving order.
-- Remember all elements ever seen.
--
-- _unique_everseen('AAAABBBCCDAABBB') --> A B C D
--
-- _unique_everseen('ABBCcAD', str.lower) --> A B C D
-- """
-- seen = set()
-- seen_add = seen.add
-- if key is None:
-- for element in itertools.filterfalse(seen.__contains__,
iterable):
-- seen_add(element)
-- yield element
-- else:
-- for element in iterable:
-- k = key(element)
-- if k not in seen:
-- seen_add(k)
-- yield element
-diff --git a/third_party/python/setuptools/setuptools/package_index.py
b/third_party/python/setuptools/setuptools/package_index.py
---- a/third_party/python/setuptools/setuptools/package_index.py
-+++ b/third_party/python/setuptools/setuptools/package_index.py
-@@ -1,58 +1,73 @@
--"""PyPI and direct package downloading"""
-+"""PyPI and direct package downloading."""
-+
- import sys
- 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,
-+ 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 setuptools import ssl_support
- 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'<a href="([^"#]+)">([^<]+)</a>\n\s+\(<a (?:title="MD5 hash"\n\s+)'
- r'href="[^?]+\?:action=show_md5&amp;digest=([0-9a-f]{32})">md5</a>\)'
- )
- 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',
-+ '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)
-+ 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,)
-@@ -114,114 +129,98 @@ def distros_for_location(location, basen
- 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,
-- )]
-+ 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)]
-+ 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
-+ location, basename, metadata, py_version=None, precedence=SOURCE_DIST,
platform=None
- ):
-- """Generate alternative interpretations of a source distro name
-+ """Generate the interpretation 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 interepretation (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
-- )
--
-+ # find the pivot (p) that splits the name from the version.
-+ # infer the version as the first item that has a digit.
-+ for p in range(len(parts)):
-+ if parts[p][:1].isdigit():
-+ break
-+ else:
-+ p = len(parts)
-
--# From Python 2.7 docs
--def unique_everseen(iterable, key=None):
-- "List unique elements, preserving order. Remember all elements ever
seen."
-- # unique_everseen('AAAABBBCCDAABBB') --> A B C D
-- # unique_everseen('ABBCcAD', str.lower) --> A B C D
-- seen = set()
-- seen_add = seen.add
-- if key is None:
-- for element in itertools.filterfalse(seen.__contains__, iterable):
-- seen_add(element)
-- yield element
-- else:
-- for element in iterable:
-- k = key(element)
-- if k not in seen:
-- seen_add(k)
-- yield element
-+ 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*=\s*['"]?([^'">]+)[^>]*)>""", re.I)
--# this line is here to fix emacs' cruddy broken syntax highlighting
-+REL = re.compile(r"""<([^>]*\srel\s{0,10}=\s{0,10}['"]?([^'"
>]+)[^>]*)>""", re.I)
-+"""
-+Regex for an HTML tag with 'rel="val"' attributes.
-+"""
-
-
- @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()
-@@ -295,37 +294,43 @@ class HashChecker(ContentChecker):
- 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
-+ 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('/')]
-+ super().__init__(*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 = []
-- use_ssl = (
-- verify_ssl
-- and ssl_support.is_available
-- and (ca_bundle or ssl_support.find_ca_bundle())
-- )
-- if use_ssl:
-- self.opener = ssl_support.opener_for(ca_bundle)
-- else:
-- self.opener = urllib.request.urlopen
-+ self.opener = urllib.request.urlopen
-
-- def process_url(self, url, retrieve=False):
-+ 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:
-@@ -391,17 +396,19 @@ class PackageIndex(Environment):
-
- 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")
-+ "https://setuptools.pypa.io/en/latest/deprecated/";
-+ "easy_install.html#restricting-downloads-with-allow-hosts 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 = (
-@@ -423,72 +430,73 @@ class PackageIndex(Environment):
-
- 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"""
-
-- def scan(link):
-- # Process a URL to see if it's for a package page
-- if link.startswith(self.index_url):
-- parts = list(map(
-- urllib.parse.unquote,
link[len(self.index_url):].split('/')
-- ))
-- if len(parts) == 2 and '#' not in parts[1]:
-- # 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)
-- return None, None
--
- # process an index page into the package-page index
- for match in HREF.finditer(page):
- try:
-- scan(urllib.parse.urljoin(url, htmldecode(match.group(1))))
-+ self._scan(urllib.parse.urljoin(url,
htmldecode(match.group(1))))
- except ValueError:
- pass
-
-- pkg, ver = scan(url) # ensure this page is in the page index
-- if pkg:
-- # 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)
-+ pkg, ver = self._scan(url) # ensure this page is in the page index
-+ if not pkg:
-+ return "" # no sense double-scanning non-package pages
-
-- return PYPI_MD5.sub(
-- lambda m: '<a href="%s#md5=%s">%s</a>' % m.group(1, 3, 2),
page
-- )
-- else:
-- 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: '<a href="%s#md5=%s">%s</a>' % 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
-+ "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.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 + '/')
-@@ -509,19 +517,17 @@ class PackageIndex(Environment):
- 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)
-+ 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))
- )
-@@ -548,17 +554,18 @@ class PackageIndex(Environment):
- 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?)")
-+ "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
-@@ -586,19 +593,25 @@ class PackageIndex(Environment):
- 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(
-- self, requirement, tmpdir, force_scan=False, source=False,
-- develop_ok=False, local_index=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
-@@ -620,25 +633,23 @@ class PackageIndex(Environment):
- 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,
-+ "Skipping development or system egg: %s",
-+ dist,
- )
- skipped[dist] = 1
- continue
-
-- test = (
-- dist in req
-- and (dist.precedence <= SOURCE_DIST or not source)
-- )
-+ 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()
-@@ -677,39 +688,44 @@ class PackageIndex(Environment):
- """
- 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 []
-+ 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):
-+ if not (os.path.exists(dst) and os.path.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]
-+ 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 "
-@@ -757,50 +773,50 @@ class PackageIndex(Environment):
- return headers
- finally:
- if fp:
- fp.close()
-
- def reporthook(self, url, filename, blocknum, blksize, size):
- pass # no-op
-
-- def open_url(self, url, warning=None):
-+ # 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
-+ 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)
-+ '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
-+ raise DistutilsError("Download error for %s: %s" % (url,
v)) from v
-
- def _download_url(self, scheme, url, tmpdir):
- # Determine download filename
- #
- name, fragment = egg_info_for_url(url)
- if name:
- while '..' in name:
- name = name.replace('..', '.').replace('\\', '_')
-@@ -827,56 +843,26 @@ class PackageIndex(Environment):
- 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)
-+ return self._invalid_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'<title>([^- ]+ - )?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()
-+ def _invalid_download_html(self, url, headers, filename):
- os.unlink(filename)
-- raise DistutilsError("Unexpected HTML page found at " + url)
-+ raise DistutilsError(f"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 = " --username=%s --password=%s" % (user, pw)
-- else:
-- creds = " --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)
-- os.system("svn checkout%s -q %s %s" % (creds, url, filename))
-- return filename
-+ def _download_svn(self, url, _filename):
-+ raise DistutilsError(f"Invalid config, SVN download is not
supported: {url}")
-
- @staticmethod
- def _vcs_split_rev_from_url(url, pop_prefix=False):
- scheme, netloc, path, query, frag = urllib.parse.urlsplit(url)
-
- scheme = scheme.split('+', 1)[-1]
-
- # Some fragment identification fails
-@@ -895,36 +881,42 @@ class PackageIndex(Environment):
- filename = filename.split('#', 1)[0]
- url, rev = self._vcs_split_rev_from_url(url, pop_prefix=True)
-
- self.info("Doing git clone from %s to %s", url, filename)
- os.system("git clone --quiet %s %s" % (url, filename))
-
- if rev is not None:
- self.info("Checking out %s", rev)
-- os.system("git -C %s checkout --quiet %s" % (
-- filename,
-- rev,
-- ))
-+ os.system(
-+ "git -C %s checkout --quiet %s"
-+ % (
-+ filename,
-+ rev,
-+ )
-+ )
-
- return filename
-
- def _download_hg(self, url, filename):
- filename = filename.split('#', 1)[0]
- url, rev = self._vcs_split_rev_from_url(url, pop_prefix=True)
-
- self.info("Doing hg clone from %s to %s", url, filename)
- os.system("hg clone --quiet %s %s" % (url, filename))
-
- if rev is not None:
- self.info("Updating to %s", rev)
-- os.system("hg --cwd %s up -C -r %s -q" % (
-- filename,
-- rev,
-- ))
-+ os.system(
-+ "hg --cwd %s up -C -r %s -q"
-+ % (
-+ filename,
-+ rev,
-+ )
-+ )
-
- return filename
-
- def debug(self, msg, *args):
- log.debug(msg, *args)
-
- def info(self, msg, *args):
- log.info(msg, *args)
-@@ -1009,26 +1001,27 @@ class Credential:
-
-
- class PyPIConfig(configparser.RawConfigParser):
- def __init__(self):
- """
- Load from ~/.pypirc
- """
- defaults = dict.fromkeys(['username', 'password', 'repository'], '')
-- configparser.RawConfigParser.__init__(self, defaults)
-+ super().__init__(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()
-+ 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(
-@@ -1122,18 +1115,18 @@ def local_open(url):
- 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}</title>"
-- "</head><body>{files}</body></html>")
-+ "<html><head><title>{url}</title>"
"</head><body>{files}</body></html>"
-+ )
- 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/third_party/python/setuptools/setuptools/py312compat.py
b/third_party/python/setuptools/setuptools/py312compat.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/py312compat.py
-@@ -0,0 +1,12 @@
-+import sys
-+import shutil
-+
-+
-+def shutil_rmtree(path, ignore_errors=False, onexc=None):
-+ if sys.version_info >= (3, 12):
-+ return shutil.rmtree(path, ignore_errors, onexc=onexc)
-+
-+ def _handler(fn, path, excinfo):
-+ return onexc(fn, path, excinfo[1])
-+
-+ return shutil.rmtree(path, ignore_errors, onerror=_handler)
-diff --git a/third_party/python/setuptools/setuptools/py34compat.py
b/third_party/python/setuptools/setuptools/py34compat.py
-deleted file mode 100644
---- a/third_party/python/setuptools/setuptools/py34compat.py
-+++ /dev/null
-@@ -1,13 +0,0 @@
--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/third_party/python/setuptools/setuptools/sandbox.py
b/third_party/python/setuptools/setuptools/sandbox.py
---- a/third_party/python/setuptools/setuptools/sandbox.py
-+++ b/third_party/python/setuptools/setuptools/sandbox.py
-@@ -21,17 +21,20 @@ else:
- try:
- _file = file
- except NameError:
- _file = None
- _open = open
-
-
- __all__ = [
-- "AbstractSandbox", "DirectorySandbox", "SandboxViolation", "run_setup",
-+ "AbstractSandbox",
-+ "DirectorySandbox",
-+ "SandboxViolation",
-+ "run_setup",
- ]
-
-
- def _execfile(filename, globals, locals=None):
- """
- Python 3 implementation of execfile.
- """
- mode = 'rb'
-@@ -101,16 +104,17 @@ class UnpickleableException(Exception):
- 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.
- """
-@@ -149,17 +153,18 @@ def save_modules():
- """
- 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
-+ 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()
-
-@@ -227,17 +232,17 @@ 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()
-+ _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))
-@@ -260,17 +265,18 @@ def run_setup(setup_script, args):
-
- 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)
-+ 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):
-@@ -315,19 +321,35 @@ class AbstractSandbox:
- 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"
-+ "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):
-@@ -368,51 +390,61 @@ class AbstractSandbox:
- 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)
-+ 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",
-- ])
-+ write_ops = dict.fromkeys(
-+ [
-+ "open",
-+ "chmod",
-+ "chown",
-+ "mkdir",
-+ "remove",
-+ "unlink",
-+ "rmdir",
-+ "utime",
-+ "lchown",
-+ "chroot",
-+ "mkfifo",
-+ "mknod",
-+ "tempnam",
-+ ]
-+ )
-
-- _exception_patterns = [
-- # Allow lib2to3 to attempt to save a pickled grammar object (#121)
-- r'.*lib2to3.*\.pickle$',
-- ]
-+ _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
-+ 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)
-@@ -435,22 +467,20 @@ class DirectorySandbox(AbstractSandbox):
- 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
-+ filepath.startswith(exception) for exception in self._exceptions
- )
- pattern_matches = (
-- re.match(pattern, filepath)
-- for pattern in self._exception_patterns
-+ 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)
-@@ -465,32 +495,36 @@ class DirectorySandbox(AbstractSandbox):
- 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()]
-+ 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("""
-+ 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()
-+ """
-+ ).lstrip()
-
- def __str__(self):
- cmd, args, kwargs = self.args
- return self.tmpl.format(**locals())
-diff --git a/third_party/python/setuptools/setuptools/ssl_support.py
b/third_party/python/setuptools/setuptools/ssl_support.py
-deleted file mode 100644
---- a/third_party/python/setuptools/setuptools/ssl_support.py
-+++ /dev/null
-@@ -1,266 +0,0 @@
--import os
--import socket
--import atexit
--import re
--import functools
--import urllib.request
--import http.client
--
--
--from pkg_resources import ResolutionError, ExtractionError
--
--try:
-- import ssl
--except ImportError:
-- ssl = None
--
--__all__ = [
-- 'VerifyingHTTPSHandler', 'find_ca_bundle', 'is_available', 'cert_paths',
-- 'opener_for'
--]
--
--cert_paths = """
--/etc/pki/tls/certs/ca-bundle.crt
--/etc/ssl/certs/ca-certificates.crt
--/usr/share/ssl/certs/ca-bundle.crt
--/usr/local/share/certs/ca-root.crt
--/etc/ssl/cert.pem
--/System/Library/OpenSSL/certs/cert.pem
--/usr/local/share/certs/ca-root-nss.crt
--/etc/ssl/ca-bundle.pem
--""".strip().split()
--
--try:
-- HTTPSHandler = urllib.request.HTTPSHandler
-- HTTPSConnection = http.client.HTTPSConnection
--except AttributeError:
-- HTTPSHandler = HTTPSConnection = object
--
--is_available = ssl is not None and object not in (
-- HTTPSHandler, HTTPSConnection)
--
--
--try:
-- from ssl import CertificateError, match_hostname
--except ImportError:
-- try:
-- from backports.ssl_match_hostname import CertificateError
-- from backports.ssl_match_hostname import match_hostname
-- except ImportError:
-- CertificateError = None
-- match_hostname = None
--
--if not CertificateError:
--
-- class CertificateError(ValueError):
-- pass
--
--
--if not match_hostname:
--
-- def _dnsname_match(dn, hostname, max_wildcards=1):
-- """Matching according to RFC 6125, section 6.4.3
--
-- https://tools.ietf.org/html/rfc6125#section-6.4.3
-- """
-- pats = []
-- if not dn:
-- return False
--
-- # Ported from python3-syntax:
-- # leftmost, *remainder = dn.split(r'.')
-- parts = dn.split(r'.')
-- leftmost = parts[0]
-- remainder = parts[1:]
--
-- wildcards = leftmost.count('*')
-- if wildcards > max_wildcards:
-- # Issue #17980: avoid denials of service by refusing more
-- # than one wildcard per fragment. A survey of established
-- # policy among SSL implementations showed it to be a
-- # reasonable choice.
-- raise CertificateError(
-- "too many wildcards in certificate DNS name: " + repr(dn))
--
-- # speed up common case w/o wildcards
-- if not wildcards:
-- return dn.lower() == hostname.lower()
--
-- # RFC 6125, section 6.4.3, subitem 1.
-- # The client SHOULD NOT attempt to match a
-- # presented identifier in which the wildcard
-- # character comprises a label other than the
-- # left-most label.
-- if leftmost == '*':
-- # When '*' is a fragment by itself, it matches a non-empty
dotless
-- # fragment.
-- pats.append('[^.]+')
-- elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
-- # RFC 6125, section 6.4.3, subitem 3.
-- # The client SHOULD NOT attempt to match a presented identifier
-- # where the wildcard character is embedded within an A-label or
-- # U-label of an internationalized domain name.
-- pats.append(re.escape(leftmost))
-- else:
-- # Otherwise, '*' matches any dotless string, e.g. www*
-- pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
--
-- # add the remaining fragments, ignore any wildcards
-- for frag in remainder:
-- pats.append(re.escape(frag))
--
-- pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
-- return pat.match(hostname)
--
-- def match_hostname(cert, hostname):
-- """Verify that *cert* (in decoded format as returned by
-- SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC
6125
-- rules are followed, but IP addresses are not accepted for
*hostname*.
--
-- CertificateError is raised on failure. On success, the function
-- returns nothing.
-- """
-- if not cert:
-- raise ValueError("empty or no certificate")
-- dnsnames = []
-- san = cert.get('subjectAltName', ())
-- for key, value in san:
-- if key == 'DNS':
-- if _dnsname_match(value, hostname):
-- return
-- dnsnames.append(value)
-- if not dnsnames:
-- # The subject is only checked when there is no dNSName entry
-- # in subjectAltName
-- for sub in cert.get('subject', ()):
-- for key, value in sub:
-- # XXX according to RFC 2818, the most specific Common
Name
-- # must be used.
-- if key == 'commonName':
-- if _dnsname_match(value, hostname):
-- return
-- dnsnames.append(value)
-- if len(dnsnames) > 1:
-- raise CertificateError(
-- "hostname %r doesn't match either of %s"
-- % (hostname, ', '.join(map(repr, dnsnames))))
-- elif len(dnsnames) == 1:
-- raise CertificateError(
-- "hostname %r doesn't match %r"
-- % (hostname, dnsnames[0]))
-- else:
-- raise CertificateError(
-- "no appropriate commonName or "
-- "subjectAltName fields were found")
--
--
--class VerifyingHTTPSHandler(HTTPSHandler):
-- """Simple verifying handler: no auth, subclasses, timeouts, etc."""
--
-- def __init__(self, ca_bundle):
-- self.ca_bundle = ca_bundle
-- HTTPSHandler.__init__(self)
--
-- def https_open(self, req):
-- return self.do_open(
-- lambda host, **kw: VerifyingHTTPSConn(host, self.ca_bundle,
**kw),
-- req
-- )
--
--
--class VerifyingHTTPSConn(HTTPSConnection):
-- """Simple verifying connection: no auth, subclasses, timeouts, etc."""
--
-- def __init__(self, host, ca_bundle, **kw):
-- HTTPSConnection.__init__(self, host, **kw)
-- self.ca_bundle = ca_bundle
--
-- def connect(self):
-- sock = socket.create_connection(
-- (self.host, self.port), getattr(self, 'source_address', None)
-- )
--
-- # Handle the socket if a (proxy) tunnel is present
-- if hasattr(self, '_tunnel') and getattr(self, '_tunnel_host', None):
-- self.sock = sock
-- self._tunnel()
-- # http://bugs.python.org/issue7776: Python>=3.4.1 and >=2.7.7
-- # change self.host to mean the proxy server host when tunneling
is
-- # being used. Adapt, since we are interested in the destination
-- # host for the match_hostname() comparison.
-- actual_host = self._tunnel_host
-- else:
-- actual_host = self.host
--
-- if hasattr(ssl, 'create_default_context'):
-- ctx = ssl.create_default_context(cafile=self.ca_bundle)
-- self.sock = ctx.wrap_socket(sock, server_hostname=actual_host)
-- else:
-- # This is for python < 2.7.9 and < 3.4?
-- self.sock = ssl.wrap_socket(
-- sock, cert_reqs=ssl.CERT_REQUIRED, ca_certs=self.ca_bundle
-- )
-- try:
-- match_hostname(self.sock.getpeercert(), actual_host)
-- except CertificateError:
-- self.sock.shutdown(socket.SHUT_RDWR)
-- self.sock.close()
-- raise
--
--
--def opener_for(ca_bundle=None):
-- """Get a urlopen() replacement that uses ca_bundle for verification"""
-- return urllib.request.build_opener(
-- VerifyingHTTPSHandler(ca_bundle or find_ca_bundle())
-- ).open
--
--
--# from jaraco.functools
--def once(func):
-- @functools.wraps(func)
-- def wrapper(*args, **kwargs):
-- if not hasattr(func, 'always_returns'):
-- func.always_returns = func(*args, **kwargs)
-- return func.always_returns
-- return wrapper
--
--
--@once
--def get_win_certfile():
-- try:
-- import wincertstore
-- except ImportError:
-- return None
--
-- class CertFile(wincertstore.CertFile):
-- def __init__(self):
-- super(CertFile, self).__init__()
-- atexit.register(self.close)
--
-- def close(self):
-- try:
-- super(CertFile, self).close()
-- except OSError:
-- pass
--
-- _wincerts = CertFile()
-- _wincerts.addstore('CA')
-- _wincerts.addstore('ROOT')
-- return _wincerts.name
--
--
--def find_ca_bundle():
-- """Return an existing CA bundle path, or None"""
-- extant_cert_paths = filter(os.path.isfile, cert_paths)
-- return (
-- get_win_certfile()
-- or next(extant_cert_paths, None)
-- or _certifi_where()
-- )
--
--
--def _certifi_where():
-- try:
-- return __import__('certifi').where()
-- except (ImportError, ResolutionError, ExtractionError):
-- pass
-diff --git a/third_party/python/setuptools/setuptools/version.py
b/third_party/python/setuptools/setuptools/version.py
---- a/third_party/python/setuptools/setuptools/version.py
-+++ b/third_party/python/setuptools/setuptools/version.py
-@@ -1,6 +1,6 @@
--import pkg_resources
-+from ._importlib import metadata
-
- try:
-- __version__ = pkg_resources.get_distribution('setuptools').version
-+ __version__ = metadata.version('setuptools') or '0.dev0+unknown'
- except Exception:
-- __version__ = 'unknown'
-+ __version__ = '0.dev0+unknown'
-diff --git a/third_party/python/setuptools/setuptools/warnings.py
b/third_party/python/setuptools/setuptools/warnings.py
-new file mode 100644
---- /dev/null
-+++ b/third_party/python/setuptools/setuptools/warnings.py
-@@ -0,0 +1,104 @@
-+"""Provide basic warnings used by setuptools modules.
-+
-+Using custom classes (other than ``UserWarning``) allow users to set
-+``PYTHONWARNINGS`` filters to run tests and prepare for upcoming changes in
-+setuptools.
-+"""
-+
-+import os
-+import warnings
-+from datetime import date
-+from inspect import cleandoc
-+from textwrap import indent
-+from typing import Optional, Tuple
-+
-+_DueDate = Tuple[int, int, int] # time tuple
-+_INDENT = 8 * " "
-+_TEMPLATE = f"""{80 * '*'}\n{{details}}\n{80 * '*'}"""
-+
-+
-+class SetuptoolsWarning(UserWarning):
-+ """Base class in ``setuptools`` warning hierarchy."""
-+
-+ @classmethod
-+ def emit(
-+ cls,
-+ summary: Optional[str] = None,
-+ details: Optional[str] = None,
-+ due_date: Optional[_DueDate] = None,
-+ see_docs: Optional[str] = None,
-+ see_url: Optional[str] = None,
-+ stacklevel: int = 2,
-+ **kwargs
-+ ):
-+ """Private: reserved for ``setuptools`` internal use only"""
-+ # Default values:
-+ summary_ = summary or getattr(cls, "_SUMMARY", None) or ""
-+ details_ = details or getattr(cls, "_DETAILS", None) or ""
-+ due_date = due_date or getattr(cls, "_DUE_DATE", None)
-+ docs_ref = see_docs or getattr(cls, "_SEE_DOCS", None)
-+ docs_url = docs_ref and
f"https://setuptools.pypa.io/en/latest/{docs_ref}";
-+ see_url = see_url or getattr(cls, "_SEE_URL", None)
-+ due = date(*due_date) if due_date else None
-+
-+ text = cls._format(summary_, details_, due, see_url or docs_url,
kwargs)
-+ if due and due < date.today() and _should_enforce():
-+ raise cls(text)
-+ warnings.warn(text, cls, stacklevel=stacklevel + 1)
-+
-+ @classmethod
-+ def _format(
-+ cls,
-+ summary: str,
-+ details: str,
-+ due_date: Optional[date] = None,
-+ see_url: Optional[str] = None,
-+ format_args: Optional[dict] = None,
-+ ):
-+ """Private: reserved for ``setuptools`` internal use only"""
-+ today = date.today()
-+ summary = cleandoc(summary).format_map(format_args or {})
-+ possible_parts = [
-+ cleandoc(details).format_map(format_args or {}),
-+ (
-+ f"\nBy {due_date:%Y-%b-%d}, you need to update your project
and remove "
-+ "deprecated calls\nor your builds will no longer be
supported."
-+ if due_date and due_date > today else None
-+ ),
-+ (
-+ "\nThis deprecation is overdue, please update your project
and remove "
-+ "deprecated\ncalls to avoid build errors in the future."
-+ if due_date and due_date < today else None
-+ ),
-+ (f"\nSee {see_url} for details." if see_url else None)
-+
-+ ]
-+ parts = [x for x in possible_parts if x]
-+ if parts:
-+ body = indent(_TEMPLATE.format(details="\n".join(parts)),
_INDENT)
-+ return "\n".join([summary, "!!\n", body, "\n!!"])
-+ return summary
-+
-+
-+class InformationOnly(SetuptoolsWarning):
-+ """Currently there is no clear way of displaying messages to the users
-+ that use the setuptools backend directly via ``pip``.
-+ The only thing that might work is a warning, although it is not the
-+ most appropriate tool for the job...
-+
-+ See pypa/packaging-problems#558.
-+ """
-+
-+
-+class SetuptoolsDeprecationWarning(SetuptoolsWarning):
-+ """
-+ Base class for warning deprecations in ``setuptools``
-+
-+ This class is not derived from ``DeprecationWarning``, and as such is
-+ visible by default.
-+ """
-+
-+
-+def _should_enforce():
-+ enforce = os.getenv("SETUPTOOLS_ENFORCE_DEPRECATION", "false").lower()
-+ return enforce in ("true", "on", "ok", "1")
-diff --git a/third_party/python/setuptools/setuptools/wheel.py
b/third_party/python/setuptools/setuptools/wheel.py
---- a/third_party/python/setuptools/setuptools/wheel.py
-+++ b/third_party/python/setuptools/setuptools/wheel.py
-@@ -1,37 +1,47 @@
- """Wheels support."""
-
--from distutils.util import get_platform
--from distutils import log
- import email
- import itertools
-+import functools
- import os
- import posixpath
- import re
- import zipfile
-+import contextlib
-
--import pkg_resources
-+from distutils.util import get_platform
-+
- import setuptools
--from pkg_resources import parse_version
-+from setuptools.extern.packaging.version import Version as 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
-+from setuptools.command.egg_info import write_requirements, _egg_basename
-+from setuptools.archive_util import _unpack_zipfile_obj
-
-
- WHEEL_NAME = re.compile(
- r"""^(?P<project_name>.+?)-(?P<version>\d.*?)
- ((-(?P<build>\d.*?))?-(?P<py_version>.+?)-(?P<abi>.+?)-(?P<platform>.+?)
- )\.whl$""",
- re.VERBOSE).match
-
- NAMESPACE_PACKAGE_INIT = \
- "__import__('pkg_resources').declare_namespace(__name__)\n"
-
-
-+@functools.lru_cache(maxsize=None)
-+def _get_supported_tags():
-+ # We calculate the supported tags only once, otherwise calling
-+ # this method on thousands of wheels takes seconds instead of
-+ # milliseconds.
-+ return {(t.interpreter, t.abi, t.platform) for t in sys_tags()}
-+
-+
- 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)
-@@ -44,16 +54,29 @@ def unpack(src_dir, dst_dir):
- 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)
-
-
-+@contextlib.contextmanager
-+def disable_info_traces():
-+ """
-+ Temporarily disable info traces.
-+ """
-+ from distutils import log
-+ saved = log.set_threshold(log.WARN)
-+ try:
-+ yield
-+ finally:
-+ log.set_threshold(saved)
-+
-+
- 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():
-@@ -63,26 +86,25 @@ class Wheel:
- '''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)
-+ '''Is the wheel compatible with the current platform?'''
-+ return next((True for t in self.tags() if t in
_get_supported_tags()), False)
-
- def egg_name(self):
-- return pkg_resources.Distribution(
-- project_name=self.project_name, version=self.version,
-+ return _egg_basename(
-+ self.project_name,
-+ self.version,
- platform=(None if self.platform == 'any' else get_platform()),
-- ).egg_name() + '.egg'
-+ ) + ".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))):
-@@ -101,77 +123,73 @@ class Wheel:
- 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):
-+ import pkg_resources
-+
- 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)
-+ _unpack_zipfile_obj(zf, 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())))
-+ install_requires = list(map(raw_req, dist.requires()))
- extras_require = {
-- extra: sorted(
-+ extra: [
- 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:
-+ with disable_info_traces():
- 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(
-diff --git a/third_party/python/setuptools/setuptools/windows_support.py
b/third_party/python/setuptools/setuptools/windows_support.py
---- a/third_party/python/setuptools/setuptools/windows_support.py
-+++ b/third_party/python/setuptools/setuptools/windows_support.py
-@@ -1,10 +1,9 @@
- import platform
--import ctypes
-
-
- def windows_only(func):
- if platform.system() != 'Windows':
- return lambda *args, **kwargs: None
- return func
-
-
-@@ -12,16 +11,17 @@ def windows_only(func):
- 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
- __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)
-
diff --git a/http/firefox/patches-for-python-3.12/0003-no-distutils.patch
b/http/firefox/patches-for-python-3.12/0003-no-distutils.patch
deleted file mode 100644
index f0120bf..0000000
--- a/http/firefox/patches-for-python-3.12/0003-no-distutils.patch
+++ /dev/null
@@ -1,362 +0,0 @@
-
-# HG changeset patch
-# User serge-sans-paille <sguelton AT mozilla.com>
-# Date 1697615883 0
-# Node ID ee3b3779af7fc81a53859fa92856ef9deae17a75
-# Parent 9e4f4dfc09522e65b6dfe0113cac8c8fba516089
-Bug 1858065 - Replace distutils' StrictVersion
r=saschanaz,perftest-reviewers,sparky
-
-distutils have been removed from Python 3.12, so replace it:
-
-Sometimes using packaging's Version, sometimes providing our own in the
-case of mozrelease/versions.py. Add more tests for the latter.
-
-Differential Revision: https://phabricator.services.mozilla.com/D190540
-
-diff --git a/python/mozbuild/mozbuild/nodeutil.py
b/python/mozbuild/mozbuild/nodeutil.py
---- a/python/mozbuild/mozbuild/nodeutil.py
-+++ b/python/mozbuild/mozbuild/nodeutil.py
-@@ -1,23 +1,23 @@
- # This Source Code Form is subject to the terms of the Mozilla Public
- # License, v. 2.0. If a copy of the MPL was not distributed with this
- # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
- import os
- import platform
- import subprocess
--from distutils.version import StrictVersion
-
- from mozboot.util import get_tools_dir
- from mozfile import which
-+from packaging.version import Version
- from six import PY3
-
--NODE_MIN_VERSION = StrictVersion("12.22.12")
--NPM_MIN_VERSION = StrictVersion("6.14.16")
-+NODE_MIN_VERSION = Version("12.22.12")
-+NPM_MIN_VERSION = Version("6.14.16")
-
-
- def find_node_paths():
- """Determines the possible paths for node executables.
-
- Returns a list of paths, which includes the build state directory.
- """
- mozbuild_tools_dir = get_tools_dir()
-@@ -63,17 +63,17 @@ def check_executable_version(exe, wrap_c
- # If we can't find node, or we don't need to wrap it, fallback to
calling
- # direct.
- if not out:
- out = (
- subprocess.check_output([exe, "--version"],
universal_newlines=PY3)
- .lstrip("v")
- .rstrip()
- )
-- return StrictVersion(out)
-+ return Version(out)
-
-
- def find_node_executable(
- nodejs_exe=os.environ.get("NODEJS"), min_version=NODE_MIN_VERSION
- ):
- """Find a Node executable from the mozbuild directory.
-
- Returns a tuple containing the the path to an executable binary and a
-@@ -82,17 +82,17 @@ def find_node_executable(
- """
- if nodejs_exe:
- try:
- version = check_executable_version(nodejs_exe)
- except (subprocess.CalledProcessError, ValueError):
- return None, None
-
- if version >= min_version:
-- return nodejs_exe, version.version
-+ return nodejs_exe, version.release
-
- return None, None
-
- # "nodejs" is first in the tuple on the assumption that it's only
likely to
- # exist on systems (probably linux distros) where there is a program in
the path
- # called "node" that does something else.
- return find_executable("node", min_version)
-
-@@ -118,9 +118,9 @@ def find_executable(name, min_version, u
- try:
- version = check_executable_version(exe, use_node_for_version_check)
- except (subprocess.CalledProcessError, ValueError):
- return None, None
-
- if version < min_version:
- return None, None
-
-- return exe, version.version
-+ return exe, version.release
-diff --git a/python/mozrelease/mozrelease/versions.py
b/python/mozrelease/mozrelease/versions.py
---- a/python/mozrelease/mozrelease/versions.py
-+++ b/python/mozrelease/mozrelease/versions.py
-@@ -1,16 +1,66 @@
- # This Source Code Form is subject to the terms of the Mozilla Public
- # License, v. 2.0. If a copy of the MPL was not distributed with this
- # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
- import re
--from distutils.version import StrictVersion
-
- from looseversion import LooseVersion
-+from packaging.version import InvalidVersion
-+
-+
-+class StrictVersion:
-+ def __init__(self, vstring):
-+ self.parse(vstring)
-+
-+ def __repr__(self):
-+ return "%s ('%s')" % (self.__class__.__name__, str(self))
-+
-+ def __eq__(self, other):
-+ return self._cmp(other) == 0
-+
-+ def __lt__(self, other):
-+ return self._cmp(other) < 0
-+
-+ def parse(self, vstring):
-+ match = self.version_re.match(vstring)
-+ if not match:
-+ raise InvalidVersion("invalid version number '%s'" % vstring)
-+
-+ major, minor, patch, pre, pre_num = match.group(1, 2, 4, 5, 6)
-+ self.version = int(major), int(minor), int(patch or 0)
-+ self.pre = (pre[0], int(pre_num)) if pre else ()
-+
-+ def __str__(self):
-+ return ".".join(map(str, self.version)) + (
-+ "".join(map(str, self.pre)) if self.pre else ""
-+ )
-+
-+ def _cmp(self, other):
-+ if isinstance(other, str):
-+ other = StrictVersion(other)
-+ elif not isinstance(other, StrictVersion):
-+ raise NotImplementedError
-+
-+ if self.version < other.version:
-+ return -1
-+ elif self.version == other.version:
-+ if self.pre == other.pre:
-+ return 0
-+ elif not self.pre:
-+ return 1
-+ elif not other.pre:
-+ return -1
-+ elif self.pre < other.pre:
-+ return -1
-+ else:
-+ return 1
-+ else:
-+ return 1
-
-
- class MozillaVersionCompareMixin:
- def __cmp__(self, other):
- # We expect this function to never be called.
- raise AssertionError()
-
- def _cmp(self, other):
-@@ -88,22 +138,22 @@ class LooseModernMozillaVersion(MozillaV
-
- def __repr__(self):
- return "LooseModernMozillaVersion ('%s')" % str(self)
-
-
- def MozillaVersion(version):
- try:
- return ModernMozillaVersion(version)
-- except ValueError:
-+ except InvalidVersion:
- pass
- try:
- if version.count(".") == 3:
- return AncientMozillaVersion(version)
-- except ValueError:
-+ except InvalidVersion:
- pass
- try:
- return LooseModernMozillaVersion(version)
- except ValueError:
- pass
- raise ValueError("Version number %s is invalid." % version)
-
-
-diff --git a/python/mozrelease/test/test_versions.py
b/python/mozrelease/test/test_versions.py
---- a/python/mozrelease/test/test_versions.py
-+++ b/python/mozrelease/test/test_versions.py
-@@ -1,12 +1,16 @@
- import mozunit
- import pytest
-
--from mozrelease.versions import MozillaVersion
-+from mozrelease.versions import (
-+ AncientMozillaVersion,
-+ ModernMozillaVersion,
-+ MozillaVersion,
-+)
-
- ALL_VERSIONS = [ # Keep this sorted
- "3.0",
- "3.0.1",
- "3.0.2",
- "3.0.3",
- "3.0.4",
- "3.0.5",
-@@ -86,16 +90,31 @@ def test_versions_compare_less(comparabl
-
-
- def test_versions_compare_greater(comparable_versions):
- """Test that versions properly compare in order."""
- smaller_version, larger_version = comparable_versions
- assert MozillaVersion(larger_version) > MozillaVersion(smaller_version)
-
-
-+def test_ModernMozillaVersion():
-+ """Test properties specific to ModernMozillaVersion"""
-+ assert isinstance(MozillaVersion("1.2.4"), ModernMozillaVersion)
-+ assert isinstance(MozillaVersion("1.2.4rc3"), ModernMozillaVersion)
-+ assert MozillaVersion("1.2rc3") == MozillaVersion("1.2.0rc3")
-+
-+
-+def test_AncientMozillaVersion():
-+ """Test properties specific to AncientMozillaVersion"""
-+ assert isinstance(MozillaVersion("1.2.0.4"), AncientMozillaVersion)
-+ assert isinstance(MozillaVersion("1.2.0.4pre1"), AncientMozillaVersion)
-+ assert MozillaVersion("1.2pre1") == MozillaVersion("1.2.0pre1")
-+ assert MozillaVersion("1.2.0.4pre1") == MozillaVersion("1.2.4pre1")
-+
-+
- @pytest.mark.parametrize("version", ALL_VERSIONS)
- def test_versions_compare_equal(version):
- """Test that versions properly compare as equal through multiple
passes."""
- assert MozillaVersion(version) == MozillaVersion(version)
-
-
- if __name__ == "__main__":
- mozunit.main()
-diff --git a/testing/raptor/mach_commands.py
b/testing/raptor/mach_commands.py
---- a/testing/raptor/mach_commands.py
-+++ b/testing/raptor/mach_commands.py
-@@ -258,28 +258,28 @@ class RaptorRunner(MozbuildObject):
- initial_config_file=self.args["initial_config_file"],
- )
- return raptor_mh.run()
-
-
- def setup_node(command_context):
- """Fetch the latest node-16 binary and install it into the .mozbuild
directory."""
- import platform
-- from distutils.version import StrictVersion
-
- from mozbuild.artifact_commands import artifact_toolchain
- from mozbuild.nodeutil import find_node_executable
-+ from packaging.version import Version
-
- print("Setting up node for browsertime...")
- state_dir = get_state_dir()
- cache_path = os.path.join(state_dir, "browsertime", "node-16")
-
- def __check_for_node():
- # Check standard locations first
-- node_exe = find_node_executable(min_version=StrictVersion("16.0.0"))
-+ node_exe = find_node_executable(min_version=Version("16.0.0"))
- if node_exe and (node_exe[0] is not None):
- return node_exe[0]
- if not os.path.exists(cache_path):
- return None
-
- # Check the browsertime-specific node location next
- node_name = "node"
- if platform.system() == "Windows":
-diff --git a/tools/browsertime/mach_commands.py
b/tools/browsertime/mach_commands.py
---- a/tools/browsertime/mach_commands.py
-+++ b/tools/browsertime/mach_commands.py
-@@ -76,33 +76,33 @@ def silence():
- sys.stdout, sys.stderr = StringIO(), StringIO()
- yield
- finally:
- sys.stdout, sys.stderr = oldout, olderr
-
-
- def node_path(command_context):
- import platform
-- from distutils.version import StrictVersion
-
- from mozbuild.nodeutil import find_node_executable
-+ from packaging.version import Version
-
- state_dir = command_context._mach_context.state_dir
- cache_path = os.path.join(state_dir, "browsertime", "node-16")
-
- NODE_FAILURE_MSG = (
- "Could not locate a node binary that is at least version {}.
".format(
- MIN_NODE_VERSION
- )
- + "Please run `./mach raptor --browsertime -t amazon` to install it
"
- + "from the Taskcluster Toolchain artifacts."
- )
-
- # Check standard locations first
-- node_exe =
find_node_executable(min_version=StrictVersion(MIN_NODE_VERSION))
-+ node_exe = find_node_executable(min_version=Version(MIN_NODE_VERSION))
- if node_exe and (node_exe[0] is not None):
- return os.path.abspath(node_exe[0])
- if not os.path.exists(cache_path):
- raise Exception(NODE_FAILURE_MSG)
-
- # Check the browsertime-specific node location next
- node_name = "node"
- if platform.system() == "Windows":
-diff --git a/tools/lint/clippy.yml b/tools/lint/clippy.yml
---- a/tools/lint/clippy.yml
-+++ b/tools/lint/clippy.yml
-@@ -99,12 +99,12 @@ clippy:
- - security/manager/ssl/osclientcerts/
- extensions:
- - rs
- support-files:
- - 'tools/lint/clippy/**'
- # the version of cargo-clippy is:
- # clippy 0.1.65 (2019147 2022-09-19)
- # we use the date instead to facilitate the check
-- # replacing - by . because Python StrictVersion expects this
-+ # replacing - by . because Python packaging.version.Version expects this
- min_clippy_version: 2022.09.19
- type: external
- payload: clippy:lint
-diff --git a/tools/power/mach_commands.py b/tools/power/mach_commands.py
---- a/tools/power/mach_commands.py
-+++ b/tools/power/mach_commands.py
-@@ -1,22 +1,21 @@
- # This Source Code Form is subject to the terms of the Mozilla Public
- # License, v. 2.0. If a copy of the MPL was not distributed with this
- # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
--from distutils.version import StrictVersion
--
- from mach.decorators import Command, CommandArgument
-+from packaging.version import Version
-
-
- def is_osx_10_10_or_greater(cls):
- import platform
-
- release = platform.mac_ver()[0]
-- return release and StrictVersion(release) >= StrictVersion("10.10")
-+ return release and Version(release) >= Version("10.10")
-
-
- # Get system power consumption and related measurements.
- @Command(
- "power",
- category="misc",
- conditions=[is_osx_10_10_or_greater],
- description="Get system power consumption and related measurements for "
-
diff --git
a/http/firefox/patches-for-python-3.12/0004-no-distutils-part-2.patch
b/http/firefox/patches-for-python-3.12/0004-no-distutils-part-2.patch
deleted file mode 100644
index 75190b8..0000000
--- a/http/firefox/patches-for-python-3.12/0004-no-distutils-part-2.patch
+++ /dev/null
@@ -1,128 +0,0 @@
-
-# HG changeset patch
-# User serge-sans-paille <sguelton AT mozilla.com>
-# Date 1699342987 0
-# Node ID 201fc291a2c0f9c8fd86c7bf5647cf7d179ca563
-# Parent b2826966ef8a149fffb1e8ca7ea0bd235975e19b
-Bug 1858062 - Remove reference to distutils in mozboot r=saschanaz
-
-distutils is no longer available in Python 3.12, which means we cannot
-require it on bootstrap, otherwise we cannot bootstrap from Py 3.12.
-
-We already require setuptools as part of mach's requirements, so it's
-fine to depend on setuptools elsewhere.
-
-Differential Revision: https://phabricator.services.mozilla.com/D190537
-
-diff --git a/python/mozboot/mozboot/base.py b/python/mozboot/mozboot/base.py
---- a/python/mozboot/mozboot/base.py
-+++ b/python/mozboot/mozboot/base.py
-@@ -165,23 +165,16 @@ class BaseBootstrapper(object):
-
- def validate_environment(self):
- """
- Called once the current firefox checkout has been detected.
- Platform-specific implementations should check the environment and
offer advice/warnings
- to the user, if necessary.
- """
-
-- def suggest_install_distutils(self):
-- """Called if distutils.{sysconfig,spawn} can't be imported."""
-- print(
-- "Does your distro require installing another package for
distutils?",
-- file=sys.stderr,
-- )
--
- def suggest_install_pip3(self):
- """Called if pip3 can't be found."""
- print(
- "Try installing pip3 with your system's package manager.",
file=sys.stderr
- )
-
- def install_system_packages(self):
- """
-diff --git a/python/mozboot/mozboot/bootstrap.py
b/python/mozboot/mozboot/bootstrap.py
---- a/python/mozboot/mozboot/bootstrap.py
-+++ b/python/mozboot/mozboot/bootstrap.py
-@@ -487,32 +487,16 @@ class Bootstrapper(object):
- # The mozconfig file is created by user.
- self._show_mozconfig_suggestion(raw_mozconfig)
- elif raw_mozconfig:
- # No mozconfig file exists yet
- self._write_default_mozconfig(raw_mozconfig)
-
- def _validate_python_environment(self, topsrcdir):
- valid = True
-- try:
-- # distutils is singled out here because some distros (namely
Ubuntu)
-- # include it in a separate package outside of the main Python
-- # installation.
-- import distutils.spawn
-- import distutils.sysconfig
--
-- assert distutils.sysconfig is not None and distutils.spawn is
not None
-- except ImportError as e:
-- print("ERROR: Could not import package %s" % e.name,
file=sys.stderr)
-- self.instance.suggest_install_distutils()
-- valid = False
-- except AssertionError:
-- print("ERROR: distutils is not behaving as expected.",
file=sys.stderr)
-- self.instance.suggest_install_distutils()
-- valid = False
- pip3 = to_optional_path(which("pip3"))
- if not pip3:
- print("ERROR: Could not find pip3.", file=sys.stderr)
- self.instance.suggest_install_pip3()
- valid = False
- if not valid:
- print(
- "ERROR: Your Python installation will not be able to run "
-diff --git a/python/mozboot/mozboot/debian.py
b/python/mozboot/mozboot/debian.py
---- a/python/mozboot/mozboot/debian.py
-+++ b/python/mozboot/mozboot/debian.py
-@@ -12,23 +12,16 @@ class DebianBootstrapper(LinuxBootstrapp
- def __init__(self, distro, version, dist_id, codename, **kwargs):
- BaseBootstrapper.__init__(self, **kwargs)
-
- self.distro = distro
- self.version = version
- self.dist_id = dist_id
- self.codename = codename
-
-- def suggest_install_distutils(self):
-- print(
-- "HINT: Try installing distutils with "
-- "`apt-get install python3-distutils`.",
-- file=sys.stderr,
-- )
--
- def suggest_install_pip3(self):
- print(
- "HINT: Try installing pip3 with `apt-get install python3-pip`.",
- file=sys.stderr,
- )
-
- def install_packages(self, packages):
- try:
-diff --git a/python/mozboot/setup.py b/python/mozboot/setup.py
---- a/python/mozboot/setup.py
-+++ b/python/mozboot/setup.py
-@@ -1,13 +1,16 @@
- # This Source Code Form is subject to the terms of the Mozilla Public
- # License, v. 2.0. If a copy of the MPL was not distributed with this file,
- # You can obtain one at http://mozilla.org/MPL/2.0/.
-
--from distutils.core import setup
-+try:
-+ from setuptools import setup
-+except ImportError:
-+ from distutils.core import setup
-
- VERSION = "0.1"
-
- setup(
- name="mozboot",
- description="System bootstrap for building Mozilla projects.",
- license="MPL 2.0",
- packages=["mozboot"],
-



  • [SM-Commit] GIT changes to master grimoire by Pavel Vinogradov (f86235cb62ddcae3278cb85234e165b965766e9e), Pavel Vinogradov, 11/21/2023

Archive powered by MHonArc 2.6.24.

Top of Page