├── src └── slap │ ├── py.typed │ ├── templates │ ├── poetry │ │ ├── LICENSE │ │ ├── README.md │ │ ├── src │ │ │ └── {path} │ │ │ │ ├── py.typed │ │ │ │ └── __init__.py │ │ ├── tests │ │ │ └── test_import.py │ │ ├── .gitignore │ │ ├── .flake8 │ │ └── pyproject.toml │ └── github │ │ └── .github │ │ └── workflows │ │ ├── python.yaml │ │ └── changelog.yaml │ ├── ext │ ├── __init__.py │ ├── checks │ │ ├── __init__.py │ │ ├── changelog.py │ │ ├── general.py │ │ └── release.py │ ├── version_incrementing_rule.py │ ├── release │ │ ├── changelog.py │ │ └── source_code_version.py │ ├── project_handlers │ │ ├── uv.py │ │ └── flit.py │ ├── application │ │ ├── config.py │ │ ├── info.py │ │ ├── report.py │ │ └── run.py │ └── repository_handlers │ │ └── default.py │ ├── __init__.py │ ├── util │ ├── strings.py │ ├── notset.py │ ├── supplier.py │ ├── pygments.py │ ├── once.py │ ├── cleo.py │ ├── text.py │ ├── logging.py │ ├── orderedset.py │ ├── url.py │ ├── toml_file.py │ ├── external │ │ └── pypi_classifiers.py │ ├── weak_property.py │ └── plugins.py │ ├── __main__.py │ ├── configuration.py │ ├── release.py │ └── check.py ├── docs ├── .python-version ├── docs │ ├── .github │ ├── api │ │ ├── documentation.md │ │ └── plugins.md │ ├── changelog.md │ ├── commands │ │ ├── info.md │ │ ├── init.md │ │ ├── install.md │ │ ├── publish.md │ │ ├── link.md │ │ ├── check.md │ │ ├── report.md │ │ ├── run.md │ │ ├── add.md │ │ ├── venv.md │ │ ├── release.md │ │ └── test.md │ ├── guides │ │ └── github.md │ ├── configuration.md │ └── glossary.md ├── pyproject.toml └── mkdocs.yml ├── .changelog ├── 1.5.2.toml ├── 0.2.0.toml ├── 0.7.2.toml ├── 1.0.0a3.toml ├── 1.6.13.toml ├── 1.6.18.toml ├── 0.10.1.toml ├── 1.6.29.toml ├── 0.13.2.toml ├── 0.14.1.toml ├── 0.15.1.toml ├── 1.9.4.toml ├── 1.0.0a1.toml ├── 1.5.1.toml ├── 1.6.15.toml ├── 0.13.3.toml ├── 0.14.2.toml ├── 0.15.0.toml ├── 0.21.0.toml ├── 1.0.0a5.toml ├── 1.10.3.toml ├── 1.3.5.toml ├── 1.5.0.toml ├── 1.6.16.toml ├── 1.6.20.toml ├── 0.13.1.toml ├── 0.7.3.toml ├── 1.0.10.toml ├── 1.4.3.toml ├── 1.9.6.toml ├── 1.9.7.toml ├── 1.3.6.toml ├── 1.6.21.toml ├── 0.11.1.toml ├── 1.3.2.toml ├── 1.7.6.toml ├── 0.17.3.toml ├── 0.18.2.toml ├── 0.5.1.toml ├── 1.13.0.toml ├── 1.3.3.toml ├── 1.4.2.toml ├── 1.6.26.toml ├── 0.10.2.toml ├── 1.11.0.toml ├── 1.5.4.toml ├── 1.6.28.toml ├── 1.6.7.toml ├── 1.9.2.toml ├── 1.10.4.toml ├── 1.14.2.toml ├── 1.6.8.toml ├── 1.9.1.toml ├── 0.11.4.toml ├── 0.14.0.toml ├── 0.5.3.toml ├── 1.14.1.toml ├── 1.6.23.toml ├── 1.1.2.toml ├── 1.6.3.toml ├── 1.6.32.toml ├── 0.17.4.toml ├── 1.11.2.toml ├── 1.13.2.toml ├── 1.2.3.toml ├── 1.6.17.toml ├── 0.11.2.toml ├── 1.0.1.toml ├── 1.6.12.toml ├── 1.0.6.toml ├── 1.3.4.toml ├── 1.6.30.toml ├── 0.17.1.toml ├── 1.6.31.toml ├── 1.7.5.toml ├── 1.15.0.toml ├── 1.6.25.toml ├── 0.10.3.toml ├── 1.10.1.toml ├── 1.6.10.toml ├── 1.9.5.toml ├── 1.6.1.toml ├── 1.6.6.toml ├── 1.9.3.toml ├── 1.6.9.toml ├── 1.6.5.toml ├── 1.10.2.toml ├── 0.18.1.toml ├── 1.13.1.toml ├── 0.12.0.toml ├── 1.4.0.toml ├── 1.1.1.toml ├── 0.11.0.toml ├── 1.0.2.toml ├── 0.3.0.toml ├── 1.0.4.toml ├── 1.6.11.toml ├── 1.7.1.toml ├── 0.5.4.toml ├── 1.4.4.toml ├── 0.7.1.toml ├── 1.0.9.toml ├── 0.5.5.toml ├── 0.13.0.toml ├── 1.6.27.toml ├── 0.5.2.toml ├── 1.9.8.toml ├── 1.1.0.toml ├── 1.0.5.toml ├── 1.6.22.toml ├── 1.0.0a6.toml ├── 1.0.7.toml ├── 0.16.1.toml ├── 1.6.24.toml ├── 1.0.3.toml ├── 1.8.1.toml ├── 0.11.3.toml ├── 1.3.1.toml ├── 1.6.4.toml ├── 1.0.8.toml ├── 0.21.1.toml ├── 0.17.2.toml ├── 1.2.4.toml ├── 1.5.3.toml ├── 1.6.2.toml ├── 0.8.0.toml ├── 1.6.14.toml ├── 0.20.0.toml ├── 1.10.0.toml ├── 1.7.3.toml ├── 0.7.0.toml ├── 1.7.4.toml ├── 1.6.19.toml ├── 1.8.0.toml ├── 1.14.0.toml ├── 0.16.0.toml ├── 1.6.33.toml ├── 1.2.1.toml ├── 1.12.0.toml ├── 0.19.0.toml ├── 1.9.0.toml ├── 0.14.3.toml ├── 1.7.2.toml ├── 0.9.0.toml ├── 1.2.2.toml ├── 1.4.1.toml ├── 1.7.0.toml ├── 1.11.1.toml ├── 0.10.0.toml ├── 1.6.0.toml ├── 1.2.0.toml ├── 1.0.0a2.toml ├── 1.3.0.toml ├── 0.5.0.toml ├── 0.18.0.toml ├── 1.4.5.toml ├── 0.4.0.toml └── 0.17.0.toml ├── .gitignore ├── .flake8 ├── renovate.json ├── tests └── slap │ └── python │ ├── test_environment.py │ └── test_pep508.py ├── LICENSE └── .github └── workflows ├── changelog.yaml └── python.yml /src/slap/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.python-version: -------------------------------------------------------------------------------- 1 | 3.13 2 | -------------------------------------------------------------------------------- /docs/docs/.github: -------------------------------------------------------------------------------- 1 | ../../.github/ -------------------------------------------------------------------------------- /src/slap/templates/poetry/LICENSE: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/slap/templates/poetry/README.md: -------------------------------------------------------------------------------- 1 | # {name} 2 | -------------------------------------------------------------------------------- /src/slap/templates/poetry/src/{path}/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/slap/ext/__init__.py: -------------------------------------------------------------------------------- 1 | # needed for mkdocstrings 2 | pass 3 | -------------------------------------------------------------------------------- /src/slap/ext/checks/__init__.py: -------------------------------------------------------------------------------- 1 | # needed for mkdocstrings 2 | pass 3 | -------------------------------------------------------------------------------- /src/slap/templates/poetry/src/{path}/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.1.0" 2 | -------------------------------------------------------------------------------- /src/slap/templates/poetry/tests/test_import.py: -------------------------------------------------------------------------------- 1 | def test_import() -> None: 2 | exec("from {dotted_name} import *") 3 | -------------------------------------------------------------------------------- /src/slap/__init__.py: -------------------------------------------------------------------------------- 1 | """Slap is a command-line utility for developing Python applications.""" 2 | 3 | __version__ = "1.15.0" 4 | -------------------------------------------------------------------------------- /docs/docs/api/documentation.md: -------------------------------------------------------------------------------- 1 | # API Documentation 2 | 3 | ::: slap.application.Application 4 | 5 | --- 6 | 7 | ::: slap.plugins 8 | -------------------------------------------------------------------------------- /docs/docs/changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/slap/templates/poetry/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | .dmypy.json 3 | .venv/ 4 | *.egg-info/ 5 | /.vscode 6 | /build 7 | /dist 8 | poetry.lock 9 | -------------------------------------------------------------------------------- /src/slap/util/strings.py: -------------------------------------------------------------------------------- 1 | def split_by_commata(string: str) -> list[str]: 2 | if not string: 3 | return [] 4 | return string.split(",") 5 | -------------------------------------------------------------------------------- /src/slap/__main__.py: -------------------------------------------------------------------------------- 1 | def main(): 2 | from slap.application import Application 3 | 4 | Application().run() 5 | 6 | 7 | if __name__ == "__main__": 8 | main() 9 | -------------------------------------------------------------------------------- /.changelog/1.5.2.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-04-13" 2 | 3 | [[entries]] 4 | id = "f18d91ff-e7ab-4ebd-b662-b28e8c2c0b93" 5 | type = "fix" 6 | description = "fix in pep508" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/0.2.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2020-08-29" 2 | 3 | [[entries]] 4 | id = "259aa30c-1bab-4963-87d9-48a23983e041" 5 | type = "docs" 6 | description = "fix README example" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python 2 | .venv 3 | *.egg-info 4 | /dist 5 | /build 6 | __pycache__/ 7 | poetry.lock 8 | 9 | # VSCode 10 | /.vscode 11 | 12 | /_old_shut 13 | .dmypy.json 14 | .DS_Store 15 | 16 | /docs/site/ 17 | -------------------------------------------------------------------------------- /src/slap/util/notset.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | 4 | class NotSet(enum.Enum): 5 | "A type to include in a union where `None` is a valid value and needs to be differentiated from 'not present'." 6 | 7 | Value = 0 8 | -------------------------------------------------------------------------------- /.changelog/0.7.2.toml: -------------------------------------------------------------------------------- 1 | release-date = "2020-09-08" 2 | 3 | [[entries]] 4 | id = "908c3908-a2fc-4845-b445-c253c38dbc86" 5 | type = "fix" 6 | description = "cli: Fix `shut pkg install` command" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.0.0a3.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-02-13" 2 | 3 | [[entries]] 4 | id = "96dbd746-81a5-4a30-99be-30dfb5fd2b62" 5 | type = "fix" 6 | description = "fix entrypoint for `slam` script" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.6.13.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-05-06" 2 | 3 | [[entries]] 4 | id = "ba0fa5c7-ee08-447c-a731-5aec1366a449" 5 | type = "fix" 6 | description = "Fix misspelling in GH template" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.6.18.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-07-01" 2 | 3 | [[entries]] 4 | id = "050356ba-b156-410b-b397-feedff4d0e5c" 5 | type = "feature" 6 | description = "add `slap test -x,--exclude` option" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/0.10.1.toml: -------------------------------------------------------------------------------- 1 | release-date = "2020-11-27" 2 | 3 | [[entries]] 4 | id = "f79c5c43-e0f4-41b9-8e07-cdc45aad4b15" 5 | type = "fix" 6 | description = "remove debug print in \"package-url\" check" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.6.29.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-09-12" 2 | 3 | [[entries]] 4 | id = "0c8fd676-4ccc-44c2-bfee-90926bd83b64" 5 | type = "fix" 6 | description = "fix return code of `slap venv --exists,-e`" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/0.13.2.toml: -------------------------------------------------------------------------------- 1 | release-date = "2021-06-20" 2 | 3 | [[entries]] 4 | id = "cd10f33a-eabd-4c68-a487-8ba2e3c3cb78" 5 | type = "fix" 6 | description = "fix format of requirements in requirements.txt" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/0.14.1.toml: -------------------------------------------------------------------------------- 1 | release-date = "2021-07-23" 2 | 3 | [[entries]] 4 | id = "a899b9e0-f400-4a77-bc6a-e448e7788c07" 5 | type = "fix" 6 | description = "`TypeError` when using --extra and --dev/--test" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/0.15.1.toml: -------------------------------------------------------------------------------- 1 | release-date = "2021-08-09" 2 | 3 | [[entries]] 4 | id = "ec7ec6a4-c3d4-4176-8c29-2572db2f953c" 5 | type = "feature" 6 | description = "add BSD2, BSD3, BSD4 and Apache2 license templates" 7 | author = "@ndjeong" 8 | -------------------------------------------------------------------------------- /.changelog/1.9.4.toml: -------------------------------------------------------------------------------- 1 | release-date = "2023-05-19" 2 | 3 | [[entries]] 4 | id = "9012d9f0-b77f-4073-827e-afb7f7195b6d" 5 | type = "fix" 6 | description = "add missing readme key to pyproject.toml template" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.0.0a1.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-02-13" 2 | 3 | [[entries]] 4 | id = "294b1ba5-dfb6-4930-970c-b33b0891ac01" 5 | type = "breaking change" 6 | description = "Fully reimplment Shut, rename it to Slam" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.5.1.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-04-13" 2 | 3 | [[entries]] 4 | id = "98390028-20e1-405f-980d-ed715bb791c1" 5 | type = "fix" 6 | description = "`slap.python.pep508` no longer requires `dataclasses`" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.6.15.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-05-27" 2 | 3 | [[entries]] 4 | id = "e28158db-ec95-4189-a525-c142bec6a1a7" 5 | type = "improvement" 6 | description = "add `strict` option to Mypy settings in template" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /src/slap/util/supplier.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Callable, TypeVar 4 | 5 | T_co = TypeVar("T_co", covariant=True) 6 | T_Supplier = TypeVar("T_Supplier", bound="Supplier") 7 | Supplier = Callable[[], T_co] 8 | -------------------------------------------------------------------------------- /.changelog/0.13.3.toml: -------------------------------------------------------------------------------- 1 | release-date = "2021-06-20" 2 | 3 | [[entries]] 4 | id = "2b90774e-46da-4bd7-b4f7-0f5f18980335" 5 | type = "fix" 6 | description = "exclude python requirements when rendering `requirements.txt`" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/0.14.2.toml: -------------------------------------------------------------------------------- 1 | release-date = "2021-08-03" 2 | 3 | [[entries]] 4 | id = "1f52c4ad-b3e6-458a-a0b6-df2ea53418b9" 5 | type = "improvement" 6 | description = "update how install hooks are rendered into `setup.py`" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/0.15.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2021-08-09" 2 | 3 | [[entries]] 4 | id = "45104b69-935c-4b9e-9a71-145d295cf2db" 5 | type = "feature" 6 | description = "add `shut mono|pkg status --json --include-config` options" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/0.21.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2021-09-18" 2 | 3 | [[entries]] 4 | id = "e15729ec-0a3a-41b4-9134-adc0829d2393" 5 | type = "feature" 6 | description = "add `$.scripts` in `package.yml` and `shut pkg run` command" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.0.0a5.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-02-15" 2 | 3 | [[entries]] 4 | id = "fc6d88e2-0df5-494c-8714-cac083a6be2d" 5 | type = "fix" 6 | description = "test runner without TTY now does the correct line prefixing" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.10.3.toml: -------------------------------------------------------------------------------- 1 | release-date = "2023-09-12" 2 | 3 | [[entries]] 4 | id = "501201eb-fecb-419a-97ac-2aa7a09e8b11" 5 | type = "fix" 6 | description = "Allow `SHELL` to be not set when running the `slap venv` command" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.3.5.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-03-24" 2 | 3 | [[entries]] 4 | id = "ebf93969-c37a-4e38-b46c-62435a5f4225" 5 | type = "fix" 6 | description = "Fix `slap release` to consider the monorepository config as well" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.5.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-04-13" 2 | 3 | [[entries]] 4 | id = "6e2ef375-48ba-4a31-9841-86b5c7738ffc" 5 | type = "fix" 6 | description = "fix pep508 invokation when installing into Pytho 3.6 environments" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.6.16.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-06-20" 2 | 3 | [[entries]] 4 | id = "b61f6f13-c563-4369-b59d-434570d6cfc1" 5 | type = "improvement" 6 | description = "Add `W503` and `W504` to `.flake8` file of `slap init`" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.6.20.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-07-16" 2 | 3 | [[entries]] 4 | id = "443f4a96-6bcc-42d6-8971-591567cf710b" 5 | type = "fix" 6 | description = "`slap publish` now builds from the current project subdirectory" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/0.13.1.toml: -------------------------------------------------------------------------------- 1 | release-date = "2021-06-20" 2 | 3 | [[entries]] 4 | id = "73308d9b-f600-486a-98c8-1051268c4ee9" 5 | type = "fix" 6 | description = "also install `PackageModel.test_requirements` on `shut pkg install`" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/0.7.3.toml: -------------------------------------------------------------------------------- 1 | release-date = "2020-09-09" 2 | 3 | [[entries]] 4 | id = "96065c0d-9544-4566-bdf7-b9e415270bab" 5 | type = "fix" 6 | description = "cli: Fix `AttributeError` when running `shut mono bump --snapshot`" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.0.10.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-02-25" 2 | 3 | [[entries]] 4 | id = "b139657e-488f-49ac-a58b-3101738044f6" 5 | type = "fix" 6 | description = "`slam install` now skips projects that dont expose Python packages" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.4.3.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-04-03" 2 | 3 | [[entries]] 4 | id = "4f20c613-8319-4794-bd81-ecb08a919491" 5 | type = "fix" 6 | description = "Add missing `tomlkit` dependency (needed by the `slap add` command)" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.9.6.toml: -------------------------------------------------------------------------------- 1 | release-date = "2023-06-19" 2 | 3 | [[entries]] 4 | id = "486f39d3-1168-47d9-a557-9641bbe6d187" 5 | type = "fix" 6 | description = "Fix `slap install --from` and make it work with the `--only` option." 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.9.7.toml: -------------------------------------------------------------------------------- 1 | release-date = "2023-06-20" 2 | 3 | [[entries]] 4 | id = "8ff127f5-0c69-48bf-905b-74cd7745fc1f" 5 | type = "fix" 6 | description = "Avoid trying to render files in __pycache__ when using `slap init`." 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.3.6.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-03-27" 2 | 3 | [[entries]] 4 | id = "9c86aab8-397c-4aff-a1b9-bc07b983bc57" 5 | type = "fix" 6 | description = "Fix `NameError` in non-monorepository scenario when using `slap install`" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.6.21.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-07-17" 2 | 3 | [[entries]] 4 | id = "eb66dd36-3fa3-4c12-88e3-3b5654c74626" 5 | type = "fix" 6 | description = "fix `slap report dependencies` command (regression in previous release)" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/0.11.1.toml: -------------------------------------------------------------------------------- 1 | release-date = "2020-12-04" 2 | 3 | [[entries]] 4 | id = "e2207347-229c-4591-89be-9f8cb4e0d782" 5 | type = "fix" 6 | description = "fix error in `setuptools` renderer if no license is configured in the package" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.3.2.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-03-24" 2 | 3 | [[entries]] 4 | id = "ed53ae06-f485-43a4-b220-3e726e2447bb" 5 | type = "feature" 6 | description = "support `[install.extras]` option in the monorepository and project config" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.7.6.toml: -------------------------------------------------------------------------------- 1 | release-date = "2023-03-16" 2 | 3 | [[entries]] 4 | id = "fd7ce130-6026-4a00-8cd5-75c89b685c56" 5 | type = "fix" 6 | description = "Fix extracting package \"extras\" from the Poetry dependencies configuration." 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/0.17.3.toml: -------------------------------------------------------------------------------- 1 | release-date = "2021-08-20" 2 | 3 | [[entries]] 4 | id = "58676c94-44a1-4d50-ac42-f78d6a448b68" 5 | type = "fix" 6 | description = "`shut mono|pkg test --isolate` no longer installs the main package in develop mode" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/0.18.2.toml: -------------------------------------------------------------------------------- 1 | release-date = "2021-09-04" 2 | 3 | [[entries]] 4 | id = "822ae588-1f29-4d49-81ef-fa6888e3dec7" 5 | type = "fix" 6 | description = "min `click` version must be `7.1` for `no_args_is_help` option, also allow `8.x`" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/0.5.1.toml: -------------------------------------------------------------------------------- 1 | release-date = "2020-09-05" 2 | 3 | [[entries]] 4 | id = "981defc7-b7ae-4314-ab3e-da635528ecb4" 5 | type = "fix" 6 | description = "cli: fix running \"shut pkg install\" from inside \"shut pkg test --isolate\" call" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.13.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2024-02-16" 2 | 3 | [[entries]] 4 | id = "15722c92-47eb-4c24-9515-f0ee9db7dcfc" 5 | type = "feature" 6 | description = "Add `uv` support" 7 | author = "@NiklasRosenstein" 8 | pr = "https://github.com/NiklasRosenstein/slap/pull/132" 9 | -------------------------------------------------------------------------------- /.changelog/1.3.3.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-03-24" 2 | 3 | [[entries]] 4 | id = "526cee77-df13-4b71-b974-e0640cfc7a36" 5 | type = "fix" 6 | description = "Fix how `[install.extras]` is taken into account (used to always take all extras)" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.4.2.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-03-31" 2 | 3 | [[entries]] 4 | id = "f4aa3bdc-3561-4576-af81-5571b5cef395" 5 | type = "fix" 6 | description = "adjust for breaking changes in databind 2.0.0 (no more databind.json.settings module)" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.6.26.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-07-31" 2 | 3 | [[entries]] 4 | id = "64fbce5a-dce6-4d67-9d63-07f660f0267c" 5 | type = "fix" 6 | description = "bump dependency on `nr.util` which is guaranteed to have the `nr.utils.url` package" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/0.10.2.toml: -------------------------------------------------------------------------------- 1 | release-date = "2020-11-27" 2 | 3 | [[entries]] 4 | id = "8b58da3b-cb36-483f-a94c-3ef269e8f23b" 5 | type = "fix" 6 | description = "add `PackageModel.license_file` and fix license rendering into MANIFEST.in and setup.py" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.11.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2023-11-07" 2 | 3 | [[entries]] 4 | id = "b9e94c62-363d-4dbf-8aba-1238d4bc5f04" 5 | type = "feature" 6 | description = "Add `component` key to changelog entries, and `slap add -C ` option" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.5.4.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-04-14" 2 | 3 | [[entries]] 4 | id = "0170d749-9e2b-4827-872e-d5b06abf8238" 5 | type = "fix" 6 | description = "Fix issue with \"cannot install into global environment\" even if a Slap venv is active" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.6.28.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-09-12" 2 | 3 | [[entries]] 4 | id = "844de497-b518-470f-8a9c-33107ad19760" 5 | type = "feature" 6 | description = "add `--use-venv` option to all Venv-aware commands, add `slap venv --exists,-e` flag" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.6.7.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-04-26" 2 | 3 | [[entries]] 4 | id = "3557911a-ead5-4833-9730-54972dd2a7ca" 5 | type = "fix" 6 | description = "Fix `slap venv X.Y` to create an environment of the specified Python major/minor version" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.9.2.toml: -------------------------------------------------------------------------------- 1 | release-date = "2023-05-15" 2 | 3 | [[entries]] 4 | id = "8b6cf74b-a68a-49e8-b03f-00bc80768880" 5 | type = "fix" 6 | description = "Fix using `slap install --from ` by passing the right CWD in `find_repository()`" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.10.4.toml: -------------------------------------------------------------------------------- 1 | release-date = "2023-10-16" 2 | 3 | [[entries]] 4 | id = "c259ed87-48f7-4a7b-8e16-6f33bd8a7c67" 5 | type = "improvement" 6 | description = "Hide git fatal error" 7 | author = "alex.spencer@helsing.ai" 8 | pr = "https://github.com/NiklasRosenstein/slap/pull/97" 9 | -------------------------------------------------------------------------------- /.changelog/1.14.2.toml: -------------------------------------------------------------------------------- 1 | release-date = "2024-09-02" 2 | 3 | [[entries]] 4 | id = "28678c39-12c1-4472-80d5-2bb608fed9da" 5 | type = "improvement" 6 | description = "Improve error message when version number is not consistent across files" 7 | author = "niklas.rosenstein@helsing.ai" 8 | -------------------------------------------------------------------------------- /.changelog/1.6.8.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-04-28" 2 | 3 | [[entries]] 4 | id = "7fd54dfa-8411-4d1c-a24b-61b5ebd93a19" 5 | type = "fix" 6 | description = "Automatic detection of packages now ignores subdirectories that have a `pyproject.toml` file" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.9.1.toml: -------------------------------------------------------------------------------- 1 | release-date = "2023-05-15" 2 | 3 | [[entries]] 4 | id = "c0987d3e-cce7-4b55-a194-3f9c75ad7727" 5 | type = "fix" 6 | description = "Fix running command defined in Repository root with `slap run` broken by the previous version." 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/0.11.4.toml: -------------------------------------------------------------------------------- 1 | release-date = "2021-02-01" 2 | 3 | [[entries]] 4 | id = "bdcf272a-48ac-4ae7-9d31-0f948ccce5d8" 5 | type = "fix" 6 | description = "`shut pkg install` now correctly installs transitive package interdependencies in the right order" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/0.14.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2021-07-22" 2 | 3 | [[entries]] 4 | id = "ae6475d8-3842-48f2-b005-8ccee5b71d85" 5 | type = "feature" 6 | description = "add `--dev/--no-dev` and `--test/--no-test` options to `shut mono install` and `shut pkg install`" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/0.5.3.toml: -------------------------------------------------------------------------------- 1 | release-date = "2020-09-05" 2 | 3 | [[entries]] 4 | id = "1bbf3d01-34ee-4a34-9796-df048ec64973" 5 | type = "feature" 6 | description = "cli: Support `VIRTUALENV` and `PIP` environment variables in `shut pkg test` and `shut mono test`" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.14.1.toml: -------------------------------------------------------------------------------- 1 | release-date = "2024-05-31" 2 | 3 | [[entries]] 4 | id = "5088b6ce-d9f2-498c-87f8-26ffab740f1e" 5 | type = "feature" 6 | description = "add `SLAP_VERBOSE` environment variable" 7 | author = "@NiklasRosenstein" 8 | pr = "https://github.com/NiklasRosenstein/slap/pull/150" 9 | -------------------------------------------------------------------------------- /.changelog/1.6.23.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-07-21" 2 | 3 | [[entries]] 4 | id = "dcb74f4d-d3a3-4896-9ef8-f54b88444ec8" 5 | type = "fix" 6 | description = "fix an AttributeError when using new `--ignore-active-venv` option (no clue why Mypy did not pick it up)" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.1.2.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-03-02" 2 | 3 | [[entries]] 4 | id = "224ba10d-b075-43f1-97e3-316c9e79fb55" 5 | type = "fix" 6 | description = "`git+https://...` requirements are now understood and no longer converted into a dependency caled just \"git\"" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.6.3.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-04-18" 2 | 3 | [[entries]] 4 | id = "bf998b81-091c-4cfd-baa2-496391344f60" 5 | type = "fix" 6 | description = "Fix venv aware commands to add the absolute path to the `PATH` environment variable instead of a relative path" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.6.32.toml: -------------------------------------------------------------------------------- 1 | release-date = "2023-01-22" 2 | 3 | [[entries]] 4 | id = "3f897353-c1c5-4a0c-b502-551c6db2b333" 5 | type = "fix" 6 | description = "Fix running Slap on Windows for commands run from `slap test` and detecting the active Python virtualenv." 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/0.17.4.toml: -------------------------------------------------------------------------------- 1 | release-date = "2021-08-20" 2 | 3 | [[entries]] 4 | id = "90fd5c6e-8bca-4b1b-a06f-a95b40311487" 5 | type = "fix" 6 | description = "fix missing test-requirements installation with `shut mono|pkg test --isolate` if the comand was run previously" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.11.2.toml: -------------------------------------------------------------------------------- 1 | release-date = "2024-01-11" 2 | 3 | [[entries]] 4 | id = "b08f0b1f-b3f1-4aee-adc8-d83f27a1b7e5" 5 | type = "fix" 6 | description = "Running `slap release` in a sub-project of a mono repository is now supported and only affects that sub-project." 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.13.2.toml: -------------------------------------------------------------------------------- 1 | release-date = "2024-03-06" 2 | 3 | [[entries]] 4 | id = "42e4627a-55f8-47e8-9ec9-34b82a92cb98" 5 | type = "fix" 6 | description = "Try first to import `importlib.metadata` before `importlib_metadata` in `PythonEnvironment.get_distributions()`" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.2.3.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-03-20" 2 | 3 | [[entries]] 4 | id = "04ac6c34-39c7-43ec-8b88-e9c76b902ff1" 5 | type = "fix" 6 | description = "Fix `slam install` command accessing the wrong option name for `--no-venv-check` causing an error when `--link` was used" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.6.17.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-06-20" 2 | 3 | [[entries]] 4 | id = "74ad840e-acb8-4dd4-ae8f-20c376880857" 5 | type = "fix" 6 | description = "Fix `slap install --upgrade` option which is now correctly a flag when it previously incorrectly expected an argument" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/0.11.2.toml: -------------------------------------------------------------------------------- 1 | release-date = "2020-12-19" 2 | 3 | [[entries]] 4 | id = "3d603efa-2877-4da4-878f-238b8bd8ecd4" 5 | type = "fix" 6 | description = "fix license_file relative path generation in setuptools renderer, now correctly inherits the license file from the monorepo" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.0.1.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-02-23" 2 | 3 | [[entries]] 4 | id = "bd0f7737-d797-4cca-bc1d-0e4ee53aa418" 5 | type = "fix" 6 | description = "fix the URL generated for pull request IDs when passed to the `slam changelog add` command for Github hosted repositories" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.6.12.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-05-06" 2 | 3 | [[entries]] 4 | id = "8c24c380-9a85-4b48-88a4-43543404175f" 5 | type = "improvement" 6 | description = "Update GH action reference after Slap repository was moved from `NiklasRosenstein/slap` to `python-slaps/slap.cli`" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.0.6.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-02-23" 2 | 3 | [[entries]] 4 | id = "a638368d-2f7d-492a-a11c-f3de05a32f19" 5 | type = "improvement" 6 | description = "`slam changelog update-pr` no longer overwrites existing PR references in entries unless the new `--overwrite` option is passed" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.3.4.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-03-24" 2 | 3 | [[entries]] 4 | id = "1ee0ef1c-40de-4177-b578-8e688fa45b4a" 5 | type = "improvement" 6 | description = "`slam install` without `--no-dev` now installs _all_ extras for a project unless the new `[install.dev-extras]` option is set" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.6.30.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-09-20" 2 | 3 | [[entries]] 4 | id = "3c4cb586-eaff-4259-9b5d-ed828cdd3518" 5 | type = "fix" 6 | description = "fixed an issue with `poetry-core ==1.2.0`" 7 | author = "@NiklasRosenstein" 8 | issues = [ 9 | "https://github.com/python-slap/slap-cli/issues/66", 10 | ] 11 | -------------------------------------------------------------------------------- /.changelog/0.17.1.toml: -------------------------------------------------------------------------------- 1 | release-date = "2021-08-13" 2 | 3 | [[entries]] 4 | id = "8e3394ed-a3c1-4168-be6a-554e5b140772" 5 | type = "improvement" 6 | description = "dd `shut mono|pkg test -q,--quiet` option to quietly install test requirements with Pip, changed default to not do silent installs" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.6.31.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-12-01" 2 | 3 | [[entries]] 4 | id = "7ab372e2-5882-4986-9713-2c5ff0660542" 5 | type = "fix" 6 | description = "fix `.flake8` config in template created by `slap init`, newer versions of Flake will error if a comment is on the same line with a value" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.7.5.toml: -------------------------------------------------------------------------------- 1 | release-date = "2023-03-13" 2 | 3 | [[entries]] 4 | id = "8b1ec691-fe64-4355-b109-e3c49ee92b37" 5 | type = "fix" 6 | description = "Fix a `NameError` that occurred in `slap changelog format --markdown` when Slap does not understand the repository host of your project." 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /src/slap/templates/poetry/.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | # Black can yield formatted code that triggers these Flake8 warnings. 4 | ignore= 5 | # line break before binary operator 6 | W503, 7 | # line break after binary operator 8 | W504, 9 | # whitespace before ':' 10 | E203, 11 | -------------------------------------------------------------------------------- /.changelog/1.15.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2025-01-03" 2 | 3 | [[entries]] 4 | id = "95676706-7b7b-40f3-99ac-b7149e2f81b5" 5 | type = "feature" 6 | description = "Add basic support for `pyproject.toml` that uses Uv to prevent it causing errors" 7 | author = "@NiklasRosenstein" 8 | pr = "https://github.com/NiklasRosenstein/slap/pull/160" 9 | -------------------------------------------------------------------------------- /.changelog/1.6.25.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-07-29" 2 | 3 | [[entries]] 4 | id = "8dbf84a9-dbb5-4a49-81a4-2245a0287aae" 5 | type = "breaking change" 6 | description = "replace `--index-url` option with `--index` option which takes the same format as `--extra-index` (but the `name=` key can be omitted)" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/0.10.3.toml: -------------------------------------------------------------------------------- 1 | release-date = "2020-11-27" 2 | 3 | [[entries]] 4 | id = "4495a424-90a2-4d90-97dd-d52e6345aaa9" 5 | type = "fix" 6 | description = "referencing a LICENSE or README outside the package directory now does not fail on install if the source file does not exist, but it will print a warning" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.10.1.toml: -------------------------------------------------------------------------------- 1 | release-date = "2023-09-08" 2 | 3 | [[entries]] 4 | id = "70140882-6084-4717-a530-0abc4610edb9" 5 | type = "fix" 6 | description = "Pin `poetry-core` to `<1.7.0` to prevent issues with the `poetry.core.semver` module that is being imported by Slap and removed in `poetry-core 1.7.0`" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.6.10.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-05-02" 2 | 3 | [[entries]] 4 | id = "ab18a5cf-47e1-462b-8422-ba32f2c36efe" 5 | type = "fix" 6 | description = "The `slap add` command now installs the dependency even if `--upgrade` is not specified if the constraint does not accept the currently installed version." 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.9.5.toml: -------------------------------------------------------------------------------- 1 | release-date = "2023-06-19" 2 | 3 | [[entries]] 4 | id = "429d780d-af40-4606-8a2d-a97ba0cc4362" 5 | type = "feature" 6 | description = "the `--only` option to `slap install` can now receive multiple values delimited by commas, the `slap test` command now also takes an `--only` option." 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.6.1.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-04-16" 2 | 3 | [[entries]] 4 | id = "698d0f0d-272a-4f8e-a6e8-0803a0719e3d" 5 | type = "fix" 6 | description = "Fix `slap link` to produce absolute paths in the shebang for generated scripts by supplying an absolute path to the Python interpreter to the Flit installer" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.6.6.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-04-26" 2 | 3 | [[entries]] 4 | id = "6f24d702-905c-4986-a427-1dc0959a5e42" 5 | type = "fix" 6 | description = "Fix (workaround) to installing packages with `slap install` option without the `--link` option if it has runtime dependencies that require extra index URLs." 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.9.3.toml: -------------------------------------------------------------------------------- 1 | release-date = "2023-05-19" 2 | 3 | [[entries]] 4 | id = "71955475-a55c-4e49-a573-1548260b9d7d" 5 | type = "fix" 6 | description = "Fixed `slap init` command which broke due to a change to the `ApplicationPlugin` constructor and a missing custom implementation of `InstallCommandPlugin.__init__()`" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | # Black can yield formatted code that triggers these Flake8 warnings. 4 | ignore= 5 | # line break before binary operator 6 | W503, 7 | # line break after binary operator 8 | W504, 9 | # whitespace before ':' 10 | E203, 11 | # multiple statements on one line (def) 12 | E704, 13 | -------------------------------------------------------------------------------- /.changelog/1.6.9.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-04-28" 2 | 3 | [[entries]] 4 | id = "6505b5da-dc4b-4c96-9dd9-f7ac29fc1e05" 5 | type = "fix" 6 | description = "Use SPDX license information only in `slap.util.extenral.licenses` instead of Deja code which now requires a login; this fixes `slap init` when it tries to get the license text" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /docs/docs/commands/info.md: -------------------------------------------------------------------------------- 1 | # `slap info` 2 | 3 | Shows details about your repository and project(s) as Slap understands them. 4 | 5 |
Synopsis 6 | ``` 7 | @shell slap info --help 8 | ``` 9 |
10 | 11 | __Example from the Slap repository itself:__ 12 | 13 | ``` title="$ slap info" 14 | @shell cd .. && slap info 15 | ``` 16 | -------------------------------------------------------------------------------- /.changelog/1.6.5.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-04-21" 2 | 3 | [[entries]] 4 | id = "a2211405-4841-4230-af3f-de805b4a0b31" 5 | type = "fix" 6 | description = "The `slap link` command no longer fails when run as root (due to Flit adding a check here; if we want to have the same check in Slap we need to do it ourselves in `slap install` as well)" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/1.10.2.toml: -------------------------------------------------------------------------------- 1 | release-date = "2023-09-08" 2 | 3 | [[entries]] 4 | id = "bd6c84f9-120b-4262-ad66-949e7529d11f" 5 | type = "fix" 6 | description = "Use new Version for poetry-core and lock minor version" 7 | author = "@alexespencer" 8 | pr = "https://github.com/NiklasRosenstein/slap/pull/96" 9 | issues = [ 10 | "https://github.com/NiklasRosenstein/slap/issues/95", 11 | ] 12 | -------------------------------------------------------------------------------- /.changelog/0.18.1.toml: -------------------------------------------------------------------------------- 1 | release-date = "2021-08-27" 2 | 3 | [[entries]] 4 | id = "e7db93dd-6f62-414d-9a50-221a9004e16c" 5 | type = "fix" 6 | description = "fix added in `0.18.0` for #33 is actually incorrect, the `command` variable was needed when using the `_tempcopy()` feature. now it is fixed for good" 7 | author = "@NiklasRosenstein" 8 | issues = [ 9 | "https://github.com/NiklasRosenstein/shut/issues/33", 10 | ] 11 | -------------------------------------------------------------------------------- /.changelog/1.13.1.toml: -------------------------------------------------------------------------------- 1 | release-date = "2024-02-27" 2 | 3 | [[entries]] 4 | id = "d795c58c-f99f-42cb-aeb7-a0e70fd24d97" 5 | type = "fix" 6 | description = "Fix creation of activate scripts when creating virtual environments with `venv` by passing the correct flag `--upgrade-deps` instead of `--upgrade`" 7 | author = "@NiklasRosenstein" 8 | issues = [ 9 | "https://github.com/python-slap/slap-cli/issues/135", 10 | ] 11 | -------------------------------------------------------------------------------- /.changelog/0.12.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2021-06-08" 2 | 3 | [[entries]] 4 | id = "5fb472c9-1bfe-4157-b4a6-adc7489d4f59" 5 | type = "improvement" 6 | description = "allow running Shut via `python -m shut`" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "c72c7e4a-7827-44c3-a1f8-4147da3ba16d" 11 | type = "fix" 12 | description = "fix call to `run_install()` in `shut mono install`" 13 | author = "@NiklasRosenstein" 14 | -------------------------------------------------------------------------------- /.changelog/1.4.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-03-29" 2 | 3 | [[entries]] 4 | id = "79037034-fdc7-44ef-a782-90cef1729cb9" 5 | type = "feature" 6 | description = "Add `ProjectHandler.add_dependency()` and `Project.add_dependency()" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "12b6b886-eb72-444e-8118-29fc8b72246c" 11 | type = "feature" 12 | description = "Add `slap add` command" 13 | author = "@NiklasRosenstein" 14 | -------------------------------------------------------------------------------- /.changelog/1.1.1.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-02-26" 2 | 3 | [[entries]] 4 | id = "96463501-1ca4-4e5e-a51f-ad149bb818bd" 5 | type = "fix" 6 | description = "support old `break` change type and convert it to `breaking change`" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "f8cae048-5519-4daf-b68f-e43546ccb9ea" 11 | type = "feature" 12 | description = "add `slam publish --dry,-d` option" 13 | author = "@NiklasRosenstein" 14 | -------------------------------------------------------------------------------- /.changelog/0.11.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2020-12-03" 2 | 3 | [[entries]] 4 | id = "440c84d1-98cb-460a-82a0-a3de556ad3f2" 5 | type = "feature" 6 | description = "add `$.package-data` field to `package.yml`" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "0d83b50d-44ce-4fcf-b19c-a61eba056f11" 11 | type = "fix" 12 | description = "add missing package data that needs to be packaged with `shut`" 13 | author = "@NiklasRosenstein" 14 | -------------------------------------------------------------------------------- /.changelog/1.0.2.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-02-23" 2 | 3 | [[entries]] 4 | id = "5e929f72-e807-4206-ace5-353a7b1809ef" 5 | type = "improvement" 6 | description = "parse semver in `[tool.poetry.extras]`" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "6f543b15-1731-458b-b7e5-6a41ce013f3b" 11 | type = "feature" 12 | description = "add `--extras` and `--only-extras` options to `slam install`" 13 | author = "@NiklasRosenstein" 14 | -------------------------------------------------------------------------------- /.changelog/0.3.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2020-08-29" 2 | 3 | [[entries]] 4 | id = "e0ee08af-ff2e-4aee-b795-e6c37e4c16de" 5 | type = "improvement" 6 | description = "update Changelog typing for databind" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "f73bf327-b2a1-4e6c-9eef-b438599e0aa8" 11 | type = "improvement" 12 | description = "update paths printed in `shut ... bump` when changelog is released" 13 | author = "@NiklasRosenstein" 14 | -------------------------------------------------------------------------------- /.changelog/1.0.4.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-02-23" 2 | 3 | [[entries]] 4 | id = "b5c1c656-970c-4897-9498-81c8e9badf39" 5 | type = "improvement" 6 | description = "hide output from `git checkout -b` in `github-actions` plugin" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "8096986f-570a-4406-ac05-edc4ddab97e2" 11 | type = "fix" 12 | description = "fix shortform detection for GitHub pull request URLs" 13 | author = "@NiklasRosenstein" 14 | -------------------------------------------------------------------------------- /.changelog/1.6.11.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-05-06" 2 | 3 | [[entries]] 4 | id = "6086ecdb-74c6-4018-860b-3ceeca7d1113" 5 | type = "improvement" 6 | description = "Update default template to include black, isort, flake8" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "01e636c8-ad5d-4810-98b0-315ed84faff6" 11 | type = "improvement" 12 | description = "Set min Python version in init templates to 3.6" 13 | author = "@NiklasRosenstein" 14 | -------------------------------------------------------------------------------- /.changelog/1.7.1.toml: -------------------------------------------------------------------------------- 1 | release-date = "2023-02-09" 2 | 3 | [[entries]] 4 | id = "b76eaa27-9ef7-416d-a40f-79cea9ff4b72" 5 | type = "fix" 6 | description = "The Poetry key for dependency groups is `[tool.poetry.group]`, not `[tool.poetry.groups]`. We now support both but show a warning if the latter is used to inform the user that they should be using `group` instead." 7 | author = "@NiklasRosenstein" 8 | pr = "https://github.com/NiklasRosenstein/slap/pull/77" 9 | -------------------------------------------------------------------------------- /.changelog/0.5.4.toml: -------------------------------------------------------------------------------- 1 | release-date = "2020-09-05" 2 | 3 | [[entries]] 4 | id = "5789fa92-bc04-487a-ba2d-67cc889fb9db" 5 | type = "improvement" 6 | description = "shut.test.pytest: `PytestDriver.args` option now defaults to `[\"-vv\"]`" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "081fba05-a2c0-4d19-baaa-f55a280a6c36" 11 | type = "feature" 12 | description = "cli: Add `shut mono test --only` option" 13 | author = "@NiklasRosenstein" 14 | -------------------------------------------------------------------------------- /.changelog/1.4.4.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-04-04" 2 | 3 | [[entries]] 4 | id = "51f83ee8-150b-4c64-8a20-871cc75638e0" 5 | type = "improvement" 6 | description = "add `-g,--global` option to `slap venv link` command" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "0dc4307b-8937-475f-943a-df43b6cb03f6" 11 | type = "improvement" 12 | description = "read Git `user.name` and `user.email` from local repository first and then globally" 13 | author = "@NiklasRosenstein" 14 | -------------------------------------------------------------------------------- /.changelog/0.7.1.toml: -------------------------------------------------------------------------------- 1 | release-date = "2020-09-08" 2 | 3 | [[entries]] 4 | id = "5d982551-a68c-42f9-84e2-61e838f5e48d" 5 | type = "feature" 6 | description = "shore.models.monorepo: Add `Monorepo.get_inter_dependencies_graph()`" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "7165b81c-102d-4384-9158-ee42055b47df" 11 | type = "fix" 12 | description = "cli: `shut mono install` now uses topological order to retrieve package requirements" 13 | author = "@NiklasRosenstein" 14 | -------------------------------------------------------------------------------- /.changelog/1.0.9.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-02-25" 2 | 3 | [[entries]] 4 | id = "b4fcc652-2f8a-4419-a879-b693711da1dc" 5 | type = "fix" 6 | description = "fix consistent sorting of packages ordered topologically by interdependencies" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "7dff63e3-0c6c-42bd-a3c6-06b8853e07bc" 11 | type = "feature" 12 | description = "recognize `tool.poetry.packages` option in `DefaultProjectHandler.get_packages()`" 13 | author = "@NiklasRosenstein" 14 | -------------------------------------------------------------------------------- /src/slap/util/pygments.py: -------------------------------------------------------------------------------- 1 | import typing as t 2 | 3 | 4 | def toml_highlight(toml_data: dict[str, t.Any] | str) -> str: 5 | import pygments 6 | import pygments.formatters 7 | import pygments.lexers 8 | import tomli_w 9 | 10 | if not isinstance(toml_data, str): 11 | toml_data = tomli_w.dumps(toml_data) 12 | return pygments.highlight( 13 | toml_data, pygments.lexers.get_lexer_by_name("toml"), pygments.formatters.get_formatter_by_name("terminal") 14 | ) 15 | -------------------------------------------------------------------------------- /.changelog/0.5.5.toml: -------------------------------------------------------------------------------- 1 | release-date = "2020-09-05" 2 | 3 | [[entries]] 4 | id = "9598ef35-7c6d-4056-bf2a-73718d30b3db" 5 | type = "improvement" 6 | description = "shut.test.pytest: set altname for `PytestDriver.report_file` to `report-file`" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "3f78940f-74af-426e-95e3-c78e56a38c8a" 11 | type = "improvement" 12 | description = "cli: `shut mono test` now prints a summary of the status for every tested package" 13 | author = "@NiklasRosenstein" 14 | -------------------------------------------------------------------------------- /.changelog/0.13.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2021-06-20" 2 | 3 | [[entries]] 4 | id = "31abbcd5-284e-4123-b261-898f38afbbb4" 5 | type = "feature" 6 | description = "add `PackageModel.dev_requirements` which will be installed with `shut pkg install`" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "e0e97192-5599-4615-9373-bb71856a2657" 11 | type = "feature" 12 | description = "add `PackageModel.render_requirements_txt` which will be rendered with `shut pkg update`" 13 | author = "@NiklasRosenstein" 14 | -------------------------------------------------------------------------------- /.changelog/1.6.27.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-09-05" 2 | 3 | [[entries]] 4 | id = "f7a7e992-b470-4122-a3dc-11c9ce9deb37" 5 | type = "fix" 6 | description = "Updated how `slap install --index` and `--extra-index` are used to inject credentials; they now behave the same (with `--extra-index` marked as deprecated). The `url` is now optional, allowing you use the option only to inject credentials of a package index configured in the project (e.g. `pyproject.toml`) without specifying the URL" 7 | author = "@NiklasRosenstein" 8 | -------------------------------------------------------------------------------- /.changelog/0.5.2.toml: -------------------------------------------------------------------------------- 1 | release-date = "2020-09-05" 2 | 3 | [[entries]] 4 | id = "3181b8ce-da92-4948-aaba-3b2606320fc5" 5 | type = "feature" 6 | description = "cli: add `--checks/--no-checks` toggle to `shut pkg` and `shut mono` commands" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "d2cfb268-3732-47be-871a-451e67e3eb11" 11 | type = "fix" 12 | description = "shut.test.pytest: Fix parsing of Pytest JSON record for test functions from a `unittest.TestCase` subclass" 13 | author = "@NiklasRosenstein" 14 | -------------------------------------------------------------------------------- /.changelog/1.9.8.toml: -------------------------------------------------------------------------------- 1 | release-date = "2023-06-29" 2 | 3 | [[entries]] 4 | id = "38f73caa-d7d8-4a7a-b3dd-fbbc6faaa211" 5 | type = "feature" 6 | description = "Add `[tool.slap.release].pre_commit` option that can be used to run a command after `slap release` updated the files and before a commit is made and tagged." 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "9d94a52c-56a3-4fc1-916d-0627d8fdaf5d" 11 | type = "improvement" 12 | description = "Upgrade databind to `^4.4.0`" 13 | author = "@NiklasRosenstein" 14 | -------------------------------------------------------------------------------- /.changelog/1.1.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-02-26" 2 | 3 | [[entries]] 4 | id = "14b78ab4-7513-4091-afcb-46a35f5b017a" 5 | type = "improvement" 6 | description = "add `Project.application` member" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "768793ed-3c58-4012-bf1c-86c3822f1597" 11 | type = "feature" 12 | description = "add release plugin to automatically bump interdependencies between projects in a monorepository (can be disabled by setting `tool.slam.release.interdependencies = false`)" 13 | author = "@NiklasRosenstein" 14 | -------------------------------------------------------------------------------- /.changelog/1.0.5.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-02-23" 2 | 3 | [[entries]] 4 | id = "04e3d176-72a1-42db-806c-4e357320eca0" 5 | type = "feature" 6 | description = "add `ChangelogManager.readonly` which prevents you from saving a changelog to disk" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "77db75a0-0fa8-4ca4-a9f9-a0fe534d63a3" 11 | type = "feature" 12 | description = "add `[tool.shut.changelog].enabled` option which can be used to turn off the ability to add changelogs to the current project via the CLI" 13 | author = "@NiklasRosenstein" 14 | -------------------------------------------------------------------------------- /.changelog/1.6.22.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-07-21" 2 | 3 | [[entries]] 4 | id = "e026dc27-8dc9-406f-b164-5daeeadc51cf" 5 | type = "feature" 6 | description = "add `--ignore-active-venv` option to Venv-aware commands such as `slap install`" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "d0a1b793-d7e9-4927-9eb2-f40399b57ce5" 11 | type = "fix" 12 | description = "fix wrong return code in `slap install` if venv check fails (e.g. if attempting to install into a virtual environment without `--no-venv-check`)" 13 | author = "@NiklasRosenstein" 14 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ], 6 | "packageRules": [ 7 | { 8 | "groupName": "docs requirements", 9 | "matchPackagePatterns": ["*"], 10 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"] 11 | }, 12 | { 13 | "groupName": "minor and patch updates", 14 | "matchPackagePatterns": ["*"], 15 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"] 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.changelog/1.0.0a6.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-02-15" 2 | 3 | [[entries]] 4 | id = "be9024cb-e2d2-4226-8c22-c0ba6c9b8ed0" 5 | type = "improvement" 6 | description = "`Command.help` now treats the first line differently to support docstrings where the first line is on the same line as the quotes" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "175efd0d-7b99-42f2-a6ac-9ec7d5c1ef31" 11 | type = "improvement" 12 | description = "`slam changelog update-pr` now supports updating the PR reference for changelogs of all projects" 13 | author = "@NiklasRosenstein" 14 | -------------------------------------------------------------------------------- /.changelog/1.0.7.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-02-23" 2 | 3 | [[entries]] 4 | id = "fabc0159-3014-4bf5-9325-e24cc2c581f3" 5 | type = "fix" 6 | description = "`slam test` now only runs subproject tests instead of all tests in a monorepo if the main project (the one from the cwd) is not the same as the root project" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "4743bfcf-c2dc-4f5e-9dda-aab1b0326a6b" 11 | type = "fix" 12 | description = "`slam changelog update-pr` always counted 0 entries to update and thus never committed the changes" 13 | author = "@NiklasRosenstein" 14 | -------------------------------------------------------------------------------- /.changelog/0.16.1.toml: -------------------------------------------------------------------------------- 1 | release-date = "2021-08-12" 2 | 3 | [[entries]] 4 | id = "4e8d1a4b-1cc3-4cb8-8c28-2eeeaac6217a" 5 | type = "fix" 6 | description = "pass `filename` to databind deserializer when loading monorepo/package configuration files" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "3eb47c19-33c9-4921-a750-4e3d3b254a56" 11 | type = "fix" 12 | description = "Cannot run \"pip install\" without develop mode on package that inherits monorepo license" 13 | author = "@NiklasRosenstein" 14 | issues = [ 15 | "https://github.com/NiklasRosenstein/shut/issues/22", 16 | ] 17 | -------------------------------------------------------------------------------- /.changelog/1.6.24.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-07-21" 2 | 3 | [[entries]] 4 | id = "f883516d-424c-4dfd-9ea9-9d90d5f4b9b2" 5 | type = "improvement" 6 | description = "make use of `nr.python.environment` package to detect and deactivate/activate a virtual env" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "61c89680-487c-45d2-b096-7f8d32597707" 11 | type = "feature" 12 | description = "add `--index-url` and `--extra-index` option to `slap install` (note that `--extra-index` requires a different value format than Pip's `--extra-index-url` option)" 13 | author = "@NiklasRosenstein" 14 | -------------------------------------------------------------------------------- /.changelog/1.0.3.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-02-23" 2 | 3 | [[entries]] 4 | id = "86cb742b-9867-4903-bbcd-4574d42482ba" 5 | type = "improvement" 6 | description = "use more concrete branch name for checkout in `github-actions` plugin to work around issues if the branch name is the same as a top level folder in the repository" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "35582ab9-11d1-456b-99d7-e7e9b5bf0376" 11 | type = "fix" 12 | description = "fix `github-actions` tendency to swallow command output which could have been of use in case of an error" 13 | author = "@NiklasRosenstein" 14 | -------------------------------------------------------------------------------- /docs/docs/commands/init.md: -------------------------------------------------------------------------------- 1 | # `slap init` 2 | 3 | This command bootstrap Python project files. 4 | 5 |
Synopsis 6 | ``` 7 | @shell slap init --help 8 | ``` 9 |
10 | 11 | ## Templates 12 | 13 | ### Poetry 14 | 15 | ``` title="$ slap init -t poetry --name my.pkg" 16 | @shell slap init -t poetry --name my.pkg -f --dry 17 | ``` 18 | 19 | @shell slap init -t poetry --name my.pkg --as-markdown -q 20 | 21 | ### Github 22 | 23 | ``` title="$ slap init -t github" 24 | @shell slap init -t github -f --dry 25 | ``` 26 | 27 | @shell slap init -t github --as-markdown -q 28 | -------------------------------------------------------------------------------- /docs/docs/guides/github.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: GitHub 3 | --- 4 | 5 | [0]: https://github.com/NiklasRosenstein/slap 6 | 7 | # Using Slap in GitHub repositories 8 | 9 | ## GitHub Actions 10 | 11 | Slap comes with a set of helpful GitHub actions that you can use in your workflows. These actions are available on 12 | the [`NiklasRosenstein/slap`][0] repository. The templates generated by `slap init -t github` make use of these 13 | actions to provide a good out-of-the-box experience. 14 | 15 | @shell slap init -t github --as-markdown -q 16 | 17 | These files demonstrate the usage of the GitHub actions. 18 | -------------------------------------------------------------------------------- /.changelog/1.8.1.toml: -------------------------------------------------------------------------------- 1 | release-date = "2023-05-13" 2 | 3 | [[entries]] 4 | id = "e178f3c0-e5c7-4d71-848a-5f033ee9b697" 5 | type = "improvement" 6 | description = "add E203 to ignored lints in flake8" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "78a841db-cd55-46c4-a95d-a2d4239b544e" 11 | type = "fix" 12 | description = "Fix parsing version constraints that are wrapped with parentheses" 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "a9d38705-64cf-434d-8d0b-26c7a29a0e91" 17 | type = "fix" 18 | description = "Fix Github workflow templates" 19 | author = "@NiklasRosenstein" 20 | -------------------------------------------------------------------------------- /docs/docs/api/plugins.md: -------------------------------------------------------------------------------- 1 | # Plugins 2 | 3 | A lot of Slap's internal functionality is provided through a plugin interface, allowing other tools to extend the 4 | functionality of Slap further. 5 | 6 | ## Types of plugins 7 | 8 | * [slap.plugins.ApplicationPlugin][] – This is the main type of plugin. Most other types of plugins are registered through an 9 | application plugin using the `Application.plugins` registry. 10 | * [slap.plugins.CheckPlugin][] – The type of plugin used by `slap check`. 11 | * [slap.plugins.ReleasePlugin][] – The type of plugin used by `slap release` to detect version references. 12 | -------------------------------------------------------------------------------- /.changelog/0.11.3.toml: -------------------------------------------------------------------------------- 1 | release-date = "2021-01-29" 2 | 3 | [[entries]] 4 | id = "d301d7ad-75b9-4ff3-b556-a2c618619d2a" 5 | type = "fix" 6 | description = "fix error rendering `setup.py` if the license file did not exist" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "66ec16ee-ec8b-4e4e-8578-b31dc13954bf" 11 | type = "feature" 12 | description = "add `get-version` command to `shut mono` and `shut pkg`" 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "87fd558c-78f1-42d3-afbe-19b2719e6aee" 17 | type = "fix" 18 | description = "`shut pkg requirements add` now adds the InstallConfiguration pip args" 19 | author = "@NiklasRosenstein" 20 | -------------------------------------------------------------------------------- /.changelog/1.3.1.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-03-24" 2 | 3 | [[entries]] 4 | id = "a2daee85-5134-493d-9013-8f87a403f23d" 5 | type = "feature" 6 | description = "add `github` template to `slap init`" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "ac74d490-a8a4-4189-9990-ec82527fcb90" 11 | type = "fix" 12 | description = "`slap run` now uses the reopsitory `slap.toml` configuration file to read the run configuration if no main project exists; `Application.main_project()` no longer tries to find the \"closest\" project because right now Slap does not actually try to find a \"better\" project root than the current working directory" 13 | author = "@NiklasRosenstein" 14 | -------------------------------------------------------------------------------- /.changelog/1.6.4.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-04-19" 2 | 3 | [[entries]] 4 | id = "45c763dc-5a21-4115-9b0f-1d31aac9e062" 5 | type = "improvement" 6 | description = "Set `VIRTUAL_ENV` when venv-aware command activates the environment" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "724c83cf-db53-4143-8681-7eb36625cf1a" 11 | type = "feature" 12 | description = "Add `--from` action to `slap install` command" 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "12d46ef8-8342-4fd2-aefe-9032b83dc4db" 17 | type = "improvement" 18 | description = "The `slap link` command is no longer limited to symlinking only one package per project" 19 | author = "@NiklasRosenstein" 20 | -------------------------------------------------------------------------------- /.changelog/1.0.8.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-02-25" 2 | 3 | [[entries]] 4 | id = "a184b816-3f48-4bb5-a737-a133332ecc3f" 5 | type = "fix" 6 | description = "`slam changelog convert-pr` command now provides YAML error details if the file cannot be parsed and allows the `release_date` to not be set (to convert `_unreleased.yml`)" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "a8395763-e329-4eae-816f-15007a73a802" 11 | type = "fix" 12 | description = "`Project.get_packages()` no longer delegates to the `ProjectHandler` if the project is not a Python project (does not have a `pyproject.toml`) to avoid weird namespace packages to be detected unintentionally" 13 | author = "@NiklasRosenstein" 14 | -------------------------------------------------------------------------------- /.changelog/0.21.1.toml: -------------------------------------------------------------------------------- 1 | release-date = "2021-11-21" 2 | 3 | [[entries]] 4 | id = "f6349bc8-8f86-4efb-850c-84eef3011668" 5 | type = "fix" 6 | description = "using `VirtualFiles.get_modified_files()` no longer creates non-existent directories" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "3b5b4a64-5ea0-4aa4-b63f-d5743534396b" 11 | type = "fix" 12 | description = "catch malformed ref error when trying to determine main branch name and use `develop` as default (see #40)" 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "85c9c5e2-e19e-4fb5-b3e1-cfbec86c59e5" 17 | type = "fix" 18 | description = "`$package.render-requirements-txt` is now handled correctly" 19 | author = "@NiklasRosenstein" 20 | -------------------------------------------------------------------------------- /docs/docs/commands/install.md: -------------------------------------------------------------------------------- 1 | # `slap install` 2 | 3 | > This command is venv aware. 4 | 5 | Install the current project or all projects in a mono-repository into the current Python environment, including 6 | development dependencies and extras. After cloning a new repository, this is often the first command you run 7 | after creating a virtual environment (for that, see [`slap venv`](venv.md)). 8 | 9 | Common options to add are `--link` if you want to develop on the project(s) and `--no-venv-check` if you want 10 | don't want Slap to protect you from accidentally installing the project(s) into a non-virtual Python environment. 11 | 12 |
Synopsis 13 | ``` 14 | @shell slap install --help 15 | ``` 16 |
17 | -------------------------------------------------------------------------------- /tests/slap/python/test_environment.py: -------------------------------------------------------------------------------- 1 | import platform 2 | import sys 3 | 4 | from slap.python.environment import PythonEnvironment 5 | 6 | 7 | def test__PythonEnvironment__with_current_python_instance(): 8 | environment = PythonEnvironment.of(sys.executable) 9 | assert environment.executable == sys.executable 10 | assert environment.version == sys.version 11 | assert environment.platform == platform.platform() 12 | assert environment.prefix == sys.prefix 13 | assert environment.base_prefix == getattr(sys, "base_prefix", None) 14 | assert environment.real_prefix == getattr(sys, "real_prefix", None) 15 | assert environment.has_importlib_metadata() 16 | assert environment.get_distribution("setuptools") is not None 17 | -------------------------------------------------------------------------------- /.changelog/0.17.2.toml: -------------------------------------------------------------------------------- 1 | release-date = "2021-08-14" 2 | 3 | [[entries]] 4 | id = "b495129f-737b-4853-b860-71d1fbd3fdf2" 5 | type = "fix" 6 | description = "`shut mono|pkg update --verify-tag \"\"` does not show an error when it should" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "95492ca5-00e9-4b92-b742-ce3be585f047" 11 | type = "feature" 12 | description = "`shut mono|pkg update --verify-tag` now accepts strings prefixed with `refs/tags/` and strips that prefix (useful for GitHub Actions to use the `$GITHUB_REF` variable)" 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "6fa7e8d8-4d86-4fc9-842a-9b35301594ee" 17 | type = "fix" 18 | description = "`shut pkg update --dry` option not respected" 19 | author = "@NiklasRosenstein" 20 | -------------------------------------------------------------------------------- /.changelog/1.2.4.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-03-22" 2 | 3 | [[entries]] 4 | id = "4633c865-b69e-4fa6-acc5-e1bafdd37f34" 5 | type = "improvement" 6 | description = "The default repository handler now only matches if it appears to make sense to use the Slam CLI inside it." 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "c6891ffa-be1b-40e4-9d0f-b4358cdf95f9" 11 | type = "feature" 12 | description = "add `slam venv` and `slam venv link` commands" 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "37af7703-8dcb-4512-868b-1ab42e21b1be" 17 | type = "fix" 18 | description = "`slam init` now identifies the author to use in the template from Git's global config if the current directory is not already a Git repository" 19 | author = "@NiklasRosenstein" 20 | -------------------------------------------------------------------------------- /.changelog/1.5.3.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-04-14" 2 | 3 | [[entries]] 4 | id = "8c72fd82-89db-4e4e-a983-4d50eb8c5623" 5 | type = "fix" 6 | description = "Fixed forwarding `--python` argument to `slap link` command when using `slap install --link`" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "85ce1c45-b56a-4552-9a82-8a0768555003" 11 | type = "improvement" 12 | description = "For all Slap commands that operate on a Python environment (`install`, `link`, `add`), they will now look for the environment to target in the following order: 1) the `-p,--python` option, 2) the `PYTHON` environment variable, 3) the last local activated environment with `slap venv`, 4) just use `python`" 13 | author = "@NiklasRosenstein" 14 | issues = [ 15 | "https://github.com/NiklasRosenstein/slap/issues/58", 16 | ] 17 | -------------------------------------------------------------------------------- /.changelog/1.6.2.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-04-18" 2 | 3 | [[entries]] 4 | id = "73d18915-6178-4081-a192-e769fe3550dc" 5 | type = "improvement" 6 | description = "Add `PythonEnvironment.version_tuple`" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "4040fbf2-9942-4b8b-9555-2c7234f2bdb4" 11 | type = "improvement" 12 | description = "The `slap venv` command now automatically picks an environment name if you do not specify one with the `-c,--create` flag" 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "7e8d8257-aa01-4848-b83b-723c4b4009f9" 17 | type = "improvement" 18 | description = "Changed `slap install` to no longer install all extras defined in the project by default, now you need to explicitly specify them in `[tool.slap.install].dev-extras`" 19 | author = "@NiklasRosenstein" 20 | -------------------------------------------------------------------------------- /docs/docs/commands/publish.md: -------------------------------------------------------------------------------- 1 | # `slap publish` 2 | 3 | [Twine]: https://twine.readthedocs.io/en/stable/ 4 | 5 | This command builds an `sdist` and `wheel` distribution for your project or every project in your mono-repository and 6 | publishes it using [Twine][] (the command has pretty much all the same options). Often you will use this command 7 | immediately after running [`slap release`](release.md) or from a CI job when new tag/release was created. 8 | 9 | If you just want to build your package(s) and not actually publish them, add the `-d,--dry` option. To be able to 10 | inspect the resulting distribution files and not loose them to the temporary directory, pass an explicit build 11 | directory using `-b,--build-directory`. 12 | 13 |
Synopsis 14 | ``` 15 | @shell slap publish --help 16 | ``` 17 |
18 | -------------------------------------------------------------------------------- /.changelog/0.8.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2020-09-09" 2 | 3 | [[entries]] 4 | id = "fd179760-7132-4446-beaf-04b0ab07393c" 5 | type = "feature" 6 | description = "cli: Add `shut ... test --keep-test-env` option" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "65037ae6-baad-425a-871c-83475c2465fb" 11 | type = "feature" 12 | description = "shut.test.base: Add `TestStatus.SKIPPED` enumeration value" 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "55570104-afc4-4cca-a9ce-bbdc9fb5e221" 17 | type = "fix" 18 | description = "shut.test.pytest: Support skipped tests instead of failing ungracefully" 19 | author = "@NiklasRosenstein" 20 | 21 | [[entries]] 22 | id = "90b21894-1004-4233-b388-95f693873993" 23 | type = "feature" 24 | description = "cli: Support skipped tests `shut ... test` output" 25 | author = "@NiklasRosenstein" 26 | -------------------------------------------------------------------------------- /.changelog/1.6.14.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-05-23" 2 | 3 | [[entries]] 4 | id = "a3bfa069-d920-4269-92af-1a7c74f74a56" 5 | type = "fix" 6 | description = "Add missing black options to init template" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "bfa2dff6-fb5f-4c23-ba26-1cd4c657b708" 11 | type = "improvement" 12 | description = "Init template now uses dmypy by default" 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "4f109020-213c-4c33-adfd-4c12fc9dbc18" 17 | type = "improvement" 18 | description = "Set python_version = 3.6 in Init template" 19 | author = "@NiklasRosenstein" 20 | 21 | [[entries]] 22 | id = "c1192d47-238f-4564-a020-ff7a7f6bde1d" 23 | type = "fix" 24 | description = "`slap install` no longer calls Pip without dependenciesto install, instead it prints this info and exits successfully" 25 | author = "@NiklasRosenstein" 26 | -------------------------------------------------------------------------------- /.changelog/0.20.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2021-09-16" 2 | 3 | [[entries]] 4 | id = "931c3165-1b8b-4186-9773-9e6d6ded1a61" 5 | type = "feature" 6 | description = "add `shut init` command group with initial `shut init mkdocs` command" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "2ea4f693-0040-41f4-8091-0d96f2894f7f" 11 | type = "feature" 12 | description = "add `documentation` job in GitHub Actions template" 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "ebba5079-ef43-4dc6-930e-6b8249c2164e" 17 | type = "feature" 18 | description = "ignore GitHub Actions template when checking package config in GitHub Actions (this is mainly used when using an unreleased version of shut to generate the action template or when needing to do quick modifications to the action config without triggering the shut checks in CI)." 19 | author = "@NiklasRosenstein" 20 | -------------------------------------------------------------------------------- /.changelog/1.10.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2023-08-16" 2 | 3 | [[entries]] 4 | id = "232946ab-fb46-416e-a798-fa7c6a54484f" 5 | type = "feature" 6 | description = "Add fish to supported shells for shimming" 7 | author = "@jonhoo" 8 | pr = "https://github.com/NiklasRosenstein/slap/pull/92" 9 | 10 | [[entries]] 11 | id = "212485b5-ea5d-42cc-b892-755b118102df" 12 | type = "fix" 13 | description = "Fix: CI failing due to black complaining" 14 | author = "florian.eich@helsing.ai" 15 | pr = "https://github.com/NiklasRosenstein/slap/pull/94" 16 | 17 | [[entries]] 18 | id = "c07051de-2ea8-466a-8cf8-ad7737df0fed" 19 | type = "fix" 20 | description = "Fix `GithubActionsRepositoryCiPlugin.publish_changes()` for when a repo lives under a symlinked directory (such as `/tmp -> /private/tmp` on OSX)" 21 | author = "@NiklasRosenstein" 22 | pr = "https://github.com/NiklasRosenstein/slap/pull/94" 23 | -------------------------------------------------------------------------------- /.changelog/1.7.3.toml: -------------------------------------------------------------------------------- 1 | release-date = "2023-03-05" 2 | 3 | [[entries]] 4 | id = "1326c953-8cd4-486d-8a5e-a78f7e3d045c" 5 | type = "fix" 6 | description = "Dependency with extras are now written correctly into `pyprojec.toml` for Poetry projects" 7 | author = "@NiklasRosenstein" 8 | pr = "https://github.com/NiklasRosenstein/slap/pull/84" 9 | issues = [ 10 | "https://github.com/NiklasRosenstein/slap/issues/81", 11 | ] 12 | 13 | [[entries]] 14 | id = "be756b0c-cfbd-40c2-8312-09f5d17889fa" 15 | type = "feature" 16 | description = "The `slap run` command can now run the commands defined in every project of a monorepository (if set), only if the monorepository does not define the command itself (in `slap.toml`)." 17 | author = "@NiklasRosenstein" 18 | pr = "https://github.com/NiklasRosenstein/slap/pull/85" 19 | issues = [ 20 | "https://github.com/NiklasRosenstein/slap/issues/82", 21 | ] 22 | -------------------------------------------------------------------------------- /.changelog/0.7.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2020-09-08" 2 | 3 | [[entries]] 4 | id = "2d8c0806-d334-473a-aefc-ddbf4d27df9f" 5 | type = "improvement" 6 | description = "cli: Removed `-a,--all` flag from `shut mono update`, the command now always behaves like that flag is set" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "f733f6a3-27b9-4a7c-bc05-29b7061dd1e7" 11 | type = "feature" 12 | description = "cli: Add `shut mono update` flags `--verify` and `--verify-tag`" 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "16fcd00b-8e75-4c3b-861c-9a6aca371a7f" 17 | type = "fix" 18 | description = "shut.renderers.setuptools: Fix rendering of `extras_require`" 19 | author = "@NiklasRosenstein" 20 | 21 | [[entries]] 22 | id = "ff44ab7b-e562-4629-9b84-d5a4d7d5fd0c" 23 | type = "feature" 24 | description = "cli: Add `shut mono install` command" 25 | author = "@NiklasRosenstein" 26 | -------------------------------------------------------------------------------- /.changelog/1.7.4.toml: -------------------------------------------------------------------------------- 1 | release-date = "2023-03-05" 2 | 3 | [[entries]] 4 | id = "78dd7d1b-a714-4458-9249-7668429b493b" 5 | type = "improvement" 6 | description = "Add `.dmypy.json` to `.gitignore` generated with `slap init`." 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "a7d9d372-529c-4097-a3c1-745f8484edd7" 11 | type = "fix" 12 | description = "Fix regression when using `slap run ` where `` is not a command defined in the project or repository configuration introduced in the previous version." 13 | author = "@NiklasRosenstein" 14 | pr = "https://github.com/NiklasRosenstein/slap/pull/87" 15 | 16 | [[entries]] 17 | id = "4ae592bb-a3ef-4460-856a-436c2adf58e4" 18 | type = "improvement" 19 | description = "To avoid confusion, the `slap run` command now must be run from a virtual environment." 20 | author = "@NiklasRosenstein" 21 | pr = "https://github.com/NiklasRosenstein/slap/pull/89" 22 | -------------------------------------------------------------------------------- /docs/docs/commands/link.md: -------------------------------------------------------------------------------- 1 | # `slap link` 2 | 3 | [Flit]: https://flit.readthedocs.io/en/latest/ 4 | [Poetry]: https://python-poetry.org/ 5 | 6 | > This command is venv aware. 7 | 8 | Symlink your project or all projects in a mono-repository into the current Python environment. This works for [Poetry][] 9 | projects as well. 10 | 11 | !!! warning 12 | 13 | Independent from the Python build system you are using, Slap reuses [Flit][]'s symlinking feature to perform 14 | this action. This actually symbolically links your source code into the Python site-packages. Be aware that this 15 | _can_ cause your code to be overwritten for example by Pip if you end up overwriting the symlinked installation 16 | of your package by installing another version of it into the same environment. 17 | 18 |
Synopsis 19 | ``` 20 | @shell slap link --help 21 | ``` 22 |
23 | -------------------------------------------------------------------------------- /.changelog/1.6.19.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-07-16" 2 | 3 | [[entries]] 4 | id = "77d1e90e-4d46-40e6-9cce-a28f0cd3a88f" 5 | type = "fix" 6 | description = "fix error message on missing `py.typed` file" 7 | author = "@NiklasRosenstein" 8 | issues = [ 9 | "https://github.com/NiklasRosenstein/slap/issues/62", 10 | ] 11 | 12 | [[entries]] 13 | id = "9cf88811-de43-4b9a-ac52-a9919fcd68b2" 14 | type = "improvement" 15 | description = "add `--no-venv-check` option to `VenvAwareCommand`, fixes #61" 16 | author = "@NiklasRosenstein" 17 | issues = [ 18 | "https://github.com/NiklasRosenstein/slap/issues/61", 19 | ] 20 | 21 | [[entries]] 22 | id = "87694a2f-65d3-45f1-a758-0533b85a40e8" 23 | type = "improvement" 24 | description = "use `build` module instead of hand-crafted build backend, add `Dependencies.build` property" 25 | author = "@NiklasRosenstein" 26 | issues = [ 27 | "https://github.com/NiklasRosenstein/slap/issues/53", 28 | ] 29 | -------------------------------------------------------------------------------- /src/slap/util/once.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing as t 4 | 5 | from .supplier import Supplier, T_co 6 | 7 | 8 | class Once(t.Generic[T_co]): 9 | def __init__(self, supplier: Supplier[T_co]) -> None: 10 | self._supplier = supplier 11 | self._cached: bool = False 12 | self._value: T_co | None = None 13 | 14 | def __repr__(self) -> str: 15 | return f"Once({self._supplier!r})" 16 | 17 | def __bool__(self) -> bool: 18 | return self._cached 19 | 20 | def __call__(self) -> T_co: 21 | if not self._cached: 22 | self._value = self._supplier() 23 | self._cached = True 24 | return t.cast(T_co, self._value) 25 | 26 | def flush(self) -> None: 27 | self._cached = False 28 | 29 | def get(self, resupply: bool = False) -> T_co: 30 | if resupply: 31 | self._cached = False 32 | return self() 33 | -------------------------------------------------------------------------------- /docs/docs/commands/check.md: -------------------------------------------------------------------------------- 1 | # `slap check` 2 | 3 | Check your project configuration for errors, warnings or recommendations. 4 | 5 | ## Configuration 6 | 7 | Option scope: `[tool.slap.check]` or `[check]` 8 | 9 | | Option | Type | Default | Description | 10 | | ------ | ---- | ------- | ----------- | 11 | | `plugins` | `list[str]` | `["changelog", "general", "poetry", "release"]` | A list of check plugins to use. Note that the Poetry plugin only fire checks if your project appears to be using Poetry, so there is no harm in leaving it enabled even if you don't it. Additional plugins can be registered via an `ApplicationPlugin` under the `CheckPlugin` group. | 12 | 13 | --- 14 | 15 | ## Built-in check plugins 16 | 17 | ::: slap.ext.checks.changelog.ChangelogValidationCheckPlugin 18 | 19 | ::: slap.ext.checks.general.GeneralChecksPlugin 20 | 21 | ::: slap.ext.checks.poetry.PoetryChecksPlugin 22 | 23 | ::: slap.ext.checks.release.ReleaseChecksPlugin 24 | -------------------------------------------------------------------------------- /.changelog/1.8.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2023-05-13" 2 | 3 | [[entries]] 4 | id = "ffb21822-a989-49f8-9e7d-ea8af5c2de2a" 5 | type = "fix" 6 | description = "Fix `slap changelog diff pr update --use github-actions` command used by the `NklasRosenstein/slap@gha/changelog/update/v2` action to support forks." 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "84d57dba-1ae8-41a8-86f1-122ef992098e" 11 | type = "improvement" 12 | description = "Store templates that can be created with `slap init` in files instead of in code, improve Python template to include `pycln` and a dummy for running `slap run docs:build`, updated Github actions workflow templates to use `pull_request_target` for changelog validation" 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "724c14e9-d83a-43de-95ec-03a02153c907" 17 | type = "improvement" 18 | description = "add `skip-checks: true` to commits pushed by `slap changelog diff pr update`" 19 | author = "@NiklasRosenstein" 20 | -------------------------------------------------------------------------------- /.changelog/1.14.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2024-05-31" 2 | 3 | [[entries]] 4 | id = "f065877b-eecb-4577-90fb-d18c1fb381af" 5 | type = "breaking change" 6 | description = "Drop support for installing into Python 3.6 virtual environments" 7 | author = "@NiklasRosenstein" 8 | pr = "https://github.com/NiklasRosenstein/slap/pull/118" 9 | 10 | [[entries]] 11 | id = "1e511db2-f7b6-450b-b303-0003a007d9ce" 12 | type = "improvement" 13 | description = "Upgrade Build to ^1.0.0" 14 | author = "@NiklasRosenstein" 15 | pr = "https://github.com/NiklasRosenstein/slap/pull/119" 16 | 17 | [[entries]] 18 | id = "f98854c7-5ec9-4c17-a98c-78809e81bae9" 19 | type = "improvement" 20 | description = "Upgrade UV to ^0.2.0" 21 | author = "@NiklasRosenstein" 22 | pr = "https://github.com/NiklasRosenstein/slap/pull/141" 23 | 24 | [[entries]] 25 | id = "f71eac90-2178-4538-b99b-07b2eae96758" 26 | type = "fix" 27 | description = "Use correct cmd option" 28 | author = "@mcintel" 29 | pr = "https://github.com/NiklasRosenstein/slap/pull/146" 30 | -------------------------------------------------------------------------------- /.changelog/0.16.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2021-08-10" 2 | 3 | [[entries]] 4 | id = "aecfe99c-0a36-49d6-958d-61d7170654ac" 5 | type = "fix" 6 | description = "fix missing import for `typing.Dict` in `pytest` driver module, which could in some Python versions result in a `NameError`" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "504cf094-a7ac-4787-8f6c-2bb8660c2968" 11 | type = "improvement" 12 | description = "add `extra_requires[test]` in generated `setup.py`" 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "94067fd8-9502-46f8-8657-67cbd80d0413" 17 | type = "fix" 18 | description = "fix `setup.py` rendering for inherit license files" 19 | author = "@NiklasRosenstein" 20 | issues = [ 21 | "https://github.com/NiklasRosenstein/shut/issues/21", 22 | ] 23 | 24 | [[entries]] 25 | id = "a0a604ce-23a5-4996-ad81-d216f36a59c6" 26 | type = "improvement" 27 | description = "`_tempcopy()` function in `setup.py` now fails if the file cannot be copied from the source file" 28 | author = "@NiklasRosenstein" 29 | -------------------------------------------------------------------------------- /.changelog/1.6.33.toml: -------------------------------------------------------------------------------- 1 | release-date = "2023-01-29" 2 | 3 | [[entries]] 4 | id = "d3cdc955-98da-4cac-9cd5-93af636845e8" 5 | type = "improvement" 6 | description = "improve error message when there is no current environment set via `slap venv -s {env}`" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "1edc3046-6211-486b-9a1d-8416a97491c3" 11 | type = "feature" 12 | description = "support Poetry 1.2.0+ new `tool.poetry.group.dev.dependencies`" 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "530c41ef-0948-41e0-ba0b-c5f81cbe32c0" 17 | type = "fix" 18 | description = "fix `pyproject.toml` for Poetry 1.3.0+ validation of `tool.poetry.extras` which can only list dependency names, but not their version constraints. Instead, use `tool.poetry.groups.*` introduced in Poetry 1.2.0" 19 | author = "@NiklasRosenstein" 20 | 21 | [[entries]] 22 | id = "7e540a8b-198a-441a-a156-cab07c4354be" 23 | type = "fix" 24 | description = "some internal mypy fixes that come up with newer versions of MyPy" 25 | author = "@NiklasRosenstein" 26 | -------------------------------------------------------------------------------- /docs/docs/commands/report.md: -------------------------------------------------------------------------------- 1 | # report 2 | 3 | The `slap report` commands generate reports about your project. 4 | 5 | ## Subcommands 6 | 7 | ### `slap report dependencies` 8 | 9 | > This command is venv aware. 10 | 11 | Generates a JSON report on the dependencies of your project based on the packages 12 | installed in your environment. Usually, the most interesting piece is the license 13 | information of every dependency, which is contained in the JSON report. The license 14 | name (and text if `--with-license-text` is specified) is read from the package 15 | distribution metadata. 16 | 17 | The command will only resolve only runtime dependencies by default. You can specify 18 | additional extras to include in the resolution using the `--extras` option. 19 | 20 |
Synopsis report dependencies 21 | ``` 22 | @shell slap report dependencies --help 23 | ``` 24 |
25 | 26 |
Dependencies report for Slap 27 | ```json 28 | @shell cd .. && slap report dependencies 29 | ``` 30 |
31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2022 Niklas Rosenstein 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | this software and associated documentation files (the "Software"), to deal in 7 | Software without restriction, including without limitation the rights to use, 8 | modify, merge, publish, distribute, sublicense, and/or sell copies of the 9 | and to permit persons to whom the Software is furnished to do so, subject to 10 | following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 17 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 18 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 20 | USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /.changelog/1.2.1.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-03-14" 2 | 3 | [[entries]] 4 | id = "cb9ff9fc-b96b-4996-9a02-ff9f1ca004ca" 5 | type = "feature" 6 | description = "add `slam changelog ` argument, defaults to the same as `--name`" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "8e765aad-d150-4baa-b942-80161afbdcc3" 11 | type = "improvement" 12 | description = "`slam init` now understands dots as namespace packages" 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "b7dad5e7-e1d9-4977-aa0f-08156cf0fbf8" 17 | type = "improvement" 18 | description = "`slam init` package name written into `pyproject.toml` keeps dots" 19 | author = "@NiklasRosenstein" 20 | 21 | [[entries]] 22 | id = "06af48b7-a6c2-48af-957b-3078cd489720" 23 | type = "feature" 24 | description = "add `slam run` command" 25 | author = "@NiklasRosenstein" 26 | 27 | [[entries]] 28 | id = "04c3396e-981b-4059-b000-00188c658cf2" 29 | type = "improvement" 30 | description = "added `-vv` flag to Pytest command rendered in `pyproject.toml` by `slam init -t poetry` " 31 | author = "@NiklasRosenstein" 32 | -------------------------------------------------------------------------------- /.changelog/1.12.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2024-01-27" 2 | 3 | [[entries]] 4 | id = "69eb006e-0206-4aea-87fa-69bf2000a3f3" 5 | type = "improvement" 6 | description = "Get rid of deprecated `nr.util` dependency by placing all the required bits of code into Slap itself" 7 | author = "@NiklasRosenstein" 8 | pr = "https://github.com/NiklasRosenstein/slap/pull/108" 9 | 10 | [[entries]] 11 | id = "a685b161-8d42-435d-8edd-bdb9be130454" 12 | type = "improvement" 13 | description = "Add `Once.flush()` to use that instead of `.get(resupply=True)`" 14 | author = "@NiklasRosenstein" 15 | pr = "https://github.com/NiklasRosenstein/slap/pull/108" 16 | 17 | [[entries]] 18 | id = "712b2efd-32e6-4ed6-b131-4669d36fc951" 19 | type = "improvement" 20 | description = "Use `importlib-metadata` package over `pkg_resources`" 21 | author = "@NiklasRosenstein" 22 | pr = "https://github.com/NiklasRosenstein/slap/pull/108" 23 | 24 | [[entries]] 25 | id = "2b4061fd-0028-4520-8e76-f7926c1c6ff7" 26 | type = "improvement" 27 | description = "Python 3.12 support" 28 | author = "@NiklasRosenstein" 29 | pr = "https://github.com/NiklasRosenstein/slap/pull/108" 30 | -------------------------------------------------------------------------------- /.changelog/0.19.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2021-09-15" 2 | 3 | [[entries]] 4 | id = "27257b52-912b-47bf-a142-20e82271befa" 5 | type = "improvement" 6 | description = "Changelog entry `fixes` is now optional" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "1427644c-8603-472d-9397-ff9587ccb2ad" 11 | type = "improvement" 12 | description = "switch to Mako as a template engine from Jinja2" 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "5ed6a125-b3b5-4b03-b19c-345a69e19762" 17 | type = "feature" 18 | description = "add `$.templates` to `AbstractProjectModel` and the `github-actions` template" 19 | author = "@NiklasRosenstein" 20 | 21 | [[entries]] 22 | id = "6ab1779f-26dd-4c1c-81b8-5e7e4ab2f4dd" 23 | type = "improvement" 24 | description = "`shut pkg new` now adds the `github-template` to the generated `package.yml`" 25 | author = "@NiklasRosenstein" 26 | 27 | [[entries]] 28 | id = "32b75d0d-ba4b-430b-b597-513d6b8fe52a" 29 | type = "improvement" 30 | description = "remove template rendering capability of `pylint` test driver, instead add `pylintrc` template plugin" 31 | author = "@NiklasRosenstein" 32 | -------------------------------------------------------------------------------- /src/slap/templates/github/.github/workflows/python.yaml: -------------------------------------------------------------------------------- 1 | name: "Python" 2 | 3 | on: 4 | push: {{ branches: [ "develop" ], tags: [ "*" ] }} 5 | pull_request: {{ branches: [ "develop" ] }} 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | python-version: ["3.8", "3.9", "3.10", "3.11"] 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: NiklasRosenstein/slap@gha/install/v1 16 | - uses: actions/setup-python@v5 17 | with: {{ python-version: "${{{{ matrix.python-version }}}}" }} 18 | - run: slap install --link --no-venv-check 19 | - run: slap test 20 | 21 | documentation: 22 | runs-on: ubuntu-latest 23 | needs: test 24 | steps: 25 | - uses: actions/checkout@v4 26 | - uses: NiklasRosenstein/slap@gha/install/v1 27 | - run: slap install --no-venv-check --only-extras docs 28 | - run: slap run --no-venv-check docs:build 29 | - uses: JamesIves/github-pages-deploy-action@v4.6.1 30 | if: github.ref == 'refs/heads/develop' 31 | with: {{ branch: gh-pages, folder: docs/_site, ssh-key: "${{{{ secrets.DEPLOY_KEY }}}}" }} 32 | -------------------------------------------------------------------------------- /.changelog/1.9.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2023-05-15" 2 | 3 | [[entries]] 4 | id = "5b9f7374-2d99-4a46-b359-e876e34e6beb" 5 | type = "breaking change" 6 | description = "Slap now tries to find the repository root even if the current working directory is in a sub-directory of a project in that mono repository. By default, projects in a mono repository will all share the same Virtual environment. This can be disabled by setting the `[tool.slap].shared_venv` setting in `pyproject.toml` to `false`." 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "b66b2aa6-2abe-4dbe-a189-4a70e8fa2352" 11 | type = "improvement" 12 | description = "`slap install` from a project inside a mono repository will only install that specific project and, if any, its dependencies among the projects in the same monorepository." 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "13afd41a-e4db-40b5-bc6a-1fa25fc3425b" 17 | type = "improvement" 18 | description = "Ensure that `slap check`, `slap publish`, `slap report`, `slap run` and `slap test` only run in the context of the main project when in a subdirectory mapping to a Project instead of the entire repository." 19 | author = "@NiklasRosenstein" 20 | -------------------------------------------------------------------------------- /docs/docs/commands/run.md: -------------------------------------------------------------------------------- 1 | # `slap run` 2 | 3 | > This command is venv aware. 4 | 5 | Runs a command, using the commands configured under the `[tool.slap.run]` section as a source for aliases. 6 | 7 | If there is an active virtual environment and you are not already in a virtual environment, it will be activated 8 | before the command is run. 9 | 10 |
Synopsis 11 | ``` 12 | @shell slap run --help 13 | ``` 14 |
15 | 16 | ## Configuration 17 | 18 | Option scope: `[tool.slap.run]` or `[run]` 19 | 20 | | Option | Type | Default | Description | 21 | | ------ | ---- | ------- | ----------- | 22 | | `` | `str` | n/a | A command as a string to run with the system shell. | 23 | 24 | __Example configuration__ 25 | 26 | === "pyproject.toml" 27 | 28 | ```toml 29 | [tool.slap.run] 30 | "docs:build" = "cd docs && novella --base-url slap/" 31 | "docs:dev" = "cd docs && novella --serve" 32 | ``` 33 | 34 | === "slap.toml" 35 | 36 | ```toml 37 | [run] 38 | "docs:build" = "cd docs && novella --base-url slap/" 39 | "docs:dev" = "cd docs && novella --serve" 40 | ``` 41 | 42 | ``` 43 | $ slap run docs:dev 44 | ... 45 | ``` 46 | -------------------------------------------------------------------------------- /.github/workflows/changelog.yaml: -------------------------------------------------------------------------------- 1 | # This workflow will run on every pull request to the develop branch to do the following: 2 | # 3 | # - Insert the Pull Request URL into new changelog entries. 4 | # - Assert that a new changelog entry has been added in the PR unless a "no changelog" label is added to the PR. 5 | 6 | name: "Changelog" 7 | 8 | on: 9 | pull_request_target: { branches: [ "develop" ] } 10 | 11 | jobs: 12 | changelog-update: 13 | name: "Insert Pull Request URL into new changelog entries" 14 | runs-on: ubuntu-latest 15 | if: github.event_name == 'pull_request_target' 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: NiklasRosenstein/slap@gha/changelog/update/v2 19 | with: { pr-id: '${{ github.event.pull_request.number }}' } 20 | 21 | assert-new-changelog-entries: 22 | name: "At least one new changelog entry must be added" 23 | runs-on: ubuntu-latest 24 | if: github.base_ref != '' && !contains(github.event.pull_request.labels.*.name, 'no changelog') 25 | steps: 26 | - uses: actions/checkout@v4 27 | - uses: NiklasRosenstein/slap@gha/changelog/assert-added/v2 28 | with: { pr-id: '${{ github.event.pull_request.number }}' } 29 | -------------------------------------------------------------------------------- /.changelog/0.14.3.toml: -------------------------------------------------------------------------------- 1 | release-date = "2021-08-08" 2 | 3 | [[entries]] 4 | id = "38873e70-a1a6-4dc7-8691-146346acf94a" 5 | type = "fix" 6 | description = "fix `VendoredRequirement._normpath()` which corrupted absolute paths on Windows (e.g. `C:/path/to` would be converted to `./C:\\path\\to` when it should just be `C:\\path\\to`). This caused `shut pkg install` on Windows to fail." 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "163c5fc3-4362-49eb-8756-98ee6b28dd3c" 11 | type = "fix" 12 | description = "`No module named setuptools` error when using `shut pkg install` in venv on Windows" 13 | author = "@NiklasRosenstein" 14 | issues = [ 15 | "https://github.com/NiklasRosenstein/shut/issues/16", 16 | ] 17 | 18 | [[entries]] 19 | id = "bc1526be-cb81-4f01-a962-af86a7a3e1a4" 20 | type = "fix" 21 | description = "fix additional CR in generated `LICENSE.txt` on Windows when installing Shut from source" 22 | author = "@NiklasRosenstein" 23 | issues = [ 24 | "https://github.com/NiklasRosenstein/shut/issues/17", 25 | ] 26 | 27 | [[entries]] 28 | id = "6a1afb87-6db1-4008-92dd-205e226c2580" 29 | type = "fix" 30 | description = "`py.typed` file is rendered into `MANIFEST.in` with backslashes" 31 | author = "@NiklasRosenstein" 32 | -------------------------------------------------------------------------------- /src/slap/templates/github/.github/workflows/changelog.yaml: -------------------------------------------------------------------------------- 1 | # This workflow will run on every pull request to the develop branch to do the following: 2 | # 3 | # - Insert the Pull Request URL into new changelog entries. 4 | # - Assert that a new changelog entry has been added in the PR unless a "no changelog" label is added to the PR. 5 | 6 | name: "Changelog" 7 | 8 | on: 9 | pull_request_target: {{ branches: [ "develop" ] }} 10 | 11 | jobs: 12 | changelog-update: 13 | name: "Insert Pull Request URL into new changelog entries" 14 | runs-on: ubuntu-latest 15 | if: github.event_name == 'pull_request_target' 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: NiklasRosenstein/slap@gha/changelog/update/v2 19 | with: {{ pr-id: '${{{{ github.event.pull_request.number }}}}' }} 20 | 21 | assert-new-changelog-entries: 22 | name: "At least one new changelog entry must be added" 23 | runs-on: ubuntu-latest 24 | if: github.base_ref != '' && !contains(github.event.pull_request.labels.*.name, 'no changelog') 25 | steps: 26 | - uses: actions/checkout@v4 27 | - uses: NiklasRosenstein/slap@gha/changelog/assert-added/v2 28 | with: {{ pr-id: '${{{{ github.event.pull_request.number }}}}' }} 29 | -------------------------------------------------------------------------------- /.changelog/1.7.2.toml: -------------------------------------------------------------------------------- 1 | release-date = "2023-02-20" 2 | 3 | [[entries]] 4 | id = "e68bf649-8a8e-4dcb-85d9-d4b30a9e934b" 5 | type = "improvement" 6 | description = "fix spelling in warning log" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "67ac876d-50e1-498d-bf18-25bc52c6caa8" 11 | type = "feature" 12 | description = "Add zsh to supported shells for shimming" 13 | author = "@cdbrkfxrpt" 14 | 15 | [[entries]] 16 | id = "65cc3464-7565-48f0-9d94-cece8114cbbe" 17 | type = "fix" 18 | description = "Fix outdated command documentation" 19 | author = "@cdbrkfxrpt" 20 | 21 | [[entries]] 22 | id = "ab82d50e-1635-41a8-8ae5-1759ad8eba92" 23 | type = "improvement" 24 | description = "Rename `GitRepositoryHostPlugin` to `RepositoryCIPlugin`; this clarifies the intent and reduces confusion with the `RepositoryHost` interface." 25 | author = "@NiklasRosenstein" 26 | pr = "https://github.com/NiklasRosenstein/slap/pull/79" 27 | 28 | [[entries]] 29 | id = "d3d0055e-c957-4d41-97f9-dd5ce27f62ad" 30 | type = "improvement" 31 | description = "allow GitHub username resolution to raise any Exception; we catch it, log it and fall back to using the configured Git email address instead." 32 | author = "@NiklasRosenstein" 33 | pr = "https://github.com/NiklasRosenstein/slap/pull/80" 34 | -------------------------------------------------------------------------------- /.changelog/0.9.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2020-11-20" 2 | 3 | [[entries]] 4 | id = "c2dc9b8a-678f-45bb-94a1-a5e7a2b7f55a" 5 | type = "fix" 6 | description = "cli: Fix `shut mono bump` not updating requirements in package `setup.py` if packages have inter dependencies" 7 | author = "@NiklasRosenstein" 8 | issues = [ 9 | "https://github.com/NiklasRosenstein/shut/issues/7", 10 | ] 11 | 12 | [[entries]] 13 | id = "e665b36f-2c40-44ea-b941-500e9473860c" 14 | type = "feature" 15 | description = "shut.model.package: Add `$.install.index-url` and `$.install.extra-index-urls` fields" 16 | author = "@NiklasRosenstein" 17 | 18 | [[entries]] 19 | id = "e5b1ebf7-8319-4de1-aed7-a9c0a086ed9a" 20 | type = "feature" 21 | description = "cli: `shut pkg install` now supports `$.install.index-url` and `$.install.extra-index-urls`, added a `--pipx` option" 22 | author = "@NiklasRosenstein" 23 | 24 | [[entries]] 25 | id = "f9beed95-d76c-4714-b229-43939340d441" 26 | type = "feature" 27 | description = "checks: Add check for namespace files" 28 | author = "@NiklasRosenstein" 29 | 30 | [[entries]] 31 | id = "03a946c0-4209-4ec8-97a1-c4b37e88ebe4" 32 | type = "fix" 33 | description = "fix order of dependencies in the same mono repository when using `shut pkg install`" 34 | author = "@NiklasRosenstein" 35 | -------------------------------------------------------------------------------- /.changelog/1.2.2.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-03-20" 2 | 3 | [[entries]] 4 | id = "4d25d208-2f53-46ee-a99d-27d71e731b2c" 5 | type = "improvement" 6 | description = "`test` command no info-logs the command being run" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "00410e5a-28d2-422f-bc93-d4c0695907a4" 11 | type = "feature" 12 | description = "add `slam test -l,--list` option" 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "4d2ff9b3-dbde-42d1-8e4d-5902e01cf35e" 17 | type = "improvement" 18 | description = "`slam init -t poetry` now creates a test file" 19 | author = "@NiklasRosenstein" 20 | 21 | [[entries]] 22 | id = "7c61697e-dc56-40ba-886c-26b544774fae" 23 | type = "fix" 24 | description = "Fix `slam init` to create a `test/test_import.py` instead of `test/test.py`" 25 | author = "@NiklasRosenstein" 26 | 27 | [[entries]] 28 | id = "9ded719d-4ae4-41b1-82f9-f0640e1c13b0" 29 | type = "fix" 30 | description = "Fix `slam install` command to forward to the `--no-venv-check` option to the `slam link` command" 31 | author = "@NiklasRosenstein" 32 | 33 | [[entries]] 34 | id = "2260124e-ce9f-46a2-9401-4fbf5fe198d0" 35 | type = "fix" 36 | description = "Fix `slam changelog convert` to use the proper default author" 37 | author = "@NiklasRosenstein" 38 | -------------------------------------------------------------------------------- /.changelog/1.4.1.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-03-31" 2 | 3 | [[entries]] 4 | id = "b04948f0-abde-4327-a090-7e8ee30180c6" 5 | type = "improvement" 6 | description = "Enhance docs and output of `slap venv` command" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "07c4879b-bbce-4a60-b9fb-7389ad1d3ef5" 11 | type = "improvement" 12 | description = "The `slap venv` command can now be used with `-a,--activate` and without a `name` argument to activate the previously activated environment or the only one that is available in the context (local vs. global)." 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "8d85b2a5-5fdc-49b2-916a-905348e3978f" 17 | type = "improvement" 18 | description = "Improve `ChangelogReleasePlugin` to write \"releasing changelog\" line only once" 19 | author = "@NiklasRosenstein" 20 | 21 | [[entries]] 22 | id = "790ff132-9782-43fa-99f1-f2866f01cf17" 23 | type = "improvement" 24 | description = "Improve changelog checks plugin to catch more types of errors to return in the check details" 25 | author = "@NiklasRosenstein" 26 | 27 | [[entries]] 28 | id = "acad8c34-9c3b-4ed7-a198-3ba0f2ef9b4e" 29 | type = "improvement" 30 | description = "improve output produces by `slap venv -a`, `slap publish` and `slap release`" 31 | author = "@NiklasRosenstein" 32 | -------------------------------------------------------------------------------- /docs/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "docs" 3 | version = "0.1.0" 4 | description = "Add your description here" 5 | readme = "README.md" 6 | requires-python = ">=3.10, <4" 7 | dependencies = [ 8 | "babel==2.15.0", 9 | "certifi==2024.2.2", 10 | "charset-normalizer==3.3.2", 11 | "click==8.1.7", 12 | "colorama==0.4.6", 13 | "ghp-import==2.1.0", 14 | "griffe==0.45.2", 15 | "idna==3.7", 16 | "jinja2==3.1.4", 17 | "markdown==3.6", 18 | "markupsafe==3.0.2", 19 | "mergedeep==1.3.4", 20 | "mkdocs-autorefs==1.0.1", 21 | "mkdocs==1.6.0", 22 | "mkdocs-material==9.5.25", 23 | "mkdocs-material-extensions==1.3.1", 24 | "mkdocstrings==0.25.1", 25 | "mkdocstrings-python==1.10.3", 26 | "mksync==0.1.4", 27 | "networkx==3.3", 28 | "packaging==24.0", 29 | "paginate==0.5.6", 30 | "pathspec==0.12.1", 31 | "platformdirs==4.2.2", 32 | "pygments==2.18.0", 33 | "pymdown-extensions==10.8.1", 34 | "python-adjudicator==0.3.2", 35 | "python-dateutil==2.9.0.post0", 36 | "pyyaml==6.0.1", 37 | "pyyaml-env-tag==0.1", 38 | "regex==2024.5.15", 39 | "requests==2.32.3", 40 | "six==1.16.0", 41 | "typeapi==1.5.1", 42 | "typing-extensions==4.12.0", 43 | "urllib3==2.2.1", 44 | "watchdog==6.0.0", 45 | ] 46 | -------------------------------------------------------------------------------- /.changelog/1.7.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2023-02-06" 2 | 3 | [[entries]] 4 | id = "ce84fd8b-2093-46c2-aff0-ff937101f809" 5 | type = "breaking change" 6 | description = "Reimplement command to update PRs in added changelog entries and rename it to `slap changelog diff pr update`" 7 | author = "@NiklasRosenstein" 8 | pr = "https://github.com/NiklasRosenstein/slap/pull/75" 9 | 10 | [[entries]] 11 | id = "8628d6e9-3906-4810-aa5a-279dcd830f6a" 12 | type = "improvement" 13 | description = "Update `GithubActionsChangelogUpdateAutomationPlugin.get_pr()` to return the full PR URL instead of just the number." 14 | author = "@NiklasRosenstein" 15 | pr = "https://github.com/NiklasRosenstein/slap/pull/75" 16 | 17 | [[entries]] 18 | id = "5cbf8941-8b3d-40d0-85d9-51cc25f983da" 19 | type = "breaking change" 20 | description = "Rename `ChangelogUpdateAutomationPlugin` to `GitRepositoryHostPlugin`, add `GitRepositoryHostPlugin.all()` and `~.get()`, rename plugin entrypoint from `slap.plugins.changelog_update_automation` to `slap.plugins.git_repository_host`" 21 | author = "@NiklasRosenstein" 22 | pr = "https://github.com/NiklasRosenstein/slap/pull/75" 23 | 24 | [[entries]] 25 | id = "43f8c133-975a-4b99-8531-53dd4404ea29" 26 | type = "feature" 27 | description = "add `slap changelog diff assert-added`" 28 | author = "@NiklasRosenstein" 29 | pr = "https://github.com/NiklasRosenstein/slap/pull/75" 30 | -------------------------------------------------------------------------------- /docs/docs/commands/add.md: -------------------------------------------------------------------------------- 1 | # `slap add` 2 | 3 | > This command is venv aware. 4 | 5 | This command adds one or more Python packages to the dependencies defined in `pyproject.toml`. If the packages 6 | are not already installed, they will be installed into the current Python environment using Pip. 7 | 8 |
Synopsis 9 | ``` 10 | @shell slap add --help 11 | ``` 12 |
13 | 14 | ## Usage example 15 | 16 | The below example installs `httpx` using Pip and adds the dependency to `pyproject.toml`. If your project is using 17 | Poetry as the build backend, it will add `httpx = "^0.22.0"` wheras if it is using Flit, the command will add instead 18 | `'httpx (>=0.22.0,<0.23.0)'`. 19 | 20 | $ slap add httpx 21 | Installing httpx 22 | Adding httpx ^0.22.0 23 | 24 | !!! note 25 | 26 | Slap uses `importlib.metadata.distribution()` to retrieve the version of the package that got installed, or was 27 | already installed, and assumes that the module is available in the target Python environment. The module is part 28 | of the Python standard library starting with Python 3.8. 29 | 30 | ## Support matrix 31 | 32 | | Build system | Supported | 33 | |--------------|-----------------------------------------| 34 | | Flit | ✅ | 35 | | Poetry | ✅ | 36 | | Setuptools | ❌ (dependencies defined in `setup.cfg`) | 37 | -------------------------------------------------------------------------------- /.changelog/1.11.1.toml: -------------------------------------------------------------------------------- 1 | release-date = "2023-12-14" 2 | 3 | [[entries]] 4 | id = "dcd60d11-3358-422d-a86a-474795372cd8" 5 | type = "improvement" 6 | description = "allow passing `none` or `null` for the `slap init --license` parameter" 7 | author = "@NiklasRosenstein" 8 | pr = "https://github.com/NiklasRosenstein/slap/pull/103" 9 | 10 | [[entries]] 11 | id = "af82da0c-1c32-45e0-a28d-534a17c63ea6" 12 | type = "feature" 13 | description = "Add `slap venv --no-upgrade-pip` option and automatically upgrade Pip after creating a virtual environment if the option is not specified." 14 | author = "@NiklasRosenstein" 15 | pr = "https://github.com/NiklasRosenstein/slap/pull/103" 16 | issues = [ 17 | "https://github.com/NiklasRosenstein/slap/issues/102", 18 | ] 19 | 20 | [[entries]] 21 | id = "d8af87d8-191f-40e8-8099-e5a5618cf230" 22 | type = "improvement" 23 | description = "remove dependency on `pkg_resources` directly in the module, although a transitive dependency (`nr.util.plugin`) still uses it" 24 | author = "@NiklasRosenstein" 25 | pr = "https://github.com/NiklasRosenstein/slap/pull/104" 26 | issues = [ 27 | "https://github.com/NiklasRosenstein/slap/issues/98", 28 | ] 29 | 30 | [[entries]] 31 | id = "c5d94767-38b7-4b01-8669-c5aaf027d0ab" 32 | type = "fix" 33 | description = "Fixed a bug in `slap release` where an unreleased changelog in the project root was not renamed and updated in a mono repository" 34 | author = "@NiklasRosenstein" 35 | pr = "https://github.com/NiklasRosenstein/slap/pull/105" 36 | -------------------------------------------------------------------------------- /.changelog/0.10.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2020-11-27" 2 | 3 | [[entries]] 4 | id = "1457094f-b0ee-4c4a-b517-a41e8e535014" 5 | type = "feature" 6 | description = "packages now inherit the monorepos license if it is unset (using PackageModel.get_license())" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "552e756c-aeca-46cd-b20c-765ff2465e45" 11 | type = "feature" 12 | description = "If a package inherits the monoreop license, the LICENSE file will not be replicated in the package directory but instead be copied from the monorepo (using `_tempcopy()` in `setup.py`)" 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "98731f72-57be-44bc-8956-5041f713e588" 17 | type = "feature" 18 | description = "`shut {mono,pkg} update` commands now produce a LICENSE.txt file if a license template exists for the specified license (currently only `MIT`)" 19 | author = "@NiklasRosenstein" 20 | 21 | [[entries]] 22 | id = "1bbd20ac-2e8f-4bf1-a410-e2cee3b449c2" 23 | type = "feature" 24 | description = "packages now inherit the monorepo `author` and `url`, and `version` if `release.single_version == True`" 25 | author = "@NiklasRosenstein" 26 | 27 | [[entries]] 28 | id = "69522f75-4205-4930-8b91-4456350bbff0" 29 | type = "feature" 30 | description = "add \"shut pkg format\" command" 31 | author = "@NiklasRosenstein" 32 | 33 | [[entries]] 34 | id = "19c8a510-a343-472a-bb2b-9a6fd321af3a" 35 | type = "feature" 36 | description = "package `author` and `url` are now inherited from the monorepo" 37 | author = "@NiklasRosenstein" 38 | -------------------------------------------------------------------------------- /.changelog/1.6.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-04-16" 2 | 3 | [[entries]] 4 | id = "6ac10f2d-25b1-4193-8007-c9c695fe54ab" 5 | type = "feature" 6 | description = "add `-p,--path` option to `slap venv` command, note that `-p` was repurposed from `--python` and the option no longer has a short form" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "efa1e01f-ea4d-4e0c-b7b3-05c60617c629" 11 | type = "improvement" 12 | description = "`slap test` and `slap run` commands now activate the active virtual environment if any before running the commands" 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "b3c70f28-839c-4da1-88e0-39194777ffc1" 17 | type = "feature" 18 | description = "Add `-s,--set` option to `slap venv`" 19 | author = "@NiklasRosenstein" 20 | 21 | [[entries]] 22 | id = "dfdc1851-cc98-449c-86f1-c8ccf8884397" 23 | type = "improvement" 24 | description = "The `slap run` command can now run arbitrary commands, but falls back to the ones aliased on the configuration on a match." 25 | author = "@NiklasRosenstein" 26 | 27 | [[entries]] 28 | id = "950fa9c2-f999-4a94-8050-4329035f21b8" 29 | type = "improvement" 30 | description = "Add `VenvAwareCommand` and use it in `add`, `install`, `link`, `report`, `run` and `test`" 31 | author = "@NiklasRosenstein" 32 | 33 | [[entries]] 34 | id = "1ca84add-be07-4226-af48-fbc97409efe0" 35 | type = "improvement" 36 | description = "Add `location` to `DistributionMetadata`, which propagates into the output of `slap report dependencies`" 37 | author = "@NiklasRosenstein" 38 | -------------------------------------------------------------------------------- /.changelog/1.2.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-03-03" 2 | 3 | [[entries]] 4 | id = "e9114573-01d9-41d3-b701-39a18c057559" 5 | type = "refactor" 6 | description = "add `Repository` and `RepositoryHandlerPlugin` which gets to decide which projects are loaded, remove most of the related members from the `Application` class" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "895eae85-f569-43a8-bc94-34b4462e3dd1" 11 | type = "improvement" 12 | description = "morph `VcsHost` into `RepositoryHost` class" 13 | author = "NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "93d5a5e9-b9a3-4e7c-ae63-689e4a534876" 17 | type = "fix" 18 | description = "fix possible `AssertionError` in `Application._get_main_project()` that can occur if Slam is invoked in a directory that does not contain a `pyproject.toml` nor `slam.toml` file." 19 | author = "@NiklasRosenstein" 20 | 21 | [[entries]] 22 | id = "336311d4-26f3-406f-91bd-4e764a90738e" 23 | type = "improvement" 24 | description = "`slam changelog format` in the Terminal now colorizes inline code (indicated by backticks)" 25 | author = "@NiklasRosenstein" 26 | 27 | [[entries]] 28 | id = "165a458d-fec8-44c7-ac44-6ad63e44d743" 29 | type = "improvement" 30 | description = "All application plugins are now loaded by default (no explicit list of plugins to load), but the `tool.slam.application.disable` config persists to disable the activation of a particular set of plugins" 31 | author = "@NiklasRosenstein" 32 | 33 | [[entries]] 34 | id = "6a36eba2-468e-4aca-8a3a-17be5246bc10" 35 | type = "feature" 36 | description = "add `slam init` command" 37 | author = "@NiklasRosenstein" 38 | -------------------------------------------------------------------------------- /src/slap/ext/version_incrementing_rule.py: -------------------------------------------------------------------------------- 1 | import typing as t 2 | 3 | from poetry.core.constraints.version import Version # type: ignore[import] 4 | 5 | from slap.plugins import VersionIncrementingRulePlugin 6 | 7 | 8 | def incrementing_rule(func: t.Callable[[Version], Version]) -> type[VersionIncrementingRulePlugin]: 9 | class _Incrementor(VersionIncrementingRulePlugin): 10 | def increment_version(self, version: Version) -> Version: 11 | return func(version) 12 | 13 | _Incrementor.__name__ = func.__name__ 14 | return _Incrementor 15 | 16 | 17 | @incrementing_rule 18 | def major(version: Version) -> Version: 19 | return version.next_major() 20 | 21 | 22 | @incrementing_rule 23 | def premajor(version: Version) -> Version: 24 | return version.next_major().first_prerelease() 25 | 26 | 27 | @incrementing_rule 28 | def minor(version: Version) -> Version: 29 | return version.next_minor() 30 | 31 | 32 | @incrementing_rule 33 | def preminor(version: Version) -> Version: 34 | return version.next_minor().first_prerelease() 35 | 36 | 37 | @incrementing_rule 38 | def patch(version: Version) -> Version: 39 | return version.next_patch() 40 | 41 | 42 | @incrementing_rule 43 | def prepatch(version: Version) -> Version: 44 | return version.next_patch().first_prerelease() 45 | 46 | 47 | @incrementing_rule 48 | def prerelease(version: Version) -> Version: 49 | if version.is_unstable(): 50 | assert version.pre 51 | return Version(version.epoch, version.release, version.pre.next()) 52 | else: 53 | return version.next_patch().first_prerelease() 54 | -------------------------------------------------------------------------------- /.changelog/1.0.0a2.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-02-13" 2 | 3 | [[entries]] 4 | id = "e096fd2d-43b4-4e26-819f-e3b03b66a020" 5 | type = "improvement" 6 | description = "reduce code duplicity in `shut.commands.link` and `shut.util.python_package`" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "97db9bc6-4da8-4a53-b228-080524425621" 11 | type = "fix" 12 | description = "fix `shut log command --author,-a` option being a flag" 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "b5aa999a-b8c4-4587-b249-e61c92d1b74e" 17 | type = "improvement" 18 | description = "allow `pyproject.toml` to not exist when using `shut release` command" 19 | author = "@NiklasRosenstein" 20 | 21 | [[entries]] 22 | id = "bc4244d6-18e4-4002-809d-08b3a4b9b2e0" 23 | type = "improvement" 24 | description = "fix pattern replacing `{version}` string in release references" 25 | author = "@NiklasRosenstein" 26 | 27 | [[entries]] 28 | id = "ec53c3ce-d20e-4dbd-a2de-b44e51fc9fcb" 29 | type = "improvement" 30 | description = "Add `Application.load_subapp()` and `Application.subapps`" 31 | author = "@NiklasRosenstein" 32 | 33 | [[entries]] 34 | id = "18ae011f-46f8-4981-af84-007f08e8abc5" 35 | type = "improvement" 36 | description = "`shut release` now has an `include` configuration key that can be used to include the version refs of sub projects, for example in a mono repository where a root `shut.toml` is used to manage the same version number across all components" 37 | author = "@NiklasRosenstein" 38 | 39 | [[entries]] 40 | id = "da3db5ee-c19c-471e-b1ce-b70e210f6d45" 41 | type = "improvement" 42 | description = "add support for `SLAM_DIR` environment variable" 43 | author = "@NiklasRosenstein" 44 | -------------------------------------------------------------------------------- /tests/slap/python/test_pep508.py: -------------------------------------------------------------------------------- 1 | from slap.python.pep508 import Pep508Environment 2 | 3 | 4 | def test__Pep508Environment__sample_markers(): 5 | env = Pep508Environment( 6 | python_version="3.10", 7 | python_full_version="3.10.2", 8 | os_name="posix", 9 | sys_platform="darwin", 10 | platform_release="21.3.0", 11 | platform_system="Darwin", 12 | platform_machine="arm64", 13 | platform_python_implementation="CPython", 14 | implementation_name="cpython", 15 | implementation_version="3.10.2", 16 | ) 17 | 18 | assert env.evaluate_markers('os_name == "posix"') 19 | assert not env.evaluate_markers('os_name == "Posix"') 20 | 21 | assert env.evaluate_markers('python_version<"3.11"') 22 | assert not env.evaluate_markers('python_version<"3.10"') 23 | 24 | assert env.evaluate_markers('(os_name=="nt") or python_version<="3.10"') 25 | assert not env.evaluate_markers('(os_name=="nt") and python_version<="3.10"') 26 | assert env.evaluate_markers('(os_name=="posix") and python_version<="3.10"') 27 | 28 | assert env.evaluate_markers( 29 | '(extra == "docs" or extra == "dev") and ((os_name=="posix") and python_version<="3.10")', {"docs"} 30 | ) 31 | assert env.evaluate_markers( 32 | '(extra == "docs" or extra == "dev") and ((os_name=="posix") and python_version<="3.10")', {"dev"} 33 | ) 34 | assert not env.evaluate_markers( 35 | '(extra == "docs" or extra == "dev") and ((os_name=="posix") and python_version<="3.10")', {"foobar"} 36 | ) 37 | 38 | # All fields are supposed to be strings 39 | for key, value in env.as_json().items(): 40 | assert isinstance(value, str) 41 | -------------------------------------------------------------------------------- /docs/docs/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | The Slap configuration is read either from a `slap.toml` file or from the `[tool.slap]` section in `pyproject.toml`. 4 | 5 | !!! note 6 | 7 | A `slap.toml` configuration file is usually only used at the project root in case of mono-repositories for multiple 8 | Python projects. The file is often used to 9 | 10 | * disable mono-repository level changelogs ([`slap changelog`](commands/changelog.md#configuration)) 11 | * configure global tests or commands ([`slap run`](commands/run.md#configuration), [`slap test`](commands/test.md#configuration)) 12 | * global version references ([`slap release`](commands/release.md#configuration)) 13 | 14 | In this section, we describe the global configuration options that affect Slap directly and are not specifically 15 | tied to any single Slap command. 16 | 17 | | Option | Type | Default | Description | 18 | | ------ | ---- | ------- | ----------- | 19 | | `typed` | `bool | None` | `None` | Whether the Python code uses type hints. If not set, Slap acts as if this is not known. | 20 | | `disable` | `list[str]` | `[]` | A list of Slap application plugins to disable. | 21 | | `enable-only` | `list[str]` | `[]` | A list of Slap application plugins to enable. | 22 | 23 | ## Plugin loading 24 | 25 | All Slap commands are implemented as [`ApplicationPlugin`s][slap.plugins.ApplicationPlugin]. By default, Slap will load plugin 26 | that is registered under the `slap.plugins.application` entrypoint, however plugins can be disabled using the `disable` 27 | option or an explicit list of plugins to load and none other can be set with `enable-only`. 28 | 29 | Restricting the plugins to load will impact the set of commands available at your disposal through the Slap CLI. 30 | -------------------------------------------------------------------------------- /.changelog/1.3.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-03-23" 2 | 3 | [[entries]] 4 | id = "3f6cc68c-9ae1-4814-a9d9-a5799af9a300" 5 | type = "improvement" 6 | description = "upgrade to using `databind` 2.x" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "f0bcce7c-345c-42eb-851f-7c4b8f44a546" 11 | type = "breaking change" 12 | description = "Rename Slam to Slap" 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "52a29516-ef0a-4b53-bd22-1ca5d36b3d9d" 17 | type = "improvement" 18 | description = "Split default project handler into separate implementations for Poetry, Flit and Setuptools and properly support Setuptools now (reading `setup.cfg`)" 19 | author = "@NiklasRosenstein" 20 | 21 | [[entries]] 22 | id = "71d0c4b2-e7c4-4474-b688-20bc9a06a624" 23 | type = "feature" 24 | description = "add `-b,--build-directory` option to `slap publish` command" 25 | author = "@NiklasRosenstein" 26 | 27 | [[entries]] 28 | id = "fb745286-30a0-40cf-9a2f-b40970e48ffa" 29 | type = "feature" 30 | description = "add `slap.check.check()` decorator and `slap.check.get_checks()` function to make implementing check plugins more convenient" 31 | author = "@NiklasRosenstein" 32 | 33 | [[entries]] 34 | id = "ac717886-ad1e-4e5c-a918-b8729e215745" 35 | type = "improvement" 36 | description = "add `Project.version()` and `Project.get_version_refs()`" 37 | author = "@NiklasRosenstein" 38 | 39 | [[entries]] 40 | id = "1ca3f595-8aed-4fd2-b395-28cce4312dad" 41 | type = "improvement" 42 | description = "move interdependencies version ref detection into `DefaultProjectHandler`" 43 | author = "@NiklasRosenstein" 44 | 45 | [[entries]] 46 | id = "fb08ead1-ea8e-45ad-ab7e-b1922dd31756" 47 | type = "improvement" 48 | description = "add overload for `match_version_ref_pattern()` that can return a fallback value" 49 | author = "@NiklasRosenstein" 50 | -------------------------------------------------------------------------------- /.changelog/0.5.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2020-09-05" 2 | 3 | [[entries]] 4 | id = "cfc74db5-ca34-4886-8489-f389ebda990f" 5 | type = "improvement" 6 | description = "shut.model.package: Remove `PackageData` class, merge it's members into `PackageModel` (this does not change the interface of `PackageModel`), Removed `PackageModel.data` property" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "e93be978-2b58-48b1-8c53-0af2458ffcf9" 11 | type = "tests" 12 | description = "fix unit tests" 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "eb64cc96-9b79-4a29-abe9-501ddd058ebf" 17 | type = "improvement" 18 | description = "shore.checkers.package: `package-author` check is now an error level check" 19 | author = "@NiklasRosenstein" 20 | 21 | [[entries]] 22 | id = "b0da2642-9aff-4181-b14a-2a1fe0637f8c" 23 | type = "improvement" 24 | description = "cli: `shut mono bump` and `shut pkg bump` no longer run checks, the command group already does it" 25 | author = "@NiklasRosenstein" 26 | 27 | [[entries]] 28 | id = "2fdcd037-2e4c-4633-a91d-c7bbcd2e5fb9" 29 | type = "fix" 30 | description = "shut.renderers.setuptools: fix rendering of `python_requires` option in `setup.py` if package is universal (i.e. the `python` requirement uses an OR (|) which we cannot currently translate to setuptools correctly)" 31 | author = "@NiklasRosenstein" 32 | 33 | [[entries]] 34 | id = "21080c05-4ad2-4f28-a1c5-2529d28ead3c" 35 | type = "feature" 36 | description = "cli: add `shut pkg test` command which and `PackageModel.test_driver` configuration, the `shore.test` sub-package implements test drivers (currently just `pytest`)" 37 | author = "@NiklasRosenstein" 38 | 39 | [[entries]] 40 | id = "27e4da05-f7ec-44fb-918a-eda00a2b1a4e" 41 | type = "feature" 42 | description = "cli: add `shut mono test` command" 43 | author = "@NiklasRosenstein" 44 | -------------------------------------------------------------------------------- /src/slap/templates/poetry/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["poetry-core"] 3 | build-backend = "poetry.core.masonry.api" 4 | 5 | [tool.poetry] 6 | name = "{name}" 7 | version = "0.1.0" 8 | description = "" 9 | authors = ["{author_name} <{author_email}>"] 10 | license = "{license}" 11 | readme = "README.md" 12 | packages = [{ include = "{path}", from = "src" }] 13 | classifiers = [] 14 | keywords = [] 15 | 16 | [tool.poetry.urls] 17 | # "Bug Tracker" = "" 18 | # Documentation = "" 19 | # Homepage = "" 20 | # Repository = "" 21 | 22 | [tool.poetry.dependencies] 23 | python = "^3.8" 24 | 25 | [tool.poetry.dev-dependencies] 26 | black = "*" 27 | flake8 = "*" 28 | isort = "*" 29 | mypy = "*" 30 | pycln = "^2.1.3" 31 | pytest = "*" 32 | 33 | [tool.poetry.group.docs] 34 | optional = true 35 | 36 | [tool.poetry.group.docs.dependencies] 37 | # ... 38 | 39 | [tool.slap] 40 | typed = true 41 | 42 | [tool.slap.test] 43 | check = "slap check" 44 | black = "black --check src/ tests/" 45 | flake8 = "flake8 src/ tests/" 46 | isort = "isort --check-only src/ tests/" 47 | mypy = "dmypy run src/" 48 | pycln = "pycln src/ tests/ --check" 49 | pytest = "pytest tests/ -vv" 50 | 51 | [tool.slap.run] 52 | "docs:build" = ">&2 echo 'Not implemented' && exit 1" 53 | fmt = "pycln src/ tests/ && black src/ tests/ && isort src/ tests/" 54 | 55 | [tool.mypy] 56 | python_version = "3.8" 57 | explicit_package_bases = true 58 | mypy_path = ["src"] 59 | namespace_packages = true 60 | pretty = true 61 | show_error_codes = true 62 | show_error_context = true 63 | strict = true 64 | warn_no_return = true 65 | warn_redundant_casts = true 66 | warn_unreachable = true 67 | warn_unused_ignores = true 68 | 69 | [tool.isort] 70 | profile = "black" 71 | line_length = 120 72 | combine_as_imports = true 73 | 74 | [tool.black] 75 | line-length = 120 76 | -------------------------------------------------------------------------------- /docs/mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Slap 2 | repo_url: https://github.com/NiklasRosenstein/slap 3 | repo_name: NiklasRosenstein/slap 4 | theme: 5 | name: material 6 | palette: 7 | scheme: slate 8 | primary: blue 9 | accent: teal 10 | 11 | exclude_docs: | 12 | !.github 13 | 14 | plugins: 15 | - search 16 | - mkdocstrings: 17 | default_handler: python 18 | handlers: 19 | python: 20 | paths: [../src] 21 | options: 22 | heading_level: 3 23 | show_root_heading: true 24 | show_root_full_path: true 25 | show_symbol_type_heading: true 26 | show_symbol_type_toc: true 27 | signature_crossrefs: true 28 | separate_signature: true 29 | show_signature_annotations: true 30 | 31 | markdown_extensions: 32 | - admonition 33 | - pymdownx.highlight: 34 | anchor_linenums: true 35 | line_spans: __span 36 | pygments_lang_class: true 37 | - pymdownx.inlinehilite 38 | - pymdownx.snippets 39 | - pymdownx.superfences 40 | 41 | nav: 42 | - Home: index.md 43 | - getting-started.md 44 | - configuration.md 45 | - glossary.md 46 | - Commands: 47 | - slap add: commands/add.md 48 | - slap changelog: commands/changelog.md 49 | - slap check: commands/check.md 50 | - slap info: commands/info.md 51 | - slap init: commands/init.md 52 | - slap install: commands/install.md 53 | - slap link: commands/link.md 54 | - slap publish: commands/publish.md 55 | - slap release: commands/release.md 56 | - slap report: commands/report.md 57 | - slap run: commands/run.md 58 | - slap test: commands/test.md 59 | - slap venv: commands/venv.md 60 | - Guides: 61 | - guides/github.md 62 | - API: 63 | - api/plugins.md 64 | - api/documentation.md 65 | - changelog.md 66 | -------------------------------------------------------------------------------- /.changelog/0.18.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2021-08-26" 2 | 3 | [[entries]] 4 | id = "287e9b95-ef77-4291-9777-55450991c836" 5 | type = "feature" 6 | description = "add `pylint` test driver" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "cca2fc74-0e72-4ba0-95df-9dbf73811454" 11 | type = "fix" 12 | description = "the fix from `0.17.4` actually made the install cache not work at all and just always install test requirements, now shut knows per driver selection whether dependencies have been installed before or not" 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "46347dda-988f-4f40-bfe4-1078e694c03e" 17 | type = "feature" 18 | description = "test drivers are now loaded via the `shut.test_drivers` entrypoint, allowing plugins to be registered from external packages" 19 | author = "@NiklasRosenstein" 20 | 21 | [[entries]] 22 | id = "5ef2b46e-14cb-442c-9c6f-6542b13b3558" 23 | type = "feature" 24 | description = "internal: add `AbstractProjectModel.get_auxiliary_renderers()`" 25 | author = "@NiklasRosenstein" 26 | 27 | [[entries]] 28 | id = "c47cbd43-0f2c-4a9b-a2a5-0b38545fd231" 29 | type = "feature" 30 | description = "`pylint` now comes with `.pylintrc` templates (only `shut` for now) and can also render templates from HTTP(S) URLs" 31 | author = "@NiklasRosenstein" 32 | 33 | [[entries]] 34 | id = "5528cc39-dcf7-46c7-803a-77cbcec228c5" 35 | type = "feature" 36 | description = "Shut now recognizes lowercase readme/license files" 37 | author = "@NiklasRosenstein" 38 | issues = [ 39 | "https://github.com/NiklasRosenstein/shut/issues/31", 40 | ] 41 | 42 | [[entries]] 43 | id = "2d0270e0-49af-4e8c-bc48-022235b86709" 44 | type = "fix" 45 | description = "render `command` variable into `setup.py` only if package `$.install.hooks` is used" 46 | author = "@NiklasRosenstein" 47 | issues = [ 48 | "https://github.com/NiklasRosenstein/shut/issues/33", 49 | ] 50 | -------------------------------------------------------------------------------- /src/slap/ext/release/changelog.py: -------------------------------------------------------------------------------- 1 | import typing as t 2 | from pathlib import Path 3 | 4 | from slap.ext.application.changelog import get_changelog_manager 5 | from slap.plugins import ReleasePlugin 6 | from slap.project import Project 7 | from slap.repository import Repository 8 | 9 | 10 | class ChangelogReleasePlugin(ReleasePlugin): 11 | """Renames the `_unreleased.toml` file when a release is created.""" 12 | 13 | def create_release( 14 | self, repository: Repository, project: Project | None, target_version: str, dry: bool 15 | ) -> t.Sequence[Path]: 16 | changed_files: list[Path] = [] 17 | 18 | config_sources: list[Project | None] = [*repository.projects()] if project is None else [project] 19 | if repository.is_monorepo and project is None: 20 | config_sources.append(None) 21 | 22 | for project in config_sources: 23 | manager = get_changelog_manager(repository, project) 24 | unreleased = manager.unreleased() 25 | if unreleased.path in changed_files: 26 | # We hit this case in a single-project repository, where "None" and the single project will 27 | # both return the same changelog manager. 28 | continue 29 | new_version = manager.version(target_version) 30 | if unreleased.exists(): 31 | cwd = Path.cwd() 32 | old = unreleased.path.relative_to(cwd) 33 | new = new_version.path.relative_to(cwd) 34 | if not changed_files: 35 | self.io.write_line("releasing changelog") 36 | self.io.write_line(f" {old} → {new}") 37 | if not dry: 38 | unreleased.release(target_version) 39 | changed_files += [unreleased.path, new_version.path] 40 | return changed_files 41 | -------------------------------------------------------------------------------- /docs/docs/commands/venv.md: -------------------------------------------------------------------------------- 1 | # `slap venv` 2 | 3 | The `slap venv` command is a utility for managing virtual environments created using the standard library `venv` 4 | module in the current directory or globally (in `~/.local/venvs`). 5 | 6 | Slap keeps track of an "active" environment that is used activated by default by commands such as `slap install`, 7 | `slap link`, `slap run` and `slap test` before they perform their regular duties. This allows you to not have to 8 | actually activate a virtual environment while still operating within it while using Slap commands (similar to 9 | `poetry run`, etc.). 10 | 11 |
Synopsis venv 12 | ``` 13 | @shell slap venv --help 14 | ``` 15 |
16 | 17 |
Synopsis venv link 18 | ``` 19 | @shell slap venv link --help 20 | ``` 21 |
22 | 23 | ## Configuration 24 | 25 | The `venv` command does not have any Slap configuration options. However, in order to use the `slap venv --activate` 26 | option properly, you need to configure your shell to shadow the `slap` command with a function such that it can 27 | source the activate script. 28 | 29 | To do this, run `$ slap venv -i ` and add the output to your shell init scripts. Currently, the `-i` option 30 | only supports `bash` as an argument. 31 | 32 | ```sh title="$ slap venv -i bash" 33 | @shell slap venv -i bash 34 | ``` 35 | 36 | What will be evaluated in your init script then implements the shadow function: 37 | 38 | ```sh title="$ SLAP_SHADOW=true slap venv -i bash" 39 | @shell SLAP_SHADOW=true slap venv -i bash 40 | ``` 41 | 42 | Now you can enjoy using `slap venv -a [-g] []` to activate a virtual environment. 43 | 44 | ## Activate behaviour 45 | 46 | Using `-a,--activate` without arguments will pick the most recently activated environment in the current context, 47 | or if that is not available, the one and only virtual environment. If multiple environments exist in that case, 48 | it is an error. 49 | -------------------------------------------------------------------------------- /.github/workflows/python.yml: -------------------------------------------------------------------------------- 1 | name: "Python Package" 2 | 3 | on: 4 | push: 5 | branches: [ "develop" ] 6 | tags: [ '*' ] 7 | pull_request: 8 | branches: [ "develop" ] 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-22.04 13 | strategy: 14 | matrix: 15 | python-version: ["3.10", "3.11", "3.12", "3.13"] 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/setup-python@v5 19 | with: { python-version: "${{ matrix.python-version }}" } 20 | - run: pip install . && slap install --no-venv-check 21 | - run: slap info 22 | - run: slap test 23 | - run: slap publish --dry 24 | 25 | test-pep508: 26 | runs-on: ubuntu-22.04 27 | strategy: 28 | matrix: 29 | python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] 30 | steps: 31 | - uses: actions/checkout@v4 32 | - uses: actions/setup-python@v5 33 | with: { python-version: "${{ matrix.python-version }}" } 34 | - run: pip install mypy dataclasses types-dataclasses -q 35 | - run: mypy src/slap/python/pep508.py --follow-imports silent 36 | - run: python src/slap/python/pep508.py 37 | 38 | docs: 39 | runs-on: ubuntu-22.04 40 | steps: 41 | - uses: actions/checkout@v4 42 | with: { fetch-depth: 0 } 43 | - run: git checkout -b $GITHUB_HEAD_REF 44 | if: github.event_name == 'pull_request' 45 | - uses: actions/setup-python@v5 46 | with: { python-version: "3.10" } 47 | - uses: astral-sh/setup-uv@v5 48 | - run: uv tool install . 49 | - run: slap run --no-venv-check docs:build 50 | - uses: actions/upload-artifact@v4 51 | with: { name: docs, path: docs/site } 52 | 53 | docs-publish: 54 | needs: [ test, docs ] 55 | runs-on: ubuntu-22.04 56 | if: ${{ github.ref == 'refs/heads/develop' }} 57 | steps: 58 | - uses: actions/download-artifact@v4 59 | with: { name: docs, path: docs/site } 60 | - uses: Cecilapp/GitHub-Pages-deploy@v3 61 | env: { GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" } 62 | with: { email: rosensteinniklas@gmail.com, build_dir: docs/site } 63 | -------------------------------------------------------------------------------- /src/slap/util/cleo.py: -------------------------------------------------------------------------------- 1 | import typing as t 2 | 3 | from cleo.commands.help_command import HelpCommand as _HelpCommand # type: ignore[import] 4 | from cleo.commands.list_command import ListCommand # type: ignore[import] 5 | from cleo.formatters.formatter import Formatter # type: ignore[import] 6 | from cleo.formatters.style import Style # type: ignore[import] 7 | from cleo.io.io import IO # type: ignore[import] 8 | 9 | 10 | @t.overload 11 | def add_style( 12 | io: IO | Formatter, 13 | name: str, 14 | foreground: str | None = ..., 15 | background: str | None = ..., 16 | options: list[str] | None = ..., 17 | ) -> None: ... 18 | 19 | 20 | @t.overload 21 | def add_style( 22 | io: IO | Formatter, 23 | name: str, 24 | style: Style, 25 | ) -> None: ... 26 | 27 | 28 | def add_style( # type: ignore[misc] 29 | io: IO | Formatter, 30 | name: str, 31 | foreground: str | Style | None = None, 32 | background: str | None = None, 33 | options: list[str] | None = None, 34 | *, 35 | style: Style | None = None, 36 | ) -> None: 37 | """ 38 | Add a style to a Cleo IO or Formatter instance. 39 | """ 40 | 41 | if style is not None: 42 | assert foreground is None and background is None and options is None 43 | elif isinstance(foreground, Style): 44 | style = foreground 45 | assert background is None and options is None 46 | else: 47 | style = Style(foreground, background, options) 48 | 49 | if isinstance(io, IO): 50 | io.output.formatter.set_style(name, style) 51 | io.error_output.formatter.set_style(name, style) 52 | elif isinstance(io, Formatter): 53 | io.set_style(name, style) 54 | else: 55 | raise TypeError(f"expected IO|Formatter, got {type(io).__name__}") 56 | 57 | 58 | class HelpCommand(_HelpCommand, ListCommand): 59 | arguments = ListCommand.arguments 60 | 61 | def handle(self) -> int: 62 | self.io.input._arguments["namespace"] = None 63 | if not self._command: 64 | return ListCommand.handle(self) 65 | return _HelpCommand.handle(self) 66 | -------------------------------------------------------------------------------- /src/slap/util/text.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import io 4 | import typing as t 5 | 6 | T = t.TypeVar("T") 7 | SubstRange = t.Tuple[int, int, str] 8 | 9 | 10 | def substitute_ranges(text: str, ranges: t.Iterable[SubstRange], is_sorted: bool = False) -> str: 11 | """Replaces parts of *text* using the specified *ranges* and returns the new text. Ranges must not overlap. 12 | *is_sorted* can be set to `True` if the input *ranges* are already sorted from lowest to highest starting index to 13 | optimize the function. 14 | """ 15 | 16 | if not is_sorted: 17 | ranges = sorted(ranges, key=lambda x: x[0]) 18 | 19 | out = io.StringIO() 20 | max_end_index = 0 21 | for index, (istart, iend, subst) in enumerate(ranges): 22 | if iend < istart: 23 | raise ValueError(f"invalid range at index {index}: (istart: {istart!r}, iend: {iend!r})") 24 | if istart < max_end_index: 25 | raise ValueError(f"invalid range at index {index}: overlap with previous range") 26 | 27 | subst = str(subst) 28 | out.write(text[max_end_index:istart]) 29 | out.write(subst) 30 | max_end_index = iend 31 | 32 | out.write(text[max_end_index:]) 33 | return out.getvalue() 34 | 35 | 36 | def longest_common_substring( 37 | seq1: t.Sequence[T], 38 | seq2: t.Sequence[T], 39 | *args: t.Sequence[T], 40 | start_only: bool = False, 41 | ) -> t.Sequence[T]: 42 | """Finds the longest common contiguous sequence of elements in *seq1* and *seq2* and returns it.""" 43 | 44 | longest: tuple[int, int] = (0, 0) 45 | for i in (0,) if start_only else range(len(seq1)): 46 | for j in (0,) if start_only else range(len(seq2)): 47 | k = 0 48 | while (i + k < len(seq1) and (j + k) < len(seq2)) and seq1[i + k] == seq2[j + k]: 49 | k += 1 50 | if k > longest[1] - longest[0]: 51 | longest = (i, i + k) 52 | 53 | result = seq1[longest[0] : longest[1]] 54 | if args: 55 | result = longest_common_substring(result, *args, start_only=start_only) 56 | return result 57 | -------------------------------------------------------------------------------- /src/slap/util/logging.py: -------------------------------------------------------------------------------- 1 | """Provides a logging formatter that understands color hints in the message and decorates it with""" 2 | 3 | from __future__ import annotations 4 | 5 | import logging 6 | 7 | import typing_extensions as te 8 | 9 | from slap.util.notset import NotSet 10 | from slap.util.terminal import StyleManager 11 | 12 | 13 | def get_default_styles() -> StyleManager: 14 | manager = StyleManager() 15 | manager.add_style("info", "blue") 16 | manager.add_style("warning", "magenta") 17 | manager.add_style("error", "red") 18 | manager.add_style("critical", "bright red", None, "bold,underline") 19 | return manager 20 | 21 | 22 | class TerminalColorFormatter(logging.Formatter): 23 | """A formatter that enhances text decorated with HTML-style tags with ANSI terminal colors. It can also be 24 | configured to eliminate the HTML tags instead of converting them to terminal styles.""" 25 | 26 | def __init__(self, fmt: str, styles: StyleManager | None | NotSet = NotSet.Value) -> None: 27 | super().__init__(fmt) 28 | self.styles = get_default_styles() if styles is NotSet.Value else styles 29 | 30 | def format(self, record: logging.LogRecord) -> str: 31 | message = super().format(record) 32 | if self.styles is None: 33 | return StyleManager.strip_tags(message) 34 | else: 35 | return self.styles.format(message, True) 36 | 37 | def install(self, target: te.Literal["tty", "notty"] | None = None) -> None: 38 | """Install the formatter on stream handlers on all handlers of the root logger that are attached to a TTY, 39 | or otherwise on all that are not attached to a TTY based on the *target* value. If no value is specified, it 40 | will install into TTY-attached stream handlers if #styles is set.""" 41 | 42 | if target is None: 43 | target = "notty" if self.styles is None else "tty" 44 | 45 | for handler in logging.root.handlers: 46 | if isinstance(handler, logging.StreamHandler) and handler.stream.isatty(): 47 | if target == "tty": 48 | handler.setFormatter(self) 49 | elif target == "notty": 50 | handler.setFormatter(self) 51 | -------------------------------------------------------------------------------- /src/slap/ext/release/source_code_version.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from slap.plugins import ReleasePlugin 4 | from slap.project import Project 5 | from slap.release import VersionRef, match_version_ref_pattern 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | class SourceCodeVersionReferencesPlugin(ReleasePlugin): 11 | """This plugin searches for a `__version__` key in the source code of the project and return it as a version 12 | reference. Based on the Poetry configuration (considering `tool.poetry.packages` and searching in the `src/` 13 | folder if it exists), the following source files will be checked: 14 | 15 | * `__init__.py` 16 | * `__about__.py` 17 | * `_version.py` 18 | 19 | Note that configuring `tool.poetry.packages` is needed for the detection to work correctly with PEP420 20 | namespace packages. 21 | """ 22 | 23 | VERSION_REGEX = r'^__version__\s*=\s*[\'"]([^\'"]+)[\'"]' 24 | FILENAMES = ["__init__.py", "__about__.py", "_version.py", ".py"] 25 | 26 | def get_version_refs(self, project: Project) -> list[VersionRef]: 27 | packages = project.packages() 28 | if not packages: 29 | return [] 30 | 31 | results = [] 32 | packages_without_version = [] 33 | for package in packages: 34 | for path in [package.path] if package.path.is_file() else [package.path / f for f in self.FILENAMES]: 35 | if path.exists(): 36 | try: 37 | version_ref = match_version_ref_pattern(path, self.VERSION_REGEX) 38 | except ValueError as exc: 39 | logger.warning("%s", exc) 40 | continue 41 | if version_ref: 42 | results.append(version_ref) 43 | break 44 | else: 45 | packages_without_version.append(package) 46 | 47 | if packages_without_version: 48 | logger.warning( 49 | "Unable to detect __version__ in the following packages of project " 50 | "%s: %s", 51 | project, 52 | [p.name for p in packages_without_version], 53 | ) 54 | 55 | return results 56 | -------------------------------------------------------------------------------- /src/slap/util/orderedset.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import functools 3 | import typing as t 4 | 5 | T = t.TypeVar("T") 6 | T_OrderedSet = t.TypeVar("T_OrderedSet", bound="OrderedSet") 7 | 8 | 9 | @functools.total_ordering 10 | class OrderedSet(t.MutableSet[T]): 11 | def __init__(self, iterable: t.Optional[t.Iterable[T]] = None) -> None: 12 | self._index_map: t.Dict[T, int] = {} 13 | self._content: t.Deque[T] = collections.deque() 14 | if iterable is not None: 15 | self.update(iterable) 16 | 17 | def __repr__(self) -> str: 18 | if not self._content: 19 | return "%s()" % (type(self).__name__,) 20 | return "%s(%r)" % (type(self).__name__, list(self)) 21 | 22 | def __iter__(self) -> t.Iterator[T]: 23 | return iter(self._content) 24 | 25 | def __reversed__(self) -> "OrderedSet[T]": 26 | return OrderedSet(reversed(self._content)) 27 | 28 | def __eq__(self, other: t.Any) -> bool: 29 | if isinstance(other, OrderedSet): 30 | return len(self) == len(other) and list(self) == list(other) 31 | return False 32 | 33 | def __le__(self, other: t.Any) -> bool: 34 | return all(e in other for e in self) 35 | 36 | def __len__(self) -> int: 37 | return len(self._content) 38 | 39 | def __contains__(self, key: t.Any) -> bool: 40 | return key in self._index_map 41 | 42 | def __getitem__(self, index: int) -> T: 43 | return self._content[index] 44 | 45 | def add(self, key: T) -> None: 46 | if key not in self._index_map: 47 | self._index_map[key] = len(self._content) 48 | self._content.append(key) 49 | 50 | def copy(self: T_OrderedSet) -> "T_OrderedSet": 51 | return type(self)(self) 52 | 53 | def discard(self, key: T) -> None: 54 | if key in self._index_map: 55 | index = self._index_map.pop(key) 56 | del self._content[index] 57 | 58 | def pop(self, last: bool = True) -> T: 59 | if not self._content: 60 | raise KeyError("set is empty") 61 | key = self._content.pop() if last else self._content.popleft() 62 | self._index_map.pop(key) 63 | return key 64 | 65 | def update(self, iterable: t.Iterable[T]) -> None: 66 | for x in iterable: 67 | self.add(x) 68 | -------------------------------------------------------------------------------- /src/slap/util/url.py: -------------------------------------------------------------------------------- 1 | """Tools for URL handling.""" 2 | 3 | from __future__ import annotations 4 | 5 | import dataclasses 6 | import urllib.parse 7 | 8 | 9 | @dataclasses.dataclass 10 | class Url: 11 | """Helper to represent the components of a URL, including first class support for username, password, host 12 | and port.""" 13 | 14 | scheme: str = "" 15 | hostname: str = "" 16 | path: str = "" 17 | params: str = "" 18 | query: str = "" 19 | fragment: str = "" 20 | 21 | username: str | None = None 22 | password: str | None = None 23 | port: int | None = None 24 | 25 | def __str__(self) -> str: 26 | return urllib.parse.urlunparse((self.scheme, self.netloc, self.path, self.params, self.query, self.fragment)) 27 | 28 | @property 29 | def netloc(self) -> str: 30 | """Returns the entire network location with auth and port.""" 31 | 32 | auth = self.auth 33 | if auth: 34 | return f"{self.auth}@{self.netloc_no_auth}" 35 | 36 | return self.netloc_no_auth 37 | 38 | @property 39 | def auth(self) -> str | None: 40 | """Returns just the auth part of the network location.""" 41 | 42 | if self.username or self.password: 43 | return f'{urllib.parse.quote(self.username or "")}:{urllib.parse.quote(self.password or "")}' 44 | 45 | return None 46 | 47 | @property 48 | def netloc_no_auth(self) -> str: 49 | """Returns the network location without the auth part.""" 50 | 51 | if self.port is None: 52 | return self.hostname 53 | 54 | return f"{self.hostname}:{self.port}" 55 | 56 | @staticmethod 57 | def of(url: str) -> Url: 58 | """Parses the *url* string into its parts. 59 | 60 | Raises: 61 | ValueError: If an invalid URL is passed (for example if the port number cannot be parsed to an integer). 62 | """ 63 | parsed = urllib.parse.urlparse(url) 64 | return Url( 65 | scheme=parsed.scheme, 66 | hostname=parsed.hostname or "", 67 | path=parsed.path, 68 | params=parsed.params, 69 | query=parsed.query, 70 | fragment=parsed.fragment, 71 | username=parsed.username, 72 | password=parsed.password, 73 | port=parsed.port, 74 | ) 75 | -------------------------------------------------------------------------------- /src/slap/ext/checks/changelog.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | import typing as t 3 | 4 | from slap.check import Check, CheckResult, check, get_checks 5 | from slap.ext.application.changelog import get_changelog_manager 6 | from slap.plugins import CheckPlugin 7 | from slap.project import Project 8 | 9 | 10 | @dataclasses.dataclass 11 | class ChangelogValidationCheckPlugin(CheckPlugin): 12 | """This check plugin validates the structured changelog files, if any. 13 | 14 | Plugin ID: `changelog`""" 15 | 16 | def get_project_checks(self, project: Project) -> t.Iterable[Check]: 17 | return get_checks(self, project) 18 | 19 | @check("validate") 20 | def _validate_changelogs(self, project: Project) -> tuple[CheckResult, str | None, str | None]: 21 | import tomli 22 | from databind.core.converter import ConversionError 23 | 24 | manager = get_changelog_manager(project.repository, project) 25 | bad_files = [] 26 | bad_changelogs = [] 27 | count = 0 28 | for changelog in manager.all(): 29 | count += 1 30 | try: 31 | for entry in changelog.load().entries: 32 | try: 33 | manager.validate_entry(entry) 34 | except (ConversionError, ValueError) as exc: 35 | bad_changelogs.append((changelog.path.name, str(exc), entry.id)) 36 | except (tomli.TOMLDecodeError, ConversionError) as exc: 37 | bad_files.append((changelog.path.name, str(exc))) 38 | 39 | if not count: 40 | return CheckResult.SKIPPED, None, None 41 | 42 | return ( 43 | Check.ERROR if bad_changelogs else Check.Result.OK, 44 | "Broken or invalid changelogs" if bad_changelogs else f"All {count} changelogs are valid.", 45 | ( 46 | "\n".join(f"{fn}: {err}" for fn, err in bad_files) 47 | if bad_files 48 | else ( 49 | "" 50 | + "\n" 51 | + "\n".join( 52 | f'{fn}: id="{entry_id}": {err}' for fn, err, entry_id in bad_changelogs 53 | ) 54 | if bad_changelogs 55 | else "" 56 | ) 57 | ).strip() 58 | or None, 59 | ) 60 | -------------------------------------------------------------------------------- /src/slap/util/toml_file.py: -------------------------------------------------------------------------------- 1 | """Represents a mutable TOML configuration file in memory.""" 2 | 3 | import typing as t 4 | from pathlib import Path 5 | 6 | T = t.TypeVar("T") 7 | 8 | 9 | class TomlFile(t.MutableMapping[str, t.Any]): 10 | def __init__(self, path: Path, data: dict[str, t.Any] | None = None) -> None: 11 | self._path = path 12 | self._data: dict[str, t.Any] | None = data 13 | 14 | def __repr__(self) -> str: 15 | return f'TomlConfig(path="{self.path}")' 16 | 17 | def __bool__(self) -> bool: 18 | return self._data is not None or self.exists() 19 | 20 | def __len__(self) -> int: 21 | return len(self.load()) 22 | 23 | def __iter__(self) -> t.Iterator[str]: 24 | return iter(self.load()) 25 | 26 | def __delitem__(self, key: str) -> None: 27 | del self.load()[key] 28 | 29 | def __getitem__(self, key: str) -> t.Any: 30 | return self.load()[key] 31 | 32 | def __setitem__(self, key: str, value: t.Any) -> None: 33 | self.load()[key] = value 34 | 35 | def exists(self) -> bool: 36 | return self._path.is_file() 37 | 38 | def load(self, force_reload: bool = False) -> dict[str, t.Any]: 39 | import tomli 40 | 41 | if self._data is None or force_reload: 42 | with self._path.open("rb") as fp: 43 | self._data = tomli.load(fp) 44 | return self._data 45 | 46 | def save(self) -> None: 47 | import tomli_w 48 | 49 | if self._data is None: 50 | raise RuntimeError("TomlDocument is empty, call load() or value(data)") 51 | with self._path.open("wb") as fp: 52 | tomli_w.dump(self._data, fp) 53 | 54 | @t.overload 55 | def value(self) -> dict[str, t.Any]: ... 56 | 57 | @t.overload 58 | def value(self, data: dict[str, t.Any]) -> None: ... 59 | 60 | def value(self, data: dict[str, t.Any] | None = None) -> dict[str, t.Any] | None: 61 | if data is None: 62 | return self.load() 63 | else: 64 | self._data = data 65 | return None 66 | 67 | def value_or(self, default: T) -> dict[str, t.Any] | T: 68 | if self._data is not None: 69 | return self._data 70 | if self.exists(): 71 | return self.load() 72 | return default 73 | 74 | @property 75 | def path(self) -> Path: 76 | return self._path 77 | -------------------------------------------------------------------------------- /.changelog/1.4.5.toml: -------------------------------------------------------------------------------- 1 | release-date = "2022-04-04" 2 | 3 | [[entries]] 4 | id = "a0847448-c31f-4fe4-97d5-ec706c999a81" 5 | type = "fix" 6 | description = "Fix `slap venv link` linking to an absolute path (correct) instead of a relartive path (wrong) when linking from a local environment" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "3160e954-c7ff-4c2f-af02-658003c18fcc" 11 | type = "improvement" 12 | description = "No longer error when running Slap in a Python project directory using Poetry where some dependencies are declared using the table format in TOML, but log that the type of dependency is currently unsupported" 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "bd6e203d-1a23-4f59-a521-ba83866e9eb7" 17 | type = "improvement" 18 | description = "Allow unrecognized keys in `ChangelogConfig`" 19 | author = "@NiklasRosenstein" 20 | 21 | [[entries]] 22 | id = "bec1a134-1576-46c8-8159-f52090d42c35" 23 | type = "improvement" 24 | description = "Add a separate API to retrieve the projects in a repository in topological order such that most Slap commands that do not care about the order do not have the overhead of inspecting the project dependencies and sorting the projects." 25 | author = "@NiklasRosenstein" 26 | issues = [ 27 | "https://github.com/NiklasRosenstein/slam/issues/50", 28 | ] 29 | 30 | [[entries]] 31 | id = "9f018ae5-04d8-468f-b2b6-ca993476da47" 32 | type = "feature" 33 | description = "Add `slap.dependency` module" 34 | author = "@NiklasRosenstein" 35 | 36 | [[entries]] 37 | id = "978c013e-3c79-427a-8c1f-ce56d34d5bc9" 38 | type = "feature" 39 | description = "Add `slap report dependencies` command" 40 | author = "@NiklasRosenstein" 41 | 42 | [[entries]] 43 | id = "8550fb4f-e65f-4fe0-b270-f697476c8716" 44 | type = "fix" 45 | description = "Fix detection of added changelog entries in GitHub Actions for forked repositories; but that does not fix #49" 46 | author = "@NiklasRosenstein" 47 | 48 | [[entries]] 49 | id = "5407fdd7-2700-45cf-a837-cfbed82525fb" 50 | type = "fix" 51 | description = "Fix a potential issue in `slap add` when `tomlkit` returns an `OutOfOrderTableProxy` instead of a `Table` container in which case we did not mutate the actual TOML document" 52 | author = "@NiklasRosenstein" 53 | 54 | [[entries]] 55 | id = "f3fbf0e6-9b6a-4698-bb2a-e0a2b894ca21" 56 | type = "feature" 57 | description = "Add `--source` option to `slap add` command" 58 | author = "@NiklasRosenstein" 59 | -------------------------------------------------------------------------------- /src/slap/util/external/pypi_classifiers.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import logging 3 | import os 4 | import time 5 | 6 | import requests 7 | 8 | CACHE_FILENAME = os.path.expanduser("~/.local/slap/classifiers-cache.txt") 9 | CACHE_TTL = 60 * 60 * 24 * 7 # 7 days 10 | CLASSIFIERS_URL = "https://pypi.org/pypi?%3Aaction=list_classifiers" 11 | logger = logging.getLogger(__name__) 12 | _runtime_cache: list[str] | None = None 13 | 14 | 15 | def get_classifiers(force_refresh: bool = False) -> list[str]: 16 | """ 17 | Loads the classifiers list from PyPI. Once loaded, the classifiers are cached on disk and 18 | in memory. The cache on disk is valid for a maxmium of seven days. Specify the *force_refresh* 19 | argument to ignore any caches. 20 | """ 21 | 22 | global _runtime_cache 23 | if not force_refresh and _runtime_cache is not None: 24 | return list(_runtime_cache) 25 | 26 | def _load_cachefile(): 27 | global _runtime_cache 28 | with open(CACHE_FILENAME) as fp: 29 | _runtime_cache = [x.rstrip("\n") for x in fp] 30 | return list(_runtime_cache) 31 | 32 | has_cachefile = not force_refresh and os.path.isfile(CACHE_FILENAME) 33 | if has_cachefile and (time.time() - os.path.getmtime(CACHE_FILENAME)) < CACHE_TTL: 34 | return _load_cachefile() 35 | try: 36 | classifiers = requests.get(CLASSIFIERS_URL, timeout=1).text.split("\n") 37 | except requests.exceptions.ReadTimeout as exc: 38 | if has_cachefile: 39 | cache_timestamp = datetime.datetime.fromtimestamp(os.path.getmtime(CACHE_FILENAME)) 40 | message = " Falling back to cached classifiers (timestamp: {})".format(cache_timestamp) 41 | else: 42 | message = " Returning no classifiers. Subsequent classifier validation might raise." 43 | logger.warning('Error retrieving classifiers from "%s" (%s).%s', CLASSIFIERS_URL, exc, message) 44 | if has_cachefile: 45 | return _load_cachefile() 46 | _runtime_cache = [] 47 | return [] 48 | try: 49 | os.makedirs(os.path.dirname(CACHE_FILENAME), exist_ok=True) 50 | with open(CACHE_FILENAME, "w") as fp: 51 | fp.writelines((x + "\n" for x in classifiers)) 52 | except BaseException: 53 | logger.exception("Unable to write classifiers cache file.") 54 | 55 | _runtime_cache = classifiers 56 | return list(classifiers) 57 | -------------------------------------------------------------------------------- /src/slap/ext/checks/general.py: -------------------------------------------------------------------------------- 1 | import typing as t 2 | 3 | from slap.check import Check, CheckResult, check, get_checks 4 | from slap.plugins import CheckPlugin 5 | from slap.project import Project 6 | 7 | 8 | class GeneralChecksPlugin(CheckPlugin): 9 | """This plugin provides general checks applicable to all types of projects managed with Slap. 10 | 11 | Plugin ID: `general`.""" 12 | 13 | # TODO (@NiklasRosenstein): Check if VCS remote is configured? 14 | 15 | def get_project_checks(self, project: Project) -> t.Iterable[Check]: 16 | return get_checks(self, project) 17 | 18 | @check("packages") 19 | def _check_detect_packages(self, project: Project) -> tuple[CheckResult, str | None]: 20 | """Checks if the project handler employed by Slap for your project is detecting any Python packages. If no 21 | Python packages can be detected, it might hint at a configuration issue.""" 22 | 23 | packages = project.packages() 24 | result = Check.Result.SKIPPED if packages is None else Check.Result.OK if packages else Check.Result.ERROR 25 | message = "Detected " + ", ".join(f"{p.root}/{p.name}" for p in packages) if packages else None 26 | return result, message 27 | 28 | @check("typed") 29 | def _check_py_typed(self, project: Project) -> tuple[CheckResult, str]: 30 | expect_typed = project.config().typed 31 | if expect_typed is None: 32 | return Check.Result.WARNING, "tool.slap.typed is not set" 33 | 34 | has_py_typed = set[str]() 35 | has_no_py_typed = set[str]() 36 | for package in project.packages() or []: 37 | (has_py_typed if (package.path / "py.typed").is_file() else has_no_py_typed).add(package.name) 38 | 39 | if expect_typed and has_no_py_typed: 40 | error = True 41 | message = f'py.typed missing in package(s) {", ".join(has_no_py_typed)}' 42 | elif not expect_typed and has_py_typed: 43 | error = True 44 | message = f'py.typed in package(s) should not exist {", ".join(has_py_typed)}' 45 | else: 46 | error = False 47 | message = ( 48 | "py.typed exists as expected" if expect_typed else "py.typed does not exist as expected" 49 | ) 50 | 51 | return (Check.Result.ERROR if error else Check.Result.OK, message) 52 | -------------------------------------------------------------------------------- /src/slap/util/weak_property.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import weakref 4 | from typing import Any, Generic, Literal, Optional, TypeVar, overload 5 | 6 | T = TypeVar("T") 7 | 8 | 9 | class WeakProperty(Generic[T]): 10 | def __init__(self, attr_name: str, once: bool = False) -> None: 11 | self._name = attr_name 12 | self._once = once 13 | self._value: Optional[weakref.ReferenceType[T]] = None 14 | 15 | def __set__(self, instance: Any, value: T) -> None: 16 | has_value: Optional[weakref.ReferenceType[T]] = getattr(instance, self._name, None) 17 | if self._once and has_value is not None: 18 | raise RuntimeError("property can not be set more than once") 19 | setattr(instance, self._name, weakref.ref(value)) 20 | 21 | def __get__(self, instance: Any, owner: Any) -> T: 22 | if instance is None: 23 | raise AttributeError() 24 | has_value: Optional[weakref.ReferenceType[T]] = getattr(instance, self._name, None) 25 | if has_value is None: 26 | raise AttributeError("property value is not set") 27 | value = has_value() 28 | if value is None: 29 | raise RuntimeError("lost weak reference") 30 | return value 31 | 32 | 33 | class OptionalWeakProperty(WeakProperty[Optional[T]]): 34 | def __set__(self, instance: Any, value: Optional[T]) -> None: 35 | has_value: Optional[weakref.ReferenceType[T]] = getattr(instance, self._name, None) 36 | if self._once and has_value is not None: 37 | raise RuntimeError("property can not be set more than once") 38 | setattr(instance, self._name, weakref.ref(value) if value is not None else None) 39 | 40 | def __get__(self, instance: Any, owner: Any) -> Optional[T]: 41 | if instance is None: 42 | raise AttributeError() 43 | try: 44 | return super().__get__(instance, owner) 45 | except AttributeError: 46 | return None 47 | 48 | 49 | @overload 50 | def weak_property(attr_name: str, once: bool = False, optional: Literal[False] = False) -> Any: ... 51 | 52 | 53 | @overload 54 | def weak_property(attr_name: str, once: bool = False, optional: Literal[True] = True) -> Any | None: ... 55 | 56 | 57 | def weak_property(attr_name: str, once: bool = False, optional: bool = False) -> Any | None: 58 | return WeakProperty(attr_name, once) if optional else OptionalWeakProperty(attr_name, once) 59 | -------------------------------------------------------------------------------- /docs/docs/glossary.md: -------------------------------------------------------------------------------- 1 | # Glossary 2 | 3 | ## Project 4 | 5 | At it's core, any directory can be a "project", but most projects would have a `pyproject.toml` or `slap.toml` 6 | configuration file. Most commonly, it will have a `pyproject.toml` file that contains the Slap configuration 7 | under the `[tool.slap]` namespace. Many concepts in Slap are attached directly to a project. For example if 8 | the project has a `pyproject.toml`, it is considered a Python project, and Python projects can have packages 9 | that can be installed, built or published to PyPI or another repository. 10 | 11 | A project with a `slap.toml` configuration file (or with no configuration file) is usually used as the parent 12 | project for two or more sub-projects. This configuration represents a mono-repository where multiple projects 13 | are stored and versioned together. 14 | 15 | Slap understands most details about a project through a [`ProjectHandlerPlugin`][slap.plugins.ProjectHandlerPlugin]. 16 | It comes with a default implementation that supports Poetry and Flit Python projects. 17 | 18 | ## Repository 19 | 20 | A repository is a directory that contains one or more projects. For singular projects, the repository directory 21 | is the same as the project directory. For a mono-repository configuration, any directory that contains multiple 22 | projects is considered the repository directory (it usually contains a `slap.toml` configuration file). 23 | 24 | Note that the repository directory is _also_ considered a project directory, but if it isn't also a Python 25 | project (i.e. if it has a `pyproject.toml` instead of a `slap.toml`) it will be ignored for most Slap commands. 26 | 27 | === "Single project" 28 | 29 | ``` 30 | my_pacakge/ -> Repository / Project "my_package" 31 | pyproject.toml 32 | readme.md 33 | src/ 34 | tests/ 35 | ``` 36 | 37 | === "Mono repository" 38 | 39 | ``` 40 | / -> Repository / Project "$" 41 | my_first_package/ -> Project "my_first_package" 42 | pyproject.toml 43 | readme.md 44 | src/ 45 | tests/ 46 | my_second_package/ -> Project "my_second_package" 47 | pyproject.toml 48 | readme.md 49 | src/ 50 | tests/ 51 | slap.toml 52 | ``` 53 | 54 | Slap understands most details about a repository through a [`RepositoryHandlerPlugin`][slap.plugins.RepositoryHandlerPlugin]. 55 | It comes with a default implementation that supports Git repositories and GitHub. 56 | -------------------------------------------------------------------------------- /src/slap/ext/project_handlers/uv.py: -------------------------------------------------------------------------------- 1 | """Project handler for projects using the Poetry build system.""" 2 | 3 | from __future__ import annotations 4 | 5 | import logging 6 | import typing as t 7 | 8 | from slap.ext.project_handlers.base import PyprojectHandler 9 | from slap.project import Dependencies, Project 10 | from slap.python.dependency import VersionSpec 11 | 12 | if t.TYPE_CHECKING: 13 | from slap.python.dependency import Dependency 14 | 15 | logger = logging.getLogger(__name__) 16 | 17 | 18 | class UvProjectHandler(PyprojectHandler): 19 | # ProjectHandlerPlugin 20 | 21 | def matches_project(self, project: Project) -> bool: 22 | if not project.pyproject_toml.exists(): 23 | return False 24 | if (project.directory / "uv.lock").exists(): 25 | return True 26 | if project.pyproject_toml.get("tool", {}).get("uv") is not None: 27 | return True 28 | return False 29 | 30 | def get_dist_name(self, project: Project) -> str | None: 31 | return project.pyproject_toml.get("project", {}).get("name") 32 | 33 | def get_readme(self, project: Project) -> str | None: 34 | return project.pyproject_toml.get("project", {}).get("readme") or super().get_readme(project) 35 | 36 | def get_dependencies(self, project: Project) -> Dependencies: 37 | from slap.install.installer import Indexes 38 | from slap.python.dependency import PypiDependency, parse_dependencies 39 | 40 | python_version = project.pyproject_toml.get("project", {}).get("python") 41 | dependencies = parse_dependencies(project.pyproject_toml.get("project", {}).get("dependencies", [])) 42 | dev_dependencies = parse_dependencies( 43 | project.pyproject_toml.get("tool", {}).get("uv", {}).get("dev-dependencies", []) 44 | ) 45 | 46 | # TODO: Support index-url / extra-index-urls options. 47 | # TODO: Support dependency groups. 48 | 49 | return Dependencies( 50 | python=VersionSpec(python_version) if python_version else None, 51 | run=dependencies, 52 | dev=dev_dependencies, 53 | extra={}, 54 | build=PypiDependency.parse_list(project.pyproject_toml.get("build-system", {}).get("requires", [])), 55 | indexes=Indexes(), 56 | ) 57 | 58 | def get_add_dependency_toml_location_and_config( 59 | self, 60 | project: Project, 61 | dependency: Dependency, 62 | where: str, 63 | ) -> tuple[list[str], list | dict]: 64 | raise NotImplementedError("Uv project handler does not support adding dependencies") 65 | -------------------------------------------------------------------------------- /.changelog/0.4.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2020-09-04" 2 | 3 | [[entries]] 4 | id = "0e3f87ff-ee12-45f1-b5b6-72ca4e39e8d6" 5 | type = "improvement" 6 | description = "cli: Updated temrinal output of `shut ... checks`" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "6a3ddaef-13c8-42b4-b489-e57a74ac5630" 11 | type = "improvement" 12 | description = "shut.model.package: Add `PackageError` exception class" 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "a10443a2-8599-49d2-908b-d99c76d58d9d" 17 | type = "improvement" 18 | description = "shut.checkers.monorepo: Add `bad-package-directory` check to Monorepo checker" 19 | author = "@NiklasRosenstein" 20 | 21 | [[entries]] 22 | id = "eea8dbfb-a42d-4e10-9e5f-ecca673be409" 23 | type = "improvement" 24 | description = "cli: `shut pkg checks` now inherits checks from the Monorepo if the check result targets the package directly (e.g. the `bad-package-directory` check)" 25 | author = "@NiklasRosenstein" 26 | 27 | [[entries]] 28 | id = "773518a2-a764-4a21-8221-4ccb2dbe6217" 29 | type = "improvement" 30 | description = "shut.checkers.core: Add `CheckResult.subject` member (default: `None`)" 31 | author = "@NiklasRosenstein" 32 | 33 | [[entries]] 34 | id = "a041f86c-b8be-4933-936a-4bf4a971bb77" 35 | type = "improvement" 36 | description = "cli: `shut pkg` and `shut mono` commands will now print failed checks to stderr before executing the requested subcommand." 37 | author = "@NiklasRosenstein" 38 | 39 | [[entries]] 40 | id = "652aa37b-7649-4ac3-a4e3-0679901045e8" 41 | type = "improvement" 42 | description = "shut.checkers.monorepo: include error message in `invalid-package` check result" 43 | author = "@NiklasRosenstein" 44 | 45 | [[entries]] 46 | id = "51fd7931-f5ae-4d26-9890-d81b0433f46f" 47 | type = "improvement" 48 | description = "shut.checkers.generic: `unknown-config-options` check no longer shows if it passes" 49 | author = "@NiklasRosenstein" 50 | 51 | [[entries]] 52 | id = "32067862-b629-4e55-8c76-d3f2d63f1f3e" 53 | type = "improvement" 54 | description = "shut.checkers.generic: rename `unknown-config` check to `unknown-config-options`" 55 | author = "@NiklasRosenstein" 56 | 57 | [[entries]] 58 | id = "4039d28f-3693-4bdb-b5d1-2f595a0e6279" 59 | type = "improvement" 60 | description = "cli: no longer set `PYTHONWARNINGS` environment variable" 61 | author = "@NiklasRosenstein" 62 | 63 | [[entries]] 64 | id = "1d06586e-d28e-4d22-b6ea-27be360d202b" 65 | type = "improvement" 66 | description = "shut.model.monorepo: add `Monorepo.get_inter_dependencies_for()` method" 67 | author = "@NiklasRosenstein" 68 | 69 | [[entries]] 70 | id = "a497c99d-5462-4ebb-a285-ecb5d7554c05" 71 | type = "feature" 72 | description = "cli: add `shut pkg install` command" 73 | author = "@NiklasRosenstein" 74 | -------------------------------------------------------------------------------- /src/slap/configuration.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import logging 4 | import typing as t 5 | from pathlib import Path 6 | 7 | from slap.util.toml_file import TomlFile 8 | 9 | if t.TYPE_CHECKING: 10 | from slap.util.once import Once 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | class Configuration: 16 | """Represents the configuration stored in a directory, which is either read from `slap.toml` or `pyproject.toml`.""" 17 | 18 | id: str 19 | 20 | #: The directory of the project. This is the directory where the `slap.toml` or `pyproject.toml` configuration 21 | #: would usually reside in, but the existence of neither is absolutely required. 22 | directory: Path 23 | 24 | #: Points to the `pyproject.toml` file in the project and can be used to conveniently check for its existence 25 | #: or to access its contents. 26 | pyproject_toml: TomlFile 27 | 28 | #: Points to the `slap.toml` file in the project and can be used to conveniently check for its existence 29 | #: or to access its contents. 30 | slap_toml: TomlFile 31 | 32 | #: Use this to access the Slap configuration, automatically loaded from either `slap.toml` or the `tool.slap` 33 | #: section in `pyproject.toml`. The attribute is a #Once instance, thus it needs to be called to retrieve 34 | #: the contents. This is the same as #get_raw_configuration(), but is more efficient. 35 | raw_config: Once[dict[str, t.Any]] 36 | 37 | def __init__(self, directory: Path) -> None: 38 | from slap.util.once import Once 39 | 40 | self.directory = directory 41 | self.pyproject_toml = TomlFile(directory / "pyproject.toml") 42 | self.slap_toml = TomlFile(directory / "slap.toml") 43 | self.raw_config = Once(self.get_raw_configuration) 44 | 45 | def __repr__(self) -> str: 46 | return f'{type(self).__name__}(directory="{self.directory}")' 47 | 48 | def get_raw_configuration(self) -> dict[str, t.Any]: 49 | """Loads the raw configuration data for Slap from either the `slap.toml` configuration file or `pyproject.toml` 50 | under the `[slap.tool]` section. If neither of the files exist or the section in the pyproject does not exist, 51 | an empty dictionary will be returned.""" 52 | 53 | if self.slap_toml.exists(): 54 | logger.debug("Reading configuration for %s from %s", self, self.slap_toml.path) 55 | return self.slap_toml.value() 56 | if self.pyproject_toml.exists(): 57 | logger.debug("Reading configuration for %s from %s", self, self.pyproject_toml.path) 58 | return self.pyproject_toml.value().get("tool", {}).get("slap", {}) 59 | return {} 60 | -------------------------------------------------------------------------------- /src/slap/release.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import dataclasses 4 | import re 5 | import typing as t 6 | from pathlib import Path 7 | 8 | from slap.util.notset import NotSet 9 | 10 | T = t.TypeVar("T") 11 | 12 | 13 | @t.overload 14 | def match_version_ref_pattern(filename: Path, pattern: str) -> VersionRef: ... 15 | 16 | 17 | @t.overload 18 | def match_version_ref_pattern(filename: Path, pattern: str, fallback: T) -> T | VersionRef: ... 19 | 20 | 21 | def match_version_ref_pattern(filename: Path, pattern: str, fallback: NotSet | T = NotSet.Value) -> T | VersionRef: 22 | """Matches a regular expression in the given file and returns the location of the match. The *pattern* 23 | should contain at least one capturing group. The first capturing group is considered the one that contains 24 | the version number exactly. 25 | 26 | Arguments: 27 | filename: The file of which the contents will be checked against the pattern. 28 | pattern: The regular expression that contains at least one capturing group. 29 | """ 30 | 31 | compiled_pattern = re.compile(pattern, re.M | re.S) 32 | if not compiled_pattern.groups: 33 | raise ValueError( 34 | f"pattern must contain at least one capturing group (filename: {filename!r}, pattern: {pattern!r})" 35 | ) 36 | 37 | with open(filename) as fp: 38 | match = compiled_pattern.search(fp.read()) 39 | if match: 40 | return VersionRef(filename, match.start(1), match.end(1), match.group(1), match.group(0)) 41 | 42 | if fallback is not NotSet.Value: 43 | return fallback 44 | raise ValueError(f"pattern {pattern!r} does not match in file {filename!r}") 45 | 46 | 47 | def match_version_ref_pattern_on_lines(filename: Path, pattern: str) -> list[VersionRef]: 48 | """Like #match_version_ref_pattern(), but returns all matches, but matches it on a line-by-line basis. The 49 | *pattern* must have a `version` group. The pattern is compiled with #re.M and #re.S flags.""" 50 | 51 | compiled_pattern = re.compile(pattern, re.M | re.S) 52 | refs = [] 53 | for match in re.finditer(compiled_pattern, filename.read_text()): 54 | refs.append( 55 | VersionRef( 56 | file=filename, 57 | start=match.start("version"), 58 | end=match.end("version"), 59 | value=match.group("version"), 60 | content=match.group(0), 61 | ) 62 | ) 63 | return refs 64 | 65 | 66 | @dataclasses.dataclass 67 | class VersionRef: 68 | """Represents a reference to a version number in a file.""" 69 | 70 | file: Path 71 | start: int 72 | end: int 73 | value: str 74 | content: str 75 | 76 | def __post_init__(self) -> None: 77 | assert isinstance(self.file, Path), self 78 | -------------------------------------------------------------------------------- /src/slap/ext/application/config.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Any 3 | 4 | import tomli 5 | import tomli_w 6 | 7 | from slap.application import Application, Command, option 8 | from slap.ext.application.venv import VenvType 9 | from slap.plugins import ApplicationPlugin 10 | 11 | CONFIG_FILE = Path.home() / ".config" / "slap" / "config.toml" 12 | 13 | 14 | def get_config() -> "ConfigModel": 15 | config = ConfigModel(CONFIG_FILE) 16 | config.load() 17 | return config 18 | 19 | 20 | class ConfigModel: 21 | def __init__(self, path: Path) -> None: 22 | self.path = path 23 | self.data: dict[str, Any] | None = {} 24 | 25 | def load(self) -> None: 26 | if self.path.exists(): 27 | with self.path.open("rb") as file: 28 | self.data = tomli.load(file) 29 | else: 30 | self.data = {} 31 | 32 | def save(self) -> None: 33 | assert self.data is not None 34 | self.path.parent.mkdir(exist_ok=True, parents=True) 35 | with self.path.open("wb") as file: 36 | tomli_w.dump(self.data, file) 37 | 38 | def set_venv_type(self, venv_type: VenvType) -> None: 39 | assert self.data is not None 40 | self.data["venv_type"] = venv_type.value 41 | 42 | def get_venv_type(self) -> VenvType | None: 43 | assert self.data is not None 44 | value = self.data.get("venv_type") 45 | return VenvType(value) if value is not None else None 46 | 47 | 48 | class SlapConfigCommand(ApplicationPlugin, Command): 49 | """Command to manage global user configuration.""" 50 | 51 | name = "config" 52 | 53 | options = [ 54 | option( 55 | "--venv-type", 56 | description="Set the default type of virtual environment to create. [venv|uv]", 57 | flag=False, 58 | ) 59 | ] 60 | 61 | def __init__(self, app: Application) -> None: 62 | ApplicationPlugin.__init__(self, app) 63 | Command.__init__(self) 64 | 65 | def handle(self) -> int: 66 | model = ConfigModel(CONFIG_FILE) 67 | model.load() 68 | 69 | if venv_type := self.option("venv-type"): 70 | try: 71 | model.set_venv_type(VenvType(venv_type.lower())) 72 | except ValueError: 73 | self.line_error(f"Invalid virtual environment type: {venv_type}.") 74 | return 1 75 | model.save() 76 | self.line(f"Default virtual environment type set to {venv_type}.") 77 | return 0 78 | 79 | self.line_error("No option provided.") 80 | return 1 81 | 82 | def load_configuration(self, app: Application) -> Any: 83 | return None 84 | 85 | def activate(self, app: Application, config: Any) -> None: 86 | app.cleo.add(SlapConfigCommand(app)) 87 | -------------------------------------------------------------------------------- /docs/docs/commands/release.md: -------------------------------------------------------------------------------- 1 | # `slap release` 2 | 3 | This command updates the version numbers in your project(s). 4 | 5 |
Synopsis 6 | ``` 7 | @shell slap release --help 8 | ``` 9 |
10 | 11 | ## Tool comparison 12 | 13 | | Feature | `poetry bump` | `slap release` | 14 | | ------- | ------ | ---- | 15 | | Check for consistent version numbers across files in your repository | ❌ | ✅ | 16 | | Update version number in `pyproject.toml` | ✅ | ✅ | 17 | | Update dependencies of another project within the same mono-repository (interdependencies) | | ✅ | 18 | | Update `__version__` in source code | ❌ | ✅ | 19 | | Update and rename Slap structure changelog files | | ✅ | 20 | | Commit changes, create a tag and push to Git remote | ❌ | ✅ | 21 | | Create GitHub releases | ❌ | ❌ (planned in [#29](https://github.com/NiklasRosenstein/slap/issues/29)) | 22 | 23 | > __Legend__: ✅ supported, ❌ not supported, (blank) conceptually irrelevant 24 | 25 | ## Configuration 26 | 27 | Option scope: `[tool.slap.release]` or `[release]` 28 | 29 | | Option | Type | Default | Description | 30 | | ------ | ---- | ------- | ----------- | 31 | | `branch` | `str` | `"develop"` | The branch on which releases are created. Unless `--no-branch-check` is passed to `slap release`, the command will refuse to continue if the current branch name does not match this value. | 32 | | `commit-message` | `str` | `release {version}` | The commit message to use when using the `--tag, -t` option. The string `{version}` will be replaced with the new version number. | 33 | | `tag-name` | `str` | `{version}` | The tag name to use when using the `--tag, -t` option. The string `{version}` will be replaced with the new version number. | 34 | | `references` | `list[VersionRefConfig]` | `[]` | A list of version references that should be considered in addition to the version references that are automatically detected by Slap when updating version numbers across the project with the `slap release` command. A `VersionRefConfig` contains the fields `file: str` and `pattern: str`. The `file` is considered relative to the project directory (which is the directory where the `slap.toml` or `pyproject.toml` configuration file resides). | 35 | 36 |
ReleaseConfig 37 | 38 | ::: slap.ext.application.release.ReleaseConfig 39 | 40 |
41 | 42 |
VersionRefConfig 43 | 44 | ::: slap.ext.application.release.VersionRefConfig 45 | 46 |
47 | 48 | ## Usage example 49 | 50 | ``` 51 | $ slap release patch --tag --push 52 | bumping 2 version references: 53 | pyproject.toml: 0.1.0 → 0.1.1 54 | src/my_package/__init__.py: 0.1.0 → 0.1.1 55 | 56 | release staged changelog 57 | .changelog/_unreleased.toml → .changelog/0.1.1.toml 58 | 59 | tagging 0.1.1 60 | 61 | pushing develop, 0.1.1 to origin 62 | ``` 63 | -------------------------------------------------------------------------------- /.changelog/0.17.0.toml: -------------------------------------------------------------------------------- 1 | release-date = "2021-08-13" 2 | 3 | [[entries]] 4 | id = "0af3f005-2ea1-4944-bca5-2840a85260c7" 5 | type = "feature" 6 | description = "add `shut pkg test --install/--no-install` which will default to `--install` if the current Python installation (determined by the `PYTHON` variable or falling back to just the `python` command) is a virtual env" 7 | author = "@NiklasRosenstein" 8 | 9 | [[entries]] 10 | id = "9e318fd3-9d01-40cc-851a-9862a080fb3a" 11 | type = "feature" 12 | description = "add support for a `mypy` test driver" 13 | author = "@NiklasRosenstein" 14 | 15 | [[entries]] 16 | id = "0d7b0978-4333-4a48-9712-1e88be17b57e" 17 | type = "feature" 18 | description = "api: add `Package.get_source_directory()`" 19 | author = "@NiklasRosenstein" 20 | 21 | [[entries]] 22 | id = "7686bfff-3540-4256-9ed7-8cd01fbaa436" 23 | type = "feature" 24 | description = "add `$.test-drivers` field in package model and deprecate `$.test-driver` for it." 25 | author = "@NiklasRosenstein" 26 | 27 | [[entries]] 28 | id = "c72ff2b1-0c36-479a-aa38-4743fc19418d" 29 | type = "feature" 30 | description = "add `shut mono test --install/--no-install` option (parallel to the existing option for `shut pkg test`), without the flag test requirements are only installed if Shut did not install the same requirements into the same environment before" 31 | author = "@NiklasRosenstein" 32 | 33 | [[entries]] 34 | id = "dd12e587-2dc7-4372-b494-6bfbd4aa2e49" 35 | type = "feature" 36 | description = "make sure `$.test-requirements` are installed when running `shut mono|pkg test` in addition to the test-driver requirements. Update some output formatting of `shut mono|pkg test`" 37 | author = "@NiklasRosenstein" 38 | 39 | [[entries]] 40 | id = "af6d1ce6-3597-4174-89d3-1d0d78a94139" 41 | type = "fix" 42 | description = "lines in `MANIFEST.in` outside the shut-specific block are not kept on `shut pkg update` (fixed in `VirtualFiles.write_all()`)" 43 | author = "@NiklasRosenstein" 44 | 45 | [[entries]] 46 | id = "f071beb4-e838-4d58-a30d-5a50fed6ba4f" 47 | type = "fix" 48 | description = "package `$.author` data can now be de-serialized from a mapping again (as used to be possible before migrating to `databind.core^1.x`" 49 | author = "@NiklasRosenstein" 50 | 51 | [[entries]] 52 | id = "c03550f8-f599-45d6-b6b6-663286c7d012" 53 | type = "fix" 54 | description = "fix printing of `Failed test details:` when there were no failures but at least one skipped test case" 55 | author = "@NiklasRosenstein" 56 | 57 | [[entries]] 58 | id = "ab2f431e-cc3f-445c-9864-12c7baa5855f" 59 | type = "feature" 60 | description = "`pytest` driver now supports a `$.parallelism` option which automatically pulls in `pytest-xdist` as a test requirement" 61 | author = "@NiklasRosenstein" 62 | 63 | [[entries]] 64 | id = "14909c6f-63fc-4d2d-a2d0-1104c53a42ed" 65 | type = "feature" 66 | description = "`shut mono|pkg bump --tag|--push|--dry` options now have short versions `-t|-p|-d` respectively" 67 | author = "@NiklasRosenstein" 68 | -------------------------------------------------------------------------------- /src/slap/ext/checks/release.py: -------------------------------------------------------------------------------- 1 | import typing as t 2 | 3 | from cleo.io.null_io import NullIO # type: ignore[import] 4 | 5 | from slap.application import Application 6 | from slap.check import Check, CheckResult, check, get_checks 7 | from slap.ext.application.release import ReleaseCommandPlugin 8 | from slap.ext.release.source_code_version import SourceCodeVersionReferencesPlugin 9 | from slap.plugins import CheckPlugin 10 | from slap.project import Project 11 | 12 | 13 | class ReleaseChecksPlugin(CheckPlugin): 14 | """Performs some checks relevant for the `slap release` command.""" 15 | 16 | def get_project_checks(self, project: Project) -> t.Iterable[Check]: 17 | return get_checks(self, project) 18 | 19 | def get_application_checks(self, app: Application) -> t.Iterable[Check]: 20 | return get_checks(self, app) 21 | 22 | @check("source-code-version") 23 | def check_packages_have_source_code_version(self, project: Project) -> tuple[CheckResult, str]: 24 | """Checks if all Python packages in the project have a version defined in the source code.""" 25 | 26 | if not project.packages(): 27 | return Check.WARNING, "No packages detected" 28 | 29 | matcher = SourceCodeVersionReferencesPlugin() 30 | matcher.io = NullIO() 31 | version_refs = matcher.get_version_refs(project) 32 | packages_without_version = {p.name for p in project.packages() or []} 33 | 34 | for ref in version_refs: 35 | for package in project.packages() or []: 36 | if ref.file.is_relative_to(package.path): 37 | packages_without_version.discard(package.name) 38 | 39 | return ( 40 | Check.ERROR if packages_without_version else Check.OK, 41 | ( 42 | (f'The following packages have no __version__: {", ".join(packages_without_version)}') 43 | if packages_without_version 44 | else f'Found __version__ in {", ".join(x.name for x in project.packages() or [])}' 45 | ), 46 | ) 47 | 48 | @check("consistent-versions") 49 | def check_version_number_consistency(self, app: Application) -> tuple[CheckResult, str]: 50 | """Checks if the version numbers in the project source code, project configuration and any other instances 51 | that are detected by release plugins or in the `[tool.slap.release].references` option are consistent.""" 52 | 53 | releaser = ReleaseCommandPlugin(app) 54 | releaser.load_configuration(app) 55 | 56 | version_refs = releaser._get_version_refs() 57 | cardinality = len(set(r.value for r in version_refs)) 58 | 59 | if cardinality == 0: 60 | result = Check.WARNING 61 | message = "No version references found" 62 | elif cardinality == 1: 63 | result = Check.OK 64 | message = "All version references are equal" 65 | else: 66 | result = Check.ERROR 67 | message = f"Found {cardinality} differing version references" 68 | 69 | return result, message 70 | -------------------------------------------------------------------------------- /src/slap/util/plugins.py: -------------------------------------------------------------------------------- 1 | """Helpers to implement a plugin infrastructure in Python.""" 2 | 3 | from __future__ import annotations 4 | 5 | import logging 6 | import typing as t 7 | 8 | import importlib_metadata 9 | import typing_extensions as te 10 | 11 | T = t.TypeVar("T") 12 | 13 | logger = logging.getLogger(__name__) 14 | 15 | # NOTE (@NiklasRosenstein): I wished we could use a TypeVar bound to a protocol instead of T, but mypy does 16 | # not seem to like it. 17 | 18 | 19 | class NoSuchEntrypointError(RuntimeError): 20 | pass 21 | 22 | 23 | @t.overload 24 | def load_entrypoint(group: str, name: str) -> t.Any: ... 25 | 26 | 27 | @t.overload 28 | def load_entrypoint(group: type[T], name: str) -> type[T]: ... 29 | 30 | 31 | def load_entrypoint(group: str | type[T], name: str) -> t.Any | type[T]: 32 | """Load a single entrypoint value. Raises a #RuntimeError if no such entrypoint exists.""" 33 | 34 | if isinstance(group, type): 35 | group_name = group.ENTRYPOINT # type: ignore 36 | else: 37 | group_name = group 38 | 39 | for ep in importlib_metadata.entry_points(group=group_name, name=name): 40 | value = ep.load() 41 | break 42 | else: 43 | raise NoSuchEntrypointError(f'no entrypoint "{name}" in group "{group}"') 44 | 45 | if isinstance(group, type): 46 | if not isinstance(value, type): 47 | raise TypeError(f'entrypoint "{name}" in group "{group}" is not a type (found "{type(value).__name__}")') 48 | if not issubclass(value, group): # type: ignore 49 | raise TypeError(f'entrypoint "{name}" in group "{group}" is not a subclass of {group.__name__}') 50 | 51 | return value 52 | 53 | 54 | _Iter_Entrypoints_1: te.TypeAlias = "t.Iterator[importlib_metadata.EntryPoint]" 55 | _Iter_Entrypoints_2: te.TypeAlias = "t.Iterator[tuple[str, t.Callable[[], type[T]]]]" 56 | 57 | 58 | @t.overload 59 | def iter_entrypoints(group: str) -> _Iter_Entrypoints_1: ... 60 | 61 | 62 | @t.overload 63 | def iter_entrypoints(group: type[T]) -> _Iter_Entrypoints_2: ... 64 | 65 | 66 | def iter_entrypoints(group: str | type[T]) -> _Iter_Entrypoints_1 | _Iter_Entrypoints_2: 67 | """Loads all entrypoints from the given group.""" 68 | 69 | if isinstance(group, type): 70 | group_name = group.ENTRYPOINT # type: ignore 71 | else: 72 | group_name = group 73 | 74 | def _make_loader(ep: importlib_metadata.EntryPoint) -> t.Callable[[], type[T]]: 75 | def loader(): 76 | assert isinstance(group, type) 77 | value = ep.load() 78 | if not isinstance(value, type): 79 | raise TypeError( 80 | f'entrypoint "{ep.name}" in group "{group}" is not a type (found "{type(value).__name__}")' 81 | ) 82 | if not issubclass(value, group): # type: ignore 83 | raise TypeError(f'entrypoint "{ep.name}" in group "{group}" is not a subclass of {group.__name__}') 84 | return value 85 | 86 | return loader 87 | 88 | for ep in importlib_metadata.entry_points(group=group_name): 89 | if isinstance(group, type): 90 | yield ep.name, _make_loader(ep) 91 | else: 92 | yield ep 93 | -------------------------------------------------------------------------------- /src/slap/ext/application/info.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import shlex 5 | import typing as t 6 | from pathlib import Path 7 | 8 | from slap.application import Application, Command 9 | from slap.plugins import ApplicationPlugin 10 | 11 | if t.TYPE_CHECKING: 12 | from slap.python.dependency import Dependency 13 | 14 | 15 | class InfoCommandPlugin(Command, ApplicationPlugin): 16 | """Show info about the Slap application workspace and the loaded projects.""" 17 | 18 | app: Application 19 | name = "info" 20 | 21 | def __init__(self, app: Application) -> None: 22 | Command.__init__(self) 23 | ApplicationPlugin.__init__(self, app) 24 | 25 | def load_configuration(self, app: Application) -> None: 26 | return None 27 | 28 | def activate(self, app: Application, config: None) -> None: 29 | self.app = app 30 | app.cleo.add(self) 31 | 32 | def handle(self) -> int: 33 | projects = self.app.repository.get_projects_ordered() 34 | 35 | self.line(f'Repository "{self.app.repository.directory}"') 36 | self.line(f" vcs: {self.app.repository.vcs()}") 37 | self.line(f" host: {self.app.repository.host()}") 38 | self.line(f" projects: {[p.id for p in projects]}") 39 | 40 | for project in projects: 41 | if not project.is_python_project: 42 | continue 43 | packages_list = project.packages() 44 | packages = ( 45 | "none" 46 | if packages_list is None 47 | else ( 48 | "[]" 49 | if len(packages_list or []) == 0 50 | else ", ".join( 51 | f"{p.name} ({os.path.relpath(p.root, project.directory)})" for p in packages_list 52 | ) 53 | ) 54 | ) 55 | self.line( 56 | f'Project "{os.path.relpath(project.directory, Path.cwd())}" (id: {project.id})' 57 | ) 58 | self.line(f" version: {project.version()}") 59 | self.line(f" dist-name: {project.dist_name()}") 60 | self.line(f" packages: {packages}") 61 | self.line(f" readme: {project.handler().get_readme(project)}") 62 | self.line(f" handler: {project.handler()}") 63 | 64 | inter_deps = project.get_interdependencies(projects) 65 | if inter_deps: 66 | project_names = ", ".join(f"{p.dist_name()}" for p in inter_deps) 67 | self.line(f" depends on: {project_names}") 68 | 69 | deps = project.dependencies() 70 | self.line(" dependencies:") 71 | self._print_deps("run", deps.run) 72 | self._print_deps("dev", deps.dev) 73 | for key, value in deps.extra.items(): 74 | self._print_deps(f"extra.{key}", value) 75 | 76 | return 0 77 | 78 | def _print_deps(self, prefix: str, deps: t.Sequence[Dependency]) -> None: 79 | from slap.install.installer import PipInstaller 80 | 81 | if deps: 82 | self.line(f" {prefix}:") 83 | for dep in sorted(deps, key=lambda s: s.name.lower()): 84 | self.line( 85 | f' - {" ".join(map(shlex.quote, PipInstaller.dependency_to_pip_arguments(dep)))}' 86 | ) 87 | else: 88 | self.line(f" {prefix}: none") 89 | -------------------------------------------------------------------------------- /src/slap/ext/application/report.py: -------------------------------------------------------------------------------- 1 | """Commands that produce reports.""" 2 | 3 | import json 4 | import logging 5 | import typing as t 6 | 7 | from importlib_metadata import Distribution 8 | 9 | from slap.application import Application, option 10 | from slap.ext.application.venv import VenvAwareCommand 11 | from slap.plugins import ApplicationPlugin 12 | 13 | if t.TYPE_CHECKING: 14 | from slap.python.dependency import Dependency 15 | 16 | logger = logging.getLogger(__name__) 17 | 18 | 19 | class ReportDependenciesCommand(VenvAwareCommand): 20 | """Reports the installed run dependencies of your current project(s) as JSON.""" 21 | 22 | name = "report dependencies" 23 | options = VenvAwareCommand.options + [ 24 | option( 25 | "extras", 26 | description="A comma-separated list of extra dependencies to include.", 27 | flag=False, 28 | ), 29 | option("with-license-text", description="Include license text in the output."), 30 | ] 31 | 32 | def handle(self) -> int: 33 | import databind.json 34 | import tqdm # type: ignore[import] 35 | 36 | from slap.python.environment import DistributionGraph, PythonEnvironment, build_distribution_graph 37 | from slap.python.pep508 import filter_dependencies 38 | 39 | result = super().handle() 40 | if result != 0: 41 | return result 42 | 43 | extras = set(filter(bool, map(str.strip, (self.option("extras") or "").split(",")))) 44 | 45 | requirements: list[Dependency] = [] 46 | for project in self.app.get_target_projects(): 47 | requirements += project.dependencies().run 48 | if "dev" in extras: 49 | requirements += project.dependencies().dev 50 | for extra in extras: 51 | requirements += project.dependencies().extra.get(extra, []) 52 | 53 | dists_cache: dict[str, Distribution | None] = {} 54 | python_environment = PythonEnvironment.of("python") 55 | requirements = filter_dependencies(requirements, python_environment.pep508, extras) 56 | with tqdm.tqdm(desc="Resolving requirements graph") as progress: 57 | graph = build_distribution_graph( 58 | env=python_environment, 59 | dependencies=requirements, 60 | resolved_callback=lambda d: progress.update(len(d)), 61 | dists_cache=dists_cache, 62 | ) 63 | 64 | graph.sort() 65 | output = t.cast(dict[str, t.Any], databind.json.dump(graph, DistributionGraph)) 66 | 67 | # Retrieve the license text from the distributions. 68 | if self.option("with-license-text"): 69 | for dist_name, dist_data in output["metadata"].items(): 70 | if (dist := dists_cache[dist_name]) is None: 71 | continue 72 | 73 | dist_data["license_text"] = None 74 | for file in dist.files or []: 75 | if not file.parent.name.endswith(".dist-info"): 76 | continue 77 | if file.name == "LICENSE" or file.name.startswith("LICENSE."): 78 | dist_data["license_text"] = file.read_text() 79 | break 80 | 81 | print(json.dumps(output, indent=2, sort_keys=True)) 82 | return 0 83 | 84 | 85 | class ReportPlugin(ApplicationPlugin): 86 | def load_configuration(self, app: Application) -> None: 87 | return None 88 | 89 | def activate(self, app: Application, config: None) -> None: 90 | app.cleo.add(ReportDependenciesCommand(app)) 91 | -------------------------------------------------------------------------------- /docs/docs/commands/test.md: -------------------------------------------------------------------------------- 1 | # `slap test` 2 | 3 | > This command is venv aware. 4 | 5 | Runs some or all of the tests from the Slap configuration. This is different from [`slap run`](run.md) in that by 6 | default it runs multiple commands, prefixes the output them with the test name (similar to `docker-compose logs`), 7 | works across multiple projects in a mono-repository and prints a summary of the results at the end. 8 | 9 | If there is an active virtual environment and you are not already in a virtual environment, it will be activated 10 | before the test commands are run. 11 | 12 |
Synopsis 13 | ``` 14 | @shell slap test --help 15 | ``` 16 |
17 | 18 | ## Configuration 19 | 20 | Option scope: `[tool.slap.run]` or `[run]` 21 | 22 | | Option | Type | Default | Description | 23 | | ------ | ---- | ------- | ----------- | 24 | | `` | `str` | n/a | A command as a string to run with the system shell. | 25 | 26 |
An example configuration 27 | 28 | ```toml title="pyproject.toml" 29 | [tool.slap.test] 30 | check = "slap check" 31 | mypy = "mypy src/" 32 | pytest = "pytest test/" 33 | ``` 34 | 35 |
36 | 37 |
Example from the databind project 38 | 39 | Databind is a mono-repository of three Python projects, two of which have tests set up. Running `slap test` in the 40 | project root folder runs all tests of all projects. 41 | 42 | ``` 43 | $ slap test 44 | databind.core:mypy| Success: no issues found in 8 source files 45 | databind.core:pytest| ================== test session starts ================== 46 | databind.core:pytest| platform linux -- Python 3.10.2, pytest-7.1.1, pluggy-1.0.0 47 | databind.core:pytest| rootdir: /home/niklas/git/databind/databind.core 48 | collected 17 items 49 | databind.core:pytest| 50 | databind.core:pytest| test/test_context.py . [ 5%] 51 | databind.core:pytest| test/test_schema.py ................ [100%] 52 | databind.core:pytest| 53 | databind.core:pytest| ================== 17 passed in 0.05s =================== 54 | databind.json:mypy| Success: no issues found in 5 source files 55 | databind.json:pytest| ================== test session starts ================== 56 | databind.json:pytest| platform linux -- Python 3.10.2, pytest-7.1.1, pluggy-1.0.0 57 | databind.json:pytest| rootdir: /home/niklas/git/databind/databind.json 58 | collected 32 items 59 | databind.json:pytest| 60 | databind.json:pytest| test/test_converters.py ......................... [ 78%] 61 | databind.json:pytest| ....... [100%] 62 | databind.json:pytest| 63 | databind.json:pytest| ================== 32 passed in 0.11s =================== 64 | 65 | test summary: 66 | • databind.core:mypy (exit code: 0) 67 | • databind.core:pytest (exit code: 0) 68 | • databind.json:mypy (exit code: 0) 69 | • databind.json:pytest (exit code: 0) 70 | ``` 71 | 72 |
73 | 74 | 75 | ## Test selection 76 | 77 | * If no `test` positional argument is specified, all tests in the project or projects of the repository will be run. (`$ slap test`) 78 | * To run the tests of only one project while in a mono-repository folder, pass the project name as the `test` argument. (`$ slap test databind.core`) 79 | * To run tests of the same name across all projects, pass the test name prefixed with a colon as the `test` argument. (`$ slap test :mypy`) 80 | * To run only one particular test from a given project, pass the project name and test name separated by a colon as the 81 | `test` argument. (`$ slap test databind.core:mypy`) 82 | -------------------------------------------------------------------------------- /src/slap/check.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import dataclasses 4 | import enum 5 | import inspect 6 | import typing as t 7 | 8 | if t.TYPE_CHECKING: 9 | from slap.application import Application 10 | from slap.project import Project 11 | 12 | 13 | class CheckResult(enum.IntEnum): 14 | OK = enum.auto() 15 | RECOMMENDATION = enum.auto() 16 | WARNING = enum.auto() 17 | ERROR = enum.auto() 18 | SKIPPED = enum.auto() 19 | 20 | 21 | @dataclasses.dataclass 22 | class Check: 23 | Result: t.ClassVar[t.Type[CheckResult]] = CheckResult 24 | OK: t.ClassVar[CheckResult] = CheckResult.OK 25 | RECOMMENDATION: t.ClassVar[CheckResult] = CheckResult.RECOMMENDATION 26 | WARNING: t.ClassVar[CheckResult] = CheckResult.WARNING 27 | ERROR: t.ClassVar[CheckResult] = CheckResult.ERROR 28 | SKIPPED: t.ClassVar[CheckResult] = CheckResult.SKIPPED 29 | 30 | name: str 31 | result: CheckResult 32 | description: str | None 33 | details: str | None = None 34 | 35 | 36 | def check( 37 | name: str, 38 | ) -> t.Callable[ 39 | [ 40 | t.Callable[ 41 | [t.Any, t.Any], CheckResult | tuple[CheckResult, str | None] | tuple[CheckResult, str | None, str | None] 42 | ] 43 | ], 44 | t.Callable[[t.Any, t.Any], Check], 45 | ]: 46 | """Decorator for methods on a #CheckPlugin subclass to mark it as a function that returns detauls for a check. 47 | That method should return either a #CheckResult, a tuple of a #CheckResult and a description and optionally a 48 | third element containing more details. 49 | 50 | The second argument of the decorated method _must_ be annotated with either #Project or #Application to deduce 51 | if this method runs on an application or project. 52 | 53 | Example: 54 | 55 | ```py 56 | class MyCheckPlugin(CheckPlugin): 57 | @check("mycheck") 58 | def get_mycheck(self, project: Project) -> tuple[CheckResult, str]: 59 | return (CheckResult.OK, 'everything is in order') 60 | ``` 61 | 62 | Use the #get_checks() method to run all methods on an object decorated with this decorator. 63 | """ 64 | 65 | import functools 66 | 67 | from slap.application import Application 68 | from slap.project import Project 69 | 70 | def decorator(f: t.Callable) -> t.Callable: 71 | sig = inspect.signature(f) 72 | subject_type = sig.parameters[list(sig.parameters)[1]].annotation 73 | if subject_type not in (Project, Application): 74 | raise ValueError(f"{f} second argument must be annotated with Project or Application, got {subject_type}") 75 | 76 | @functools.wraps(f) 77 | def wrapper(self, subject) -> Check: 78 | result = f(self, subject) 79 | if isinstance(result, CheckResult): 80 | return Check(name, result, None, None) 81 | elif isinstance(result, tuple): 82 | return Check(name, *result) 83 | elif isinstance(result, Check): 84 | result.name = name 85 | return result 86 | else: 87 | raise TypeError(f"bad return value for check {name!r}: {result!r}") 88 | 89 | wrapper.__check_name__ = name # type: ignore 90 | wrapper.__check_type__ = subject_type # type: ignore 91 | return wrapper 92 | 93 | return decorator 94 | 95 | 96 | def get_checks(obj: t.Any, subject: t.Union[Application, Project]) -> t.Iterable[Check]: 97 | """Call all methods decorated with #check() on the members of *obj*.""" 98 | 99 | for key in dir(obj): 100 | value = getattr(obj, key) 101 | check_type = getattr(value, "__check_type__", None) 102 | if check_type is type(subject): 103 | yield value(subject) 104 | -------------------------------------------------------------------------------- /src/slap/ext/application/run.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import logging 4 | import shlex 5 | import subprocess as sp 6 | from pathlib import Path 7 | from typing import ClassVar 8 | 9 | from slap.application import Application, argument 10 | from slap.ext.application.venv import VenvAwareCommand 11 | from slap.plugins import ApplicationPlugin 12 | 13 | logger = logging.getLogger(__name__) 14 | 15 | 16 | class RunCommandPlugin(VenvAwareCommand, ApplicationPlugin): 17 | """Run a command in the current active environment. If the command name is an alias 18 | configured in [tool.slap.run], run it instead. 19 | 20 | In order to pass command-line options and flags, you need to add an addition `--` to 21 | ensure that arguments following it are parsed as positional arguments. 22 | 23 | Example: 24 | 25 | $ slap run pytest -- -vv 26 | """ 27 | 28 | name: str = "run" 29 | requires_venv: ClassVar[bool] = True 30 | 31 | arguments = [ 32 | argument( 33 | "args", 34 | description="Command name and arguments.", 35 | multiple=True, 36 | ) 37 | ] 38 | 39 | def load_configuration(self, app: Application) -> dict[str, str]: 40 | config = (app.main_project() or app.repository).raw_config() 41 | return config.get("run", {}) 42 | 43 | def activate(self, app: Application, config: dict[str, str]) -> None: 44 | self.app = app 45 | self.config = config 46 | app.cleo.add(self) 47 | 48 | def handle(self) -> int: 49 | result = super().handle() 50 | if result != 0: 51 | return result 52 | 53 | main_project = self.app.main_project() 54 | commands_to_execute = {} 55 | working_dirs = {} 56 | 57 | command: list[str] = self.argument("args") 58 | if main_project and command[0] in self.config: 59 | command_string = self.config[command[0]] + " " + _join_args(command[1:]) 60 | commands_to_execute[main_project.id if main_project else "/"] = command_string 61 | working_dirs[main_project.id if main_project else "/"] = Path.cwd() 62 | elif not main_project: 63 | for project in self.app.configurations(targets_only=True): 64 | config = project.raw_config().get("run", {}) 65 | if command[0] in config: 66 | command_string = config[command[0]] + " " + _join_args(command[1:]) 67 | commands_to_execute[project.id] = command_string 68 | working_dirs[project.id] = project.directory 69 | 70 | if not commands_to_execute: 71 | commands_to_execute["$"] = _join_args(command) 72 | working_dirs["$"] = Path.cwd() 73 | 74 | if len(commands_to_execute) > 1: 75 | level = logging.WARNING 76 | else: 77 | level = logging.INFO 78 | 79 | results = {} 80 | for key, command_string in commands_to_execute.items(): 81 | logger.log(level, "(%s) Running command: $ %s", key, command_string) 82 | results[key] = sp.call(command_string, shell=True, cwd=working_dirs[key]) 83 | 84 | if any(x != 0 for x in results.values()): 85 | level = logging.WARNING 86 | exit_code = results[next(iter(results))] if len(results) == 1 else 127 87 | status = "FAILED" 88 | else: 89 | level = logging.INFO 90 | exit_code = 0 91 | status = "SUCCESS" 92 | 93 | if len(results) == 1: 94 | logging.log(level, "Exit code: %s (status: %s)", exit_code, status) 95 | else: 96 | logging.log(level, "Multi-run results: (status: %s)", status) 97 | for key in results: 98 | logging.log(level, " %s: %s", key, results[key]) 99 | 100 | return exit_code 101 | 102 | 103 | def _join_args(args: list[str]) -> str: 104 | return " ".join(map(shlex.quote, args)) 105 | -------------------------------------------------------------------------------- /src/slap/ext/project_handlers/flit.py: -------------------------------------------------------------------------------- 1 | """Project handler for projects using the Flit build system.""" 2 | 3 | from __future__ import annotations 4 | 5 | import logging 6 | import typing as t 7 | 8 | from slap.ext.project_handlers.base import PyprojectHandler 9 | 10 | if t.TYPE_CHECKING: 11 | from slap.project import Dependencies, Project 12 | from slap.python.dependency import Dependency 13 | 14 | logger = logging.getLogger(__name__) 15 | 16 | 17 | class FlitProjectHandler(PyprojectHandler): 18 | # ProjectHandlerPlugin 19 | 20 | def matches_project(self, project: Project) -> bool: 21 | if not project.pyproject_toml.exists(): 22 | return False 23 | build_backend = project.pyproject_toml.get("build-system", {}).get("build-backend") 24 | return build_backend == "flit_core.buildapi" 25 | 26 | def get_dist_name(self, project: Project) -> str | None: 27 | return project.pyproject_toml.get("tool", {}).get("flit", {}).get("metadata", {}).get("module", {}).get("name") 28 | 29 | def get_readme(self, project: Project) -> str | None: 30 | return ( 31 | project.pyproject_toml.get("project", {}).get("readme") 32 | or project.pyproject_toml.get("tool", {}).get("flit", {}).get("metadata", {}).get("description-file") 33 | or super().get_readme(project) 34 | ) 35 | 36 | def get_dependencies(self, project: Project) -> Dependencies: 37 | from slap.project import Dependencies 38 | from slap.python.dependency import PypiDependency, VersionSpec 39 | 40 | flit: dict[str, t.Any] | None = project.pyproject_toml.get("tool", {}).get("flit") 41 | project_conf: dict[str, t.Any] | None = project.pyproject_toml.get("project") 42 | build_dependencies = PypiDependency.parse_list( 43 | project.pyproject_toml.get("build-system", {}).get("requires", []) 44 | ) 45 | 46 | if project_conf is not None: 47 | optional = project_conf.get("optional-dependencies", {}) 48 | return Dependencies( 49 | VersionSpec(project_conf["requires-python"]) if "requires-python" in project_conf else None, 50 | PypiDependency.parse_list(project_conf.get("dependencies", [])), 51 | PypiDependency.parse_list(optional.pop("dev", [])), 52 | {extra: PypiDependency.parse_list(value) for extra, value in optional.items()}, 53 | build_dependencies, 54 | ) 55 | elif flit is not None: 56 | optional = flit.get("requires-extra", {}) 57 | return Dependencies( 58 | VersionSpec(flit["requires-python"]) if "requires-python" in flit else None, 59 | PypiDependency.parse_list(flit.get("requires", [])), 60 | PypiDependency.parse_list(optional.pop("dev", [])), 61 | {extra: PypiDependency.parse_list(value) for extra, value in optional.items()}, 62 | build_dependencies, 63 | ) 64 | else: 65 | logger.warning("Unable to read dependencies for project %s", project) 66 | return Dependencies(None, [], [], {}, build_dependencies) 67 | 68 | def get_add_dependency_toml_location_and_config( 69 | self, 70 | project: Project, 71 | dependency: Dependency, 72 | where: str, 73 | ) -> tuple[list[str], list | dict]: 74 | from slap.python.dependency import PypiDependency 75 | 76 | if not isinstance(dependency, PypiDependency): 77 | raise Exception(f"Flit project handler only supports PypiDependency, got {dependency!r}") 78 | 79 | flit: dict[str, t.Any] | None = project.pyproject_toml.get("tool", {}).get("flit") 80 | if flit is not None: 81 | locator = ["requires"] if where == "run" else ["requires-extras", where] 82 | return ["tool", "flit"] + locator, [dependency.version.to_pep_508()] 83 | else: 84 | locator = ["dependencies"] if where == "run" else ["optional-dependencies", where] 85 | return ["project"] + locator, [dependency.version.to_pep_508()] 86 | -------------------------------------------------------------------------------- /src/slap/ext/repository_handlers/default.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | import typing as t 3 | 4 | from databind.core.settings import Alias 5 | 6 | from slap.plugins import RepositoryHandlerPlugin 7 | from slap.project import Project 8 | from slap.repository import Repository, RepositoryHost 9 | from slap.util.fs import get_file_in_directory 10 | from slap.util.vcs import Vcs, detect_vcs 11 | 12 | 13 | @dataclasses.dataclass 14 | class DefaultRepositoryConfig: 15 | #: A list of paths pointing to projects to include in the application invokation. This is useful if multiple 16 | #: projects should be usable with the Slap CLI in unison. Note that if this option is not set and either no 17 | #: configuration file exists in the CWD or the `slap.toml` is used, all immediate subdirectories that contain 18 | #: a `pyproject.toml` will be considered included projects. 19 | include: list[str] | None = None 20 | 21 | #: The repository hosting service. If not specified, it will be detected automatically. 22 | repository_host: t.Annotated[RepositoryHost | None, Alias("repository-host")] = None 23 | 24 | 25 | class DefaultRepositoryHandler(RepositoryHandlerPlugin): 26 | """The default implementation of the repository handler. 27 | 28 | Applies only if either 29 | 30 | * A VCS can be detected (and projects are loaded from the VCS root). 31 | * If a README file, LICENSE file, or a Pyproject or Slap configuration file exists. This is to avoid mistakenly 32 | considering a directory that contains independent Python projects as a mono-repository. 33 | 34 | !!! note 35 | 36 | In a future version, this handler may update the #Repository.directory to point to the VCS root directory 37 | (if a VCS can be detected) to allow using the Slap CLI from a subdirectory as if it were used in the root 38 | directory. 39 | """ 40 | 41 | def _get_config(self, repository: Repository) -> DefaultRepositoryConfig: 42 | import databind.json 43 | 44 | raw_config = repository.raw_config().get("repository", {}) 45 | raw_config.pop("handler", None) 46 | config = databind.json.load(raw_config, DefaultRepositoryConfig) 47 | return config 48 | 49 | def matches_repository(self, repository: Repository) -> bool: 50 | if repository.pyproject_toml.exists() or repository.slap_toml.exists(): 51 | return True 52 | # NOTE(@NiklasRosenstein): This is where we would update the repository root directory. 53 | # vcs = self.get_vcs(repository) 54 | # if vcs is not None: 55 | # repository.__init__(vcs.get_toplevel()) 56 | # return True 57 | if get_file_in_directory(repository.directory, "readme", [], case_sensitive=False): 58 | return True 59 | if get_file_in_directory(repository.directory, "license", [], case_sensitive=False): 60 | return True 61 | return False 62 | 63 | def get_vcs(self, repository: Repository) -> Vcs | None: 64 | return detect_vcs(repository.directory) 65 | 66 | def get_repository_host(self, repository: Repository) -> RepositoryHost | None: 67 | from slap.util.plugins import iter_entrypoints 68 | 69 | config = self._get_config(repository) 70 | if config.repository_host: 71 | return config.repository_host 72 | for _plugin_name, loader in iter_entrypoints(RepositoryHost): # type: ignore[type-abstract] 73 | if instance := loader().detect_repository_host(repository): 74 | return instance 75 | return None 76 | 77 | def get_projects(self, repository: Repository) -> list[Project]: 78 | from slap.project import Project 79 | 80 | projects = [] 81 | if repository.pyproject_toml.exists(): 82 | projects.append(Project(repository, repository.directory)) 83 | 84 | config = self._get_config(repository) 85 | if config.include is None or not repository.pyproject_toml.exists(): 86 | for path in repository.directory.iterdir(): 87 | if not path.is_dir(): 88 | continue 89 | project = Project(repository, path) 90 | if project.pyproject_toml.exists(): 91 | projects.append(project) 92 | else: 93 | for subdir in config.include: 94 | projects.append(Project(repository, repository.directory / subdir)) 95 | 96 | return projects 97 | --------------------------------------------------------------------------------