duty 1.4.2__tar.gz → 1.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {duty-1.4.2 → duty-1.5.0}/CHANGELOG.md +16 -0
- {duty-1.4.2 → duty-1.5.0}/CONTRIBUTING.md +2 -3
- {duty-1.4.2 → duty-1.5.0}/PKG-INFO +5 -9
- {duty-1.4.2 → duty-1.5.0}/README.md +2 -6
- {duty-1.4.2 → duty-1.5.0}/config/ruff.toml +1 -1
- {duty-1.4.2 → duty-1.5.0}/docs/index.md +1 -1
- {duty-1.4.2 → duty-1.5.0}/docs/usage.md +19 -5
- {duty-1.4.2 → duty-1.5.0}/duties.py +5 -2
- {duty-1.4.2 → duty-1.5.0}/mkdocs.yml +4 -2
- {duty-1.4.2 → duty-1.5.0}/pyproject.toml +33 -7
- {duty-1.4.2 → duty-1.5.0}/scripts/gen_credits.py +6 -6
- duty-1.5.0/scripts/get_version.py +27 -0
- duty-1.5.0/scripts/make +1 -0
- duty-1.4.2/scripts/make → duty-1.5.0/scripts/make.py +49 -68
- {duty-1.4.2 → duty-1.5.0}/src/duty/callables/blacken_docs.py +5 -1
- {duty-1.4.2 → duty-1.5.0}/src/duty/callables/ruff.py +2 -2
- {duty-1.4.2 → duty-1.5.0}/src/duty/callables/safety.py +4 -1
- {duty-1.4.2 → duty-1.5.0}/src/duty/cli.py +29 -1
- {duty-1.4.2 → duty-1.5.0}/src/duty/collection.py +31 -2
- duty-1.5.0/src/duty/completions.bash +30 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/context.py +5 -2
- {duty-1.4.2 → duty-1.5.0}/src/duty/decorator.py +3 -1
- {duty-1.4.2 → duty-1.5.0}/src/duty/tools/_blacken_docs.py +5 -1
- {duty-1.4.2 → duty-1.5.0}/src/duty/tools/_ruff.py +2 -2
- {duty-1.4.2 → duty-1.5.0}/src/duty/tools/_safety.py +4 -1
- {duty-1.4.2 → duty-1.5.0}/src/duty/validation.py +5 -2
- {duty-1.4.2 → duty-1.5.0}/tests/test_collection.py +16 -0
- {duty-1.4.2 → duty-1.5.0}/tests/test_context.py +5 -1
- duty-1.4.2/devdeps.txt +0 -32
- {duty-1.4.2 → duty-1.5.0}/CODE_OF_CONDUCT.md +0 -0
- {duty-1.4.2 → duty-1.5.0}/LICENSE +0 -0
- {duty-1.4.2 → duty-1.5.0}/config/coverage.ini +0 -0
- {duty-1.4.2 → duty-1.5.0}/config/git-changelog.toml +0 -0
- {duty-1.4.2 → duty-1.5.0}/config/mypy.ini +0 -0
- {duty-1.4.2 → duty-1.5.0}/config/pytest.ini +0 -0
- {duty-1.4.2 → duty-1.5.0}/config/vscode/launch.json +0 -0
- {duty-1.4.2 → duty-1.5.0}/config/vscode/settings.json +0 -0
- {duty-1.4.2 → duty-1.5.0}/config/vscode/tasks.json +0 -0
- {duty-1.4.2 → duty-1.5.0}/docs/.overrides/main.html +0 -0
- {duty-1.4.2 → duty-1.5.0}/docs/.overrides/partials/comments.html +0 -0
- {duty-1.4.2 → duty-1.5.0}/docs/changelog.md +0 -0
- {duty-1.4.2 → duty-1.5.0}/docs/code_of_conduct.md +0 -0
- {duty-1.4.2 → duty-1.5.0}/docs/contributing.md +0 -0
- {duty-1.4.2 → duty-1.5.0}/docs/credits.md +0 -0
- {duty-1.4.2 → duty-1.5.0}/docs/css/material.css +0 -0
- {duty-1.4.2 → duty-1.5.0}/docs/css/mkdocstrings.css +0 -0
- {duty-1.4.2 → duty-1.5.0}/docs/demo.svg +0 -0
- {duty-1.4.2 → duty-1.5.0}/docs/gen_credits.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/docs/js/feedback.js +0 -0
- {duty-1.4.2 → duty-1.5.0}/docs/license.md +0 -0
- {duty-1.4.2 → duty-1.5.0}/scripts/gen_ref_nav.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/__init__.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/__main__.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/callables/__init__.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/callables/_io.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/callables/autoflake.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/callables/black.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/callables/build.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/callables/coverage.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/callables/flake8.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/callables/git_changelog.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/callables/griffe.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/callables/interrogate.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/callables/isort.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/callables/mkdocs.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/callables/mypy.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/callables/pytest.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/callables/ssort.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/callables/twine.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/debug.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/exceptions.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/py.typed +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/tools/__init__.py +3 -3
- {duty-1.4.2 → duty-1.5.0}/src/duty/tools/_autoflake.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/tools/_base.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/tools/_black.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/tools/_build.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/tools/_coverage.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/tools/_flake8.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/tools/_git_changelog.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/tools/_griffe.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/tools/_interrogate.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/tools/_isort.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/tools/_mkdocs.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/tools/_mypy.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/tools/_pytest.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/tools/_ssort.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/src/duty/tools/_twine.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/tests/__init__.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/tests/conftest.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/tests/fixtures/arguments.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/tests/fixtures/basic.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/tests/fixtures/booleans.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/tests/fixtures/code.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/tests/fixtures/list.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/tests/fixtures/multiple.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/tests/fixtures/precedence.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/tests/fixtures/validation.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/tests/test_cli.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/tests/test_decorator.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/tests/test_running.py +0 -0
- {duty-1.4.2 → duty-1.5.0}/tests/test_validation.py +0 -0
|
@@ -5,6 +5,22 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
|
5
5
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
7
|
<!-- insertion marker -->
|
|
8
|
+
## [1.5.0](https://github.com/pawamoy/duty/releases/tag/1.5.0) - 2025-02-02
|
|
9
|
+
|
|
10
|
+
<small>[Compare with 1.4.3](https://github.com/pawamoy/duty/compare/1.4.3...1.5.0)</small>
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
- Enable Bash completions ([9ed4400](https://github.com/pawamoy/duty/commit/9ed44002ff8e122ea6e5aaaf4a968e08d0dc83fd) by Bartosz Sławecki). [Issue-27](https://github.com/pawamoy/duty/issues/27), [PR-33](https://github.com/pawamoy/duty/pull/33), Co-authored-by: Timothée Mazzucotelli <dev@pawamoy.fr>
|
|
15
|
+
|
|
16
|
+
## [1.4.3](https://github.com/pawamoy/duty/releases/tag/1.4.3) - 2024-10-17
|
|
17
|
+
|
|
18
|
+
<small>[Compare with 1.4.2](https://github.com/pawamoy/duty/compare/1.4.2...1.4.3)</small>
|
|
19
|
+
|
|
20
|
+
### Build
|
|
21
|
+
|
|
22
|
+
- Drop support for Python 3.8 ([4f5d6ec](https://github.com/pawamoy/duty/commit/4f5d6ecbb0a84e5c42cab4d584239f16e8397d86) by Timothée Mazzucotelli).
|
|
23
|
+
|
|
8
24
|
## [1.4.2](https://github.com/pawamoy/duty/releases/tag/1.4.2) - 2024-09-10
|
|
9
25
|
|
|
10
26
|
<small>[Compare with 1.4.1](https://github.com/pawamoy/duty/compare/1.4.1...1.4.2)</small>
|
|
@@ -23,12 +23,11 @@ make setup
|
|
|
23
23
|
> You can install it with:
|
|
24
24
|
>
|
|
25
25
|
> ```bash
|
|
26
|
-
>
|
|
27
|
-
> pipx install uv
|
|
26
|
+
> curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
28
27
|
> ```
|
|
29
28
|
>
|
|
30
29
|
> Now you can try running `make setup` again,
|
|
31
|
-
> or simply `uv
|
|
30
|
+
> or simply `uv sync`.
|
|
32
31
|
|
|
33
32
|
You now have the dependencies installed.
|
|
34
33
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: duty
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.0
|
|
4
4
|
Summary: A simple task runner.
|
|
5
5
|
Keywords: task-runner,task,runner,cross-platform
|
|
6
6
|
Author-Email: =?utf-8?q?Timoth=C3=A9e_Mazzucotelli?= <dev@pawamoy.fr>
|
|
@@ -10,12 +10,12 @@ Classifier: Intended Audience :: Developers
|
|
|
10
10
|
Classifier: Programming Language :: Python
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.9
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.10
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
19
|
Classifier: Topic :: Documentation
|
|
20
20
|
Classifier: Topic :: Software Development
|
|
21
21
|
Classifier: Topic :: Utilities
|
|
@@ -28,7 +28,7 @@ Project-URL: Issues, https://github.com/pawamoy/duty/issues
|
|
|
28
28
|
Project-URL: Discussions, https://github.com/pawamoy/duty/discussions
|
|
29
29
|
Project-URL: Gitter, https://gitter.im/duty/community
|
|
30
30
|
Project-URL: Funding, https://github.com/sponsors/pawamoy
|
|
31
|
-
Requires-Python: >=3.
|
|
31
|
+
Requires-Python: >=3.9
|
|
32
32
|
Requires-Dist: eval-type-backport; python_version < "3.10"
|
|
33
33
|
Requires-Dist: failprint!=1.0.0,>=0.11
|
|
34
34
|
Requires-Dist: typing-extensions>=4.0; python_version < "3.11"
|
|
@@ -39,7 +39,6 @@ Description-Content-Type: text/markdown
|
|
|
39
39
|
[](https://github.com/pawamoy/duty/actions?query=workflow%3Aci)
|
|
40
40
|
[](https://pawamoy.github.io/duty/)
|
|
41
41
|
[](https://pypi.org/project/duty/)
|
|
42
|
-
[](https://gitpod.io/#https://github.com/pawamoy/duty)
|
|
43
42
|
[](https://app.gitter.im/#/room/#duty:gitter.im)
|
|
44
43
|
|
|
45
44
|
A simple task runner.
|
|
@@ -50,17 +49,14 @@ Inspired by [Invoke](https://github.com/pyinvoke/invoke).
|
|
|
50
49
|
|
|
51
50
|
## Installation
|
|
52
51
|
|
|
53
|
-
With `pip`:
|
|
54
|
-
|
|
55
52
|
```bash
|
|
56
53
|
pip install duty
|
|
57
54
|
```
|
|
58
55
|
|
|
59
|
-
With [`
|
|
56
|
+
With [`uv`](https://docs.astral.sh/uv/):
|
|
60
57
|
|
|
61
58
|
```bash
|
|
62
|
-
|
|
63
|
-
pipx install duty
|
|
59
|
+
uv tool install duty
|
|
64
60
|
```
|
|
65
61
|
|
|
66
62
|
## Quick start
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
[](https://github.com/pawamoy/duty/actions?query=workflow%3Aci)
|
|
4
4
|
[](https://pawamoy.github.io/duty/)
|
|
5
5
|
[](https://pypi.org/project/duty/)
|
|
6
|
-
[](https://gitpod.io/#https://github.com/pawamoy/duty)
|
|
7
6
|
[](https://app.gitter.im/#/room/#duty:gitter.im)
|
|
8
7
|
|
|
9
8
|
A simple task runner.
|
|
@@ -14,17 +13,14 @@ Inspired by [Invoke](https://github.com/pyinvoke/invoke).
|
|
|
14
13
|
|
|
15
14
|
## Installation
|
|
16
15
|
|
|
17
|
-
With `pip`:
|
|
18
|
-
|
|
19
16
|
```bash
|
|
20
17
|
pip install duty
|
|
21
18
|
```
|
|
22
19
|
|
|
23
|
-
With [`
|
|
20
|
+
With [`uv`](https://docs.astral.sh/uv/):
|
|
24
21
|
|
|
25
22
|
```bash
|
|
26
|
-
|
|
27
|
-
pipx install duty
|
|
23
|
+
uv tool install duty
|
|
28
24
|
```
|
|
29
25
|
|
|
30
26
|
## Quick start
|
|
@@ -647,7 +647,7 @@ You can also pass parameters as positional arguments:
|
|
|
647
647
|
duty shoot 5,15
|
|
648
648
|
```
|
|
649
649
|
|
|
650
|
-
WARNING: **Limitation with positional arguments.**
|
|
650
|
+
WARNING: **Limitation with positional arguments.**
|
|
651
651
|
When passing positional arguments,
|
|
652
652
|
make sure there is no overlap between other duties' names
|
|
653
653
|
and the argument value, otherwise `duty` will not be able
|
|
@@ -713,7 +713,7 @@ def play(ctx, file):
|
|
|
713
713
|
```bash
|
|
714
714
|
duty --capture=none --strict play this-file.mp4
|
|
715
715
|
# or with the short options
|
|
716
|
-
duty -Zc none play this-file.mp4
|
|
716
|
+
duty -Zc none play this-file.mp4
|
|
717
717
|
```
|
|
718
718
|
|
|
719
719
|
#### Local options
|
|
@@ -724,7 +724,7 @@ you can pass them to a specific duty on the command line.
|
|
|
724
724
|
If we use the previous example again:
|
|
725
725
|
|
|
726
726
|
```bash
|
|
727
|
-
duty play -Zc none this-file.mp4
|
|
727
|
+
duty play -Zc none this-file.mp4
|
|
728
728
|
```
|
|
729
729
|
|
|
730
730
|
It allows to use different options for different duties
|
|
@@ -778,6 +778,8 @@ It is not possible to capture only stdout, or only stderr,
|
|
|
778
778
|
and let the other one be printed to the console.
|
|
779
779
|
Capturing one is capturing both, but discarding the other.
|
|
780
780
|
|
|
781
|
+
WARNING: **Windows quirks.** On Windows you might need to set the following environment variables to allow proper output capture: `PYTHONLEGACYWINDOWSSTDIO=1`, `PYTHONUTF8=1`, `PYTHONIOENCODING=UTF8`. If the `✓` and `✗` characters are mangled, try changing them by [customizing the output format](#formatting-duty-output).
|
|
782
|
+
|
|
781
783
|
### Formatting duty output
|
|
782
784
|
|
|
783
785
|
Thanks to its underlying [`failprint`](https://github.com/pawamoy/failprint) dependency,
|
|
@@ -789,7 +791,7 @@ For example, the two builtin `failprint` formats are:
|
|
|
789
791
|
```jinja
|
|
790
792
|
{% if success %}<green>✓</green>
|
|
791
793
|
{% elif nofail %}<yellow>✗</yellow>
|
|
792
|
-
{% else %}<red>✗</red>{% endif %}
|
|
794
|
+
{% else %}<red>✗</red>{% endif %}
|
|
793
795
|
<bold>{{ title or command }}</bold>
|
|
794
796
|
{% if failure %} ({{ code }}){% endif %}
|
|
795
797
|
{% if failure and output and not quiet %}\n
|
|
@@ -871,4 +873,16 @@ export FAILPRINT_FORMAT="custom={{output}}"
|
|
|
871
873
|
# always print the captured output, nothing else
|
|
872
874
|
|
|
873
875
|
duty task1 task2
|
|
874
|
-
```
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
### Shell completions
|
|
879
|
+
|
|
880
|
+
You can enable auto-completion in Bash with these commands:
|
|
881
|
+
|
|
882
|
+
```bash
|
|
883
|
+
completions_dir="${BASH_COMPLETION_USER_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion}/completions"
|
|
884
|
+
mkdir -p "${completions_dir}"
|
|
885
|
+
duty --completion > "${completions_dir}/duty"
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
Only Bash is supported for now.
|
|
@@ -7,11 +7,13 @@ import sys
|
|
|
7
7
|
from contextlib import contextmanager
|
|
8
8
|
from importlib.metadata import version as pkgversion
|
|
9
9
|
from pathlib import Path
|
|
10
|
-
from typing import TYPE_CHECKING
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
11
|
|
|
12
12
|
from duty import duty, tools
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
15
|
+
from collections.abc import Iterator
|
|
16
|
+
|
|
15
17
|
from duty.context import Context
|
|
16
18
|
|
|
17
19
|
|
|
@@ -53,7 +55,7 @@ def changelog(ctx: Context, bump: str = "") -> None:
|
|
|
53
55
|
ctx.run(tools.git_changelog(bump=bump or None), title="Updating changelog")
|
|
54
56
|
|
|
55
57
|
|
|
56
|
-
@duty(pre=["
|
|
58
|
+
@duty(pre=["check-quality", "check-types", "check-docs", "check-api"])
|
|
57
59
|
def check(ctx: Context) -> None:
|
|
58
60
|
"""Check it all!"""
|
|
59
61
|
|
|
@@ -82,6 +84,7 @@ def check_docs(ctx: Context) -> None:
|
|
|
82
84
|
@duty
|
|
83
85
|
def check_types(ctx: Context) -> None:
|
|
84
86
|
"""Check that the code is correctly typed."""
|
|
87
|
+
os.environ["FORCE_COLOR"] = "1"
|
|
85
88
|
ctx.run(
|
|
86
89
|
tools.mypy(*PY_SRC_LIST, config_file="config/mypy.ini"),
|
|
87
90
|
title=pyprefix("Type-checking"),
|
|
@@ -129,13 +129,15 @@ plugins:
|
|
|
129
129
|
show_root_heading: true
|
|
130
130
|
show_root_full_path: false
|
|
131
131
|
show_signature_annotations: true
|
|
132
|
+
show_source: true
|
|
132
133
|
show_symbol_type_heading: true
|
|
133
134
|
show_symbol_type_toc: true
|
|
134
135
|
signature_crossrefs: true
|
|
135
136
|
summary: true
|
|
136
|
-
- git-
|
|
137
|
+
- git-revision-date-localized:
|
|
137
138
|
enabled: !ENV [DEPLOY, false]
|
|
138
|
-
|
|
139
|
+
enable_creation_date: true
|
|
140
|
+
type: timeago
|
|
139
141
|
- minify:
|
|
140
142
|
minify_html: !ENV [DEPLOY, false]
|
|
141
143
|
- group:
|
|
@@ -11,7 +11,7 @@ authors = [
|
|
|
11
11
|
{ name = "Timothée Mazzucotelli", email = "dev@pawamoy.fr" },
|
|
12
12
|
]
|
|
13
13
|
readme = "README.md"
|
|
14
|
-
requires-python = ">=3.
|
|
14
|
+
requires-python = ">=3.9"
|
|
15
15
|
keywords = [
|
|
16
16
|
"task-runner",
|
|
17
17
|
"task",
|
|
@@ -25,12 +25,12 @@ classifiers = [
|
|
|
25
25
|
"Programming Language :: Python",
|
|
26
26
|
"Programming Language :: Python :: 3",
|
|
27
27
|
"Programming Language :: Python :: 3 :: Only",
|
|
28
|
-
"Programming Language :: Python :: 3.8",
|
|
29
28
|
"Programming Language :: Python :: 3.9",
|
|
30
29
|
"Programming Language :: Python :: 3.10",
|
|
31
30
|
"Programming Language :: Python :: 3.11",
|
|
32
31
|
"Programming Language :: Python :: 3.12",
|
|
33
32
|
"Programming Language :: Python :: 3.13",
|
|
33
|
+
"Programming Language :: Python :: 3.14",
|
|
34
34
|
"Topic :: Documentation",
|
|
35
35
|
"Topic :: Software Development",
|
|
36
36
|
"Topic :: Utilities",
|
|
@@ -41,7 +41,7 @@ dependencies = [
|
|
|
41
41
|
"failprint>=0.11,!=1.0.0",
|
|
42
42
|
"typing-extensions>=4.0; python_version < '3.11'",
|
|
43
43
|
]
|
|
44
|
-
version = "1.
|
|
44
|
+
version = "1.5.0"
|
|
45
45
|
|
|
46
46
|
[project.license]
|
|
47
47
|
text = "ISC"
|
|
@@ -60,11 +60,10 @@ Funding = "https://github.com/sponsors/pawamoy"
|
|
|
60
60
|
duty = "duty.cli:main"
|
|
61
61
|
|
|
62
62
|
[tool.pdm.version]
|
|
63
|
-
source = "
|
|
63
|
+
source = "call"
|
|
64
|
+
getter = "scripts.get_version:get_version"
|
|
64
65
|
|
|
65
66
|
[tool.pdm.build]
|
|
66
|
-
package-dir = "src"
|
|
67
|
-
editable-backend = "editables"
|
|
68
67
|
excludes = [
|
|
69
68
|
"**/.pytest_cache",
|
|
70
69
|
]
|
|
@@ -74,7 +73,6 @@ source-includes = [
|
|
|
74
73
|
"scripts",
|
|
75
74
|
"share",
|
|
76
75
|
"tests",
|
|
77
|
-
"devdeps.txt",
|
|
78
76
|
"duties.py",
|
|
79
77
|
"mkdocs.yml",
|
|
80
78
|
"*.md",
|
|
@@ -85,3 +83,31 @@ source-includes = [
|
|
|
85
83
|
data = [
|
|
86
84
|
{ path = "share/**/*", relative-to = "." },
|
|
87
85
|
]
|
|
86
|
+
|
|
87
|
+
[dependency-groups]
|
|
88
|
+
dev = [
|
|
89
|
+
"build>=1.2",
|
|
90
|
+
"git-changelog>=2.5",
|
|
91
|
+
"twine>=5.1",
|
|
92
|
+
"duty>=1.4",
|
|
93
|
+
"ruff>=0.4",
|
|
94
|
+
"pytest>=8.2",
|
|
95
|
+
"pytest-cov>=5.0",
|
|
96
|
+
"pytest-randomly>=3.15",
|
|
97
|
+
"pytest-xdist>=3.6",
|
|
98
|
+
"mypy>=1.10",
|
|
99
|
+
"types-markdown>=3.6",
|
|
100
|
+
"types-pyyaml>=6.0",
|
|
101
|
+
"black>=24.4",
|
|
102
|
+
"markdown-callouts>=0.4",
|
|
103
|
+
"markdown-exec>=1.8",
|
|
104
|
+
"mkdocs>=1.6",
|
|
105
|
+
"mkdocs-coverage>=1.0",
|
|
106
|
+
"mkdocs-gen-files>=0.5",
|
|
107
|
+
"mkdocs-git-revision-date-localized-plugin>=1.2",
|
|
108
|
+
"mkdocs-literate-nav>=0.6",
|
|
109
|
+
"mkdocs-material>=9.5",
|
|
110
|
+
"mkdocs-minify-plugin>=0.8",
|
|
111
|
+
"mkdocstrings[python]>=0.25",
|
|
112
|
+
"tomli>=2.0; python_version < '3.11'",
|
|
113
|
+
]
|
|
@@ -5,17 +5,18 @@ from __future__ import annotations
|
|
|
5
5
|
import os
|
|
6
6
|
import sys
|
|
7
7
|
from collections import defaultdict
|
|
8
|
+
from collections.abc import Iterable
|
|
8
9
|
from importlib.metadata import distributions
|
|
9
10
|
from itertools import chain
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
from textwrap import dedent
|
|
12
|
-
from typing import
|
|
13
|
+
from typing import Union
|
|
13
14
|
|
|
14
15
|
from jinja2 import StrictUndefined
|
|
15
16
|
from jinja2.sandbox import SandboxedEnvironment
|
|
16
17
|
from packaging.requirements import Requirement
|
|
17
18
|
|
|
18
|
-
#
|
|
19
|
+
# YORE: EOL 3.10: Replace block with line 2.
|
|
19
20
|
if sys.version_info >= (3, 11):
|
|
20
21
|
import tomllib
|
|
21
22
|
else:
|
|
@@ -26,11 +27,10 @@ with project_dir.joinpath("pyproject.toml").open("rb") as pyproject_file:
|
|
|
26
27
|
pyproject = tomllib.load(pyproject_file)
|
|
27
28
|
project = pyproject["project"]
|
|
28
29
|
project_name = project["name"]
|
|
29
|
-
|
|
30
|
-
devdeps = [line.strip() for line in devdeps_file if line.strip() and not line.strip().startswith(("-e", "#"))]
|
|
30
|
+
devdeps = [dep for dep in pyproject["dependency-groups"]["dev"] if not dep.startswith("-e")]
|
|
31
31
|
|
|
32
|
-
PackageMetadata =
|
|
33
|
-
Metadata =
|
|
32
|
+
PackageMetadata = dict[str, Union[str, Iterable[str]]]
|
|
33
|
+
Metadata = dict[str, PackageMetadata]
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
def _merge_fields(metadata: dict) -> PackageMetadata:
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Get current project version from Git tags or changelog."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from contextlib import suppress
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from pdm.backend.hooks.version import SCMVersion, Version, default_version_formatter, get_version_from_scm
|
|
8
|
+
|
|
9
|
+
_root = Path(__file__).parent.parent
|
|
10
|
+
_changelog = _root / "CHANGELOG.md"
|
|
11
|
+
_changelog_version_re = re.compile(r"^## \[(\d+\.\d+\.\d+)\].*$")
|
|
12
|
+
_default_scm_version = SCMVersion(Version("0.0.0"), None, False, None, None) # noqa: FBT003
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_version() -> str:
|
|
16
|
+
"""Get current project version from Git tags or changelog."""
|
|
17
|
+
scm_version = get_version_from_scm(_root) or _default_scm_version
|
|
18
|
+
if scm_version.version <= Version("0.1"): # Missing Git tags?
|
|
19
|
+
with suppress(OSError, StopIteration): # noqa: SIM117
|
|
20
|
+
with _changelog.open("r", encoding="utf8") as file:
|
|
21
|
+
match = next(filter(None, map(_changelog_version_re.match, file)))
|
|
22
|
+
scm_version = scm_version._replace(version=Version(match.group(1)))
|
|
23
|
+
return default_version_formatter(scm_version)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
if __name__ == "__main__":
|
|
27
|
+
print(get_version())
|
duty-1.5.0/scripts/make
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
make.py
|
|
@@ -9,15 +9,17 @@ import subprocess
|
|
|
9
9
|
import sys
|
|
10
10
|
from contextlib import contextmanager
|
|
11
11
|
from pathlib import Path
|
|
12
|
-
from
|
|
12
|
+
from textwrap import dedent
|
|
13
|
+
from typing import TYPE_CHECKING, Any
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from collections.abc import Iterator
|
|
15
17
|
|
|
16
|
-
exe = ""
|
|
17
|
-
prefix = ""
|
|
18
18
|
|
|
19
|
+
PYTHON_VERSIONS = os.getenv("PYTHON_VERSIONS", "3.9 3.10 3.11 3.12 3.13 3.14").split()
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
|
|
22
|
+
def shell(cmd: str, *, capture_output: bool = False, **kwargs: Any) -> str | None:
|
|
21
23
|
"""Run a shell command."""
|
|
22
24
|
if capture_output:
|
|
23
25
|
return subprocess.check_output(cmd, shell=True, text=True, **kwargs) # noqa: S602
|
|
@@ -37,17 +39,13 @@ def environ(**kwargs: str) -> Iterator[None]:
|
|
|
37
39
|
os.environ.update(original)
|
|
38
40
|
|
|
39
41
|
|
|
40
|
-
def uv_install() -> None:
|
|
42
|
+
def uv_install(venv: Path) -> None:
|
|
41
43
|
"""Install dependencies using uv."""
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if "CI" not in os.environ:
|
|
48
|
-
shell("uv pip install --no-deps -e .")
|
|
49
|
-
else:
|
|
50
|
-
shell("uv pip install --no-deps .")
|
|
44
|
+
with environ(UV_PROJECT_ENVIRONMENT=str(venv), PYO3_USE_ABI3_FORWARD_COMPATIBILITY="1"):
|
|
45
|
+
if "CI" in os.environ:
|
|
46
|
+
shell("uv sync --no-editable")
|
|
47
|
+
else:
|
|
48
|
+
shell("uv sync")
|
|
51
49
|
|
|
52
50
|
|
|
53
51
|
def setup() -> None:
|
|
@@ -55,51 +53,32 @@ def setup() -> None:
|
|
|
55
53
|
if not shutil.which("uv"):
|
|
56
54
|
raise ValueError("make: setup: uv must be installed, see https://github.com/astral-sh/uv")
|
|
57
55
|
|
|
58
|
-
print("Installing dependencies (default environment)")
|
|
56
|
+
print("Installing dependencies (default environment)")
|
|
59
57
|
default_venv = Path(".venv")
|
|
60
58
|
if not default_venv.exists():
|
|
61
|
-
shell("uv venv
|
|
62
|
-
uv_install()
|
|
59
|
+
shell("uv venv")
|
|
60
|
+
uv_install(default_venv)
|
|
63
61
|
|
|
64
62
|
if PYTHON_VERSIONS:
|
|
65
63
|
for version in PYTHON_VERSIONS:
|
|
66
|
-
print(f"\nInstalling dependencies (python{version})")
|
|
64
|
+
print(f"\nInstalling dependencies (python{version})")
|
|
67
65
|
venv_path = Path(f".venvs/{version}")
|
|
68
66
|
if not venv_path.exists():
|
|
69
67
|
shell(f"uv venv --python {version} {venv_path}")
|
|
70
|
-
with environ(
|
|
71
|
-
uv_install()
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def activate(path: str) -> None:
|
|
75
|
-
"""Activate a virtual environment."""
|
|
76
|
-
global exe, prefix # noqa: PLW0603
|
|
77
|
-
|
|
78
|
-
if (bin := Path(path, "bin")).exists():
|
|
79
|
-
activate_script = bin / "activate_this.py"
|
|
80
|
-
elif (scripts := Path(path, "Scripts")).exists():
|
|
81
|
-
activate_script = scripts / "activate_this.py"
|
|
82
|
-
exe = ".exe"
|
|
83
|
-
prefix = f"{path}/Scripts/"
|
|
84
|
-
else:
|
|
85
|
-
raise ValueError(f"make: activate: Cannot find activation script in {path}")
|
|
86
|
-
|
|
87
|
-
if not activate_script.exists():
|
|
88
|
-
raise ValueError(f"make: activate: Cannot find activation script in {path}")
|
|
89
|
-
|
|
90
|
-
exec(activate_script.read_text(), {"__file__": str(activate_script)}) # noqa: S102
|
|
68
|
+
with environ(UV_PROJECT_ENVIRONMENT=str(venv_path.resolve())):
|
|
69
|
+
uv_install(venv_path)
|
|
91
70
|
|
|
92
71
|
|
|
93
72
|
def run(version: str, cmd: str, *args: str, **kwargs: Any) -> None:
|
|
94
73
|
"""Run a command in a virtual environment."""
|
|
95
74
|
kwargs = {"check": True, **kwargs}
|
|
75
|
+
uv_run = ["uv", "run", "--no-sync"]
|
|
96
76
|
if version == "default":
|
|
97
|
-
|
|
98
|
-
|
|
77
|
+
with environ(UV_PROJECT_ENVIRONMENT=".venv"):
|
|
78
|
+
subprocess.run([*uv_run, cmd, *args], **kwargs) # noqa: S603, PLW1510
|
|
99
79
|
else:
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
subprocess.run([f"{prefix}{cmd}{exe}", *args], **kwargs) # noqa: S603, PLW1510
|
|
80
|
+
with environ(UV_PROJECT_ENVIRONMENT=f".venvs/{version}", MULTIRUN="1"):
|
|
81
|
+
subprocess.run([*uv_run, cmd, *args], **kwargs) # noqa: S603, PLW1510
|
|
103
82
|
|
|
104
83
|
|
|
105
84
|
def multirun(cmd: str, *args: str, **kwargs: Any) -> None:
|
|
@@ -122,18 +101,17 @@ def clean() -> None:
|
|
|
122
101
|
"""Delete build artifacts and cache files."""
|
|
123
102
|
paths_to_clean = ["build", "dist", "htmlcov", "site", ".coverage*", ".pdm-build"]
|
|
124
103
|
for path in paths_to_clean:
|
|
125
|
-
|
|
104
|
+
shutil.rmtree(path, ignore_errors=True)
|
|
126
105
|
|
|
127
|
-
cache_dirs =
|
|
128
|
-
for dirpath in Path(".").rglob("
|
|
129
|
-
if
|
|
130
|
-
shutil.rmtree(
|
|
106
|
+
cache_dirs = {".cache", ".pytest_cache", ".mypy_cache", ".ruff_cache", "__pycache__"}
|
|
107
|
+
for dirpath in Path(".").rglob("*/"):
|
|
108
|
+
if dirpath.parts[0] not in (".venv", ".venvs") and dirpath.name in cache_dirs:
|
|
109
|
+
shutil.rmtree(dirpath, ignore_errors=True)
|
|
131
110
|
|
|
132
111
|
|
|
133
112
|
def vscode() -> None:
|
|
134
113
|
"""Configure VSCode to work on this project."""
|
|
135
|
-
|
|
136
|
-
shell("cp -v config/vscode/* .vscode")
|
|
114
|
+
shutil.copytree("config/vscode", ".vscode", dirs_exist_ok=True)
|
|
137
115
|
|
|
138
116
|
|
|
139
117
|
def main() -> int:
|
|
@@ -143,21 +121,24 @@ def main() -> int:
|
|
|
143
121
|
if len(args) > 1:
|
|
144
122
|
run("default", "duty", "--help", args[1])
|
|
145
123
|
else:
|
|
146
|
-
print(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
124
|
+
print(
|
|
125
|
+
dedent(
|
|
126
|
+
"""
|
|
127
|
+
Available commands
|
|
128
|
+
help Print this help. Add task name to print help.
|
|
129
|
+
setup Setup all virtual environments (install dependencies).
|
|
130
|
+
run Run a command in the default virtual environment.
|
|
131
|
+
multirun Run a command for all configured Python versions.
|
|
132
|
+
allrun Run a command in all virtual environments.
|
|
133
|
+
3.x Run a command in the virtual environment for Python 3.x.
|
|
134
|
+
clean Delete build artifacts and cache files.
|
|
135
|
+
vscode Configure VSCode to work on this project.
|
|
136
|
+
""",
|
|
137
|
+
),
|
|
138
|
+
flush=True,
|
|
139
|
+
)
|
|
140
|
+
if os.path.exists(".venv"):
|
|
141
|
+
print("\nAvailable tasks", flush=True)
|
|
161
142
|
run("default", "duty", "--list")
|
|
162
143
|
return 0
|
|
163
144
|
|
|
@@ -206,5 +187,5 @@ if __name__ == "__main__":
|
|
|
206
187
|
sys.exit(main())
|
|
207
188
|
except subprocess.CalledProcessError as process:
|
|
208
189
|
if process.output:
|
|
209
|
-
print(process.output, file=sys.stderr)
|
|
190
|
+
print(process.output, file=sys.stderr)
|
|
210
191
|
sys.exit(process.returncode)
|
|
@@ -4,10 +4,14 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import re
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from
|
|
7
|
+
from re import Pattern
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
8
9
|
|
|
9
10
|
from failprint.lazy import lazy
|
|
10
11
|
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from collections.abc import Sequence
|
|
14
|
+
|
|
11
15
|
|
|
12
16
|
@lazy(name="blacken_docs")
|
|
13
17
|
def run(
|
|
@@ -5,12 +5,12 @@ from __future__ import annotations
|
|
|
5
5
|
import os
|
|
6
6
|
import subprocess
|
|
7
7
|
import sys
|
|
8
|
-
from functools import
|
|
8
|
+
from functools import cache
|
|
9
9
|
|
|
10
10
|
from failprint.lazy import lazy
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
@
|
|
13
|
+
@cache
|
|
14
14
|
def _find_ruff() -> str:
|
|
15
15
|
from ruff.__main__ import find_ruff_bin
|
|
16
16
|
|
|
@@ -5,10 +5,13 @@ from __future__ import annotations
|
|
|
5
5
|
import importlib
|
|
6
6
|
import sys
|
|
7
7
|
from io import StringIO
|
|
8
|
-
from typing import
|
|
8
|
+
from typing import TYPE_CHECKING, Literal, cast
|
|
9
9
|
|
|
10
10
|
from failprint.lazy import lazy
|
|
11
11
|
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from collections.abc import Sequence
|
|
14
|
+
|
|
12
15
|
|
|
13
16
|
@lazy(name="safety.check")
|
|
14
17
|
def check(
|