duty 1.4.2__py3-none-any.whl → 1.5.0__py3-none-any.whl

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.
@@ -4,10 +4,14 @@ from __future__ import annotations
4
4
 
5
5
  import re
6
6
  from pathlib import Path
7
- from typing import Pattern, Sequence
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(
duty/callables/ruff.py CHANGED
@@ -5,12 +5,12 @@ from __future__ import annotations
5
5
  import os
6
6
  import subprocess
7
7
  import sys
8
- from functools import lru_cache
8
+ from functools import cache
9
9
 
10
10
  from failprint.lazy import lazy
11
11
 
12
12
 
13
- @lru_cache(maxsize=None)
13
+ @cache
14
14
  def _find_ruff() -> str:
15
15
  from ruff.__main__ import find_ruff_bin
16
16
 
duty/callables/safety.py CHANGED
@@ -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 Literal, Sequence, cast
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(
duty/cli.py CHANGED
@@ -17,6 +17,7 @@ import argparse
17
17
  import inspect
18
18
  import sys
19
19
  import textwrap
20
+ from pathlib import Path
20
21
  from typing import Any
21
22
 
22
23
  from failprint.cli import ArgParser, add_flags
@@ -69,6 +70,18 @@ def get_parser() -> ArgParser:
69
70
  metavar="DUTY",
70
71
  help="Show this help message and exit. Pass duties names to print their help.",
71
72
  )
73
+ parser.add_argument(
74
+ "--completion",
75
+ dest="completion",
76
+ action="store_true",
77
+ help=argparse.SUPPRESS,
78
+ )
79
+ parser.add_argument(
80
+ "--complete",
81
+ dest="complete",
82
+ action="store_true",
83
+ help=argparse.SUPPRESS,
84
+ )
72
85
  parser.add_argument("-V", "--version", action="version", version=f"%(prog)s {debug.get_version()}")
73
86
  parser.add_argument("--debug-info", action=_DebugInfo, help="Print debug information.")
74
87
 
@@ -261,6 +274,18 @@ def main(args: list[str] | None = None) -> int:
261
274
  collection = Collection(opts.duties_file)
262
275
  collection.load()
263
276
 
277
+ if opts.completion:
278
+ print(Path(__file__).parent.joinpath("completions.bash").read_text())
279
+ return 0
280
+
281
+ if opts.complete:
282
+ words = collection.completion_candidates(remainder)
283
+ words += sorted(
284
+ opt for opt, action in parser._option_string_actions.items() if action.help != argparse.SUPPRESS
285
+ )
286
+ print(*words, sep="\n")
287
+ return 0
288
+
264
289
  if opts.help is not None:
265
290
  print_help(parser, opts, collection)
266
291
  return 0
@@ -279,7 +304,10 @@ def main(args: list[str] | None = None) -> int:
279
304
  print_help(parser, opts, collection)
280
305
  return 1
281
306
 
282
- global_opts = specified_options(opts, exclude={"duties_file", "list", "help", "remainder"})
307
+ global_opts = specified_options(
308
+ opts,
309
+ exclude={"duties_file", "list", "help", "remainder", "complete", "completion"},
310
+ )
283
311
  try:
284
312
  commands = parse_commands(arg_lists, global_opts, collection)
285
313
  except TypeError as error:
duty/collection.py CHANGED
@@ -6,11 +6,11 @@ import inspect
6
6
  import sys
7
7
  from copy import deepcopy
8
8
  from importlib import util as importlib_util
9
- from typing import Any, Callable, ClassVar, List, Union
9
+ from typing import Any, Callable, ClassVar, Union
10
10
 
11
11
  from duty.context import Context
12
12
 
13
- DutyListType = List[Union[str, Callable, "Duty"]]
13
+ DutyListType = list[Union[str, Callable, "Duty"]]
14
14
  default_duties_file = "duties.py"
15
15
 
16
16
 
@@ -143,6 +143,35 @@ class Collection:
143
143
  """
144
144
  return list(self.duties.keys()) + list(self.aliases.keys())
145
145
 
146
+ def completion_candidates(self, args: tuple[str, ...]) -> list[str]:
147
+ """Find shell completion candidates within this collection.
148
+
149
+ Returns:
150
+ The list of shell completion candidates, sorted alphabetically.
151
+ """
152
+ # Find last duty name in args.
153
+ name = None
154
+ names = set(self.names())
155
+ for arg in reversed(args):
156
+ if arg in names:
157
+ name = arg
158
+ break
159
+
160
+ completion_names = sorted(names)
161
+
162
+ # If no duty found, return names.
163
+ if name is None:
164
+ return completion_names
165
+
166
+ params = [
167
+ f"{param.name}="
168
+ for param in inspect.signature(self.get(name).function).parameters.values()
169
+ if param.kind is not param.VAR_POSITIONAL
170
+ ][1:]
171
+
172
+ # If duty found, return names *and* duty parameters.
173
+ return completion_names + sorted(params)
174
+
146
175
  def get(self, name_or_alias: str) -> Duty:
147
176
  """Get a duty by its name or alias.
148
177
 
duty/completions.bash ADDED
@@ -0,0 +1,30 @@
1
+ # Invoke tab-completion script to be sourced with Bash shell.
2
+ # Known to work on Bash 3.x, untested on 4.x.
3
+
4
+ _complete_duty() {
5
+ local candidates
6
+
7
+ # COMP_WORDS contains the entire command string up til now (including
8
+ # program name).
9
+ # We hand it to Invoke so it can figure out the current context: spit back
10
+ # core options, task names, the current task's options, or some combo.
11
+ candidates=$(duty --complete -- "${COMP_WORDS[@]}")
12
+
13
+ # `compgen -W` takes list of valid options & a partial word & spits back
14
+ # possible matches. Necessary for any partial word completions (vs
15
+ # completions performed when no partial words are present).
16
+ #
17
+ # $2 is the current word or token being tabbed on, either empty string or a
18
+ # partial word, and thus wants to be compgen'd to arrive at some subset of
19
+ # our candidate list which actually matches.
20
+ #
21
+ # COMPREPLY is the list of valid completions handed back to `complete`.
22
+ COMPREPLY=( $(compgen -W "${candidates}" -- $2) )
23
+ }
24
+
25
+
26
+ # Tell shell builtin to use the above for completing our invocations.
27
+ # * -F: use given function name to generate completions.
28
+ # * -o default: when function generates no results, use filenames.
29
+ # * positional args: program names to complete for.
30
+ complete -F _complete_duty -o default duty
duty/context.py CHANGED
@@ -4,14 +4,17 @@ from __future__ import annotations
4
4
 
5
5
  import os
6
6
  from contextlib import contextmanager, suppress
7
- from typing import Any, Callable, Iterator, List, Union
7
+ from typing import TYPE_CHECKING, Any, Callable, Union
8
8
 
9
9
  from failprint.runners import run as failprint_run
10
10
 
11
11
  from duty.exceptions import DutyFailure
12
12
  from duty.tools import Tool
13
13
 
14
- CmdType = Union[str, List[str], Callable]
14
+ if TYPE_CHECKING:
15
+ from collections.abc import Iterator
16
+
17
+ CmdType = Union[str, list[str], Callable]
15
18
 
16
19
 
17
20
  class Context:
duty/decorator.py CHANGED
@@ -4,11 +4,13 @@ from __future__ import annotations
4
4
 
5
5
  import inspect
6
6
  from functools import wraps
7
- from typing import TYPE_CHECKING, Any, Callable, Iterable, overload
7
+ from typing import TYPE_CHECKING, Any, Callable, overload
8
8
 
9
9
  from duty.collection import Duty, DutyListType
10
10
 
11
11
  if TYPE_CHECKING:
12
+ from collections.abc import Iterable
13
+
12
14
  from duty.context import Context
13
15
 
14
16
 
duty/tools/__init__.py CHANGED
@@ -24,10 +24,9 @@ from duty.tools._ssort import ssort
24
24
  from duty.tools._twine import twine
25
25
 
26
26
  __all__ = [
27
- "Tool",
28
- "LazyStdout",
29
27
  "LazyStderr",
30
- "lazy",
28
+ "LazyStdout",
29
+ "Tool",
31
30
  "autoflake",
32
31
  "black",
33
32
  "blacken_docs",
@@ -38,6 +37,7 @@ __all__ = [
38
37
  "griffe",
39
38
  "interrogate",
40
39
  "isort",
40
+ "lazy",
41
41
  "mkdocs",
42
42
  "mypy",
43
43
  "pytest",
@@ -4,10 +4,14 @@ from __future__ import annotations
4
4
 
5
5
  import re
6
6
  from pathlib import Path
7
- from typing import Pattern, Sequence
7
+ from re import Pattern
8
+ from typing import TYPE_CHECKING
8
9
 
9
10
  from duty.tools._base import Tool
10
11
 
12
+ if TYPE_CHECKING:
13
+ from collections.abc import Sequence
14
+
11
15
 
12
16
  class blacken_docs(Tool): # noqa: N801
13
17
  """Call [blacken-docs](https://github.com/adamchainz/blacken-docs)."""
duty/tools/_ruff.py CHANGED
@@ -5,12 +5,12 @@ from __future__ import annotations
5
5
  import os
6
6
  import subprocess
7
7
  import sys
8
- from functools import lru_cache
8
+ from functools import cache
9
9
 
10
10
  from duty.tools._base import Tool
11
11
 
12
12
 
13
- @lru_cache(maxsize=None)
13
+ @cache
14
14
  def _find_ruff() -> str:
15
15
  from ruff.__main__ import find_ruff_bin
16
16
 
duty/tools/_safety.py CHANGED
@@ -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 Literal, Sequence, cast
8
+ from typing import TYPE_CHECKING, Literal, cast
9
9
 
10
10
  from duty.tools._base import Tool
11
11
 
12
+ if TYPE_CHECKING:
13
+ from collections.abc import Sequence
14
+
12
15
 
13
16
  class safety(Tool): # noqa: N801
14
17
  """Call [Safety](https://github.com/pyupio/safety)."""
duty/validation.py CHANGED
@@ -12,9 +12,12 @@ import textwrap
12
12
  from contextlib import suppress
13
13
  from functools import cached_property, partial
14
14
  from inspect import Parameter, Signature, signature
15
- from typing import Any, Callable, ForwardRef, Sequence, Union, get_args, get_origin
15
+ from typing import TYPE_CHECKING, Any, Callable, ForwardRef, Union, get_args, get_origin
16
16
 
17
- # TODO: Update once support for Python 3.9 is dropped.
17
+ if TYPE_CHECKING:
18
+ from collections.abc import Sequence
19
+
20
+ # YORE: EOL 3.9: Replace block with lines 6-13.
18
21
  if sys.version_info < (3, 10):
19
22
  from eval_type_backport import eval_type_backport as eval_type
20
23
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: duty
3
- Version: 1.4.2
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.8
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
  [![ci](https://github.com/pawamoy/duty/workflows/ci/badge.svg)](https://github.com/pawamoy/duty/actions?query=workflow%3Aci)
40
40
  [![documentation](https://img.shields.io/badge/docs-mkdocs-708FCC.svg?style=flat)](https://pawamoy.github.io/duty/)
41
41
  [![pypi version](https://img.shields.io/pypi/v/duty.svg)](https://pypi.org/project/duty/)
42
- [![gitpod](https://img.shields.io/badge/gitpod-workspace-708FCC.svg?style=flat)](https://gitpod.io/#https://github.com/pawamoy/duty)
43
42
  [![gitter](https://badges.gitter.im/join%20chat.svg)](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 [`pipx`](https://github.com/pipxproject/pipx):
56
+ With [`uv`](https://docs.astral.sh/uv/):
60
57
 
61
58
  ```bash
62
- python3.8 -m pip install --user pipx
63
- pipx install duty
59
+ uv tool install duty
64
60
  ```
65
61
 
66
62
  ## Quick start
@@ -1,14 +1,14 @@
1
- duty-1.4.2.dist-info/METADATA,sha256=KlculFrWoh28QsweZPTaRzFqO03YW0pwozgrGCZbhhw,2900
2
- duty-1.4.2.dist-info/WHEEL,sha256=rSwsxJWe3vzyR5HCwjWXQruDgschpei4h_giTm0dJVE,90
3
- duty-1.4.2.dist-info/entry_points.txt,sha256=gxDUGvj_bg7DA77MogNmnGQSc2__ri9kAEp2RJ1p60Q,40
4
- duty-1.4.2.dist-info/licenses/LICENSE,sha256=nQxdYSduhkgEpOTmg4yyIMmFPzcr2qrlUl8Tf9QZPug,754
1
+ duty-1.5.0.dist-info/METADATA,sha256=CeSruNp_c1LB1AERsGqjy0UwRiww5pivOXdon_fRz7g,2710
2
+ duty-1.5.0.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
3
+ duty-1.5.0.dist-info/entry_points.txt,sha256=XZTh9yTgC9GDFmNoVeOqgKT6BCMifkCToilIj77B9ss,55
4
+ duty-1.5.0.dist-info/licenses/LICENSE,sha256=nQxdYSduhkgEpOTmg4yyIMmFPzcr2qrlUl8Tf9QZPug,754
5
5
  duty/__init__.py,sha256=2fdBMNEBXYSCnV1GQm56VGe8VuvMp79-Hj3SHrAb5MM,144
6
6
  duty/__main__.py,sha256=4YvloGDKmyVzOsE6ZdyCQyY0Jsl0LSlbqkO2UDExgmI,333
7
7
  duty/callables/__init__.py,sha256=R42f1qpIy7m2MMbs6Xc_OnQiEARr4g2Pghpp9ry4JFI,1042
8
8
  duty/callables/_io.py,sha256=vrMLd7ggCFDy8coWUqntmxgKC9BvFu8smK4pK2S7EeA,367
9
9
  duty/callables/autoflake.py,sha256=5IUL2TUfrbWUs_-TBjzqsSHJOxGFiGtBuWBV6AIYdu4,4699
10
10
  duty/callables/black.py,sha256=c2gWuhESEWRi1JGKAlMF_ZLoVJ5_bHLd7c4Lg0qyuX8,7153
11
- duty/callables/blacken_docs.py,sha256=P0lyXg_txxdIwr1qGuzeop1Plb1R8FMkRIo46O0BkgQ,3418
11
+ duty/callables/blacken_docs.py,sha256=OMKrfIZ1ZN6_h8SFGCBcnFCnlonQkkuRY7Vl-B2eGB0,3497
12
12
  duty/callables/build.py,sha256=_cF8B3HKy3BWsTSLfpKwhxkZ0JVhfB3v1Zh2eO1FnQw,2150
13
13
  duty/callables/coverage.py,sha256=3F2DeHyB-L-Y4v_qJU3TIGPNISnX_Nwn79JtmdE-a-0,23843
14
14
  duty/callables/flake8.py,sha256=3uqFdZUF3B35Zc-otjrZfUQgEQkQ3ecZd0ZlEH83sZc,8476
@@ -19,22 +19,23 @@ duty/callables/isort.py,sha256=WutuLM1CZiobHK4Kl2N22xbZg7d0JU6MBZV2muvyG94,26437
19
19
  duty/callables/mkdocs.py,sha256=laaToR_KGsPYX7zcVa9u0fC7mMbKR5PYi5g_5pBW_J8,7902
20
20
  duty/callables/mypy.py,sha256=qtdOxX4x1VeEXCgY_Mw9EzKs0flBU86Ih4PKgf1WyT4,19797
21
21
  duty/callables/pytest.py,sha256=GJHRZCeRhItqOA-ikcFmr9WdpN7ohwTRIIgKl7pWKyw,18345
22
- duty/callables/ruff.py,sha256=jyRo23J4qNLjUGSfRqOYqpbv7DYaxBqfvsBzO88caWA,13336
23
- duty/callables/safety.py,sha256=sTjBad1y3Cly__QOT_z29e8KD4YOPi9FFAV1UlW-niM,2389
22
+ duty/callables/ruff.py,sha256=ZBjl1_MxaZ1-atHVB6YOFNf3QAm6GTw7Xj3hX8jnofo,13314
23
+ duty/callables/safety.py,sha256=ClwwAnDdZFG71_Fbh5X2XEGSPI8PZRy4-Jz6zq2amV4,2454
24
24
  duty/callables/ssort.py,sha256=Lqak-xfJtKkj3AaI4_jPeaRkZ9SbvMCYhoUIVVHE6s8,842
25
25
  duty/callables/twine.py,sha256=hsp8TcOYlbHCCqRwglDmNHS55LdnXFd-n2tFaJkMafs,9511
26
- duty/cli.py,sha256=9uca96F31uTzWJuREU0ReWGhXntvjQaoSW6ZkUxBTV4,8521
27
- duty/collection.py,sha256=cri--t6aUmOw3c17yCy9tAp_ceHfEQMhf4tMqPssNa0,6590
28
- duty/context.py,sha256=T5PNPY6lqXQ088EriWljveFn1SWfrQNVicFbDcnk0GU,3191
26
+ duty/cli.py,sha256=XnkVQAmHCf5QAykZ4foiLvBrsSD22dhSljXmLdFhWM4,9273
27
+ duty/collection.py,sha256=cg_rInuTErP7HTrUJsHenOSdtFZtzIw8cIIsr5g-0T8,7500
28
+ duty/completions.bash,sha256=xXtsZoF1bxhfG4vEuh8hwyYkzNBNlDRxKC9myHV4LPA,1311
29
+ duty/context.py,sha256=YVF68a2w3SMEM0AsN1mBCvlOZ5jTEZ9OYCxDZvv_soI,3250
29
30
  duty/debug.py,sha256=9stdqxNJ9zA2HWsYfmy3C9BrWOXLlHYRGsQ6dHfCUfM,2813
30
- duty/decorator.py,sha256=0FLcTH4nMKHR1MDzZ7PXrblsxu-2m-OVh6Pc_h9T8v8,2952
31
+ duty/decorator.py,sha256=3puIe45Q_5vOVXlr71eFayHS1Swz6YdNANHOnDnHDk0,2984
31
32
  duty/exceptions.py,sha256=gTtqtqOiMroP5-83kC5jakuwBfYKpzCQHE9RWfAGRv0,358
32
33
  duty/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
- duty/tools/__init__.py,sha256=soQxh0HEwSV5zZhfv5DEIpBonk53-JRE0YvcXfWvPI8,1179
34
+ duty/tools/__init__.py,sha256=X-DqE9t47ybEhDf3eboxOEM0vVMQQsqKEOdmDafcuvc,1179
34
35
  duty/tools/_autoflake.py,sha256=wDqNyfuC6rju9LqyTTimaQZs_8v7YvYECzdltO5rof0,5285
35
36
  duty/tools/_base.py,sha256=MpjsSguxu_O6HAOkIy0hZdP2RkguS-NGVhYQHPnurWA,1474
36
37
  duty/tools/_black.py,sha256=avo5-J2gHeXlOneYC0j1PSV7J-QFfPFUTdbwczsMvrA,7907
37
- duty/tools/_blacken_docs.py,sha256=GZvGjPY5Fx6mnZYRwGUB6inH2WQEBkptwPkfWLqSCyY,4893
38
+ duty/tools/_blacken_docs.py,sha256=szKBSsAfQNoZVzTxtm7bZd9bCFe-UzL65XM3MZUJoZI,4972
38
39
  duty/tools/_build.py,sha256=QbOZzeR9T_mWkF9poalOkCWuYe22E1liMUC7aBQTJ5k,2567
39
40
  duty/tools/_coverage.py,sha256=UKCu-DLyW_-en_Hn9qsh4kXyfrL7DK82VOw9Hblkh_g,26341
40
41
  duty/tools/_flake8.py,sha256=ZmV_1Hy773fzP40LDNmj8YJslWatldl0qfr_g2pcgrI,9371
@@ -45,9 +46,9 @@ duty/tools/_isort.py,sha256=VbQbK4iPVNWNYlmOPDEnEcFVB6fJ66W-8s36bS9HziQ,28352
45
46
  duty/tools/_mkdocs.py,sha256=GfhXUScTjGQK5Djg6fAGtLSSssdbhebL2AonG5FGPV0,9027
46
47
  duty/tools/_mypy.py,sha256=6La6BTfTfMlpA4-JO8dn1O0QQ1zaVEUEp5M6cRmPr7k,21555
47
48
  duty/tools/_pytest.py,sha256=HG5jmRbdCKMK6-6i8cDd4MZ1CsDhHa0TQyjls0vy1WU,20064
48
- duty/tools/_ruff.py,sha256=bsj2CcKFEb4A-4lko9Lxc_KNTks74Q902AxImRqBUCs,15246
49
- duty/tools/_safety.py,sha256=3kQOko-z07w8S-zq3Qs1FdVXSdY3F13QgRsx1NPPxOg,3141
49
+ duty/tools/_ruff.py,sha256=LftbwvIahvjZQPc3ksLmP34a26dK22rafkpL7VQyoXc,15224
50
+ duty/tools/_safety.py,sha256=D0ID65aeSCx6ZG01jZVSik075YkojjoYb8DoRtaz4X8,3206
50
51
  duty/tools/_ssort.py,sha256=xhh5eYq16BC8KTSKDCxA10mR8RZoQuN10-xUjhu3QlE,1097
51
52
  duty/tools/_twine.py,sha256=9y7-X9fqZ6wbOddGtxBMePI7hgq9QldYyMHxRBy_JXg,10347
52
- duty/validation.py,sha256=NbinkhQGsfnL4ZqjMoWZgv5GxmNQ0qMDhE6pWxLxuM4,8183
53
- duty-1.4.2.dist-info/RECORD,,
53
+ duty/validation.py,sha256=z0CLIsc-2AYbh-zqm3XMhWwE9JlVYmIJkrbo_bjmqVw,8241
54
+ duty-1.5.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: pdm-backend (2.3.3)
2
+ Generator: pdm-backend (2.4.3)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,3 +1,5 @@
1
1
  [console_scripts]
2
2
  duty = duty.cli:main
3
3
 
4
+ [gui_scripts]
5
+