Skip to content

Commit

Permalink
Handle build-system section
Browse files Browse the repository at this point in the history
Signed-off-by: Bernát Gábor <[email protected]>
  • Loading branch information
gaborbernat committed Feb 21, 2022
1 parent a6bf4ad commit 0f1a966
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 11 deletions.
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
[build-system]
requires = ["hatchling>=0.14", "hatch-vcs>=0.1"]
build-backend = "hatchling.build"
requires = [
"hatch-vcs>=0.1",
"hatchling>=0.14",
]

[project]
name = "pyproject-fmt"
Expand Down
2 changes: 1 addition & 1 deletion src/pyproject_fmt/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class PyProjectFmtNamespace(Namespace):

pyproject_toml: Path
stdout: bool
indent = 2


def pyproject_toml_path_creator(argument: str) -> Path:
Expand Down Expand Up @@ -45,6 +46,5 @@ def cli_args(args: Sequence[str]) -> PyProjectFmtNamespace:
action="store_true",
help="print the formatted text to the stdout (instead of update in-place)",
)

parser.add_argument("pyproject_toml", type=pyproject_toml_path_creator, help="tox ini file to format")
return parser.parse_args(namespace=PyProjectFmtNamespace(), args=args)
21 changes: 17 additions & 4 deletions src/pyproject_fmt/formatter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,36 @@

from ..cli import PyProjectFmtNamespace
from .requires import normalize_req
from .util import multiline_sorted_array, order_keys


def format_pyproject(opts: PyProjectFmtNamespace) -> str:
text = opts.pyproject_toml.read_text()
parsed: TOMLDocument = parse(text)
_perform(parsed)
result = parsed.as_string()
return result
_perform(parsed, opts)
result = parsed.as_string().rstrip("\n")
return f"{result}\n"


def _perform(parsed: TOMLDocument) -> None:
def _perform(parsed: TOMLDocument, opts: PyProjectFmtNamespace) -> None:
_build_system(parsed, opts)


def _build_system(parsed: TOMLDocument, opts: PyProjectFmtNamespace) -> None:
# 1. Normalize entries of the requires key
build_system: Table = parsed["build-system"] # type: ignore
requires_array: Array = build_system["requires"] # type: ignore
for at in range(len(requires_array)):
normalized = String.from_raw(normalize_req(str(requires_array[at])))
requires_array[at] = normalized
# 2. Make requires multiline and sorted
multiline_sorted_array(requires_array, indent=opts.indent)
# 2. Make backend-path multiline and sorted
if "backend-path" in build_system:
backend_path: Array = build_system["backend-path"] # type: ignore
multiline_sorted_array(backend_path, indent=opts.indent)
# 3. Order build-system
order_keys(build_system.value.body, ("build-backend", "requires", "backend-path"))


__all__ = ["format_pyproject"]
51 changes: 51 additions & 0 deletions src/pyproject_fmt/formatter/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from __future__ import annotations

from dataclasses import dataclass, field
from typing import Sequence

from tomlkit.items import Array, Comment, Item, Key, String, Trivia, Whitespace


def order_keys(body: list[tuple[Key | None, Item]], order: Sequence[str]) -> None:
entries = {i.key: (i, v) for (i, v) in body if isinstance(i, Key)}
body.clear()
for key in order:
if key in entries:
body.append(entries[key])
# body.append((None, Whitespace("\n")))
del entries[key]
body.extend(entries.values()) # append the rest
body.append((None, Whitespace("\n"))) # add trailing newline to separate


@dataclass
class _ArrayEntries:
text: String
comments: list[Comment] = field(default_factory=list)


def multiline_sorted_array(array: Array, indent: int) -> None:
body = array._value

entries: list[_ArrayEntries] = []
start: list[Comment] = []
for entry in body:
if isinstance(entry, String):
entries.append(_ArrayEntries(entry))
elif isinstance(entry, Comment):
(entries[-1].comments if len(entries) else start).append(entry)

body.clear()
indent_text = " " * indent
for entry in start:
body.append(Whitespace(f"\n{indent_text}"))
entry.indent(0)
body.append(entry)
for entry in sorted(entries, key=lambda e: str(e.text)):
body.append(Whitespace(f"\n{indent_text}"))
body.append(entry.text)
body.append(Whitespace(","))
if entry.comments:
com = " ".join(i.trivia.comment[1:].strip() for i in entry.comments)
body.append(Comment(Trivia(comment=f" # {com}", trail="")))
body.append(Whitespace("\n"))
46 changes: 46 additions & 0 deletions tests/formatter/test_build_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from pathlib import Path
from textwrap import dedent

from pyproject_fmt.cli import PyProjectFmtNamespace
from pyproject_fmt.formatter import format_pyproject


def test_build_backend(tmp_path: Path) -> None:
txt = """
[build-system]
backend-path = ['A', 'B']
requires = [
# start
# two
"A",
"B", # c # d follow-up comment
"C", # magic
"D", # a
"E", # b
"F", # comment ok
]
build-backend = "hatchling.build"
"""
toml = tmp_path / "a.toml"
toml.write_text(dedent(txt))
result = format_pyproject(PyProjectFmtNamespace(pyproject_toml=toml))
expected = """
[build-system]
build-backend = "hatchling.build"
requires = [
# start
# two
"A",
"B", # c # d follow-up comment
"C", # magic
"D", # a
"E", # b
"F", # comment ok
]
backend-path = [
'A',
'B',
]
"""

assert result == dedent(expected)
10 changes: 5 additions & 5 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,15 @@ def no_color(diff: Any) -> Any:
("start", "outcome", "output"),
[
(
'[build-system]\nrequires = ["hatchling>=0.14"]',
'[build-system]\nrequires = ["hatchling>=0.14"]',
'[build-system]\nrequires = [\n "hatchling>=0.14",\n]\n',
'[build-system]\nrequires = [\n "hatchling>=0.14",\n]\n',
"no change for {0}\n",
),
(
'[build-system]\nrequires = ["hatchling>=0.14.0"]',
'[build-system]\nrequires = ["hatchling>=0.14"]',
"--- {0}\n\n+++ {0}\n\n@@ -1,2 +1,2 @@\n\n"
' [build-system]\n-requires = ["hatchling>=0.14.0"]\n+requires = ["hatchling>=0.14"]\n',
'[build-system]\nrequires = [\n "hatchling>=0.14",\n]\n',
"--- {0}\n\n+++ {0}\n\n@@ -1,2 +1,4 @@\n\n [build-system]\n-requires = "
'["hatchling>=0.14.0"]\n+requires = [\n+ "hatchling>=0.14",\n+]\n',
),
],
)
Expand Down
4 changes: 4 additions & 0 deletions whitelist.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
capsys
chdir
dedent
difflib
envs
finditer
Expand All @@ -8,13 +9,16 @@ formatter
fromfile
iread
iwrite
multiline
pathlib
pyproject
raws
readouterr
req
skipif
textwrap
tmp
tofile
toml
tomlkit
util

0 comments on commit 0f1a966

Please sign in to comment.