diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 75d1678..8429cc3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -46,7 +46,7 @@ repos: additional_dependencies: - flake8-bugbear==23.1.20 - flake8-comprehensions==3.10.1 - - flake8-pytest-style==1.6 + - flake8-pytest-style==1.7 - flake8-spellcheck==0.28 - flake8-unused-arguments==0.0.13 - flake8-noqa==1.3 diff --git a/README.md b/README.md index f99eacb..b2a3cf6 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Apply a consistent format to `pyproject.toml` files. ```yaml - repo: https://github.com/tox-dev/pyproject-fmt - rev: "0.6.0" + rev: "0.9.0" hooks: - id: pyproject-fmt ``` diff --git a/docs/changelog.rst b/docs/changelog.rst deleted file mode 100644 index ae241cf..0000000 --- a/docs/changelog.rst +++ /dev/null @@ -1,37 +0,0 @@ -changelog -========= - -v0.3.5 (2022-08-12) -------------------- -* Support ``tomlkit>=0.11`` and CPython 3.11. - -v0.3.4 (2022-06-26) -------------------- -* Fix project links - -v0.3.3 (2022-03-23) -------------------- -* Fix help message referring to tox.ini files - -v0.3.2 (2022-03-01) -------------------- -* Fix invalid newline inside inline-table - by :user:`abravalheri`. - -v0.3.1 (2022-02-27) -------------------- -* Better handling of PEP-508 dependencies by using the ``packaging`` module instead of the our own parsing logic - by - :user:`abravalheri`. - -v0.3.0 (2022-02-27) -------------------- -* Handle ``project`` section in first iteration - by :user:`gaborbernat`. -* Add documentation build to the project - by :user:`gaborbernat`. -* Add a programmatic API as :meth:`format_pyproject ` - by :user:`gaborbernat`. - -v0.2.0 (2022-02-21) -------------------- -* Handle ``build-system`` section - by :user:`gaborbernat`. - -v0.1.0 (2022-02-21) -------------------- -* Create base and reserve PyPI name - by :user:`gaborbernat`. diff --git a/docs/index.rst b/docs/index.rst index 6be18f6..da6ac3c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -2,6 +2,7 @@ pyproject-fmt ============= Apply a consistent format to your ``pyproject.toml`` file with comment support. +See `changelog here `_. Use --- @@ -24,7 +25,7 @@ See :gh:`pre-commit/pre-commit` for instructions, sample ``.pre-commit-config.ya .. code-block:: yaml - repo: https://github.com/tox-dev/pyproject-fmt - rev: "0.4.1" + rev: "0.9.0" hooks: - id: pyproject-fmt @@ -46,4 +47,3 @@ API :hidden: self - changelog diff --git a/pyproject.toml b/pyproject.toml index dd0d9aa..8aa05be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ build-backend = "hatchling.build" requires = [ "hatch-vcs>=0.3", - "hatchling>=1.12.2", + "hatchling>=1.13", ] [project] @@ -39,7 +39,7 @@ optional-dependencies.docs = [ "furo>=2022.12.7", "sphinx>=6.1.3", "sphinx-argparse-cli>=1.11", - "sphinx-autodoc-typehints!=1.23.4,>1", + "sphinx-autodoc-typehints!=1.23.4,>=1.22", "sphinx-copybutton>=0.5.1", ] optional-dependencies.test = [ @@ -51,22 +51,28 @@ optional-dependencies.test = [ urls."Bug Tracker" = "https://github.com/tox-dev/pyproject-fmt/issues" urls.Documentation = "https://github.com/tox-dev/pyproject-fmt/" urls."Source Code" = "https://github.com/tox-dev/pyproject-fmt" +urls."Changelog" = "https://github.com/tox-dev/pyproject-fmt/releases" scripts.pyproject-fmt = "pyproject_fmt.__main__:run" - [tool.hatch] build.dev-mode-dirs = ["src"] build.hooks.vcs.version-file = "src/pyproject_fmt/_version.py" build.targets.sdist.include = ["/src", "/tests"] version.source = "vcs" +[tool.black] +line-length = 120 + [tool.isort] known_first_party = ["helpers", "package_info"] profile = "black" add_imports = ["from __future__ import annotations"] -[tool.black] -line-length = 120 +[tool.flake8] +max-line-length = 120 +unused-arguments-ignore-abstract-functions = true +noqa-require-code = true +dictionaries = ["en_US", "python", "technical", "django"] [tool.pytest] ini_options.testpaths = ["tests"] @@ -82,9 +88,3 @@ run.plugins = ["covdefaults"] [tool.mypy] show_error_codes = true strict = true - -[tool.flake8] -max-line-length = 120 -unused-arguments-ignore-abstract-functions = true -noqa-require-code = true -dictionaries = ["en_US", "python", "technical", "django"] diff --git a/src/pyproject_fmt/cli.py b/src/pyproject_fmt/cli.py index d8abfc7..5e5aada 100644 --- a/src/pyproject_fmt/cli.py +++ b/src/pyproject_fmt/cli.py @@ -33,6 +33,8 @@ def pyproject_toml_path_creator(argument: str) -> Path: :return: the pyproject.toml path """ path = Path(argument).absolute() + if path.is_dir(): + path = path / "pyproject.toml" if not path.exists(): raise ArgumentTypeError("path does not exist") if not path.is_file(): diff --git a/src/pyproject_fmt/formatter/__init__.py b/src/pyproject_fmt/formatter/__init__.py index f92fb7d..f7d9de5 100644 --- a/src/pyproject_fmt/formatter/__init__.py +++ b/src/pyproject_fmt/formatter/__init__.py @@ -6,11 +6,13 @@ from .build_system import fmt_build_system from .config import Config from .project import fmt_project +from .tools import fmt_tools def _perform(parsed: TOMLDocument, conf: Config) -> None: fmt_build_system(parsed, conf) fmt_project(parsed, conf) + fmt_tools(parsed, conf) def format_pyproject(conf: Config) -> str: diff --git a/src/pyproject_fmt/formatter/build_system.py b/src/pyproject_fmt/formatter/build_system.py index b570a8b..2d31376 100644 --- a/src/pyproject_fmt/formatter/build_system.py +++ b/src/pyproject_fmt/formatter/build_system.py @@ -7,7 +7,7 @@ from .config import Config from .pep508 import normalize_pep508_array -from .util import order_keys, sorted_array +from .util import ensure_newline_at_end, order_keys, sorted_array def fmt_build_system(parsed: TOMLDocument, conf: Config) -> None: @@ -16,6 +16,7 @@ def fmt_build_system(parsed: TOMLDocument, conf: Config) -> None: normalize_pep508_array(cast(Optional[Array], system.get("requires")), conf.indent) sorted_array(cast(Optional[Array], system.get("backend-path")), indent=conf.indent) order_keys(system, ("build-backend", "requires", "backend-path")) + ensure_newline_at_end(system) __all__ = [ diff --git a/src/pyproject_fmt/formatter/project.py b/src/pyproject_fmt/formatter/project.py index eb5fabb..bcefd5d 100644 --- a/src/pyproject_fmt/formatter/project.py +++ b/src/pyproject_fmt/formatter/project.py @@ -8,7 +8,7 @@ from .config import Config from .pep508 import normalize_pep508_array -from .util import order_keys, sorted_array +from .util import ensure_newline_at_end, order_keys, sorted_array def fmt_project(parsed: TOMLDocument, conf: Config) -> None: @@ -56,6 +56,7 @@ def fmt_project(parsed: TOMLDocument, conf: Config) -> None: # these go at the end as they may be inline or exploded key_order.extend(["optional-dependencies", "urls", "scripts", "gui-scripts", "entry-points"]) order_keys(project, key_order) + ensure_newline_at_end(project) __all__ = [ diff --git a/src/pyproject_fmt/formatter/tools.py b/src/pyproject_fmt/formatter/tools.py new file mode 100644 index 0000000..5aaacc3 --- /dev/null +++ b/src/pyproject_fmt/formatter/tools.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +from typing import cast + +from tomlkit import TOMLDocument +from tomlkit.items import Table + +from .config import Config +from .util import ensure_newline_at_end, order_keys + + +def fmt_tools(parsed: TOMLDocument, conf: Config) -> None: # noqa: U100 + tools: Table | None = parsed.get("tool") + if tools is None: + return None + for tool in tools: + table = tools[tool] + ensure_newline_at_end(cast(Table, table)) + order = [ + "setuptools", + "setuptools_scm", + "hatch", + "black", + "isort", + "flake8", + "pytest", + "coverage", + "mypy", + ] + order_keys(tools, to_pin=order) + + +__all__ = [ + "fmt_tools", +] diff --git a/src/pyproject_fmt/formatter/util.py b/src/pyproject_fmt/formatter/util.py index f3a81e5..dba1f59 100644 --- a/src/pyproject_fmt/formatter/util.py +++ b/src/pyproject_fmt/formatter/util.py @@ -66,9 +66,6 @@ def order_keys( for _, elements in sorted(entries.items(), key=sort_inline_table): body.extend(elements) - if isinstance(table, Table): - body.append((None, Whitespace("\n"))) # add trailing newline to separate - @dataclass class ArrayEntries: @@ -109,8 +106,21 @@ def sorted_array( body.append(_ArrayItemGroup(indent=Whitespace("\n"))) +def ensure_newline_at_end(body: Table) -> None: + content = body + while content.value.body and isinstance(content.value.body[-1][1], Table): + content = content.value.body[-1][1] + whitespace = Whitespace("\n") + insert_body = content.value.body + if insert_body and isinstance(insert_body[-1][1], Whitespace): + insert_body[-1] = insert_body[-1][0], whitespace + else: + insert_body.append((None, whitespace)) + + __all__ = [ "ArrayEntries", "sorted_array", "order_keys", + "ensure_newline_at_end", ] diff --git a/tests/__init__.py b/tests/__init__.py index d3cf850..b82d181 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -8,4 +8,6 @@ Fmt = Callable[[Callable[[TOMLDocument, Config], None], str, str], None] -__all__ = ["Fmt"] +__all__ = [ + "Fmt", +] diff --git a/tests/formatter/test_tools.py b/tests/formatter/test_tools.py new file mode 100644 index 0000000..3de4cfe --- /dev/null +++ b/tests/formatter/test_tools.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +from pyproject_fmt.formatter.tools import fmt_tools +from tests import Fmt + + +def test_tools_ordering(fmt: Fmt) -> None: + content = """ + [tool.coverage] + a = 0 + [tool.pytest] + a = 0 + [tool.black] + a = 0 + [tool.isort] + a = 0 + [tool.flake8] + [tool.hatch] + a = 0 + [tool.setuptools_scm] + a = 0 + [tool.setuptools] + a.b = 0 + + """ + expected = """ + [tool.setuptools] + a.b = 0 + + [tool.setuptools_scm] + a = 0 + + [tool.hatch] + a = 0 + + [tool.black] + a = 0 + + [tool.isort] + a = 0 + + [tool.flake8] + + [tool.pytest] + a = 0 + + [tool.coverage] + a = 0 + """ + fmt(fmt_tools, content, expected) diff --git a/tests/test_cli.py b/tests/test_cli.py index c64bb05..88bfb82 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,5 +1,6 @@ from __future__ import annotations +import os import sys from pathlib import Path from stat import S_IREAD, S_IWRITE @@ -36,8 +37,10 @@ def test_cli_pyproject_toml_not_exists(tmp_path: Path, capsys: pytest.CaptureFix def test_cli_pyproject_toml_not_file(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: + path = tmp_path / "temp" + os.mkfifo(path) with pytest.raises(SystemExit) as context: - cli_args([str(tmp_path)]) + cli_args([str(path)]) assert context.value.code != 0 out, err = capsys.readouterr() assert not out @@ -69,3 +72,8 @@ def test_pyproject_toml_resolved(tmp_path: Path, monkeypatch: pytest.MonkeyPatch path.write_text("") result = cli_args(["tox.ini"]) assert result.inputs == [path] + + +def test_pyproject_toml_dir(tmp_path: Path) -> None: + (tmp_path / "pyproject.toml").write_text("") + cli_args([str(tmp_path)]) diff --git a/tox.ini b/tox.ini index 8d29c36..070de9e 100644 --- a/tox.ini +++ b/tox.ini @@ -44,7 +44,7 @@ description = run type check on code base setenv = {tty:MYPY_FORCE_COLOR = 1} deps = - mypy==0.991 + mypy==1 commands = mypy --strict --python-version 3.10 src tests diff --git a/whitelist.txt b/whitelist.txt index 80a099e..d335dd8 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -3,19 +3,13 @@ autodoc canonicalize capsys chdir -conf -configs -deps -difflib dunder extlinks -fmt -formatter fromfile -func intersphinx iread iwrite +mkfifo nitpicky pep508 py38 @@ -23,12 +17,9 @@ pygments pyproject raws readouterr -req sdist skipif -tmp tofile toml tomlkit typehints -util