diff --git a/poetry.lock b/poetry.lock index fab06e516..8c73be6e0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -15,10 +15,10 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] -docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] +dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "sphinx", "sphinx-notfound-page", "zope.interface"] +docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] +tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "zope.interface"] +tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six"] [[package]] name = "build" @@ -78,7 +78,6 @@ python-versions = ">=3.7" [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [[package]] name = "colorama" @@ -165,9 +164,9 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"] perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] [[package]] name = "importlib-resources" @@ -181,8 +180,8 @@ python-versions = ">=3.7" zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] -docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] +docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"] +testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [[package]] name = "iniconfig" @@ -202,10 +201,8 @@ python-versions = ">=3.7" [package.dependencies] attrs = ">=17.4.0" -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} pyrsistent = ">=0.14.0,<0.17.0 || >0.17.0,<0.17.1 || >0.17.1,<0.17.2 || >0.17.2" -typing-extensions = {version = "*", markers = "python_version < \"3.8\""} [package.extras] format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] @@ -279,8 +276,8 @@ optional = false python-versions = ">=3.7" [package.extras] -docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] -test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] +docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] [[package]] name = "pluggy" @@ -339,7 +336,7 @@ optional = false python-versions = ">=3.6.8" [package.extras] -diagrams = ["railroad-diagrams", "jinja2"] +diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pyrsistent" @@ -384,7 +381,7 @@ coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] -testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] [[package]] name = "pytest-mock" @@ -398,7 +395,7 @@ python-versions = ">=3.7" pytest = ">=5.0" [package.extras] -dev = ["pre-commit", "tox", "pytest-asyncio"] +dev = ["pre-commit", "pytest-asyncio", "tox"] [[package]] name = "pyyaml" @@ -466,6 +463,14 @@ category = "dev" optional = false python-versions = ">=3.7" +[[package]] +name = "tomli-w" +version = "1.0.0" +description = "A lil' TOML writer" +category = "dev" +optional = false +python-versions = ">=3.7" + [[package]] name = "tox" version = "3.25.0" @@ -487,7 +492,7 @@ virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2, [package.extras] docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] -testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "psutil (>=5.6.1)", "pathlib2 (>=2.3.3)"] +testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pathlib2 (>=2.3.3)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)"] [[package]] name = "typed-ast" @@ -530,8 +535,8 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] -brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] @@ -571,7 +576,7 @@ six = ">=1.9.0,<2" [package.extras] docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] -testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] +testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "packaging (>=20.0)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)"] [[package]] name = "zipp" @@ -582,13 +587,13 @@ optional = false python-versions = ">=3.7" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] +docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"] +testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "942983e12963ee3294081a5f38b6a66034dc7cd350b48a65f21e706a77f160d7" +content-hash = "cdb45bd4a1198ee51ac700c0b3614a22d62cc2c8e03906f6cc773e4ca72f7d69" [metadata.files] atomicwrites = [ @@ -810,6 +815,13 @@ pyyaml = [ {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, @@ -857,6 +869,10 @@ tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +tomli-w = [ + {file = "tomli_w-1.0.0-py3-none-any.whl", hash = "sha256:9f2a07e8be30a0729e533ec968016807069991ae2fd921a78d42f429ae5f4463"}, + {file = "tomli_w-1.0.0.tar.gz", hash = "sha256:f463434305e0336248cac9c2dc8076b707d8a12d019dd349f5c1e382dd1ae1b9"}, +] tox = [ {file = "tox-3.25.0-py2.py3-none-any.whl", hash = "sha256:0805727eb4d6b049de304977dfc9ce315a1938e6619c3ab9f38682bb04662a5a"}, {file = "tox-3.25.0.tar.gz", hash = "sha256:37888f3092aa4e9f835fc8cc6dadbaaa0782651c41ef359e3a5743fcb0308160"}, diff --git a/pyproject.toml b/pyproject.toml index e898e4da0..47805784c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,7 @@ tox = "^3.0" vendoring = {version = "^1.0", python = "^3.8"} build = "^0.7.0" mypy = ">=0.960" +tomli-w = "^1.0.0" types-jsonschema = ">=4.4.4" types-setuptools = ">=57.4.14" @@ -91,7 +92,6 @@ exclude = "(?x)(^tests/.*/fixtures | ^src/poetry/core/_vendor)" [[tool.mypy.overrides]] module = [ 'lark.*', - 'tomlkit.*', 'virtualenv.*', ] ignore_missing_imports = true diff --git a/src/poetry/core/_vendor/tomli/LICENSE b/src/poetry/core/_vendor/tomli/LICENSE new file mode 100644 index 000000000..e859590f8 --- /dev/null +++ b/src/poetry/core/_vendor/tomli/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Taneli Hukkinen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/poetry/core/_vendor/tomli/__init__.py b/src/poetry/core/_vendor/tomli/__init__.py new file mode 100644 index 000000000..4c6ec97ec --- /dev/null +++ b/src/poetry/core/_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/src/poetry/core/_vendor/tomli/_parser.py b/src/poetry/core/_vendor/tomli/_parser.py new file mode 100644 index 000000000..f1bb0aa19 --- /dev/null +++ b/src/poetry/core/_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/src/poetry/core/_vendor/tomli/_re.py b/src/poetry/core/_vendor/tomli/_re.py new file mode 100644 index 000000000..994bb7493 --- /dev/null +++ b/src/poetry/core/_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 + (?:\.[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/src/poetry/core/_vendor/tomli/_types.py b/src/poetry/core/_vendor/tomli/_types.py new file mode 100644 index 000000000..d949412e0 --- /dev/null +++ b/src/poetry/core/_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/src/poetry/core/_vendor/tomli/py.typed b/src/poetry/core/_vendor/tomli/py.typed new file mode 100644 index 000000000..7632ecf77 --- /dev/null +++ b/src/poetry/core/_vendor/tomli/py.typed @@ -0,0 +1 @@ +# Marker file for PEP 561 diff --git a/src/poetry/core/_vendor/tomlkit/LICENSE b/src/poetry/core/_vendor/tomlkit/LICENSE deleted file mode 100644 index 44cf2b30e..000000000 --- a/src/poetry/core/_vendor/tomlkit/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2018 Sébastien Eustace - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/poetry/core/_vendor/tomlkit/__init__.py b/src/poetry/core/_vendor/tomlkit/__init__.py deleted file mode 100644 index 584bd96dc..000000000 --- a/src/poetry/core/_vendor/tomlkit/__init__.py +++ /dev/null @@ -1,55 +0,0 @@ -from tomlkit.api import TOMLDocument -from tomlkit.api import aot -from tomlkit.api import array -from tomlkit.api import boolean -from tomlkit.api import comment -from tomlkit.api import date -from tomlkit.api import datetime -from tomlkit.api import document -from tomlkit.api import dump -from tomlkit.api import dumps -from tomlkit.api import float_ -from tomlkit.api import inline_table -from tomlkit.api import integer -from tomlkit.api import item -from tomlkit.api import key -from tomlkit.api import key_value -from tomlkit.api import load -from tomlkit.api import loads -from tomlkit.api import nl -from tomlkit.api import parse -from tomlkit.api import string -from tomlkit.api import table -from tomlkit.api import time -from tomlkit.api import value -from tomlkit.api import ws - - -__version__ = "0.11.6" -__all__ = [ - "aot", - "array", - "boolean", - "comment", - "date", - "datetime", - "document", - "dump", - "dumps", - "float_", - "inline_table", - "integer", - "item", - "key", - "key_value", - "load", - "loads", - "nl", - "parse", - "string", - "table", - "time", - "TOMLDocument", - "value", - "ws", -] diff --git a/src/poetry/core/_vendor/tomlkit/_compat.py b/src/poetry/core/_vendor/tomlkit/_compat.py deleted file mode 100644 index f1d3bccd6..000000000 --- a/src/poetry/core/_vendor/tomlkit/_compat.py +++ /dev/null @@ -1,22 +0,0 @@ -import contextlib -import sys - -from typing import Any -from typing import List -from typing import Optional - - -PY38 = sys.version_info >= (3, 8) - - -def decode(string: Any, encodings: Optional[List[str]] = None): - if not isinstance(string, bytes): - return string - - encodings = encodings or ["utf-8", "latin1", "ascii"] - - for encoding in encodings: - with contextlib.suppress(UnicodeEncodeError, UnicodeDecodeError): - return string.decode(encoding) - - return string.decode(encodings[0], errors="ignore") diff --git a/src/poetry/core/_vendor/tomlkit/_utils.py b/src/poetry/core/_vendor/tomlkit/_utils.py deleted file mode 100644 index 85958e93c..000000000 --- a/src/poetry/core/_vendor/tomlkit/_utils.py +++ /dev/null @@ -1,155 +0,0 @@ -import re - -from collections.abc import Mapping -from datetime import date -from datetime import datetime -from datetime import time -from datetime import timedelta -from datetime import timezone -from typing import Collection -from typing import Union - -from tomlkit._compat import decode - - -RFC_3339_LOOSE = re.compile( - "^" - r"(([0-9]+)-(\d{2})-(\d{2}))?" # Date - "(" - "([Tt ])?" # Separator - r"(\d{2}):(\d{2}):(\d{2})(\.([0-9]+))?" # Time - r"(([Zz])|([\+|\-]([01][0-9]|2[0-3]):([0-5][0-9])))?" # Timezone - ")?" - "$" -) - -RFC_3339_DATETIME = re.compile( - "^" - "([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])" # Date - "[Tt ]" # Separator - r"([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.([0-9]+))?" # Time - r"(([Zz])|([\+|\-]([01][0-9]|2[0-3]):([0-5][0-9])))?" # Timezone - "$" -) - -RFC_3339_DATE = re.compile("^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$") - -RFC_3339_TIME = re.compile( - r"^([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.([0-9]+))?$" -) - -_utc = timezone(timedelta(), "UTC") - - -def parse_rfc3339(string: str) -> Union[datetime, date, time]: - m = RFC_3339_DATETIME.match(string) - if m: - year = int(m.group(1)) - month = int(m.group(2)) - day = int(m.group(3)) - hour = int(m.group(4)) - minute = int(m.group(5)) - second = int(m.group(6)) - microsecond = 0 - - if m.group(7): - microsecond = int((f"{m.group(8):<06s}")[:6]) - - if m.group(9): - # Timezone - tz = m.group(9) - if tz.upper() == "Z": - tzinfo = _utc - else: - sign = m.group(11)[0] - hour_offset, minute_offset = int(m.group(12)), int(m.group(13)) - offset = timedelta(seconds=hour_offset * 3600 + minute_offset * 60) - if sign == "-": - offset = -offset - - tzinfo = timezone(offset, f"{sign}{m.group(12)}:{m.group(13)}") - - return datetime( - year, month, day, hour, minute, second, microsecond, tzinfo=tzinfo - ) - else: - return datetime(year, month, day, hour, minute, second, microsecond) - - m = RFC_3339_DATE.match(string) - if m: - year = int(m.group(1)) - month = int(m.group(2)) - day = int(m.group(3)) - - return date(year, month, day) - - m = RFC_3339_TIME.match(string) - if m: - hour = int(m.group(1)) - minute = int(m.group(2)) - second = int(m.group(3)) - microsecond = 0 - - if m.group(4): - microsecond = int((f"{m.group(5):<06s}")[:6]) - - return time(hour, minute, second, microsecond) - - raise ValueError("Invalid RFC 339 string") - - -# https://toml.io/en/v1.0.0#string -CONTROL_CHARS = frozenset(chr(c) for c in range(0x20)) | {chr(0x7F)} -_escaped = { - "b": "\b", - "t": "\t", - "n": "\n", - "f": "\f", - "r": "\r", - '"': '"', - "\\": "\\", -} -_compact_escapes = { - **{v: f"\\{k}" for k, v in _escaped.items()}, - '"""': '""\\"', -} -_basic_escapes = CONTROL_CHARS | {'"', "\\"} - - -def _unicode_escape(seq: str) -> str: - return "".join(f"\\u{ord(c):04x}" for c in seq) - - -def escape_string(s: str, escape_sequences: Collection[str] = _basic_escapes) -> str: - s = decode(s) - - res = [] - start = 0 - - def flush(inc=1): - if start != i: - res.append(s[start:i]) - - return i + inc - - i = 0 - while i < len(s): - for seq in escape_sequences: - seq_len = len(seq) - if s[i:].startswith(seq): - start = flush(seq_len) - res.append(_compact_escapes.get(seq) or _unicode_escape(seq)) - i += seq_len - 1 # fast-forward escape sequence - i += 1 - - flush() - - return "".join(res) - - -def merge_dicts(d1: dict, d2: dict) -> dict: - for k, v in d2.items(): - if k in d1 and isinstance(d1[k], dict) and isinstance(v, Mapping): - merge_dicts(d1[k], v) - else: - d1[k] = d2[k] diff --git a/src/poetry/core/_vendor/tomlkit/api.py b/src/poetry/core/_vendor/tomlkit/api.py deleted file mode 100644 index ed48ca9a7..000000000 --- a/src/poetry/core/_vendor/tomlkit/api.py +++ /dev/null @@ -1,287 +0,0 @@ -import datetime as _datetime - -from collections.abc import Mapping -from typing import IO -from typing import Iterable -from typing import Optional -from typing import Tuple -from typing import Union - -from tomlkit._utils import parse_rfc3339 -from tomlkit.container import Container -from tomlkit.exceptions import UnexpectedCharError -from tomlkit.items import AoT -from tomlkit.items import Array -from tomlkit.items import Bool -from tomlkit.items import Comment -from tomlkit.items import Date -from tomlkit.items import DateTime -from tomlkit.items import DottedKey -from tomlkit.items import Float -from tomlkit.items import InlineTable -from tomlkit.items import Integer -from tomlkit.items import Item as _Item -from tomlkit.items import Key -from tomlkit.items import SingleKey -from tomlkit.items import String -from tomlkit.items import StringType as _StringType -from tomlkit.items import Table -from tomlkit.items import Time -from tomlkit.items import Trivia -from tomlkit.items import Whitespace -from tomlkit.items import item -from tomlkit.parser import Parser -from tomlkit.toml_document import TOMLDocument - - -def loads(string: Union[str, bytes]) -> TOMLDocument: - """ - Parses a string into a TOMLDocument. - - Alias for parse(). - """ - return parse(string) - - -def dumps(data: Mapping, sort_keys: bool = False) -> str: - """ - Dumps a TOMLDocument into a string. - """ - if not isinstance(data, Container) and isinstance(data, Mapping): - data = item(dict(data), _sort_keys=sort_keys) - - try: - # data should be a `Container` (and therefore implement `as_string`) - # for all type safe invocations of this function - return data.as_string() # type: ignore[attr-defined] - except AttributeError as ex: - msg = f"Expecting Mapping or TOML Container, {type(data)} given" - raise TypeError(msg) from ex - - -def load(fp: IO) -> TOMLDocument: - """ - Load toml document from a file-like object. - """ - return parse(fp.read()) - - -def dump(data: Mapping, fp: IO[str], *, sort_keys: bool = False) -> None: - """ - Dump a TOMLDocument into a writable file stream. - - :param data: a dict-like object to dump - :param sort_keys: if true, sort the keys in alphabetic order - """ - fp.write(dumps(data, sort_keys=sort_keys)) - - -def parse(string: Union[str, bytes]) -> TOMLDocument: - """ - Parses a string or bytes into a TOMLDocument. - """ - return Parser(string).parse() - - -def document() -> TOMLDocument: - """ - Returns a new TOMLDocument instance. - """ - return TOMLDocument() - - -# Items -def integer(raw: Union[str, int]) -> Integer: - """Create an integer item from a number or string.""" - return item(int(raw)) - - -def float_(raw: Union[str, float]) -> Float: - """Create an float item from a number or string.""" - return item(float(raw)) - - -def boolean(raw: str) -> Bool: - """Turn `true` or `false` into a boolean item.""" - return item(raw == "true") - - -def string( - raw: str, - *, - literal: bool = False, - multiline: bool = False, - escape: bool = True, -) -> String: - """Create a string item. - - By default, this function will create *single line basic* strings, but - boolean flags (e.g. ``literal=True`` and/or ``multiline=True``) - can be used for personalization. - - For more information, please check the spec: `https://toml.io/en/v1.0.0#string`_. - - Common escaping rules will be applied for basic strings. - This can be controlled by explicitly setting ``escape=False``. - Please note that, if you disable escaping, you will have to make sure that - the given strings don't contain any forbidden character or sequence. - """ - type_ = _StringType.select(literal, multiline) - return String.from_raw(raw, type_, escape) - - -def date(raw: str) -> Date: - """Create a TOML date.""" - value = parse_rfc3339(raw) - if not isinstance(value, _datetime.date): - raise ValueError("date() only accepts date strings.") - - return item(value) - - -def time(raw: str) -> Time: - """Create a TOML time.""" - value = parse_rfc3339(raw) - if not isinstance(value, _datetime.time): - raise ValueError("time() only accepts time strings.") - - return item(value) - - -def datetime(raw: str) -> DateTime: - """Create a TOML datetime.""" - value = parse_rfc3339(raw) - if not isinstance(value, _datetime.datetime): - raise ValueError("datetime() only accepts datetime strings.") - - return item(value) - - -def array(raw: str = None) -> Array: - """Create an array item for its string representation. - - :Example: - - >>> array("[1, 2, 3]") # Create from a string - [1, 2, 3] - >>> a = array() - >>> a.extend([1, 2, 3]) # Create from a list - >>> a - [1, 2, 3] - """ - if raw is None: - raw = "[]" - - return value(raw) - - -def table(is_super_table: Optional[bool] = None) -> Table: - """Create an empty table. - - :param is_super_table: if true, the table is a super table - - :Example: - - >>> doc = document() - >>> foo = table(True) - >>> bar = table() - >>> bar.update({'x': 1}) - >>> foo.append('bar', bar) - >>> doc.append('foo', foo) - >>> print(doc.as_string()) - [foo.bar] - x = 1 - """ - return Table(Container(), Trivia(), False, is_super_table) - - -def inline_table() -> InlineTable: - """Create an inline table. - - :Example: - - >>> table = inline_table() - >>> table.update({'x': 1, 'y': 2}) - >>> print(table.as_string()) - {x = 1, y = 2} - """ - return InlineTable(Container(), Trivia(), new=True) - - -def aot() -> AoT: - """Create an array of table. - - :Example: - - >>> doc = document() - >>> aot = aot() - >>> aot.append(item({'x': 1})) - >>> doc.append('foo', aot) - >>> print(doc.as_string()) - [[foo]] - x = 1 - """ - return AoT([]) - - -def key(k: Union[str, Iterable[str]]) -> Key: - """Create a key from a string. When a list of string is given, - it will create a dotted key. - - :Example: - - >>> doc = document() - >>> doc.append(key('foo'), 1) - >>> doc.append(key(['bar', 'baz']), 2) - >>> print(doc.as_string()) - foo = 1 - bar.baz = 2 - """ - if isinstance(k, str): - return SingleKey(k) - return DottedKey([key(_k) for _k in k]) - - -def value(raw: str) -> _Item: - """Parse a simple value from a string. - - :Example: - - >>> value("1") - 1 - >>> value("true") - True - >>> value("[1, 2, 3]") - [1, 2, 3] - """ - parser = Parser(raw) - v = parser._parse_value() - if not parser.end(): - raise parser.parse_error(UnexpectedCharError, char=parser._current) - return v - - -def key_value(src: str) -> Tuple[Key, _Item]: - """Parse a key-value pair from a string. - - :Example: - - >>> key_value("foo = 1") - (Key('foo'), 1) - """ - return Parser(src)._parse_key_value() - - -def ws(src: str) -> Whitespace: - """Create a whitespace from a string.""" - return Whitespace(src, fixed=True) - - -def nl() -> Whitespace: - """Create a newline item.""" - return ws("\n") - - -def comment(string: str) -> Comment: - """Create a comment item.""" - return Comment(Trivia(comment_ws=" ", comment="# " + string)) diff --git a/src/poetry/core/_vendor/tomlkit/container.py b/src/poetry/core/_vendor/tomlkit/container.py deleted file mode 100644 index 4b40a13b7..000000000 --- a/src/poetry/core/_vendor/tomlkit/container.py +++ /dev/null @@ -1,907 +0,0 @@ -import copy - -from typing import Any -from typing import Dict -from typing import Iterator -from typing import List -from typing import Optional -from typing import Tuple -from typing import Union - -from tomlkit._compat import decode -from tomlkit._utils import merge_dicts -from tomlkit.exceptions import KeyAlreadyPresent -from tomlkit.exceptions import NonExistentKey -from tomlkit.exceptions import TOMLKitError -from tomlkit.items import AoT -from tomlkit.items import Comment -from tomlkit.items import Item -from tomlkit.items import Key -from tomlkit.items import Null -from tomlkit.items import SingleKey -from tomlkit.items import Table -from tomlkit.items import Trivia -from tomlkit.items import Whitespace -from tomlkit.items import _CustomDict -from tomlkit.items import item as _item - - -_NOT_SET = object() - - -class Container(_CustomDict): - """ - A container for items within a TOMLDocument. - - This class implements the `dict` interface with copy/deepcopy protocol. - """ - - def __init__(self, parsed: bool = False) -> None: - self._map: Dict[Key, int] = {} - self._body: List[Tuple[Optional[Key], Item]] = [] - self._parsed = parsed - self._table_keys = [] - - @property - def body(self) -> List[Tuple[Optional[Key], Item]]: - return self._body - - def unwrap(self) -> Dict[str, Any]: - unwrapped = {} - for k, v in self.items(): - if k is None: - continue - - if isinstance(k, Key): - k = k.key - - if isinstance(v, Item): - v = v.unwrap() - - if k in unwrapped: - merge_dicts(unwrapped[k], v) - else: - unwrapped[k] = v - - return unwrapped - - @property - def value(self) -> Dict[str, Any]: - d = {} - for k, v in self._body: - if k is None: - continue - - k = k.key - v = v.value - - if isinstance(v, Container): - v = v.value - - if k in d: - merge_dicts(d[k], v) - else: - d[k] = v - - return d - - def parsing(self, parsing: bool) -> None: - self._parsed = parsing - - for _, v in self._body: - if isinstance(v, Table): - v.value.parsing(parsing) - elif isinstance(v, AoT): - for t in v.body: - t.value.parsing(parsing) - - def add( - self, key: Union[Key, Item, str], item: Optional[Item] = None - ) -> "Container": - """ - Adds an item to the current Container. - - :Example: - - >>> # add a key-value pair - >>> doc.add('key', 'value') - >>> # add a comment or whitespace or newline - >>> doc.add(comment('# comment')) - """ - if item is None: - if not isinstance(key, (Comment, Whitespace)): - raise ValueError( - "Non comment/whitespace items must have an associated key" - ) - - key, item = None, key - - return self.append(key, item) - - def _handle_dotted_key(self, key: Key, value: Item) -> None: - names = tuple(iter(key)) - name = names[0] - name._dotted = True - if name in self: - if not isinstance(value, Table): - table = Table(Container(True), Trivia(), False, is_super_table=True) - _table = table - for i, _name in enumerate(names[1:]): - if i == len(names) - 2: - _name.sep = key.sep - - _table.append(_name, value) - else: - _name._dotted = True - _table.append( - _name, - Table( - Container(True), - Trivia(), - False, - is_super_table=i < len(names) - 2, - ), - ) - - _table = _table[_name] - - value = table - - self.append(name, value) - - return - else: - table = Table(Container(True), Trivia(), False, is_super_table=True) - self.append(name, table) - - for i, _name in enumerate(names[1:]): - if i == len(names) - 2: - _name.sep = key.sep - - table.append(_name, value) - else: - _name._dotted = True - if _name in table.value: - table = table.value[_name] - else: - table.append( - _name, - Table( - Container(True), - Trivia(), - False, - is_super_table=i < len(names) - 2, - ), - ) - - table = table[_name] - - def append(self, key: Union[Key, str, None], item: Item) -> "Container": - """Similar to :meth:`add` but both key and value must be given.""" - if not isinstance(key, Key) and key is not None: - key = SingleKey(key) - - if not isinstance(item, Item): - item = _item(item) - - if key is not None and key.is_multi(): - self._handle_dotted_key(key, item) - return self - - if isinstance(item, (AoT, Table)) and item.name is None: - item.name = key.key - - prev = self._previous_item() - prev_ws = isinstance(prev, Whitespace) or ends_with_whitespace(prev) - if isinstance(item, Table): - if not self._parsed: - item.invalidate_display_name() - if self._body and not (self._parsed or item.trivia.indent or prev_ws): - item.trivia.indent = "\n" - - if isinstance(item, AoT) and self._body and not self._parsed: - item.invalidate_display_name() - if item and not ("\n" in item[0].trivia.indent or prev_ws): - item[0].trivia.indent = "\n" + item[0].trivia.indent - - if key is not None and key in self: - current_idx = self._map[key] - if isinstance(current_idx, tuple): - current_body_element = self._body[current_idx[-1]] - else: - current_body_element = self._body[current_idx] - - current = current_body_element[1] - - if isinstance(item, Table): - if not isinstance(current, (Table, AoT)): - raise KeyAlreadyPresent(key) - - if item.is_aot_element(): - # New AoT element found later on - # Adding it to the current AoT - if not isinstance(current, AoT): - current = AoT([current, item], parsed=self._parsed) - - self._replace(key, key, current) - else: - current.append(item) - - return self - elif current.is_aot(): - if not item.is_aot_element(): - # Tried to define a table after an AoT with the same name. - raise KeyAlreadyPresent(key) - - current.append(item) - - return self - elif current.is_super_table(): - if item.is_super_table(): - # We need to merge both super tables - if ( - self._table_keys[-1] != current_body_element[0] - or key.is_dotted() - or current_body_element[0].is_dotted() - ): - if not isinstance(current_idx, tuple): - current_idx = (current_idx,) - - self._map[key] = current_idx + (len(self._body),) - self._body.append((key, item)) - self._table_keys.append(key) - - # Building a temporary proxy to check for errors - OutOfOrderTableProxy(self, self._map[key]) - - return self - - # Create a new element to replace the old one - current = copy.deepcopy(current) - for k, v in item.value.body: - current.append(k, v) - self._body[ - current_idx[-1] - if isinstance(current_idx, tuple) - else current_idx - ] = (current_body_element[0], current) - - return self - elif current_body_element[0].is_dotted(): - raise TOMLKitError("Redefinition of an existing table") - elif not item.is_super_table(): - raise KeyAlreadyPresent(key) - elif isinstance(item, AoT): - if not isinstance(current, AoT): - # Tried to define an AoT after a table with the same name. - raise KeyAlreadyPresent(key) - - for table in item.body: - current.append(table) - - return self - else: - raise KeyAlreadyPresent(key) - - is_table = isinstance(item, (Table, AoT)) - if key is not None and self._body and not self._parsed: - # If there is already at least one table in the current container - # and the given item is not a table, we need to find the last - # item that is not a table and insert after it - # If no such item exists, insert at the top of the table - key_after = None - for i, (k, v) in enumerate(self._body): - if isinstance(v, Null): - continue # Null elements are inserted after deletion - - if isinstance(v, Whitespace) and not v.is_fixed(): - continue - - if not is_table and isinstance(v, (Table, AoT)): - break - - key_after = k or i # last scalar, Array or InlineTable value - - if key_after is not None: - if isinstance(key_after, int): - if key_after + 1 < len(self._body): - return self._insert_at(key_after + 1, key, item) - else: - previous_item = self._body[-1][1] - if not ( - isinstance(previous_item, Whitespace) - or ends_with_whitespace(previous_item) - or is_table - or "\n" in previous_item.trivia.trail - ): - previous_item.trivia.trail += "\n" - else: - return self._insert_after(key_after, key, item) - else: - return self._insert_at(0, key, item) - - if key in self._map: - current_idx = self._map[key] - if isinstance(current_idx, tuple): - current_idx = current_idx[-1] - - current = self._body[current_idx][1] - if key is not None and not isinstance(current, Table): - raise KeyAlreadyPresent(key) - - # Adding sub tables to a currently existing table - if not isinstance(current_idx, tuple): - current_idx = (current_idx,) - - self._map[key] = current_idx + (len(self._body),) - else: - self._map[key] = len(self._body) - - self._body.append((key, item)) - if item.is_table(): - self._table_keys.append(key) - - if key is not None: - dict.__setitem__(self, key.key, item.value) - - return self - - def _remove_at(self, idx: int) -> None: - key = self._body[idx][0] - index = self._map.get(key) - if index is None: - raise NonExistentKey(key) - self._body[idx] = (None, Null()) - - if isinstance(index, tuple): - index = list(index) - index.remove(idx) - if len(index) == 1: - index = index.pop() - else: - index = tuple(index) - self._map[key] = index - else: - dict.__delitem__(self, key.key) - self._map.pop(key) - - def remove(self, key: Union[Key, str]) -> "Container": - """Remove a key from the container.""" - if not isinstance(key, Key): - key = SingleKey(key) - - idx = self._map.pop(key, None) - if idx is None: - raise NonExistentKey(key) - - if isinstance(idx, tuple): - for i in idx: - self._body[i] = (None, Null()) - else: - self._body[idx] = (None, Null()) - - dict.__delitem__(self, key.key) - - return self - - def _insert_after( - self, key: Union[Key, str], other_key: Union[Key, str], item: Any - ) -> "Container": - if key is None: - raise ValueError("Key cannot be null in insert_after()") - - if key not in self: - raise NonExistentKey(key) - - if not isinstance(key, Key): - key = SingleKey(key) - - if not isinstance(other_key, Key): - other_key = SingleKey(other_key) - - item = _item(item) - - idx = self._map[key] - # Insert after the max index if there are many. - if isinstance(idx, tuple): - idx = max(idx) - current_item = self._body[idx][1] - if "\n" not in current_item.trivia.trail: - current_item.trivia.trail += "\n" - - # Increment indices after the current index - for k, v in self._map.items(): - if isinstance(v, tuple): - new_indices = [] - for v_ in v: - if v_ > idx: - v_ = v_ + 1 - - new_indices.append(v_) - - self._map[k] = tuple(new_indices) - elif v > idx: - self._map[k] = v + 1 - - self._map[other_key] = idx + 1 - self._body.insert(idx + 1, (other_key, item)) - - if key is not None: - dict.__setitem__(self, other_key.key, item.value) - - return self - - def _insert_at(self, idx: int, key: Union[Key, str], item: Any) -> "Container": - if idx > len(self._body) - 1: - raise ValueError(f"Unable to insert at position {idx}") - - if not isinstance(key, Key): - key = SingleKey(key) - - item = _item(item) - - if idx > 0: - previous_item = self._body[idx - 1][1] - if not ( - isinstance(previous_item, Whitespace) - or ends_with_whitespace(previous_item) - or isinstance(item, (AoT, Table)) - or "\n" in previous_item.trivia.trail - ): - previous_item.trivia.trail += "\n" - - # Increment indices after the current index - for k, v in self._map.items(): - if isinstance(v, tuple): - new_indices = [] - for v_ in v: - if v_ >= idx: - v_ = v_ + 1 - - new_indices.append(v_) - - self._map[k] = tuple(new_indices) - elif v >= idx: - self._map[k] = v + 1 - - self._map[key] = idx - self._body.insert(idx, (key, item)) - - if key is not None: - dict.__setitem__(self, key.key, item.value) - - return self - - def item(self, key: Union[Key, str]) -> Item: - """Get an item for the given key.""" - if not isinstance(key, Key): - key = SingleKey(key) - - idx = self._map.get(key, None) - if idx is None: - raise NonExistentKey(key) - - if isinstance(idx, tuple): - # The item we are getting is an out of order table - # so we need a proxy to retrieve the proper objects - # from the parent container - return OutOfOrderTableProxy(self, idx) - - return self._body[idx][1] - - def last_item(self) -> Optional[Item]: - """Get the last item.""" - if self._body: - return self._body[-1][1] - - def as_string(self) -> str: - """Render as TOML string.""" - s = "" - for k, v in self._body: - if k is not None: - if isinstance(v, Table): - s += self._render_table(k, v) - elif isinstance(v, AoT): - s += self._render_aot(k, v) - else: - s += self._render_simple_item(k, v) - else: - s += self._render_simple_item(k, v) - - return s - - def _render_table( - self, key: Key, table: Table, prefix: Optional[str] = None - ) -> str: - cur = "" - - if table.display_name is not None: - _key = table.display_name - else: - _key = key.as_string() - - if prefix is not None: - _key = prefix + "." + _key - - if not table.is_super_table() or ( - any( - not isinstance(v, (Table, AoT, Whitespace, Null)) - for _, v in table.value.body - ) - and not key.is_dotted() - ): - open_, close = "[", "]" - if table.is_aot_element(): - open_, close = "[[", "]]" - - newline_in_table_trivia = ( - "\n" if "\n" not in table.trivia.trail and len(table.value) > 0 else "" - ) - cur += ( - f"{table.trivia.indent}" - f"{open_}" - f"{decode(_key)}" - f"{close}" - f"{table.trivia.comment_ws}" - f"{decode(table.trivia.comment)}" - f"{table.trivia.trail}" - f"{newline_in_table_trivia}" - ) - elif table.trivia.indent == "\n": - cur += table.trivia.indent - - for k, v in table.value.body: - if isinstance(v, Table): - if v.is_super_table(): - if k.is_dotted() and not key.is_dotted(): - # Dotted key inside table - cur += self._render_table(k, v) - else: - cur += self._render_table(k, v, prefix=_key) - else: - cur += self._render_table(k, v, prefix=_key) - elif isinstance(v, AoT): - cur += self._render_aot(k, v, prefix=_key) - else: - cur += self._render_simple_item( - k, v, prefix=_key if key.is_dotted() else None - ) - - return cur - - def _render_aot(self, key, aot, prefix=None): - _key = key.as_string() - if prefix is not None: - _key = prefix + "." + _key - - cur = "" - _key = decode(_key) - for table in aot.body: - cur += self._render_aot_table(table, prefix=_key) - - return cur - - def _render_aot_table(self, table: Table, prefix: Optional[str] = None) -> str: - cur = "" - - _key = prefix or "" - - if not table.is_super_table(): - open_, close = "[[", "]]" - - cur += ( - f"{table.trivia.indent}" - f"{open_}" - f"{decode(_key)}" - f"{close}" - f"{table.trivia.comment_ws}" - f"{decode(table.trivia.comment)}" - f"{table.trivia.trail}" - ) - - for k, v in table.value.body: - if isinstance(v, Table): - if v.is_super_table(): - if k.is_dotted(): - # Dotted key inside table - cur += self._render_table(k, v) - else: - cur += self._render_table(k, v, prefix=_key) - else: - cur += self._render_table(k, v, prefix=_key) - elif isinstance(v, AoT): - cur += self._render_aot(k, v, prefix=_key) - else: - cur += self._render_simple_item(k, v) - - return cur - - def _render_simple_item(self, key, item, prefix=None): - if key is None: - return item.as_string() - - _key = key.as_string() - if prefix is not None: - _key = prefix + "." + _key - - return ( - f"{item.trivia.indent}" - f"{decode(_key)}" - f"{key.sep}" - f"{decode(item.as_string())}" - f"{item.trivia.comment_ws}" - f"{decode(item.trivia.comment)}" - f"{item.trivia.trail}" - ) - - def __len__(self) -> int: - return dict.__len__(self) - - def __iter__(self) -> Iterator[str]: - return iter(dict.keys(self)) - - # Dictionary methods - def __getitem__(self, key: Union[Key, str]) -> Union[Item, "Container"]: - if not isinstance(key, Key): - key = SingleKey(key) - - idx = self._map.get(key, None) - if idx is None: - raise NonExistentKey(key) - - if isinstance(idx, tuple): - # The item we are getting is an out of order table - # so we need a proxy to retrieve the proper objects - # from the parent container - return OutOfOrderTableProxy(self, idx) - - item = self._body[idx][1] - if item.is_boolean(): - return item.value - - return item - - def __setitem__(self, key: Union[Key, str], value: Any) -> None: - if key is not None and key in self: - old_key = next(filter(lambda k: k == key, self._map)) - self._replace(old_key, key, value) - else: - self.append(key, value) - - def __delitem__(self, key: Union[Key, str]) -> None: - self.remove(key) - - def setdefault(self, key: Union[Key, str], default: Any) -> Any: - super().setdefault(key, default=default) - return self[key] - - def _replace( - self, key: Union[Key, str], new_key: Union[Key, str], value: Item - ) -> None: - if not isinstance(key, Key): - key = SingleKey(key) - - idx = self._map.get(key, None) - if idx is None: - raise NonExistentKey(key) - - self._replace_at(idx, new_key, value) - - def _replace_at( - self, idx: Union[int, Tuple[int]], new_key: Union[Key, str], value: Item - ) -> None: - value = _item(value) - - if isinstance(idx, tuple): - for i in idx[1:]: - self._body[i] = (None, Null()) - - idx = idx[0] - - k, v = self._body[idx] - if not isinstance(new_key, Key): - if ( - isinstance(value, (AoT, Table)) != isinstance(v, (AoT, Table)) - or new_key != k.key - ): - new_key = SingleKey(new_key) - else: # Inherit the sep of the old key - new_key = k - - del self._map[k] - self._map[new_key] = idx - if new_key != k: - dict.__delitem__(self, k) - - if isinstance(value, (AoT, Table)) != isinstance(v, (AoT, Table)): - # new tables should appear after all non-table values - self.remove(k) - for i in range(idx, len(self._body)): - if isinstance(self._body[i][1], (AoT, Table)): - self._insert_at(i, new_key, value) - idx = i - break - else: - idx = -1 - self.append(new_key, value) - else: - # Copying trivia - if not isinstance(value, (Whitespace, AoT)): - value.trivia.indent = v.trivia.indent - value.trivia.comment_ws = value.trivia.comment_ws or v.trivia.comment_ws - value.trivia.comment = value.trivia.comment or v.trivia.comment - value.trivia.trail = v.trivia.trail - self._body[idx] = (new_key, value) - - if hasattr(value, "invalidate_display_name"): - value.invalidate_display_name() # type: ignore[attr-defined] - - if isinstance(value, Table): - # Insert a cosmetic new line for tables if: - # - it does not have it yet OR is not followed by one - # - it is not the last item - last, _ = self._previous_item_with_index() - idx = last if idx < 0 else idx - has_ws = ends_with_whitespace(value) - next_ws = idx < last and isinstance(self._body[idx + 1][1], Whitespace) - if idx < last and not (next_ws or has_ws): - value.append(None, Whitespace("\n")) - - dict.__setitem__(self, new_key.key, value.value) - - def __str__(self) -> str: - return str(self.value) - - def __repr__(self) -> str: - return repr(self.value) - - def __eq__(self, other: dict) -> bool: - if not isinstance(other, dict): - return NotImplemented - - return self.value == other - - def _getstate(self, protocol): - return (self._parsed,) - - def __reduce__(self): - return self.__reduce_ex__(2) - - def __reduce_ex__(self, protocol): - return ( - self.__class__, - self._getstate(protocol), - (self._map, self._body, self._parsed, self._table_keys), - ) - - def __setstate__(self, state): - self._map = state[0] - self._body = state[1] - self._parsed = state[2] - self._table_keys = state[3] - - for key, item in self._body: - if key is not None: - dict.__setitem__(self, key.key, item.value) - - def copy(self) -> "Container": - return copy.copy(self) - - def __copy__(self) -> "Container": - c = self.__class__(self._parsed) - for k, v in dict.items(self): - dict.__setitem__(c, k, v) - - c._body += self.body - c._map.update(self._map) - - return c - - def _previous_item_with_index( - self, idx: Optional[int] = None, ignore=(Null,) - ) -> Optional[Tuple[int, Item]]: - """Find the immediate previous item before index ``idx``""" - if idx is None or idx > len(self._body): - idx = len(self._body) - for i in range(idx - 1, -1, -1): - v = self._body[i][-1] - if not isinstance(v, ignore): - return i, v - return None - - def _previous_item( - self, idx: Optional[int] = None, ignore=(Null,) - ) -> Optional[Item]: - """Find the immediate previous item before index ``idx``. - If ``idx`` is not given, the last item is returned. - """ - prev = self._previous_item_with_index(idx, ignore) - return prev[-1] if prev else None - - -class OutOfOrderTableProxy(_CustomDict): - def __init__(self, container: Container, indices: Tuple[int]) -> None: - self._container = container - self._internal_container = Container(True) - self._tables = [] - self._tables_map = {} - - for i in indices: - _, item = self._container._body[i] - - if isinstance(item, Table): - self._tables.append(item) - table_idx = len(self._tables) - 1 - for k, v in item.value.body: - self._internal_container.append(k, v) - self._tables_map[k] = table_idx - if k is not None: - dict.__setitem__(self, k.key, v) - - def unwrap(self) -> str: - return self._internal_container.unwrap() - - @property - def value(self): - return self._internal_container.value - - def __getitem__(self, key: Union[Key, str]) -> Any: - if key not in self._internal_container: - raise NonExistentKey(key) - - return self._internal_container[key] - - def __setitem__(self, key: Union[Key, str], item: Any) -> None: - if key in self._tables_map: - table = self._tables[self._tables_map[key]] - table[key] = item - elif self._tables: - table = self._tables[0] - table[key] = item - else: - self._container[key] = item - - self._internal_container[key] = item - if key is not None: - dict.__setitem__(self, key, item) - - def _remove_table(self, table: Table) -> None: - """Remove table from the parent container""" - self._tables.remove(table) - for idx, item in enumerate(self._container._body): - if item[1] is table: - self._container._remove_at(idx) - break - - def __delitem__(self, key: Union[Key, str]) -> None: - if key in self._tables_map: - table = self._tables[self._tables_map[key]] - del table[key] - if not table and len(self._tables) > 1: - self._remove_table(table) - del self._tables_map[key] - else: - raise NonExistentKey(key) - - del self._internal_container[key] - if key is not None: - dict.__delitem__(self, key) - - def __iter__(self) -> Iterator[str]: - return iter(dict.keys(self)) - - def __len__(self) -> int: - return dict.__len__(self) - - def setdefault(self, key: Union[Key, str], default: Any) -> Any: - super().setdefault(key, default=default) - return self[key] - - -def ends_with_whitespace(it: Any) -> bool: - """Returns ``True`` if the given item ``it`` is a ``Table`` or ``AoT`` object - ending with a ``Whitespace``. - """ - return ( - isinstance(it, Table) and isinstance(it.value._previous_item(), Whitespace) - ) or (isinstance(it, AoT) and len(it) > 0 and isinstance(it[-1], Whitespace)) diff --git a/src/poetry/core/_vendor/tomlkit/exceptions.py b/src/poetry/core/_vendor/tomlkit/exceptions.py deleted file mode 100644 index 3147ca2a2..000000000 --- a/src/poetry/core/_vendor/tomlkit/exceptions.py +++ /dev/null @@ -1,227 +0,0 @@ -from typing import Collection -from typing import Optional - - -class TOMLKitError(Exception): - - pass - - -class ParseError(ValueError, TOMLKitError): - """ - This error occurs when the parser encounters a syntax error - in the TOML being parsed. The error references the line and - location within the line where the error was encountered. - """ - - def __init__(self, line: int, col: int, message: Optional[str] = None) -> None: - self._line = line - self._col = col - - if message is None: - message = "TOML parse error" - - super().__init__(f"{message} at line {self._line} col {self._col}") - - @property - def line(self): - return self._line - - @property - def col(self): - return self._col - - -class MixedArrayTypesError(ParseError): - """ - An array was found that had two or more element types. - """ - - def __init__(self, line: int, col: int) -> None: - message = "Mixed types found in array" - - super().__init__(line, col, message=message) - - -class InvalidNumberError(ParseError): - """ - A numeric field was improperly specified. - """ - - def __init__(self, line: int, col: int) -> None: - message = "Invalid number" - - super().__init__(line, col, message=message) - - -class InvalidDateTimeError(ParseError): - """ - A datetime field was improperly specified. - """ - - def __init__(self, line: int, col: int) -> None: - message = "Invalid datetime" - - super().__init__(line, col, message=message) - - -class InvalidDateError(ParseError): - """ - A date field was improperly specified. - """ - - def __init__(self, line: int, col: int) -> None: - message = "Invalid date" - - super().__init__(line, col, message=message) - - -class InvalidTimeError(ParseError): - """ - A date field was improperly specified. - """ - - def __init__(self, line: int, col: int) -> None: - message = "Invalid time" - - super().__init__(line, col, message=message) - - -class InvalidNumberOrDateError(ParseError): - """ - A numeric or date field was improperly specified. - """ - - def __init__(self, line: int, col: int) -> None: - message = "Invalid number or date format" - - super().__init__(line, col, message=message) - - -class InvalidUnicodeValueError(ParseError): - """ - A unicode code was improperly specified. - """ - - def __init__(self, line: int, col: int) -> None: - message = "Invalid unicode value" - - super().__init__(line, col, message=message) - - -class UnexpectedCharError(ParseError): - """ - An unexpected character was found during parsing. - """ - - def __init__(self, line: int, col: int, char: str) -> None: - message = f"Unexpected character: {repr(char)}" - - super().__init__(line, col, message=message) - - -class EmptyKeyError(ParseError): - """ - An empty key was found during parsing. - """ - - def __init__(self, line: int, col: int) -> None: - message = "Empty key" - - super().__init__(line, col, message=message) - - -class EmptyTableNameError(ParseError): - """ - An empty table name was found during parsing. - """ - - def __init__(self, line: int, col: int) -> None: - message = "Empty table name" - - super().__init__(line, col, message=message) - - -class InvalidCharInStringError(ParseError): - """ - The string being parsed contains an invalid character. - """ - - def __init__(self, line: int, col: int, char: str) -> None: - message = f"Invalid character {repr(char)} in string" - - super().__init__(line, col, message=message) - - -class UnexpectedEofError(ParseError): - """ - The TOML being parsed ended before the end of a statement. - """ - - def __init__(self, line: int, col: int) -> None: - message = "Unexpected end of file" - - super().__init__(line, col, message=message) - - -class InternalParserError(ParseError): - """ - An error that indicates a bug in the parser. - """ - - def __init__(self, line: int, col: int, message: Optional[str] = None) -> None: - msg = "Internal parser error" - if message: - msg += f" ({message})" - - super().__init__(line, col, message=msg) - - -class NonExistentKey(KeyError, TOMLKitError): - """ - A non-existent key was used. - """ - - def __init__(self, key): - message = f'Key "{key}" does not exist.' - - super().__init__(message) - - -class KeyAlreadyPresent(TOMLKitError): - """ - An already present key was used. - """ - - def __init__(self, key): - key = getattr(key, "key", key) - message = f'Key "{key}" already exists.' - - super().__init__(message) - - -class InvalidControlChar(ParseError): - def __init__(self, line: int, col: int, char: int, type: str) -> None: - display_code = "\\u00" - - if char < 16: - display_code += "0" - - display_code += hex(char)[2:] - - message = ( - "Control characters (codes less than 0x1f and 0x7f)" - f" are not allowed in {type}, " - f"use {display_code} instead" - ) - - super().__init__(line, col, message=message) - - -class InvalidStringError(ValueError, TOMLKitError): - def __init__(self, value: str, invalid_sequences: Collection[str], delimiter: str): - repr_ = repr(value)[1:-1] - super().__init__( - f"Invalid string: {delimiter}{repr_}{delimiter}. " - f"The character sequences {invalid_sequences} are invalid." - ) diff --git a/src/poetry/core/_vendor/tomlkit/items.py b/src/poetry/core/_vendor/tomlkit/items.py deleted file mode 100644 index 77fa27d3f..000000000 --- a/src/poetry/core/_vendor/tomlkit/items.py +++ /dev/null @@ -1,1950 +0,0 @@ -import abc -import copy -import re -import string - -from datetime import date -from datetime import datetime -from datetime import time -from datetime import tzinfo -from enum import Enum -from typing import TYPE_CHECKING -from typing import Any -from typing import Collection -from typing import Dict -from typing import Iterable -from typing import Iterator -from typing import List -from typing import Optional -from typing import Sequence -from typing import TypeVar -from typing import Union -from typing import cast -from typing import overload - -from tomlkit._compat import PY38 -from tomlkit._compat import decode -from tomlkit._utils import CONTROL_CHARS -from tomlkit._utils import escape_string -from tomlkit.exceptions import InvalidStringError - - -if TYPE_CHECKING: # pragma: no cover - # Define _CustomList and _CustomDict as a workaround for: - # https://github.com/python/mypy/issues/11427 - # - # According to this issue, the typeshed contains a "lie" - # (it adds MutableSequence to the ancestry of list and MutableMapping to - # the ancestry of dict) which completely messes with the type inference for - # Table, InlineTable, Array and Container. - # - # Importing from builtins is preferred over simple assignment, see issues: - # https://github.com/python/mypy/issues/8715 - # https://github.com/python/mypy/issues/10068 - from builtins import dict as _CustomDict # noqa: N812, TC004 - from builtins import list as _CustomList # noqa: N812, TC004 - - # Allow type annotations but break circular imports - from tomlkit import container -else: - from collections.abc import MutableMapping - from collections.abc import MutableSequence - - class _CustomList(MutableSequence, list): - """Adds MutableSequence mixin while pretending to be a builtin list""" - - class _CustomDict(MutableMapping, dict): - """Adds MutableMapping mixin while pretending to be a builtin dict""" - - -ItemT = TypeVar("ItemT", bound="Item") - - -@overload -def item( - value: bool, _parent: Optional["Item"] = ..., _sort_keys: bool = ... -) -> "Bool": - ... - - -@overload -def item( - value: int, _parent: Optional["Item"] = ..., _sort_keys: bool = ... -) -> "Integer": - ... - - -@overload -def item( - value: float, _parent: Optional["Item"] = ..., _sort_keys: bool = ... -) -> "Float": - ... - - -@overload -def item( - value: str, _parent: Optional["Item"] = ..., _sort_keys: bool = ... -) -> "String": - ... - - -@overload -def item( - value: datetime, _parent: Optional["Item"] = ..., _sort_keys: bool = ... -) -> "DateTime": - ... - - -@overload -def item( - value: date, _parent: Optional["Item"] = ..., _sort_keys: bool = ... -) -> "Date": - ... - - -@overload -def item( - value: time, _parent: Optional["Item"] = ..., _sort_keys: bool = ... -) -> "Time": - ... - - -@overload -def item( - value: Sequence[dict], _parent: Optional["Item"] = ..., _sort_keys: bool = ... -) -> "AoT": - ... - - -@overload -def item( - value: Sequence, _parent: Optional["Item"] = ..., _sort_keys: bool = ... -) -> "Array": - ... - - -@overload -def item(value: dict, _parent: "Array" = ..., _sort_keys: bool = ...) -> "InlineTable": - ... - - -@overload -def item( - value: dict, _parent: Optional["Item"] = ..., _sort_keys: bool = ... -) -> "Table": - ... - - -@overload -def item( - value: ItemT, _parent: Optional["Item"] = ..., _sort_keys: bool = ... -) -> ItemT: - ... - - -def item( - value: Any, _parent: Optional["Item"] = None, _sort_keys: bool = False -) -> "Item": - """Create a TOML item from a Python object. - - :Example: - - >>> item(42) - 42 - >>> item([1, 2, 3]) - [1, 2, 3] - >>> item({'a': 1, 'b': 2}) - a = 1 - b = 2 - """ - - from tomlkit.container import Container - - if isinstance(value, Item): - return value - - if isinstance(value, bool): - return Bool(value, Trivia()) - elif isinstance(value, int): - return Integer(value, Trivia(), str(value)) - elif isinstance(value, float): - return Float(value, Trivia(), str(value)) - elif isinstance(value, dict): - table_constructor = ( - InlineTable if isinstance(_parent, (Array, InlineTable)) else Table - ) - val = table_constructor(Container(), Trivia(), False) - for k, v in sorted( - value.items(), - key=lambda i: (isinstance(i[1], dict), i[0] if _sort_keys else 1), - ): - val[k] = item(v, _parent=val, _sort_keys=_sort_keys) - - return val - elif isinstance(value, (list, tuple)): - if ( - value - and all(isinstance(v, dict) for v in value) - and (_parent is None or isinstance(_parent, Table)) - ): - a = AoT([]) - table_constructor = Table - else: - a = Array([], Trivia()) - table_constructor = InlineTable - - for v in value: - if isinstance(v, dict): - table = table_constructor(Container(), Trivia(), True) - - for k, _v in sorted( - v.items(), - key=lambda i: (isinstance(i[1], dict), i[0] if _sort_keys else 1), - ): - i = item(_v, _parent=table, _sort_keys=_sort_keys) - if isinstance(table, InlineTable): - i.trivia.trail = "" - - table[k] = i - - v = table - - a.append(v) - - return a - elif isinstance(value, str): - return String.from_raw(value) - elif isinstance(value, datetime): - return DateTime( - value.year, - value.month, - value.day, - value.hour, - value.minute, - value.second, - value.microsecond, - value.tzinfo, - Trivia(), - value.isoformat().replace("+00:00", "Z"), - ) - elif isinstance(value, date): - return Date(value.year, value.month, value.day, Trivia(), value.isoformat()) - elif isinstance(value, time): - return Time( - value.hour, - value.minute, - value.second, - value.microsecond, - value.tzinfo, - Trivia(), - value.isoformat(), - ) - - raise ValueError(f"Invalid type {type(value)}") - - -class StringType(Enum): - # Single Line Basic - SLB = '"' - # Multi Line Basic - MLB = '"""' - # Single Line Literal - SLL = "'" - # Multi Line Literal - MLL = "'''" - - @classmethod - def select(cls, literal=False, multiline=False) -> "StringType": - return { - (False, False): cls.SLB, - (False, True): cls.MLB, - (True, False): cls.SLL, - (True, True): cls.MLL, - }[(literal, multiline)] - - @property - def escaped_sequences(self) -> Collection[str]: - # https://toml.io/en/v1.0.0#string - escaped_in_basic = CONTROL_CHARS | {"\\"} - allowed_in_multiline = {"\n", "\r"} - return { - StringType.SLB: escaped_in_basic | {'"'}, - StringType.MLB: (escaped_in_basic | {'"""'}) - allowed_in_multiline, - StringType.SLL: (), - StringType.MLL: (), - }[self] - - @property - def invalid_sequences(self) -> Collection[str]: - # https://toml.io/en/v1.0.0#string - forbidden_in_literal = CONTROL_CHARS - {"\t"} - allowed_in_multiline = {"\n", "\r"} - return { - StringType.SLB: (), - StringType.MLB: (), - StringType.SLL: forbidden_in_literal | {"'"}, - StringType.MLL: (forbidden_in_literal | {"'''"}) - allowed_in_multiline, - }[self] - - @property - def unit(self) -> str: - return self.value[0] - - def is_basic(self) -> bool: - return self in {StringType.SLB, StringType.MLB} - - def is_literal(self) -> bool: - return self in {StringType.SLL, StringType.MLL} - - def is_singleline(self) -> bool: - return self in {StringType.SLB, StringType.SLL} - - def is_multiline(self) -> bool: - return self in {StringType.MLB, StringType.MLL} - - def toggle(self) -> "StringType": - return { - StringType.SLB: StringType.MLB, - StringType.MLB: StringType.SLB, - StringType.SLL: StringType.MLL, - StringType.MLL: StringType.SLL, - }[self] - - -class BoolType(Enum): - TRUE = "true" - FALSE = "false" - - def __bool__(self): - return {BoolType.TRUE: True, BoolType.FALSE: False}[self] - - def __iter__(self): - return iter(self.value) - - def __len__(self): - return len(self.value) - - -class Trivia: - """ - Trivia information (aka metadata). - """ - - def __init__( - self, - indent: str = None, - comment_ws: str = None, - comment: str = None, - trail: str = None, - ) -> None: - # Whitespace before a value. - self.indent = indent or "" - # Whitespace after a value, but before a comment. - self.comment_ws = comment_ws or "" - # Comment, starting with # character, or empty string if no comment. - self.comment = comment or "" - # Trailing newline. - if trail is None: - trail = "\n" - - self.trail = trail - - def copy(self) -> "Trivia": - return type(self)(self.indent, self.comment_ws, self.comment, self.trail) - - -class KeyType(Enum): - """ - The type of a Key. - - Keys can be bare (unquoted), or quoted using basic ("), or literal (') - quotes following the same escaping rules as single-line StringType. - """ - - Bare = "" - Basic = '"' - Literal = "'" - - -class Key(abc.ABC): - """Base class for a key""" - - sep: str - _original: str - _keys: List["SingleKey"] - _dotted: bool - key: str - - @abc.abstractmethod - def __hash__(self) -> int: - pass - - @abc.abstractmethod - def __eq__(self, __o: object) -> bool: - pass - - def is_dotted(self) -> bool: - """If the key is followed by other keys""" - return self._dotted - - def __iter__(self) -> Iterator["SingleKey"]: - return iter(self._keys) - - def concat(self, other: "Key") -> "DottedKey": - """Concatenate keys into a dotted key""" - keys = self._keys + other._keys - return DottedKey(keys, sep=self.sep) - - def is_multi(self) -> bool: - """Check if the key contains multiple keys""" - return len(self._keys) > 1 - - def as_string(self) -> str: - """The TOML representation""" - return self._original - - def __str__(self) -> str: - return self.as_string() - - def __repr__(self) -> str: - return f"" - - -class SingleKey(Key): - """A single key""" - - def __init__( - self, - k: str, - t: Optional[KeyType] = None, - sep: Optional[str] = None, - original: Optional[str] = None, - ) -> None: - if t is None: - if not k or any( - c not in string.ascii_letters + string.digits + "-" + "_" for c in k - ): - t = KeyType.Basic - else: - t = KeyType.Bare - - self.t = t - if sep is None: - sep = " = " - - self.sep = sep - self.key = k - if original is None: - key_str = escape_string(k) if t == KeyType.Basic else k - original = f"{t.value}{key_str}{t.value}" - - self._original = original - self._keys = [self] - self._dotted = False - - @property - def delimiter(self) -> str: - """The delimiter: double quote/single quote/none""" - return self.t.value - - def is_bare(self) -> bool: - """Check if the key is bare""" - return self.t == KeyType.Bare - - def __hash__(self) -> int: - return hash(self.key) - - def __eq__(self, other: Any) -> bool: - if isinstance(other, Key): - return isinstance(other, SingleKey) and self.key == other.key - - return self.key == other - - -class DottedKey(Key): - def __init__( - self, - keys: Iterable[Key], - sep: Optional[str] = None, - original: Optional[str] = None, - ) -> None: - self._keys = list(keys) - if original is None: - original = ".".join(k.as_string() for k in self._keys) - - self.sep = " = " if sep is None else sep - self._original = original - self._dotted = False - self.key = ".".join(k.key for k in self._keys) - - def __hash__(self) -> int: - return hash(tuple(self._keys)) - - def __eq__(self, __o: object) -> bool: - return isinstance(__o, DottedKey) and self._keys == __o._keys - - -class Item: - """ - An item within a TOML document. - """ - - def __init__(self, trivia: Trivia) -> None: - self._trivia = trivia - - @property - def trivia(self) -> Trivia: - """The trivia element associated with this item""" - return self._trivia - - @property - def discriminant(self) -> int: - raise NotImplementedError() - - def as_string(self) -> str: - """The TOML representation""" - raise NotImplementedError() - - @property - def value(self) -> Any: - return self - - def unwrap(self) -> Any: - """Returns as pure python object (ppo)""" - raise NotImplementedError() - - # Helpers - - def comment(self, comment: str) -> "Item": - """Attach a comment to this item""" - if not comment.strip().startswith("#"): - comment = "# " + comment - - self._trivia.comment_ws = " " - self._trivia.comment = comment - - return self - - def indent(self, indent: int) -> "Item": - """Indent this item with given number of spaces""" - if self._trivia.indent.startswith("\n"): - self._trivia.indent = "\n" + " " * indent - else: - self._trivia.indent = " " * indent - - return self - - def is_boolean(self) -> bool: - return isinstance(self, Bool) - - def is_table(self) -> bool: - return isinstance(self, Table) - - def is_inline_table(self) -> bool: - return isinstance(self, InlineTable) - - def is_aot(self) -> bool: - return isinstance(self, AoT) - - def _getstate(self, protocol=3): - return (self._trivia,) - - def __reduce__(self): - return self.__reduce_ex__(2) - - def __reduce_ex__(self, protocol): - return self.__class__, self._getstate(protocol) - - -class Whitespace(Item): - """ - A whitespace literal. - """ - - def __init__(self, s: str, fixed: bool = False) -> None: - self._s = s - self._fixed = fixed - - @property - def s(self) -> str: - return self._s - - @property - def value(self) -> str: - """The wrapped string of the whitespace""" - return self._s - - @property - def trivia(self) -> Trivia: - raise RuntimeError("Called trivia on a Whitespace variant.") - - @property - def discriminant(self) -> int: - return 0 - - def is_fixed(self) -> bool: - """If the whitespace is fixed, it can't be merged or discarded from the output.""" - return self._fixed - - def as_string(self) -> str: - return self._s - - def __repr__(self) -> str: - return f"<{self.__class__.__name__} {repr(self._s)}>" - - def _getstate(self, protocol=3): - return self._s, self._fixed - - -class Comment(Item): - """ - A comment literal. - """ - - @property - def discriminant(self) -> int: - return 1 - - def as_string(self) -> str: - return ( - f"{self._trivia.indent}{decode(self._trivia.comment)}{self._trivia.trail}" - ) - - def __str__(self) -> str: - return f"{self._trivia.indent}{decode(self._trivia.comment)}" - - -class Integer(int, Item): - """ - An integer literal. - """ - - def __new__(cls, value: int, trivia: Trivia, raw: str) -> "Integer": - return super().__new__(cls, value) - - def __init__(self, _: int, trivia: Trivia, raw: str) -> None: - super().__init__(trivia) - - self._raw = raw - self._sign = False - - if re.match(r"^[+\-]\d+$", raw): - self._sign = True - - def unwrap(self) -> int: - return int(self) - - @property - def discriminant(self) -> int: - return 2 - - @property - def value(self) -> int: - """The wrapped integer value""" - return self - - def as_string(self) -> str: - return self._raw - - def __add__(self, other): - return self._new(int(self._raw) + other) - - def __radd__(self, other): - result = super().__radd__(other) - - if isinstance(other, Integer): - return self._new(result) - - return result - - def __sub__(self, other): - result = super().__sub__(other) - - return self._new(result) - - def __rsub__(self, other): - result = super().__rsub__(other) - - if isinstance(other, Integer): - return self._new(result) - - return result - - def _new(self, result): - raw = str(result) - if self._sign: - sign = "+" if result >= 0 else "-" - raw = sign + raw - - return Integer(result, self._trivia, raw) - - def _getstate(self, protocol=3): - return int(self), self._trivia, self._raw - - -class Float(float, Item): - """ - A float literal. - """ - - def __new__(cls, value: float, trivia: Trivia, raw: str) -> Integer: - return super().__new__(cls, value) - - def __init__(self, _: float, trivia: Trivia, raw: str) -> None: - super().__init__(trivia) - - self._raw = raw - self._sign = False - - if re.match(r"^[+\-].+$", raw): - self._sign = True - - def unwrap(self) -> float: - return float(self) - - @property - def discriminant(self) -> int: - return 3 - - @property - def value(self) -> float: - """The wrapped float value""" - return self - - def as_string(self) -> str: - return self._raw - - def __add__(self, other): - result = super().__add__(other) - - return self._new(result) - - def __radd__(self, other): - result = super().__radd__(other) - - if isinstance(other, Float): - return self._new(result) - - return result - - def __sub__(self, other): - result = super().__sub__(other) - - return self._new(result) - - def __rsub__(self, other): - result = super().__rsub__(other) - - if isinstance(other, Float): - return self._new(result) - - return result - - def _new(self, result): - raw = str(result) - - if self._sign: - sign = "+" if result >= 0 else "-" - raw = sign + raw - - return Float(result, self._trivia, raw) - - def _getstate(self, protocol=3): - return float(self), self._trivia, self._raw - - -class Bool(Item): - """ - A boolean literal. - """ - - def __init__(self, t: int, trivia: Trivia) -> None: - super().__init__(trivia) - - self._value = bool(t) - - def unwrap(self) -> bool: - return bool(self) - - @property - def discriminant(self) -> int: - return 4 - - @property - def value(self) -> bool: - """The wrapped boolean value""" - return self._value - - def as_string(self) -> str: - return str(self._value).lower() - - def _getstate(self, protocol=3): - return self._value, self._trivia - - def __bool__(self): - return self._value - - __nonzero__ = __bool__ - - def __eq__(self, other): - if not isinstance(other, bool): - return NotImplemented - - return other == self._value - - def __hash__(self): - return hash(self._value) - - def __repr__(self): - return repr(self._value) - - -class DateTime(Item, datetime): - """ - A datetime literal. - """ - - def __new__( - cls, - year: int, - month: int, - day: int, - hour: int, - minute: int, - second: int, - microsecond: int, - tzinfo: Optional[tzinfo], - *_: Any, - **kwargs: Any, - ) -> datetime: - return datetime.__new__( - cls, - year, - month, - day, - hour, - minute, - second, - microsecond, - tzinfo=tzinfo, - **kwargs, - ) - - def __init__( - self, - year: int, - month: int, - day: int, - hour: int, - minute: int, - second: int, - microsecond: int, - tzinfo: Optional[tzinfo], - trivia: Optional[Trivia] = None, - raw: Optional[str] = None, - **kwargs: Any, - ) -> None: - super().__init__(trivia or Trivia()) - - self._raw = raw or self.isoformat() - - def unwrap(self) -> datetime: - ( - year, - month, - day, - hour, - minute, - second, - microsecond, - tzinfo, - _, - _, - ) = self._getstate() - return datetime(year, month, day, hour, minute, second, microsecond, tzinfo) - - @property - def discriminant(self) -> int: - return 5 - - @property - def value(self) -> datetime: - return self - - def as_string(self) -> str: - return self._raw - - def __add__(self, other): - if PY38: - result = datetime( - self.year, - self.month, - self.day, - self.hour, - self.minute, - self.second, - self.microsecond, - self.tzinfo, - ).__add__(other) - else: - result = super().__add__(other) - - return self._new(result) - - def __sub__(self, other): - if PY38: - result = datetime( - self.year, - self.month, - self.day, - self.hour, - self.minute, - self.second, - self.microsecond, - self.tzinfo, - ).__sub__(other) - else: - result = super().__sub__(other) - - if isinstance(result, datetime): - result = self._new(result) - - return result - - def replace(self, *args: Any, **kwargs: Any) -> datetime: - return self._new(super().replace(*args, **kwargs)) - - def astimezone(self, tz: tzinfo) -> datetime: - result = super().astimezone(tz) - if PY38: - return result - return self._new(result) - - def _new(self, result) -> "DateTime": - raw = result.isoformat() - - return DateTime( - result.year, - result.month, - result.day, - result.hour, - result.minute, - result.second, - result.microsecond, - result.tzinfo, - self._trivia, - raw, - ) - - def _getstate(self, protocol=3): - return ( - self.year, - self.month, - self.day, - self.hour, - self.minute, - self.second, - self.microsecond, - self.tzinfo, - self._trivia, - self._raw, - ) - - -class Date(Item, date): - """ - A date literal. - """ - - def __new__(cls, year: int, month: int, day: int, *_: Any) -> date: - return date.__new__(cls, year, month, day) - - def __init__( - self, year: int, month: int, day: int, trivia: Trivia, raw: str - ) -> None: - super().__init__(trivia) - - self._raw = raw - - def unwrap(self) -> date: - (year, month, day, _, _) = self._getstate() - return date(year, month, day) - - @property - def discriminant(self) -> int: - return 6 - - @property - def value(self) -> date: - return self - - def as_string(self) -> str: - return self._raw - - def __add__(self, other): - if PY38: - result = date(self.year, self.month, self.day).__add__(other) - else: - result = super().__add__(other) - - return self._new(result) - - def __sub__(self, other): - if PY38: - result = date(self.year, self.month, self.day).__sub__(other) - else: - result = super().__sub__(other) - - if isinstance(result, date): - result = self._new(result) - - return result - - def replace(self, *args: Any, **kwargs: Any) -> date: - return self._new(super().replace(*args, **kwargs)) - - def _new(self, result): - raw = result.isoformat() - - return Date(result.year, result.month, result.day, self._trivia, raw) - - def _getstate(self, protocol=3): - return (self.year, self.month, self.day, self._trivia, self._raw) - - -class Time(Item, time): - """ - A time literal. - """ - - def __new__( - cls, - hour: int, - minute: int, - second: int, - microsecond: int, - tzinfo: Optional[tzinfo], - *_: Any, - ) -> time: - return time.__new__(cls, hour, minute, second, microsecond, tzinfo) - - def __init__( - self, - hour: int, - minute: int, - second: int, - microsecond: int, - tzinfo: Optional[tzinfo], - trivia: Trivia, - raw: str, - ) -> None: - super().__init__(trivia) - - self._raw = raw - - def unwrap(self) -> time: - (hour, minute, second, microsecond, tzinfo, _, _) = self._getstate() - return time(hour, minute, second, microsecond, tzinfo) - - @property - def discriminant(self) -> int: - return 7 - - @property - def value(self) -> time: - return self - - def as_string(self) -> str: - return self._raw - - def replace(self, *args: Any, **kwargs: Any) -> time: - return self._new(super().replace(*args, **kwargs)) - - def _new(self, result): - raw = result.isoformat() - - return Time( - result.hour, - result.minute, - result.second, - result.microsecond, - result.tzinfo, - self._trivia, - raw, - ) - - def _getstate(self, protocol: int = 3) -> tuple: - return ( - self.hour, - self.minute, - self.second, - self.microsecond, - self.tzinfo, - self._trivia, - self._raw, - ) - - -class _ArrayItemGroup: - __slots__ = ("value", "indent", "comma", "comment") - - def __init__( - self, - value: Optional[Item] = None, - indent: Optional[Whitespace] = None, - comma: Optional[Whitespace] = None, - comment: Optional[Comment] = None, - ) -> None: - self.value = value - self.indent = indent - self.comma = comma - self.comment = comment - - def __iter__(self) -> Iterator[Item]: - return filter( - lambda x: x is not None, (self.indent, self.value, self.comma, self.comment) - ) - - def __repr__(self) -> str: - return repr(tuple(self)) - - def is_whitespace(self) -> bool: - return self.value is None and self.comment is None - - def __bool__(self) -> bool: - try: - next(iter(self)) - except StopIteration: - return False - return True - - -class Array(Item, _CustomList): - """ - An array literal - """ - - def __init__( - self, value: List[Item], trivia: Trivia, multiline: bool = False - ) -> None: - super().__init__(trivia) - list.__init__( - self, - [v.value for v in value if not isinstance(v, (Whitespace, Comment, Null))], - ) - self._index_map: Dict[int, int] = {} - self._value = self._group_values(value) - self._multiline = multiline - self._reindex() - - def _group_values(self, value: List[Item]) -> List[_ArrayItemGroup]: - """Group the values into (indent, value, comma, comment) tuples""" - groups = [] - this_group = _ArrayItemGroup() - for item in value: - if isinstance(item, Whitespace): - if "," not in item.s: - groups.append(this_group) - this_group = _ArrayItemGroup(indent=item) - else: - if this_group.value is None: - # when comma is met and no value is provided, add a dummy Null - this_group.value = Null() - this_group.comma = item - elif isinstance(item, Comment): - if this_group.value is None: - this_group.value = Null() - this_group.comment = item - elif this_group.value is None: - this_group.value = item - else: - groups.append(this_group) - this_group = _ArrayItemGroup(value=item) - groups.append(this_group) - return [group for group in groups if group] - - def unwrap(self) -> List[Any]: - unwrapped = [] - for v in self: - if isinstance(v, Item): - unwrapped.append(v.unwrap()) - else: - unwrapped.append(v) - return unwrapped - - @property - def discriminant(self) -> int: - return 8 - - @property - def value(self) -> list: - return self - - def _iter_items(self) -> Iterator[Item]: - for v in self._value: - yield from v - - def multiline(self, multiline: bool) -> "Array": - """Change the array to display in multiline or not. - - :Example: - - >>> a = item([1, 2, 3]) - >>> print(a.as_string()) - [1, 2, 3] - >>> print(a.multiline(True).as_string()) - [ - 1, - 2, - 3, - ] - """ - self._multiline = multiline - - return self - - def as_string(self) -> str: - if not self._multiline or not self._value: - return f'[{"".join(v.as_string() for v in self._iter_items())}]' - - s = "[\n" - s += "".join( - self.trivia.indent - + " " * 4 - + v.value.as_string() - + ("," if not isinstance(v.value, Null) else "") - + (v.comment.as_string() if v.comment is not None else "") - + "\n" - for v in self._value - if v.value is not None - ) - s += self.trivia.indent + "]" - - return s - - def _reindex(self) -> None: - self._index_map.clear() - index = 0 - for i, v in enumerate(self._value): - if v.value is None or isinstance(v.value, Null): - continue - self._index_map[index] = i - index += 1 - - def add_line( - self, - *items: Any, - indent: str = " ", - comment: Optional[str] = None, - add_comma: bool = True, - newline: bool = True, - ) -> None: - """Add multiple items in a line to control the format precisely. - When add_comma is True, only accept actual values and - ", " will be added between values automatically. - - :Example: - - >>> a = array() - >>> a.add_line(1, 2, 3) - >>> a.add_line(4, 5, 6) - >>> a.add_line(indent="") - >>> print(a.as_string()) - [ - 1, 2, 3, - 4, 5, 6, - ] - """ - new_values: List[Item] = [] - first_indent = f"\n{indent}" if newline else indent - if first_indent: - new_values.append(Whitespace(first_indent)) - whitespace = "" - data_values = [] - for i, el in enumerate(items): - it = item(el, _parent=self) - if isinstance(it, Comment) or add_comma and isinstance(el, Whitespace): - raise ValueError(f"item type {type(it)} is not allowed in add_line") - if not isinstance(it, Whitespace): - if whitespace: - new_values.append(Whitespace(whitespace)) - whitespace = "" - new_values.append(it) - data_values.append(it.value) - if add_comma: - new_values.append(Whitespace(",")) - if i != len(items) - 1: - new_values.append(Whitespace(" ")) - elif "," not in it.s: - whitespace += it.s - else: - new_values.append(it) - if whitespace: - new_values.append(Whitespace(whitespace)) - if comment: - indent = " " if items else "" - new_values.append( - Comment(Trivia(indent=indent, comment=f"# {comment}", trail="")) - ) - list.extend(self, data_values) - if len(self._value) > 0: - last_item = self._value[-1] - last_value_item = next( - ( - v - for v in self._value[::-1] - if v.value is not None and not isinstance(v.value, Null) - ), - None, - ) - if last_value_item is not None: - last_value_item.comma = Whitespace(",") - if last_item.is_whitespace(): - self._value[-1:-1] = self._group_values(new_values) - else: - self._value.extend(self._group_values(new_values)) - else: - self._value.extend(self._group_values(new_values)) - self._reindex() - - def clear(self) -> None: - """Clear the array.""" - list.clear(self) - self._index_map.clear() - self._value.clear() - - def __len__(self) -> int: - return list.__len__(self) - - def __getitem__(self, key: Union[int, slice]) -> Any: - return list.__getitem__(self, key) - - def __setitem__(self, key: Union[int, slice], value: Any) -> Any: - it = item(value, _parent=self) - list.__setitem__(self, key, it.value) - if isinstance(key, slice): - raise ValueError("slice assignment is not supported") - if key < 0: - key += len(self) - self._value[self._index_map[key]].value = it - - def insert(self, pos: int, value: Any) -> None: - it = item(value, _parent=self) - length = len(self) - if not isinstance(it, (Comment, Whitespace)): - list.insert(self, pos, it.value) - if pos < 0: - pos += length - if pos < 0: - pos = 0 - - idx = 0 # insert position of the self._value list - default_indent = " " - if pos < length: - try: - idx = self._index_map[pos] - except KeyError as e: - raise IndexError("list index out of range") from e - else: - idx = len(self._value) - if idx >= 1 and self._value[idx - 1].is_whitespace(): - # The last item is a pure whitespace(\n ), insert before it - idx -= 1 - if ( - self._value[idx].indent is not None - and "\n" in self._value[idx].indent.s - ): - default_indent = "\n " - indent: Optional[Item] = None - comma: Optional[Item] = Whitespace(",") if pos < length else None - if idx < len(self._value) and not self._value[idx].is_whitespace(): - # Prefer to copy the indentation from the item after - indent = self._value[idx].indent - if idx > 0: - last_item = self._value[idx - 1] - if indent is None: - indent = last_item.indent - if not isinstance(last_item.value, Null) and "\n" in default_indent: - # Copy the comma from the last item if 1) it contains a value and - # 2) the array is multiline - comma = last_item.comma - if last_item.comma is None and not isinstance(last_item.value, Null): - # Add comma to the last item to separate it from the following items. - last_item.comma = Whitespace(",") - if indent is None and (idx > 0 or "\n" in default_indent): - # apply default indent if it isn't the first item or the array is multiline. - indent = Whitespace(default_indent) - new_item = _ArrayItemGroup(value=it, indent=indent, comma=comma) - self._value.insert(idx, new_item) - self._reindex() - - def __delitem__(self, key: Union[int, slice]): - length = len(self) - list.__delitem__(self, key) - - if isinstance(key, slice): - indices_to_remove = list( - range(key.start or 0, key.stop or length, key.step or 1) - ) - else: - indices_to_remove = [length + key if key < 0 else key] - for i in sorted(indices_to_remove, reverse=True): - try: - idx = self._index_map[i] - except KeyError as e: - if not isinstance(key, slice): - raise IndexError("list index out of range") from e - else: - del self._value[idx] - if ( - idx == 0 - and len(self._value) > 0 - and "\n" not in self._value[idx].indent.s - ): - # Remove the indentation of the first item if not newline - self._value[idx].indent = None - if len(self._value) > 0: - v = self._value[-1] - if not v.is_whitespace(): - # remove the comma of the last item - v.comma = None - - self._reindex() - - def __str__(self): - return str([v.value.value for v in self._iter_items() if v.value is not None]) - - def _getstate(self, protocol=3): - return list(self._iter_items()), self._trivia, self._multiline - - -AT = TypeVar("AT", bound="AbstractTable") - - -class AbstractTable(Item, _CustomDict): - """Common behaviour of both :class:`Table` and :class:`InlineTable`""" - - def __init__(self, value: "container.Container", trivia: Trivia): - Item.__init__(self, trivia) - - self._value = value - - for k, v in self._value.body: - if k is not None: - dict.__setitem__(self, k.key, v) - - def unwrap(self) -> Dict[str, Any]: - unwrapped = {} - for k, v in self.items(): - if isinstance(k, Key): - k = k.key - if isinstance(v, Item): - v = v.unwrap() - unwrapped[k] = v - - return unwrapped - - @property - def value(self) -> "container.Container": - return self._value - - @overload - def append(self: AT, key: None, value: Union[Comment, Whitespace]) -> AT: - ... - - @overload - def append(self: AT, key: Union[Key, str], value: Any) -> AT: - ... - - def append(self, key, value): - raise NotImplementedError - - @overload - def add(self: AT, value: Union[Comment, Whitespace]) -> AT: - ... - - @overload - def add(self: AT, key: Union[Key, str], value: Any) -> AT: - ... - - def add(self, key, value=None): - if value is None: - if not isinstance(key, (Comment, Whitespace)): - msg = "Non comment/whitespace items must have an associated key" - raise ValueError(msg) - - key, value = None, key - - return self.append(key, value) - - def remove(self: AT, key: Union[Key, str]) -> AT: - self._value.remove(key) - - if isinstance(key, Key): - key = key.key - - if key is not None: - dict.__delitem__(self, key) - - return self - - def setdefault(self, key: Union[Key, str], default: Any) -> Any: - super().setdefault(key, default) - return self[key] - - def __str__(self): - return str(self.value) - - def copy(self: AT) -> AT: - return copy.copy(self) - - def __repr__(self) -> str: - return repr(self.value) - - def __iter__(self) -> Iterator[str]: - return iter(self._value) - - def __len__(self) -> int: - return len(self._value) - - def __delitem__(self, key: Union[Key, str]) -> None: - self.remove(key) - - def __getitem__(self, key: Union[Key, str]) -> Item: - return cast(Item, self._value[key]) - - def __setitem__(self, key: Union[Key, str], value: Any) -> None: - if not isinstance(value, Item): - value = item(value, _parent=self) - - is_replace = key in self - self._value[key] = value - - if key is not None: - dict.__setitem__(self, key, value) - - if is_replace: - return - m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent) - if not m: - return - - indent = m.group(1) - - if not isinstance(value, Whitespace): - m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent) - if not m: - value.trivia.indent = indent - else: - value.trivia.indent = m.group(1) + indent + m.group(2) - - -class Table(AbstractTable): - """ - A table literal. - """ - - def __init__( - self, - value: "container.Container", - trivia: Trivia, - is_aot_element: bool, - is_super_table: Optional[bool] = None, - name: Optional[str] = None, - display_name: Optional[str] = None, - ) -> None: - super().__init__(value, trivia) - - self.name = name - self.display_name = display_name - self._is_aot_element = is_aot_element - self._is_super_table = is_super_table - - @property - def discriminant(self) -> int: - return 9 - - def __copy__(self) -> "Table": - return type(self)( - self._value.copy(), - self._trivia.copy(), - self._is_aot_element, - self._is_super_table, - self.name, - self.display_name, - ) - - def append(self, key, _item): - """ - Appends a (key, item) to the table. - """ - if not isinstance(_item, Item): - _item = item(_item, _parent=self) - - self._value.append(key, _item) - - if isinstance(key, Key): - key = next(iter(key)).key - _item = self._value[key] - - if key is not None: - dict.__setitem__(self, key, _item) - - m = re.match(r"(?s)^[^ ]*([ ]+).*$", self._trivia.indent) - if not m: - return self - - indent = m.group(1) - - if not isinstance(_item, Whitespace): - m = re.match("(?s)^([^ ]*)(.*)$", _item.trivia.indent) - if not m: - _item.trivia.indent = indent - else: - _item.trivia.indent = m.group(1) + indent + m.group(2) - - return self - - def raw_append(self, key: Union[Key, str], _item: Any) -> "Table": - """Similar to :meth:`append` but does not copy indentation.""" - if not isinstance(_item, Item): - _item = item(_item) - - self._value.append(key, _item) - - if isinstance(key, Key): - key = next(iter(key)).key - _item = self._value[key] - - if key is not None: - dict.__setitem__(self, key, _item) - - return self - - def is_aot_element(self) -> bool: - """True if the table is the direct child of an AOT element.""" - return self._is_aot_element - - def is_super_table(self) -> bool: - """A super table is the intermediate parent of a nested table as in [a.b.c]. - If true, it won't appear in the TOML representation.""" - if self._is_super_table is not None: - return self._is_super_table - # If the table has only one child and that child is a table, then it is a super table. - if len(self) != 1: - return False - only_child = next(iter(self.values())) - return isinstance(only_child, (Table, AoT)) - - def as_string(self) -> str: - return self._value.as_string() - - # Helpers - - def indent(self, indent: int) -> "Table": - """Indent the table with given number of spaces.""" - super().indent(indent) - - m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent) - if not m: - indent_str = "" - else: - indent_str = m.group(1) - - for _, item in self._value.body: - if not isinstance(item, Whitespace): - item.trivia.indent = indent_str + item.trivia.indent - - return self - - def invalidate_display_name(self): - self.display_name = None - - for child in self.values(): - if hasattr(child, "invalidate_display_name"): - child.invalidate_display_name() - - def _getstate(self, protocol: int = 3) -> tuple: - return ( - self._value, - self._trivia, - self._is_aot_element, - self._is_super_table, - self.name, - self.display_name, - ) - - -class InlineTable(AbstractTable): - """ - An inline table literal. - """ - - def __init__( - self, value: "container.Container", trivia: Trivia, new: bool = False - ) -> None: - super().__init__(value, trivia) - - self._new = new - - @property - def discriminant(self) -> int: - return 10 - - def append(self, key, _item): - """ - Appends a (key, item) to the table. - """ - if not isinstance(_item, Item): - _item = item(_item, _parent=self) - - if not isinstance(_item, (Whitespace, Comment)): - if not _item.trivia.indent and len(self._value) > 0 and not self._new: - _item.trivia.indent = " " - if _item.trivia.comment: - _item.trivia.comment = "" - - self._value.append(key, _item) - - if isinstance(key, Key): - key = key.key - - if key is not None: - dict.__setitem__(self, key, _item) - - return self - - def as_string(self) -> str: - buf = "{" - for i, (k, v) in enumerate(self._value.body): - if k is None: - if i == len(self._value.body) - 1: - if self._new: - buf = buf.rstrip(", ") - else: - buf = buf.rstrip(",") - - buf += v.as_string() - - continue - - v_trivia_trail = v.trivia.trail.replace("\n", "") - buf += ( - f"{v.trivia.indent}" - f'{k.as_string() + ("." if k.is_dotted() else "")}' - f"{k.sep}" - f"{v.as_string()}" - f"{v.trivia.comment}" - f"{v_trivia_trail}" - ) - - if i != len(self._value.body) - 1: - buf += "," - if self._new: - buf += " " - - buf += "}" - - return buf - - def __setitem__(self, key: Union[Key, str], value: Any) -> None: - if hasattr(value, "trivia") and value.trivia.comment: - value.trivia.comment = "" - super().__setitem__(key, value) - - def __copy__(self) -> "InlineTable": - return type(self)(self._value.copy(), self._trivia.copy(), self._new) - - def _getstate(self, protocol: int = 3) -> tuple: - return (self._value, self._trivia) - - -class String(str, Item): - """ - A string literal. - """ - - def __new__(cls, t, value, original, trivia): - return super().__new__(cls, value) - - def __init__(self, t: StringType, _: str, original: str, trivia: Trivia) -> None: - super().__init__(trivia) - - self._t = t - self._original = original - - def unwrap(self) -> str: - return str(self) - - @property - def discriminant(self) -> int: - return 11 - - @property - def value(self) -> str: - return self - - def as_string(self) -> str: - return f"{self._t.value}{decode(self._original)}{self._t.value}" - - def __add__(self: ItemT, other: str) -> ItemT: - if not isinstance(other, str): - return NotImplemented - result = super().__add__(other) - original = self._original + getattr(other, "_original", other) - - return self._new(result, original) - - def _new(self, result: str, original: str) -> "String": - return String(self._t, result, original, self._trivia) - - def _getstate(self, protocol=3): - return self._t, str(self), self._original, self._trivia - - @classmethod - def from_raw(cls, value: str, type_=StringType.SLB, escape=True) -> "String": - value = decode(value) - - invalid = type_.invalid_sequences - if any(c in value for c in invalid): - raise InvalidStringError(value, invalid, type_.value) - - escaped = type_.escaped_sequences - string_value = escape_string(value, escaped) if escape and escaped else value - - return cls(type_, decode(value), string_value, Trivia()) - - -class AoT(Item, _CustomList): - """ - An array of table literal - """ - - def __init__( - self, body: List[Table], name: Optional[str] = None, parsed: bool = False - ) -> None: - self.name = name - self._body: List[Table] = [] - self._parsed = parsed - - super().__init__(Trivia(trail="")) - - for table in body: - self.append(table) - - def unwrap(self) -> List[Dict[str, Any]]: - unwrapped = [] - for t in self._body: - if isinstance(t, Item): - unwrapped.append(t.unwrap()) - else: - unwrapped.append(t) - return unwrapped - - @property - def body(self) -> List[Table]: - return self._body - - @property - def discriminant(self) -> int: - return 12 - - @property - def value(self) -> List[Dict[Any, Any]]: - return [v.value for v in self._body] - - def __len__(self) -> int: - return len(self._body) - - @overload - def __getitem__(self, key: slice) -> List[Table]: - ... - - @overload - def __getitem__(self, key: int) -> Table: - ... - - def __getitem__(self, key): - return self._body[key] - - def __setitem__(self, key: Union[slice, int], value: Any) -> None: - raise NotImplementedError - - def __delitem__(self, key: Union[slice, int]) -> None: - del self._body[key] - list.__delitem__(self, key) - - def insert(self, index: int, value: dict) -> None: - value = item(value, _parent=self) - if not isinstance(value, Table): - raise ValueError(f"Unsupported insert value type: {type(value)}") - length = len(self) - if index < 0: - index += length - if index < 0: - index = 0 - elif index >= length: - index = length - m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent) - if m: - indent = m.group(1) - - m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent) - if not m: - value.trivia.indent = indent - else: - value.trivia.indent = m.group(1) + indent + m.group(2) - prev_table = self._body[index - 1] if 0 < index and length else None - next_table = self._body[index + 1] if index < length - 1 else None - if not self._parsed: - if prev_table and "\n" not in value.trivia.indent: - value.trivia.indent = "\n" + value.trivia.indent - if next_table and "\n" not in next_table.trivia.indent: - next_table.trivia.indent = "\n" + next_table.trivia.indent - self._body.insert(index, value) - list.insert(self, index, value) - - def invalidate_display_name(self): - """Call ``invalidate_display_name`` on the contained tables""" - for child in self: - if hasattr(child, "invalidate_display_name"): - child.invalidate_display_name() - - def as_string(self) -> str: - b = "" - for table in self._body: - b += table.as_string() - - return b - - def __repr__(self) -> str: - return f"" - - def _getstate(self, protocol=3): - return self._body, self.name, self._parsed - - -class Null(Item): - """ - A null item. - """ - - def __init__(self) -> None: - pass - - def unwrap(self) -> None: - return None - - @property - def discriminant(self) -> int: - return -1 - - @property - def value(self) -> None: - return None - - def as_string(self) -> str: - return "" - - def _getstate(self, protocol=3) -> tuple: - return () diff --git a/src/poetry/core/_vendor/tomlkit/parser.py b/src/poetry/core/_vendor/tomlkit/parser.py deleted file mode 100644 index c6393a575..000000000 --- a/src/poetry/core/_vendor/tomlkit/parser.py +++ /dev/null @@ -1,1138 +0,0 @@ -import datetime -import re -import string - -from typing import List -from typing import Optional -from typing import Tuple -from typing import Type -from typing import Union - -from tomlkit._compat import decode -from tomlkit._utils import RFC_3339_LOOSE -from tomlkit._utils import _escaped -from tomlkit._utils import parse_rfc3339 -from tomlkit.container import Container -from tomlkit.exceptions import EmptyKeyError -from tomlkit.exceptions import EmptyTableNameError -from tomlkit.exceptions import InternalParserError -from tomlkit.exceptions import InvalidCharInStringError -from tomlkit.exceptions import InvalidControlChar -from tomlkit.exceptions import InvalidDateError -from tomlkit.exceptions import InvalidDateTimeError -from tomlkit.exceptions import InvalidNumberError -from tomlkit.exceptions import InvalidTimeError -from tomlkit.exceptions import InvalidUnicodeValueError -from tomlkit.exceptions import ParseError -from tomlkit.exceptions import UnexpectedCharError -from tomlkit.exceptions import UnexpectedEofError -from tomlkit.items import AoT -from tomlkit.items import Array -from tomlkit.items import Bool -from tomlkit.items import BoolType -from tomlkit.items import Comment -from tomlkit.items import Date -from tomlkit.items import DateTime -from tomlkit.items import Float -from tomlkit.items import InlineTable -from tomlkit.items import Integer -from tomlkit.items import Item -from tomlkit.items import Key -from tomlkit.items import KeyType -from tomlkit.items import Null -from tomlkit.items import SingleKey -from tomlkit.items import String -from tomlkit.items import StringType -from tomlkit.items import Table -from tomlkit.items import Time -from tomlkit.items import Trivia -from tomlkit.items import Whitespace -from tomlkit.source import Source -from tomlkit.toml_char import TOMLChar -from tomlkit.toml_document import TOMLDocument - - -CTRL_I = 0x09 # Tab -CTRL_J = 0x0A # Line feed -CTRL_M = 0x0D # Carriage return -CTRL_CHAR_LIMIT = 0x1F -CHR_DEL = 0x7F - - -class Parser: - """ - Parser for TOML documents. - """ - - def __init__(self, string: str) -> None: - # Input to parse - self._src = Source(decode(string)) - - self._aot_stack: List[Key] = [] - - @property - def _state(self): - return self._src.state - - @property - def _idx(self): - return self._src.idx - - @property - def _current(self): - return self._src.current - - @property - def _marker(self): - return self._src.marker - - def extract(self) -> str: - """ - Extracts the value between marker and index - """ - return self._src.extract() - - def inc(self, exception: Optional[Type[ParseError]] = None) -> bool: - """ - Increments the parser if the end of the input has not been reached. - Returns whether or not it was able to advance. - """ - return self._src.inc(exception=exception) - - def inc_n(self, n: int, exception: Optional[Type[ParseError]] = None) -> bool: - """ - Increments the parser by n characters - if the end of the input has not been reached. - """ - return self._src.inc_n(n=n, exception=exception) - - def consume(self, chars, min=0, max=-1): - """ - Consume chars until min/max is satisfied is valid. - """ - return self._src.consume(chars=chars, min=min, max=max) - - def end(self) -> bool: - """ - Returns True if the parser has reached the end of the input. - """ - return self._src.end() - - def mark(self) -> None: - """ - Sets the marker to the index's current position - """ - self._src.mark() - - def parse_error(self, exception=ParseError, *args, **kwargs): - """ - Creates a generic "parse error" at the current position. - """ - return self._src.parse_error(exception, *args, **kwargs) - - def parse(self) -> TOMLDocument: - body = TOMLDocument(True) - - # Take all keyvals outside of tables/AoT's. - while not self.end(): - # Break out if a table is found - if self._current == "[": - break - - # Otherwise, take and append one KV - item = self._parse_item() - if not item: - break - - key, value = item - if (key is not None and key.is_multi()) or not self._merge_ws(value, body): - # We actually have a table - try: - body.append(key, value) - except Exception as e: - raise self.parse_error(ParseError, str(e)) from e - - self.mark() - - while not self.end(): - key, value = self._parse_table() - if isinstance(value, Table) and value.is_aot_element(): - # This is just the first table in an AoT. Parse the rest of the array - # along with it. - value = self._parse_aot(value, key) - - try: - body.append(key, value) - except Exception as e: - raise self.parse_error(ParseError, str(e)) from e - - body.parsing(False) - - return body - - def _merge_ws(self, item: Item, container: Container) -> bool: - """ - Merges the given Item with the last one currently in the given Container if - both are whitespace items. - - Returns True if the items were merged. - """ - last = container.last_item() - if not last: - return False - - if not isinstance(item, Whitespace) or not isinstance(last, Whitespace): - return False - - start = self._idx - (len(last.s) + len(item.s)) - container.body[-1] = ( - container.body[-1][0], - Whitespace(self._src[start : self._idx]), - ) - - return True - - def _is_child(self, parent: Key, child: Key) -> bool: - """ - Returns whether a key is strictly a child of another key. - AoT siblings are not considered children of one another. - """ - parent_parts = tuple(parent) - child_parts = tuple(child) - - if parent_parts == child_parts: - return False - - return parent_parts == child_parts[: len(parent_parts)] - - def _parse_item(self) -> Optional[Tuple[Optional[Key], Item]]: - """ - Attempts to parse the next item and returns it, along with its key - if the item is value-like. - """ - self.mark() - with self._state as state: - while True: - c = self._current - if c == "\n": - # Found a newline; Return all whitespace found up to this point. - self.inc() - - return None, Whitespace(self.extract()) - elif c in " \t\r": - # Skip whitespace. - if not self.inc(): - return None, Whitespace(self.extract()) - elif c == "#": - # Found a comment, parse it - indent = self.extract() - cws, comment, trail = self._parse_comment_trail() - - return None, Comment(Trivia(indent, cws, comment, trail)) - elif c == "[": - # Found a table, delegate to the calling function. - return - else: - # Beginning of a KV pair. - # Return to beginning of whitespace so it gets included - # as indentation for the KV about to be parsed. - state.restore = True - break - - return self._parse_key_value(True) - - def _parse_comment_trail(self, parse_trail: bool = True) -> Tuple[str, str, str]: - """ - Returns (comment_ws, comment, trail) - If there is no comment, comment_ws and comment will - simply be empty. - """ - if self.end(): - return "", "", "" - - comment = "" - comment_ws = "" - self.mark() - - while True: - c = self._current - - if c == "\n": - break - elif c == "#": - comment_ws = self.extract() - - self.mark() - self.inc() # Skip # - - # The comment itself - while not self.end() and not self._current.is_nl(): - code = ord(self._current) - if code == CHR_DEL or code <= CTRL_CHAR_LIMIT and code != CTRL_I: - raise self.parse_error(InvalidControlChar, code, "comments") - - if not self.inc(): - break - - comment = self.extract() - self.mark() - - break - elif c in " \t\r": - self.inc() - else: - raise self.parse_error(UnexpectedCharError, c) - - if self.end(): - break - - trail = "" - if parse_trail: - while self._current.is_spaces() and self.inc(): - pass - - if self._current == "\r": - self.inc() - - if self._current == "\n": - self.inc() - - if self._idx != self._marker or self._current.is_ws(): - trail = self.extract() - - return comment_ws, comment, trail - - def _parse_key_value(self, parse_comment: bool = False) -> Tuple[Key, Item]: - # Leading indent - self.mark() - - while self._current.is_spaces() and self.inc(): - pass - - indent = self.extract() - - # Key - key = self._parse_key() - - self.mark() - - found_equals = self._current == "=" - while self._current.is_kv_sep() and self.inc(): - if self._current == "=": - if found_equals: - raise self.parse_error(UnexpectedCharError, "=") - else: - found_equals = True - if not found_equals: - raise self.parse_error(UnexpectedCharError, self._current) - - if not key.sep: - key.sep = self.extract() - else: - key.sep += self.extract() - - # Value - val = self._parse_value() - # Comment - if parse_comment: - cws, comment, trail = self._parse_comment_trail() - meta = val.trivia - if not meta.comment_ws: - meta.comment_ws = cws - - meta.comment = comment - meta.trail = trail - else: - val.trivia.trail = "" - - val.trivia.indent = indent - - return key, val - - def _parse_key(self) -> Key: - """ - Parses a Key at the current position; - WS before the key must be exhausted first at the callsite. - """ - self.mark() - while self._current.is_spaces() and self.inc(): - # Skip any leading whitespace - pass - if self._current in "\"'": - return self._parse_quoted_key() - else: - return self._parse_bare_key() - - def _parse_quoted_key(self) -> Key: - """ - Parses a key enclosed in either single or double quotes. - """ - # Extract the leading whitespace - original = self.extract() - quote_style = self._current - key_type = next((t for t in KeyType if t.value == quote_style), None) - - if key_type is None: - raise RuntimeError("Should not have entered _parse_quoted_key()") - - key_str = self._parse_string( - StringType.SLB if key_type == KeyType.Basic else StringType.SLL - ) - if key_str._t.is_multiline(): - raise self.parse_error(UnexpectedCharError, key_str._t.value) - original += key_str.as_string() - self.mark() - while self._current.is_spaces() and self.inc(): - pass - original += self.extract() - key = SingleKey(str(key_str), t=key_type, sep="", original=original) - if self._current == ".": - self.inc() - key = key.concat(self._parse_key()) - - return key - - def _parse_bare_key(self) -> Key: - """ - Parses a bare key. - """ - while ( - self._current.is_bare_key_char() or self._current.is_spaces() - ) and self.inc(): - pass - - original = self.extract() - key = original.strip() - if not key: - # Empty key - raise self.parse_error(EmptyKeyError) - - if " " in key: - # Bare key with spaces in it - raise self.parse_error(ParseError, f'Invalid key "{key}"') - - key = SingleKey(key, KeyType.Bare, "", original) - - if self._current == ".": - self.inc() - key = key.concat(self._parse_key()) - - return key - - def _parse_value(self) -> Item: - """ - Attempts to parse a value at the current position. - """ - self.mark() - c = self._current - trivia = Trivia() - - if c == StringType.SLB.value: - return self._parse_basic_string() - elif c == StringType.SLL.value: - return self._parse_literal_string() - elif c == BoolType.TRUE.value[0]: - return self._parse_true() - elif c == BoolType.FALSE.value[0]: - return self._parse_false() - elif c == "[": - return self._parse_array() - elif c == "{": - return self._parse_inline_table() - elif c in "+-" or self._peek(4) in { - "+inf", - "-inf", - "inf", - "+nan", - "-nan", - "nan", - }: - # Number - while self._current not in " \t\n\r#,]}" and self.inc(): - pass - - raw = self.extract() - - item = self._parse_number(raw, trivia) - if item is not None: - return item - - raise self.parse_error(InvalidNumberError) - elif c in string.digits: - # Integer, Float, Date, Time or DateTime - while self._current not in " \t\n\r#,]}" and self.inc(): - pass - - raw = self.extract() - - m = RFC_3339_LOOSE.match(raw) - if m: - if m.group(1) and m.group(5): - # datetime - try: - dt = parse_rfc3339(raw) - assert isinstance(dt, datetime.datetime) - return DateTime( - dt.year, - dt.month, - dt.day, - dt.hour, - dt.minute, - dt.second, - dt.microsecond, - dt.tzinfo, - trivia, - raw, - ) - except ValueError: - raise self.parse_error(InvalidDateTimeError) - - if m.group(1): - try: - dt = parse_rfc3339(raw) - assert isinstance(dt, datetime.date) - date = Date(dt.year, dt.month, dt.day, trivia, raw) - self.mark() - while self._current not in "\t\n\r#,]}" and self.inc(): - pass - - time_raw = self.extract() - if not time_raw.strip(): - trivia.comment_ws = time_raw - return date - - dt = parse_rfc3339(raw + time_raw) - assert isinstance(dt, datetime.datetime) - return DateTime( - dt.year, - dt.month, - dt.day, - dt.hour, - dt.minute, - dt.second, - dt.microsecond, - dt.tzinfo, - trivia, - raw + time_raw, - ) - except ValueError: - raise self.parse_error(InvalidDateError) - - if m.group(5): - try: - t = parse_rfc3339(raw) - assert isinstance(t, datetime.time) - return Time( - t.hour, - t.minute, - t.second, - t.microsecond, - t.tzinfo, - trivia, - raw, - ) - except ValueError: - raise self.parse_error(InvalidTimeError) - - item = self._parse_number(raw, trivia) - if item is not None: - return item - - raise self.parse_error(InvalidNumberError) - else: - raise self.parse_error(UnexpectedCharError, c) - - def _parse_true(self): - return self._parse_bool(BoolType.TRUE) - - def _parse_false(self): - return self._parse_bool(BoolType.FALSE) - - def _parse_bool(self, style: BoolType) -> Bool: - with self._state: - style = BoolType(style) - - # only keep parsing for bool if the characters match the style - # try consuming rest of chars in style - for c in style: - self.consume(c, min=1, max=1) - - return Bool(style, Trivia()) - - def _parse_array(self) -> Array: - # Consume opening bracket, EOF here is an issue (middle of array) - self.inc(exception=UnexpectedEofError) - - elems: List[Item] = [] - prev_value = None - while True: - # consume whitespace - mark = self._idx - self.consume(TOMLChar.SPACES + TOMLChar.NL) - indent = self._src[mark : self._idx] - newline = set(TOMLChar.NL) & set(indent) - if newline: - elems.append(Whitespace(indent)) - continue - - # consume comment - if self._current == "#": - cws, comment, trail = self._parse_comment_trail(parse_trail=False) - elems.append(Comment(Trivia(indent, cws, comment, trail))) - continue - - # consume indent - if indent: - elems.append(Whitespace(indent)) - continue - - # consume value - if not prev_value: - try: - elems.append(self._parse_value()) - prev_value = True - continue - except UnexpectedCharError: - pass - - # consume comma - if prev_value and self._current == ",": - self.inc(exception=UnexpectedEofError) - elems.append(Whitespace(",")) - prev_value = False - continue - - # consume closing bracket - if self._current == "]": - # consume closing bracket, EOF here doesn't matter - self.inc() - break - - raise self.parse_error(UnexpectedCharError, self._current) - - try: - res = Array(elems, Trivia()) - except ValueError: - pass - else: - return res - - def _parse_inline_table(self) -> InlineTable: - # consume opening bracket, EOF here is an issue (middle of array) - self.inc(exception=UnexpectedEofError) - - elems = Container(True) - trailing_comma = None - while True: - # consume leading whitespace - mark = self._idx - self.consume(TOMLChar.SPACES) - raw = self._src[mark : self._idx] - if raw: - elems.add(Whitespace(raw)) - - if not trailing_comma: - # None: empty inline table - # False: previous key-value pair was not followed by a comma - if self._current == "}": - # consume closing bracket, EOF here doesn't matter - self.inc() - break - - if ( - trailing_comma is False - or trailing_comma is None - and self._current == "," - ): - # Either the previous key-value pair was not followed by a comma - # or the table has an unexpected leading comma. - raise self.parse_error(UnexpectedCharError, self._current) - else: - # True: previous key-value pair was followed by a comma - if self._current == "}" or self._current == ",": - raise self.parse_error(UnexpectedCharError, self._current) - - key, val = self._parse_key_value(False) - elems.add(key, val) - - # consume trailing whitespace - mark = self._idx - self.consume(TOMLChar.SPACES) - raw = self._src[mark : self._idx] - if raw: - elems.add(Whitespace(raw)) - - # consume trailing comma - trailing_comma = self._current == "," - if trailing_comma: - # consume closing bracket, EOF here is an issue (middle of inline table) - self.inc(exception=UnexpectedEofError) - - return InlineTable(elems, Trivia()) - - def _parse_number(self, raw: str, trivia: Trivia) -> Optional[Item]: - # Leading zeros are not allowed - sign = "" - if raw.startswith(("+", "-")): - sign = raw[0] - raw = raw[1:] - - if len(raw) > 1 and ( - raw.startswith("0") - and not raw.startswith(("0.", "0o", "0x", "0b", "0e")) - or sign - and raw.startswith(".") - ): - return None - - if raw.startswith(("0o", "0x", "0b")) and sign: - return None - - digits = "[0-9]" - base = 10 - if raw.startswith("0b"): - digits = "[01]" - base = 2 - elif raw.startswith("0o"): - digits = "[0-7]" - base = 8 - elif raw.startswith("0x"): - digits = "[0-9a-f]" - base = 16 - - # Underscores should be surrounded by digits - clean = re.sub(f"(?i)(?<={digits})_(?={digits})", "", raw).lower() - - if "_" in clean: - return None - - if ( - clean.endswith(".") - or not clean.startswith("0x") - and clean.split("e", 1)[0].endswith(".") - ): - return None - - try: - return Integer(int(sign + clean, base), trivia, sign + raw) - except ValueError: - try: - return Float(float(sign + clean), trivia, sign + raw) - except ValueError: - return None - - def _parse_literal_string(self) -> String: - with self._state: - return self._parse_string(StringType.SLL) - - def _parse_basic_string(self) -> String: - with self._state: - return self._parse_string(StringType.SLB) - - def _parse_escaped_char(self, multiline): - if multiline and self._current.is_ws(): - # When the last non-whitespace character on a line is - # a \, it will be trimmed along with all whitespace - # (including newlines) up to the next non-whitespace - # character or closing delimiter. - # """\ - # hello \ - # world""" - tmp = "" - while self._current.is_ws(): - tmp += self._current - # consume the whitespace, EOF here is an issue - # (middle of string) - self.inc(exception=UnexpectedEofError) - continue - - # the escape followed by whitespace must have a newline - # before any other chars - if "\n" not in tmp: - raise self.parse_error(InvalidCharInStringError, self._current) - - return "" - - if self._current in _escaped: - c = _escaped[self._current] - - # consume this char, EOF here is an issue (middle of string) - self.inc(exception=UnexpectedEofError) - - return c - - if self._current in {"u", "U"}: - # this needs to be a unicode - u, ue = self._peek_unicode(self._current == "U") - if u is not None: - # consume the U char and the unicode value - self.inc_n(len(ue) + 1) - - return u - - raise self.parse_error(InvalidUnicodeValueError) - - raise self.parse_error(InvalidCharInStringError, self._current) - - def _parse_string(self, delim: StringType) -> String: - # only keep parsing for string if the current character matches the delim - if self._current != delim.unit: - raise self.parse_error( - InternalParserError, - f"Invalid character for string type {delim}", - ) - - # consume the opening/first delim, EOF here is an issue - # (middle of string or middle of delim) - self.inc(exception=UnexpectedEofError) - - if self._current == delim.unit: - # consume the closing/second delim, we do not care if EOF occurs as - # that would simply imply an empty single line string - if not self.inc() or self._current != delim.unit: - # Empty string - return String(delim, "", "", Trivia()) - - # consume the third delim, EOF here is an issue (middle of string) - self.inc(exception=UnexpectedEofError) - - delim = delim.toggle() # convert delim to multi delim - - self.mark() # to extract the original string with whitespace and all - value = "" - - # A newline immediately following the opening delimiter will be trimmed. - if delim.is_multiline() and self._current == "\n": - # consume the newline, EOF here is an issue (middle of string) - self.inc(exception=UnexpectedEofError) - - escaped = False # whether the previous key was ESCAPE - while True: - code = ord(self._current) - if ( - delim.is_singleline() - and not escaped - and (code == CHR_DEL or code <= CTRL_CHAR_LIMIT and code != CTRL_I) - ) or ( - delim.is_multiline() - and not escaped - and ( - code == CHR_DEL - or code <= CTRL_CHAR_LIMIT - and code not in [CTRL_I, CTRL_J, CTRL_M] - ) - ): - raise self.parse_error(InvalidControlChar, code, "strings") - elif not escaped and self._current == delim.unit: - # try to process current as a closing delim - original = self.extract() - - close = "" - if delim.is_multiline(): - # Consume the delimiters to see if we are at the end of the string - close = "" - while self._current == delim.unit: - close += self._current - self.inc() - - if len(close) < 3: - # Not a triple quote, leave in result as-is. - # Adding back the characters we already consumed - value += close - continue - - if len(close) == 3: - # We are at the end of the string - return String(delim, value, original, Trivia()) - - if len(close) >= 6: - raise self.parse_error(InvalidCharInStringError, self._current) - - value += close[:-3] - original += close[:-3] - - return String(delim, value, original, Trivia()) - else: - # consume the closing delim, we do not care if EOF occurs as - # that would simply imply the end of self._src - self.inc() - - return String(delim, value, original, Trivia()) - elif delim.is_basic() and escaped: - # attempt to parse the current char as an escaped value, an exception - # is raised if this fails - value += self._parse_escaped_char(delim.is_multiline()) - - # no longer escaped - escaped = False - elif delim.is_basic() and self._current == "\\": - # the next char is being escaped - escaped = True - - # consume this char, EOF here is an issue (middle of string) - self.inc(exception=UnexpectedEofError) - else: - # this is either a literal string where we keep everything as is, - # or this is not a special escaped char in a basic string - value += self._current - - # consume this char, EOF here is an issue (middle of string) - self.inc(exception=UnexpectedEofError) - - def _parse_table( - self, parent_name: Optional[Key] = None, parent: Optional[Table] = None - ) -> Tuple[Key, Union[Table, AoT]]: - """ - Parses a table element. - """ - if self._current != "[": - raise self.parse_error( - InternalParserError, "_parse_table() called on non-bracket character." - ) - - indent = self.extract() - self.inc() # Skip opening bracket - - if self.end(): - raise self.parse_error(UnexpectedEofError) - - is_aot = False - if self._current == "[": - if not self.inc(): - raise self.parse_error(UnexpectedEofError) - - is_aot = True - try: - key = self._parse_key() - except EmptyKeyError: - raise self.parse_error(EmptyTableNameError) from None - if self.end(): - raise self.parse_error(UnexpectedEofError) - elif self._current != "]": - raise self.parse_error(UnexpectedCharError, self._current) - elif not key.key.strip(): - raise self.parse_error(EmptyTableNameError) - - key.sep = "" - full_key = key - name_parts = tuple(key) - if any(" " in part.key.strip() and part.is_bare() for part in name_parts): - raise self.parse_error( - ParseError, f'Invalid table name "{full_key.as_string()}"' - ) - - missing_table = False - if parent_name: - parent_name_parts = tuple(parent_name) - else: - parent_name_parts = () - - if len(name_parts) > len(parent_name_parts) + 1: - missing_table = True - - name_parts = name_parts[len(parent_name_parts) :] - - values = Container(True) - - self.inc() # Skip closing bracket - if is_aot: - # TODO: Verify close bracket - self.inc() - - cws, comment, trail = self._parse_comment_trail() - - result = Null() - table = Table( - values, - Trivia(indent, cws, comment, trail), - is_aot, - name=name_parts[0].key if name_parts else key.key, - display_name=full_key.as_string(), - is_super_table=False, - ) - - if len(name_parts) > 1: - if missing_table: - # Missing super table - # i.e. a table initialized like this: [foo.bar] - # without initializing [foo] - # - # So we have to create the parent tables - table = Table( - Container(True), - Trivia(indent, cws, comment, trail), - is_aot and name_parts[0] in self._aot_stack, - is_super_table=True, - name=name_parts[0].key, - ) - - result = table - key = name_parts[0] - - for i, _name in enumerate(name_parts[1:]): - child = table.get( - _name, - Table( - Container(True), - Trivia(indent, cws, comment, trail), - is_aot and i == len(name_parts) - 2, - is_super_table=i < len(name_parts) - 2, - name=_name.key, - display_name=full_key.as_string() - if i == len(name_parts) - 2 - else None, - ), - ) - - if is_aot and i == len(name_parts) - 2: - table.raw_append(_name, AoT([child], name=table.name, parsed=True)) - else: - table.raw_append(_name, child) - - table = child - values = table.value - else: - if name_parts: - key = name_parts[0] - - while not self.end(): - item = self._parse_item() - if item: - _key, item = item - if not self._merge_ws(item, values): - table.raw_append(_key, item) - else: - if self._current == "[": - _, key_next = self._peek_table() - - if self._is_child(full_key, key_next): - key_next, table_next = self._parse_table(full_key, table) - - table.raw_append(key_next, table_next) - - # Picking up any sibling - while not self.end(): - _, key_next = self._peek_table() - - if not self._is_child(full_key, key_next): - break - - key_next, table_next = self._parse_table(full_key, table) - - table.raw_append(key_next, table_next) - - break - else: - raise self.parse_error( - InternalParserError, - "_parse_item() returned None on a non-bracket character.", - ) - - if isinstance(result, Null): - result = table - - if is_aot and (not self._aot_stack or full_key != self._aot_stack[-1]): - result = self._parse_aot(result, full_key) - - return key, result - - def _peek_table(self) -> Tuple[bool, Key]: - """ - Peeks ahead non-intrusively by cloning then restoring the - initial state of the parser. - - Returns the name of the table about to be parsed, - as well as whether it is part of an AoT. - """ - # we always want to restore after exiting this scope - with self._state(save_marker=True, restore=True): - if self._current != "[": - raise self.parse_error( - InternalParserError, - "_peek_table() entered on non-bracket character", - ) - - # AoT - self.inc() - is_aot = False - if self._current == "[": - self.inc() - is_aot = True - try: - return is_aot, self._parse_key() - except EmptyKeyError: - raise self.parse_error(EmptyTableNameError) from None - - def _parse_aot(self, first: Table, name_first: Key) -> AoT: - """ - Parses all siblings of the provided table first and bundles them into - an AoT. - """ - payload = [first] - self._aot_stack.append(name_first) - while not self.end(): - is_aot_next, name_next = self._peek_table() - if is_aot_next and name_next == name_first: - _, table = self._parse_table(name_first) - payload.append(table) - else: - break - - self._aot_stack.pop() - - return AoT(payload, parsed=True) - - def _peek(self, n: int) -> str: - """ - Peeks ahead n characters. - - n is the max number of characters that will be peeked. - """ - # we always want to restore after exiting this scope - with self._state(restore=True): - buf = "" - for _ in range(n): - if self._current not in " \t\n\r#,]}" + self._src.EOF: - buf += self._current - self.inc() - continue - - break - return buf - - def _peek_unicode(self, is_long: bool) -> Tuple[Optional[str], Optional[str]]: - """ - Peeks ahead non-intrusively by cloning then restoring the - initial state of the parser. - - Returns the unicode value is it's a valid one else None. - """ - # we always want to restore after exiting this scope - with self._state(save_marker=True, restore=True): - if self._current not in {"u", "U"}: - raise self.parse_error( - InternalParserError, "_peek_unicode() entered on non-unicode value" - ) - - self.inc() # Dropping prefix - self.mark() - - if is_long: - chars = 8 - else: - chars = 4 - - if not self.inc_n(chars): - value, extracted = None, None - else: - extracted = self.extract() - - if extracted[0].lower() == "d" and extracted[1].strip("01234567"): - return None, None - - try: - value = chr(int(extracted, 16)) - except (ValueError, OverflowError): - value = None - - return value, extracted diff --git a/src/poetry/core/_vendor/tomlkit/py.typed b/src/poetry/core/_vendor/tomlkit/py.typed deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/poetry/core/_vendor/tomlkit/source.py b/src/poetry/core/_vendor/tomlkit/source.py deleted file mode 100644 index d1a53cdd5..000000000 --- a/src/poetry/core/_vendor/tomlkit/source.py +++ /dev/null @@ -1,181 +0,0 @@ -from copy import copy -from typing import Any -from typing import Optional -from typing import Tuple -from typing import Type - -from tomlkit.exceptions import ParseError -from tomlkit.exceptions import UnexpectedCharError -from tomlkit.toml_char import TOMLChar - - -class _State: - def __init__( - self, - source: "Source", - save_marker: Optional[bool] = False, - restore: Optional[bool] = False, - ) -> None: - self._source = source - self._save_marker = save_marker - self.restore = restore - - def __enter__(self) -> "_State": - # Entering this context manager - save the state - self._chars = copy(self._source._chars) - self._idx = self._source._idx - self._current = self._source._current - self._marker = self._source._marker - - return self - - def __exit__(self, exception_type, exception_val, trace): - # Exiting this context manager - restore the prior state - if self.restore or exception_type: - self._source._chars = self._chars - self._source._idx = self._idx - self._source._current = self._current - if self._save_marker: - self._source._marker = self._marker - - -class _StateHandler: - """ - State preserver for the Parser. - """ - - def __init__(self, source: "Source") -> None: - self._source = source - self._states = [] - - def __call__(self, *args, **kwargs): - return _State(self._source, *args, **kwargs) - - def __enter__(self) -> None: - state = self() - self._states.append(state) - return state.__enter__() - - def __exit__(self, exception_type, exception_val, trace): - state = self._states.pop() - return state.__exit__(exception_type, exception_val, trace) - - -class Source(str): - EOF = TOMLChar("\0") - - def __init__(self, _: str) -> None: - super().__init__() - - # Collection of TOMLChars - self._chars = iter([(i, TOMLChar(c)) for i, c in enumerate(self)]) - - self._idx = 0 - self._marker = 0 - self._current = TOMLChar("") - - self._state = _StateHandler(self) - - self.inc() - - def reset(self): - # initialize both idx and current - self.inc() - - # reset marker - self.mark() - - @property - def state(self) -> _StateHandler: - return self._state - - @property - def idx(self) -> int: - return self._idx - - @property - def current(self) -> TOMLChar: - return self._current - - @property - def marker(self) -> int: - return self._marker - - def extract(self) -> str: - """ - Extracts the value between marker and index - """ - return self[self._marker : self._idx] - - def inc(self, exception: Optional[Type[ParseError]] = None) -> bool: - """ - Increments the parser if the end of the input has not been reached. - Returns whether or not it was able to advance. - """ - try: - self._idx, self._current = next(self._chars) - - return True - except StopIteration: - self._idx = len(self) - self._current = self.EOF - if exception: - raise self.parse_error(exception) - - return False - - def inc_n(self, n: int, exception: Optional[Type[ParseError]] = None) -> bool: - """ - Increments the parser by n characters - if the end of the input has not been reached. - """ - return all(self.inc(exception=exception) for _ in range(n)) - - def consume(self, chars, min=0, max=-1): - """ - Consume chars until min/max is satisfied is valid. - """ - while self.current in chars and max != 0: - min -= 1 - max -= 1 - if not self.inc(): - break - - # failed to consume minimum number of characters - if min > 0: - raise self.parse_error(UnexpectedCharError, self.current) - - def end(self) -> bool: - """ - Returns True if the parser has reached the end of the input. - """ - return self._current is self.EOF - - def mark(self) -> None: - """ - Sets the marker to the index's current position - """ - self._marker = self._idx - - def parse_error( - self, - exception: Type[ParseError] = ParseError, - *args: Any, - **kwargs: Any, - ) -> ParseError: - """ - Creates a generic "parse error" at the current position. - """ - line, col = self._to_linecol() - - return exception(line, col, *args, **kwargs) - - def _to_linecol(self) -> Tuple[int, int]: - cur = 0 - for i, line in enumerate(self.splitlines()): - if cur + len(line) + 1 > self.idx: - return (i + 1, self.idx - cur) - - cur += len(line) + 1 - - return len(self.splitlines()), 0 diff --git a/src/poetry/core/_vendor/tomlkit/toml_char.py b/src/poetry/core/_vendor/tomlkit/toml_char.py deleted file mode 100644 index b4bb4110c..000000000 --- a/src/poetry/core/_vendor/tomlkit/toml_char.py +++ /dev/null @@ -1,52 +0,0 @@ -import string - - -class TOMLChar(str): - def __init__(self, c): - super().__init__() - - if len(self) > 1: - raise ValueError("A TOML character must be of length 1") - - BARE = string.ascii_letters + string.digits + "-_" - KV = "= \t" - NUMBER = string.digits + "+-_.e" - SPACES = " \t" - NL = "\n\r" - WS = SPACES + NL - - def is_bare_key_char(self) -> bool: - """ - Whether the character is a valid bare key name or not. - """ - return self in self.BARE - - def is_kv_sep(self) -> bool: - """ - Whether the character is a valid key/value separator or not. - """ - return self in self.KV - - def is_int_float_char(self) -> bool: - """ - Whether the character if a valid integer or float value character or not. - """ - return self in self.NUMBER - - def is_ws(self) -> bool: - """ - Whether the character is a whitespace character or not. - """ - return self in self.WS - - def is_nl(self) -> bool: - """ - Whether the character is a new line character or not. - """ - return self in self.NL - - def is_spaces(self) -> bool: - """ - Whether the character is a space or not - """ - return self in self.SPACES diff --git a/src/poetry/core/_vendor/tomlkit/toml_document.py b/src/poetry/core/_vendor/tomlkit/toml_document.py deleted file mode 100644 index 71fac2e10..000000000 --- a/src/poetry/core/_vendor/tomlkit/toml_document.py +++ /dev/null @@ -1,7 +0,0 @@ -from tomlkit.container import Container - - -class TOMLDocument(Container): - """ - A TOML document. - """ diff --git a/src/poetry/core/_vendor/tomlkit/toml_file.py b/src/poetry/core/_vendor/tomlkit/toml_file.py deleted file mode 100644 index 745913080..000000000 --- a/src/poetry/core/_vendor/tomlkit/toml_file.py +++ /dev/null @@ -1,58 +0,0 @@ -import os -import re - -from typing import TYPE_CHECKING - -from tomlkit.api import loads -from tomlkit.toml_document import TOMLDocument - - -if TYPE_CHECKING: - from _typeshed import StrPath as _StrPath -else: - from typing import Union - - _StrPath = Union[str, os.PathLike] - - -class TOMLFile: - """ - Represents a TOML file. - - :param path: path to the TOML file - """ - - def __init__(self, path: _StrPath) -> None: - self._path = path - self._linesep = os.linesep - - def read(self) -> TOMLDocument: - """Read the file content as a :class:`tomlkit.toml_document.TOMLDocument`.""" - with open(self._path, encoding="utf-8", newline="") as f: - content = f.read() - - # check if consistent line endings - num_newline = content.count("\n") - if num_newline > 0: - num_win_eol = content.count("\r\n") - if num_win_eol == num_newline: - self._linesep = "\r\n" - elif num_win_eol == 0: - self._linesep = "\n" - else: - self._linesep = "mixed" - - return loads(content) - - def write(self, data: TOMLDocument) -> None: - """Write the TOMLDocument to the file.""" - content = data.as_string() - - # apply linesep - if self._linesep == "\n": - content = content.replace("\r\n", "\n") - elif self._linesep == "\r\n": - content = re.sub(r"(?= "3.7" and python_version < "4.0" pkgutil-resolve-name==1.3.10 ; python_version >= "3.7" and python_version < "3.9" pyparsing==3.0.9 ; python_version >= "3.7" and python_version < "4.0" pyrsistent==0.19.2 ; python_version >= "3.7" and python_version < "4.0" -tomlkit==0.11.6 ; python_version >= "3.7" and python_version < "4.0" +tomli==2.0.1 ; python_version >= "3.7" and python_version < "4.0" typing-extensions==4.4.0 ; python_version >= "3.7" and python_version < "4.0" diff --git a/src/poetry/core/poetry.py b/src/poetry/core/poetry.py index fbcd464f7..cca428c53 100644 --- a/src/poetry/core/poetry.py +++ b/src/poetry/core/poetry.py @@ -3,13 +3,13 @@ from typing import TYPE_CHECKING from typing import Any +from poetry.core.pyproject.toml import PyProjectTOML + if TYPE_CHECKING: from pathlib import Path from poetry.core.packages.project_package import ProjectPackage - from poetry.core.pyproject.toml import PyProjectTOML - from poetry.core.toml import TOMLFile class Poetry: @@ -18,10 +18,9 @@ def __init__( file: Path, local_config: dict[str, Any], package: ProjectPackage, + pyproject_type: type[PyProjectTOML] = PyProjectTOML, ) -> None: - from poetry.core.pyproject.toml import PyProjectTOML - - self._pyproject = PyProjectTOML(file) + self._pyproject = pyproject_type(file) self._package = package self._local_config = local_config @@ -30,7 +29,7 @@ def pyproject(self) -> PyProjectTOML: return self._pyproject @property - def file(self) -> TOMLFile: + def file(self) -> Path: return self._pyproject.file @property diff --git a/src/poetry/core/pyproject/toml.py b/src/poetry/core/pyproject/toml.py index a9be9e3fc..52864f133 100644 --- a/src/poetry/core/pyproject/toml.py +++ b/src/poetry/core/pyproject/toml.py @@ -1,56 +1,45 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from contextlib import suppress +from pathlib import Path from typing import Any -from tomlkit.api import table +import tomli - -if TYPE_CHECKING: - from pathlib import Path - - from tomlkit.toml_document import TOMLDocument - - from poetry.core.pyproject.tables import BuildSystem - from poetry.core.toml import TOMLFile +from poetry.core.pyproject.tables import BuildSystem class PyProjectTOML: def __init__(self, path: str | Path) -> None: - from poetry.core.toml import TOMLFile - - self._file = TOMLFile(path=path) - self._data: TOMLDocument | None = None + self._file = path if isinstance(path, Path) else Path(path) + self._data: dict[str, Any] | None = None self._build_system: BuildSystem | None = None @property - def file(self) -> TOMLFile: + def file(self) -> Path: return self._file @property - def data(self) -> TOMLDocument: - from tomlkit.toml_document import TOMLDocument - + def data(self) -> dict[str, Any]: if self._data is None: - if not self._file.exists(): - self._data = TOMLDocument() + if not self.file.exists(): + self._data = {} else: - self._data = self._file.read() + with self.file.open("rb") as f: + self._data = tomli.load(f) return self._data def is_build_system_defined(self) -> bool: - return self._file.exists() and "build-system" in self.data + return "build-system" in self.data @property def build_system(self) -> BuildSystem: - from poetry.core.pyproject.tables import BuildSystem - if self._build_system is None: build_backend = None requires = None - if not self._file.exists(): + if not self.file.exists(): build_backend = "poetry.core.masonry.api" requires = ["poetry-core"] @@ -64,51 +53,25 @@ def build_system(self) -> BuildSystem: @property def poetry_config(self) -> dict[str, Any]: - from tomlkit.exceptions import NonExistentKey - try: tool = self.data["tool"] assert isinstance(tool, dict) config = tool["poetry"] assert isinstance(config, dict) return config - except NonExistentKey as e: + except KeyError as e: from poetry.core.pyproject.exceptions import PyProjectException raise PyProjectException( - f"[tool.poetry] section not found in {self._file}" + f"[tool.poetry] section not found in {self._file.as_posix()}" ) from e def is_poetry_project(self) -> bool: from poetry.core.pyproject.exceptions import PyProjectException if self.file.exists(): - try: + with suppress(PyProjectException): _ = self.poetry_config return True - except PyProjectException: - pass return False - - def __getattr__(self, item: str) -> Any: - return getattr(self.data, item) - - def save(self) -> None: - data = self.data - - if self._build_system is not None: - if "build-system" not in data: - data["build-system"] = table() - - build_system = data["build-system"] - assert isinstance(build_system, dict) - - build_system["requires"] = self._build_system.requires - build_system["build-backend"] = self._build_system.build_backend - - self.file.write(data=data) - - def reload(self) -> None: - self._data = None - self._build_system = None diff --git a/src/poetry/core/toml/__init__.py b/src/poetry/core/toml/__init__.py deleted file mode 100644 index 3ce168905..000000000 --- a/src/poetry/core/toml/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import annotations - -from poetry.core.toml.exceptions import TOMLError -from poetry.core.toml.file import TOMLFile - - -__all__ = ["TOMLError", "TOMLFile"] diff --git a/src/poetry/core/toml/exceptions.py b/src/poetry/core/toml/exceptions.py deleted file mode 100644 index efa189cf0..000000000 --- a/src/poetry/core/toml/exceptions.py +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import annotations - -from tomlkit.exceptions import TOMLKitError - -from poetry.core.exceptions import PoetryCoreException - - -class TOMLError(TOMLKitError, PoetryCoreException): # type: ignore[misc] - pass diff --git a/src/poetry/core/toml/file.py b/src/poetry/core/toml/file.py deleted file mode 100644 index 7bc1cd741..000000000 --- a/src/poetry/core/toml/file.py +++ /dev/null @@ -1,42 +0,0 @@ -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING -from typing import Any - -from tomlkit.toml_file import TOMLFile as BaseTOMLFile - - -if TYPE_CHECKING: - from tomlkit.toml_document import TOMLDocument - - -class TOMLFile(BaseTOMLFile): # type: ignore[misc] - def __init__(self, path: str | Path) -> None: - if isinstance(path, str): - path = Path(path) - super().__init__(path.as_posix()) - self.__path = path - - @property - def path(self) -> Path: - return self.__path - - def exists(self) -> bool: - return self.__path.exists() - - def read(self) -> TOMLDocument: - from tomlkit.exceptions import TOMLKitError - - from poetry.core.toml import TOMLError - - try: - return super().read() - except (ValueError, TOMLKitError) as e: - raise TOMLError(f"Invalid TOML file {self.path.as_posix()}: {e}") - - def __getattr__(self, item: str) -> Any: - return getattr(self.__path, item) - - def __str__(self) -> str: - return self.__path.as_posix() diff --git a/src/poetry/core/utils/toml_file.py b/src/poetry/core/utils/toml_file.py deleted file mode 100644 index 5e64029bb..000000000 --- a/src/poetry/core/utils/toml_file.py +++ /dev/null @@ -1,20 +0,0 @@ -from __future__ import annotations - -from typing import Any - -from poetry.core.toml import TOMLFile - - -class TomlFile(TOMLFile): - @classmethod - def __new__(cls: type[TOMLFile], *args: Any, **kwargs: Any) -> TomlFile: - import warnings - - this_import = f"{cls.__module__}.{cls.__name__}" - new_import = f"{TOMLFile.__module__}.{TOMLFile.__name__}" - warnings.warn( - f"Use of {this_import} has been deprecated, use {new_import} instead.", - category=DeprecationWarning, - stacklevel=2, - ) - return super().__new__(cls) # type: ignore[no-any-return,misc] diff --git a/tests/pyproject/test_pyproject_toml.py b/tests/pyproject/test_pyproject_toml.py index dc0927e9c..0a7fb5ae6 100644 --- a/tests/pyproject/test_pyproject_toml.py +++ b/tests/pyproject/test_pyproject_toml.py @@ -1,14 +1,9 @@ from __future__ import annotations -import uuid - from pathlib import Path -from typing import Any import pytest - -from tomlkit.toml_document import TOMLDocument -from tomlkit.toml_file import TOMLFile +import tomli from poetry.core.pyproject.exceptions import PyProjectException from poetry.core.pyproject.toml import PyProjectTOML @@ -17,7 +12,8 @@ def test_pyproject_toml_simple( pyproject_toml: Path, build_system_section: str, poetry_section: str ) -> None: - data = TOMLFile(pyproject_toml.as_posix()).read() + with pyproject_toml.open("rb") as f: + data = tomli.load(f) assert PyProjectTOML(pyproject_toml).data == data @@ -38,7 +34,8 @@ def test_pyproject_toml_poetry_config( pyproject_toml: Path, poetry_section: str ) -> None: pyproject = PyProjectTOML(pyproject_toml) - doc: dict[str, Any] = TOMLFile(pyproject_toml.as_posix()).read() + with pyproject_toml.open("rb") as f: + doc = tomli.load(f) config = doc["tool"]["poetry"] assert pyproject.is_poetry_project() @@ -72,42 +69,6 @@ def test_pyproject_toml_non_existent(pyproject_toml: Path) -> None: pyproject = PyProjectTOML(pyproject_toml) build_system = pyproject.build_system - assert pyproject.data == TOMLDocument() + assert pyproject.data == {} assert build_system.requires == ["poetry-core"] assert build_system.build_backend == "poetry.core.masonry.api" - - -def test_pyproject_toml_reload(pyproject_toml: Path, poetry_section: str) -> None: - pyproject = PyProjectTOML(pyproject_toml) - name_original = pyproject.poetry_config["name"] - name_new = str(uuid.uuid4()) - - pyproject.poetry_config["name"] = name_new - assert isinstance(pyproject.poetry_config["name"], str) - assert pyproject.poetry_config["name"] == name_new - - pyproject.reload() - assert pyproject.poetry_config["name"] == name_original - - -def test_pyproject_toml_save( - pyproject_toml: Path, poetry_section: str, build_system_section: str -) -> None: - pyproject = PyProjectTOML(pyproject_toml) - - name = str(uuid.uuid4()) - build_backend = str(uuid.uuid4()) - build_requires = str(uuid.uuid4()) - - pyproject.poetry_config["name"] = name - pyproject.build_system.build_backend = build_backend - pyproject.build_system.requires.append(build_requires) - - pyproject.save() - - pyproject = PyProjectTOML(pyproject_toml) - - assert isinstance(pyproject.poetry_config["name"], str) - assert pyproject.poetry_config["name"] == name - assert pyproject.build_system.build_backend == build_backend - assert build_requires in pyproject.build_system.requires diff --git a/tests/pyproject/test_pyproject_toml_file.py b/tests/pyproject/test_pyproject_toml_file.py deleted file mode 100644 index 95fd20f91..000000000 --- a/tests/pyproject/test_pyproject_toml_file.py +++ /dev/null @@ -1,39 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING - -import pytest - -from poetry.core.exceptions import PoetryCoreException -from poetry.core.toml import TOMLFile - - -if TYPE_CHECKING: - from pathlib import Path - - -def test_old_pyproject_toml_file_deprecation( - pyproject_toml: Path, build_system_section: str, poetry_section: str -) -> None: - from poetry.core.utils.toml_file import TomlFile - - with pytest.warns(DeprecationWarning): - file = TomlFile(pyproject_toml) - - data = file.read() - assert data == TOMLFile(pyproject_toml).read() - - -def test_pyproject_toml_file_invalid(pyproject_toml: Path) -> None: - with pyproject_toml.open(mode="a") as f: - f.write("<<<<<<<<<<<") - - with pytest.raises(PoetryCoreException) as excval: - _ = TOMLFile(pyproject_toml).read() - - assert f"Invalid TOML file {pyproject_toml.as_posix()}" in str(excval.value) - - -def test_pyproject_toml_file_getattr(tmp_path: Path, pyproject_toml: Path) -> None: - file = TOMLFile(pyproject_toml) - assert file.parent == tmp_path diff --git a/tests/test_factory.py b/tests/test_factory.py index af4101dd0..32a812a36 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -6,13 +6,13 @@ from typing import cast import pytest +import tomli from packaging.utils import canonicalize_name from poetry.core.constraints.version import parse_constraint from poetry.core.factory import Factory from poetry.core.packages.url_dependency import URLDependency -from poetry.core.toml import TOMLFile from poetry.core.version.markers import SingleMarker @@ -183,16 +183,18 @@ def test_create_poetry_with_multi_constraints_dependency() -> None: def test_validate() -> None: - complete = TOMLFile(fixtures_dir / "complete.toml") - doc: dict[str, Any] = complete.read() + complete = fixtures_dir / "complete.toml" + with complete.open("rb") as f: + doc = tomli.load(f) content = doc["tool"]["poetry"] assert Factory.validate(content) == {"errors": [], "warnings": []} def test_validate_fails() -> None: - complete = TOMLFile(fixtures_dir / "complete.toml") - doc: dict[str, Any] = complete.read() + complete = fixtures_dir / "complete.toml" + with complete.open("rb") as f: + doc = tomli.load(f) content = doc["tool"]["poetry"] content["authors"] = "this is not a valid array" @@ -202,10 +204,11 @@ def test_validate_fails() -> None: def test_validate_without_strict_fails_only_non_strict() -> None: - project_failing_strict_validation = TOMLFile( + project_failing_strict_validation = ( fixtures_dir / "project_failing_strict_validation" / "pyproject.toml" ) - doc: dict[str, Any] = project_failing_strict_validation.read() + with project_failing_strict_validation.open("rb") as f: + doc = tomli.load(f) content = doc["tool"]["poetry"] assert Factory.validate(content) == { @@ -220,10 +223,11 @@ def test_validate_without_strict_fails_only_non_strict() -> None: def test_validate_strict_fails_strict_and_non_strict() -> None: - project_failing_strict_validation = TOMLFile( + project_failing_strict_validation = ( fixtures_dir / "project_failing_strict_validation" / "pyproject.toml" ) - doc: dict[str, Any] = project_failing_strict_validation.read() + with project_failing_strict_validation.open("rb") as f: + doc = tomli.load(f) content = doc["tool"]["poetry"] assert Factory.validate(content, strict=True) == { @@ -255,16 +259,18 @@ def test_validate_strict_fails_strict_and_non_strict() -> None: def test_strict_validation_success_on_multiple_readme_files() -> None: - with_readme_files = TOMLFile(fixtures_dir / "with_readme_files" / "pyproject.toml") - doc: dict[str, Any] = with_readme_files.read() + with_readme_files = fixtures_dir / "with_readme_files" / "pyproject.toml" + with with_readme_files.open("rb") as f: + doc = tomli.load(f) content = doc["tool"]["poetry"] assert Factory.validate(content, strict=True) == {"errors": [], "warnings": []} def test_strict_validation_fails_on_readme_files_with_unmatching_types() -> None: - with_readme_files = TOMLFile(fixtures_dir / "with_readme_files" / "pyproject.toml") - doc: dict[str, Any] = with_readme_files.read() + with_readme_files = fixtures_dir / "with_readme_files" / "pyproject.toml" + with with_readme_files.open("rb") as f: + doc = tomli.load(f) content = doc["tool"]["poetry"] content["readme"][0] = "README.md" diff --git a/tests/testutils.py b/tests/testutils.py index d43b4abf9..01eb3f9bd 100644 --- a/tests/testutils.py +++ b/tests/testutils.py @@ -11,7 +11,8 @@ from typing import Any from typing import Generator -from poetry.core.toml import TOMLFile +import tomli +import tomli_w __toml_build_backend_patch__ = { @@ -43,10 +44,12 @@ def temporary_project_directory( with tempfile.TemporaryDirectory(prefix="poetry-core-pep517") as tmp: dst = Path(tmp) / path.name shutil.copytree(str(path), dst) - toml = TOMLFile(str(dst / "pyproject.toml")) - data = toml.read() + toml = dst / "pyproject.toml" + with toml.open("rb") as f: + data = tomli.load(f) data.update(toml_patch or __toml_build_backend_patch__) - toml.write(data) + with toml.open("wb") as f: + tomli_w.dump(data, f) yield str(dst) diff --git a/tox.ini b/tox.ini index 082a61616..2fb181b93 100644 --- a/tox.ini +++ b/tox.ini @@ -17,6 +17,7 @@ skip_install = false deps = pytest build + tomli_w virtualenv commands = pytest --integration {posargs} tests/integration diff --git a/vendors/poetry.lock b/vendors/poetry.lock index ddafbf9d4..365ee839b 100644 --- a/vendors/poetry.lock +++ b/vendors/poetry.lock @@ -116,12 +116,12 @@ optional = false python-versions = ">=3.7" [[package]] -name = "tomlkit" -version = "0.11.6" -description = "Style preserving TOML library" +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [[package]] name = "typing-extensions" @@ -146,7 +146,7 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "6cd18db509bda7b44ca6c26f16b25885b3c0c9c91ae497b774b21ce67fbf5593" +content-hash = "8206842bad79a385435d7a27246ddeb5fdbc6ab04e4bfb735e2b2575e8fe562c" [metadata.files] attrs = [ @@ -205,9 +205,9 @@ pyrsistent = [ {file = "pyrsistent-0.19.2-py3-none-any.whl", hash = "sha256:ea6b79a02a28550c98b6ca9c35b9f492beaa54d7c5c9e9949555893c8a9234d0"}, {file = "pyrsistent-0.19.2.tar.gz", hash = "sha256:bfa0351be89c9fcbcb8c9879b826f4353be10f58f8a677efab0c017bf7137ec2"}, ] -tomlkit = [ - {file = "tomlkit-0.11.6-py3-none-any.whl", hash = "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b"}, - {file = "tomlkit-0.11.6.tar.gz", hash = "sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73"}, +tomli = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] typing-extensions = [ {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, diff --git a/vendors/pyproject.toml b/vendors/pyproject.toml index ac990e109..b27cd1546 100644 --- a/vendors/pyproject.toml +++ b/vendors/pyproject.toml @@ -24,7 +24,7 @@ python = "^3.7" jsonschema = "^4.16.0" lark = "^1.1.3" packaging = ">=21.3" -tomlkit = ">=0.11.5,<1.0.0" +tomli = "^2.0.1" # Needed by jsonschema and only at python < 3.8, but to make # sure that it is always delivered we add an unconditional