Skip to content

Commit

Permalink
Update for dom-toml v2 (#60)
Browse files Browse the repository at this point in the history
  • Loading branch information
domdfcoding committed Apr 8, 2024
1 parent 1496c55 commit b7033ac
Show file tree
Hide file tree
Showing 36 changed files with 209 additions and 115 deletions.
1 change: 0 additions & 1 deletion formate.toml
Expand Up @@ -48,7 +48,6 @@ known_third_party = [
"pytest_randomly",
"pytest_timeout",
"shippinglabel",
"toml",
"tomli",
"typing_extensions",
]
Expand Down
169 changes: 151 additions & 18 deletions pyproject_parser/__init__.py
Expand Up @@ -7,6 +7,10 @@
#
# Copyright © 2021 Dominic Davis-Foster <dominic@davis-foster.co.uk>
#
# PyProjectTomlEncoder.dumps based on https://github.com/hukkin/tomli-w
# MIT Licensed
# 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
Expand All @@ -27,13 +31,26 @@
#

# stdlib
from typing import Any, ClassVar, Dict, Mapping, MutableMapping, Optional, Type, TypeVar, Union
from typing import (
Any,
ClassVar,
Dict,
Iterator,
List,
Mapping,
MutableMapping,
Optional,
Tuple,
Type,
TypeVar,
Union
)

# 3rd party
import attr
import dom_toml
import toml
from dom_toml.encoder import _dump_str
from dom_toml.decoder import InlineTableDict
from dom_toml.encoder import TomlEncoder
from dom_toml.parser import AbstractConfigParser, BadConfigError
from domdf_python_tools.paths import PathPlus, in_directory
from domdf_python_tools.typing import PathLike
Expand Down Expand Up @@ -66,6 +83,29 @@

_PP = TypeVar("_PP", bound="PyProject")

_translation_table = {
8: "\\b",
9: "\\t",
10: "\\n",
12: "\\f",
13: "\\r",
92: "\\\\",
}


def _dump_str(v: str) -> str:
v = str(v).translate(_translation_table)

if "'" in v and '"' not in v:
quote_char = '"'
elif '"' in v and "'" not in v:
quote_char = "'"
else:
quote_char = '"'
v = v.replace('"', '\\"')

return f"{quote_char}{v}{quote_char}"


class PyProjectTomlEncoder(dom_toml.TomlEncoder):
"""
Expand All @@ -76,14 +116,108 @@ class PyProjectTomlEncoder(dom_toml.TomlEncoder):
.. autosummary-widths:: 23/64
"""

def __init__(self, _dict=dict, preserve: bool = False) -> None: # noqa: MAN001
super().__init__(_dict=_dict, preserve=preserve)
self.dump_funcs[str] = _dump_str
self.dump_funcs[_NormalisedName] = _dump_str
self.dump_funcs[Version] = self.dump_packaging_types
self.dump_funcs[Requirement] = self.dump_packaging_types
self.dump_funcs[Marker] = self.dump_packaging_types
self.dump_funcs[SpecifierSet] = self.dump_packaging_types
def __init__(self, preserve: bool = False) -> None:
super().__init__(preserve=preserve)

def dumps(
self,
table: Mapping[str, Any],
*,
name: str,
inside_aot: bool = False,
) -> Iterator[str]:
"""
Serialise the given table.
:param name: The table name.
:param inside_aot:
:rtype:
.. versionadded:: 0.11.0
"""

yielded = False
literals = []
tables: List[Tuple[str, Any, bool]] = []
for k, v in table.items():
if v is None:
continue
if self.preserve and isinstance(v, InlineTableDict):
literals.append((k, v))
elif isinstance(v, dict):
tables.append((k, v, False))
elif self._is_aot(v):
tables.extend((k, t, True) for t in v)
else:
literals.append((k, v))

if inside_aot or name and (literals or not tables):
yielded = True
yield f"[[{name}]]\n" if inside_aot else f"[{name}]\n"

if literals:
yielded = True
for k, v in literals:
yield f"{self.format_key_part(k)} = {self.format_literal(v)}\n"

for k, v, in_aot in tables:
if yielded:
yield '\n'
else:
yielded = True
key_part = self.format_key_part(k)
display_name = f"{name}.{key_part}" if name else key_part

yield from self.dumps(v, name=display_name, inside_aot=in_aot)

def format_literal(self, obj: object, *, nest_level: int = 0) -> str:
"""
Format a literal value.
:param obj:
:param nest_level:
:rtype:
.. versionadded:: 0.11.0
"""

if isinstance(obj, (str, _NormalisedName)):
return _dump_str(obj)
elif isinstance(obj, (Version, Requirement, Marker, SpecifierSet)):
return self.dump_packaging_types(obj)
else:
return super().format_literal(obj, nest_level=nest_level)

def format_inline_array(self, obj: Union[Tuple, List], nest_level: int) -> str:
"""
Format an inline array.
:param obj:
:param nest_level:
:rtype:
.. versionadded:: 0.11.0
"""

if not len(obj):
return "[]"

item_indent = " " * (1 + nest_level)
closing_bracket_indent = " " * nest_level
single_line = "[ " + ", ".join(
self.format_literal(item, nest_level=nest_level + 1) for item in obj
) + f",]"

if len(single_line) <= self.max_width:
return single_line
else:
start = "[\n"
body = ",\n".join(item_indent + self.format_literal(item, nest_level=nest_level + 1) for item in obj)
end = f",\n{closing_bracket_indent}]"
return start + body + end

@staticmethod
def dump_packaging_types(obj: Union[Version, Requirement, Marker, SpecifierSet]) -> str:
Expand Down Expand Up @@ -227,12 +361,12 @@ def load(

def dumps(
self,
encoder: Union[Type[toml.TomlEncoder], toml.TomlEncoder] = PyProjectTomlEncoder,
encoder: Union[Type[TomlEncoder], TomlEncoder] = PyProjectTomlEncoder,
) -> str:
"""
Serialise to TOML.
:param encoder: The :class:`toml.TomlEncoder` to use for constructing the output string.
:param encoder: The :class:`~dom_toml.encoder.TomlEncoder` to use for constructing the output string.
"""

# TODO: filter out default values (lists and dicts)
Expand All @@ -250,7 +384,6 @@ def dumps(
"license": toml_dict["project"]["license"].to_pep621_dict()
}

if toml_dict["project"] is not None:
if "readme" in toml_dict["project"] and toml_dict["project"]["readme"] is not None:
readme_dict = toml_dict["project"]["readme"].to_pep621_dict()

Expand All @@ -268,13 +401,13 @@ def dumps(
def dump(
self,
filename: PathLike,
encoder: Union[Type[toml.TomlEncoder], toml.TomlEncoder] = PyProjectTomlEncoder,
encoder: Union[Type[TomlEncoder], TomlEncoder] = PyProjectTomlEncoder,
) -> str:
"""
Write as TOML to the given file.
:param filename: The filename to write to.
:param encoder: The :class:`toml.TomlEncoder` to use for constructing the output string.
:param encoder: The :class:`~dom_toml.encoder.TomlEncoder` to use for constructing the output string.
:returns: A string containing the TOML representation.
"""
Expand All @@ -288,13 +421,13 @@ def dump(
def reformat(
cls: Type[_PP],
filename: PathLike,
encoder: Union[Type[toml.TomlEncoder], toml.TomlEncoder] = PyProjectTomlEncoder,
encoder: Union[Type[TomlEncoder], TomlEncoder] = PyProjectTomlEncoder,
) -> str:
"""
Reformat the given ``pyproject.toml`` file.
:param filename: The file to reformat.
:param encoder: The :class:`toml.TomlEncoder` to use for constructing the output string.
:param encoder: The :class:`~dom_toml.encoder.TomlEncoder` to use for constructing the output string.
:returns: A string containing the reformatted TOML.
Expand Down
2 changes: 1 addition & 1 deletion pyproject_parser/__main__.py
Expand Up @@ -52,8 +52,8 @@
if TYPE_CHECKING:
# 3rd party
from consolekit.terminal_colours import ColourTrilean
from dom_toml.encoder import TomlEncoder
from domdf_python_tools.typing import PathLike
from toml import TomlEncoder

__all__ = ["main", "reformat", "check"]

Expand Down
10 changes: 2 additions & 8 deletions pyproject_parser/utils.py
Expand Up @@ -34,17 +34,11 @@
from typing import TYPE_CHECKING, Any, Dict, Optional

# 3rd party
import dom_toml
from dom_toml.parser import BadConfigError
from domdf_python_tools.paths import PathPlus
from domdf_python_tools.typing import PathLike

if sys.version_info < (3, 11):
# 3rd party
import tomli as tomllib
else:
# stdlib
import tomllib

if TYPE_CHECKING:
# this package
from pyproject_parser.type_hints import ContentTypes
Expand Down Expand Up @@ -192,4 +186,4 @@ def _load_toml(filename: PathLike, ) -> Dict[str, Any]:
:returns: A mapping containing the ``TOML`` data.
"""

return tomllib.loads(PathPlus(filename).read_text())
return dom_toml.load(filename)
4 changes: 1 addition & 3 deletions requirements.txt
@@ -1,10 +1,8 @@
apeye-core>=1.0.0
attrs>=20.3.0
dom-toml>=0.4.0
dom-toml>=2.0.0b1
domdf-python-tools>=2.8.0
natsort>=7.1.1
packaging>=20.9
shippinglabel>=1.0.0
toml>=0.10.2
tomli>=1.2.3; python_version < "3.11"
typing-extensions!=4.7.0,>=3.7.4.3
1 change: 1 addition & 0 deletions tests/requirements.txt
@@ -1,4 +1,5 @@
# git+https://github.com/repo-helper/pyproject-examples
git+https://github.com/domdfcoding/dom_toml@v2
coincidence>=0.2.0
coverage>=5.1
coverage-pyver-pragma>=0.2.1
Expand Down
1 change: 0 additions & 1 deletion tests/test_cli_/test_reformat_False_COMPLETE_A_.toml
Expand Up @@ -14,7 +14,6 @@ dynamic = [ "classifiers", "requires-python",]
name = "Dominic Davis-Foster"
email = "dominic@davis-foster.co.uk"


[project.urls]
Homepage = "https://whey.readthedocs.io/en/latest"
Documentation = "https://whey.readthedocs.io/en/latest"
Expand Down
Expand Up @@ -11,14 +11,13 @@ keywords = [ "build", "distribution", "packaging", "pep517", "pep621", "sdist",
dependencies = [ 'django>2.1; os_name != "nt"', 'django>2.0; os_name == "nt"', "gidgethub[httpx]>4.0.0", "httpx",]
dynamic = [ "classifiers", "requires-python",]

[project.license]
file = "LICENSE"

[[project.authors]]
name = "Dominic Davis-Foster"
email = "dominic@davis-foster.co.uk"


[project.license]
file = "LICENSE"

[project.urls]
Homepage = "https://whey.readthedocs.io/en/latest"
Documentation = "https://whey.readthedocs.io/en/latest"
Expand Down
1 change: 0 additions & 1 deletion tests/test_cli_/test_reformat_False_COMPLETE_B_.toml
Expand Up @@ -14,7 +14,6 @@ dynamic = [ "classifiers", "requires-python",]
name = "Dominic Davis-Foster"
email = "dominic@davis-foster.co.uk"


[project.urls]
Homepage = "https://whey.readthedocs.io/en/latest"
Documentation = "https://whey.readthedocs.io/en/latest"
Expand Down
9 changes: 4 additions & 5 deletions tests/test_cli_/test_reformat_False_COMPLETE_PROJECT_A_.toml
Expand Up @@ -18,9 +18,6 @@ name = "Tzu-Ping Chung"
name = "Brett Cannon"
email = "brett@python.org"


[tool]

[project.urls]
homepage = "example.com"
documentation = "readthedocs.org"
Expand All @@ -33,8 +30,10 @@ spam-cli = "spam:main_cli"
[project.gui-scripts]
spam-gui = "spam:main_gui"

[project.entry-points."spam.magical"]
tomatoes = "spam:main_tomatoes"

[project.optional-dependencies]
test = [ "pytest<5.0.0", "pytest-cov[all]",]

[project.entry-points."spam.magical"]
tomatoes = "spam:main_tomatoes"
[tool]
Expand Up @@ -14,7 +14,6 @@ dynamic = [ "classifiers", "requires-python",]
name = "Dominic Davis-Foster"
email = "dominic@davis-foster.co.uk"


[project.urls]
Homepage = "https://whey.readthedocs.io/en/latest"
Documentation = "https://whey.readthedocs.io/en/latest"
Expand Down
1 change: 0 additions & 1 deletion tests/test_cli_/test_reformat_False_UNORDERED_.toml
Expand Up @@ -14,7 +14,6 @@ dynamic = [ "classifiers", "requires-python",]
name = "Dominic Davis-Foster"
email = "dominic@davis-foster.co.uk"


[project.urls]
Homepage = "https://whey.readthedocs.io/en/latest"
Documentation = "https://whey.readthedocs.io/en/latest"
Expand Down
3 changes: 1 addition & 2 deletions tests/test_cli_/test_reformat_True_COMPLETE_A_.diff
@@ -1,7 +1,7 @@
Reformatting 'pyproject.toml'
--- pyproject.toml (original)
+++ pyproject.toml (reformatted)
@@ -6,18 +6,14 @@
@@ -6,18 +6,13 @@
name = "whey"
version = "2021.0.0"
description = "A simple Python wheel builder for simple projects."
Expand All @@ -20,7 +20,6 @@ Reformatting 'pyproject.toml'
+name = "Dominic Davis-Foster"
email = "dominic@davis-foster.co.uk"
-name = "Dominic Davis-Foster"
+

[project.urls]
Homepage = "https://whey.readthedocs.io/en/latest"

0 comments on commit b7033ac

Please sign in to comment.