duty 1.4.3__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.
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
@@ -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/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",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: duty
3
- Version: 1.4.3
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>
@@ -1,7 +1,7 @@
1
- duty-1.4.3.dist-info/METADATA,sha256=v58pX-GBakOWoOGNVIUkNkkA66B3UcO1Zbjtm3yazyo,2710
2
- duty-1.4.3.dist-info/WHEEL,sha256=pM0IBB6ZwH3nkEPhtcp50KvKNX-07jYtnb1g1m6Z4Co,90
3
- duty-1.4.3.dist-info/entry_points.txt,sha256=XZTh9yTgC9GDFmNoVeOqgKT6BCMifkCToilIj77B9ss,55
4
- duty-1.4.3.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
@@ -23,14 +23,15 @@ duty/callables/ruff.py,sha256=ZBjl1_MxaZ1-atHVB6YOFNf3QAm6GTw7Xj3hX8jnofo,13314
23
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=Xp85w6R4Lxv9A3IcdG2ZRtOJdyf1CXSlYr6-N0wBzwo,6584
26
+ duty/cli.py,sha256=XnkVQAmHCf5QAykZ4foiLvBrsSD22dhSljXmLdFhWM4,9273
27
+ duty/collection.py,sha256=cg_rInuTErP7HTrUJsHenOSdtFZtzIw8cIIsr5g-0T8,7500
28
+ duty/completions.bash,sha256=xXtsZoF1bxhfG4vEuh8hwyYkzNBNlDRxKC9myHV4LPA,1311
28
29
  duty/context.py,sha256=YVF68a2w3SMEM0AsN1mBCvlOZ5jTEZ9OYCxDZvv_soI,3250
29
30
  duty/debug.py,sha256=9stdqxNJ9zA2HWsYfmy3C9BrWOXLlHYRGsQ6dHfCUfM,2813
30
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
@@ -50,4 +51,4 @@ 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
53
  duty/validation.py,sha256=z0CLIsc-2AYbh-zqm3XMhWwE9JlVYmIJkrbo_bjmqVw,8241
53
- duty-1.4.3.dist-info/RECORD,,
54
+ duty-1.5.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: pdm-backend (2.4.2)
2
+ Generator: pdm-backend (2.4.3)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any