duty 1.6.0__py3-none-any.whl → 1.6.1__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.
Files changed (82) hide show
  1. duty/__init__.py +49 -2
  2. duty/__main__.py +1 -1
  3. duty/_internal/__init__.py +0 -0
  4. duty/_internal/callables/__init__.py +34 -0
  5. duty/{callables → _internal/callables}/_io.py +2 -0
  6. duty/_internal/callables/autoflake.py +132 -0
  7. duty/_internal/callables/black.py +176 -0
  8. duty/_internal/callables/blacken_docs.py +92 -0
  9. duty/_internal/callables/build.py +76 -0
  10. duty/_internal/callables/coverage.py +716 -0
  11. duty/_internal/callables/flake8.py +222 -0
  12. duty/_internal/callables/git_changelog.py +178 -0
  13. duty/_internal/callables/griffe.py +227 -0
  14. duty/_internal/callables/interrogate.py +152 -0
  15. duty/_internal/callables/isort.py +573 -0
  16. duty/_internal/callables/mkdocs.py +256 -0
  17. duty/_internal/callables/mypy.py +496 -0
  18. duty/_internal/callables/pytest.py +475 -0
  19. duty/_internal/callables/ruff.py +399 -0
  20. duty/_internal/callables/safety.py +82 -0
  21. duty/_internal/callables/ssort.py +38 -0
  22. duty/_internal/callables/twine.py +284 -0
  23. duty/_internal/cli.py +322 -0
  24. duty/_internal/collection.py +246 -0
  25. duty/_internal/context.py +111 -0
  26. duty/{debug.py → _internal/debug.py} +13 -15
  27. duty/_internal/decorator.py +111 -0
  28. duty/_internal/exceptions.py +12 -0
  29. duty/_internal/tools/__init__.py +41 -0
  30. duty/{tools → _internal/tools}/_autoflake.py +8 -4
  31. duty/{tools → _internal/tools}/_base.py +15 -2
  32. duty/{tools → _internal/tools}/_black.py +5 -5
  33. duty/{tools → _internal/tools}/_blacken_docs.py +10 -5
  34. duty/{tools → _internal/tools}/_build.py +4 -4
  35. duty/{tools → _internal/tools}/_coverage.py +8 -4
  36. duty/{tools → _internal/tools}/_flake8.py +10 -12
  37. duty/{tools → _internal/tools}/_git_changelog.py +8 -4
  38. duty/{tools → _internal/tools}/_griffe.py +8 -4
  39. duty/{tools → _internal/tools}/_interrogate.py +4 -4
  40. duty/{tools → _internal/tools}/_isort.py +8 -6
  41. duty/{tools → _internal/tools}/_mkdocs.py +8 -4
  42. duty/{tools → _internal/tools}/_mypy.py +5 -5
  43. duty/{tools → _internal/tools}/_pytest.py +8 -4
  44. duty/{tools → _internal/tools}/_ruff.py +11 -5
  45. duty/{tools → _internal/tools}/_safety.py +13 -8
  46. duty/{tools → _internal/tools}/_ssort.py +10 -6
  47. duty/{tools → _internal/tools}/_twine.py +11 -5
  48. duty/_internal/tools/_yore.py +96 -0
  49. duty/_internal/validation.py +266 -0
  50. duty/callables/__init__.py +4 -4
  51. duty/callables/autoflake.py +11 -126
  52. duty/callables/black.py +12 -171
  53. duty/callables/blacken_docs.py +11 -86
  54. duty/callables/build.py +12 -71
  55. duty/callables/coverage.py +12 -711
  56. duty/callables/flake8.py +12 -217
  57. duty/callables/git_changelog.py +12 -173
  58. duty/callables/griffe.py +12 -222
  59. duty/callables/interrogate.py +12 -147
  60. duty/callables/isort.py +12 -568
  61. duty/callables/mkdocs.py +12 -251
  62. duty/callables/mypy.py +11 -490
  63. duty/callables/pytest.py +12 -470
  64. duty/callables/ruff.py +12 -394
  65. duty/callables/safety.py +11 -76
  66. duty/callables/ssort.py +12 -33
  67. duty/callables/twine.py +12 -279
  68. duty/cli.py +10 -316
  69. duty/collection.py +12 -228
  70. duty/context.py +12 -107
  71. duty/decorator.py +12 -108
  72. duty/exceptions.py +13 -10
  73. duty/tools.py +63 -0
  74. duty/validation.py +12 -262
  75. {duty-1.6.0.dist-info → duty-1.6.1.dist-info}/METADATA +4 -3
  76. duty-1.6.1.dist-info/RECORD +81 -0
  77. {duty-1.6.0.dist-info → duty-1.6.1.dist-info}/WHEEL +1 -1
  78. {duty-1.6.0.dist-info → duty-1.6.1.dist-info}/entry_points.txt +1 -1
  79. duty/tools/__init__.py +0 -50
  80. duty/tools/_yore.py +0 -54
  81. duty-1.6.0.dist-info/RECORD +0 -55
  82. {duty-1.6.0.dist-info → duty-1.6.1.dist-info}/licenses/LICENSE +0 -0
duty/callables/twine.py CHANGED
@@ -1,284 +1,17 @@
1
- """Callable for [Twine](https://github.com/pypa/twine)."""
1
+ """Deprecated. Use [`duty.tools.twine`][] instead."""
2
2
 
3
- from __future__ import annotations
3
+ # YORE: Bump 2: Remove file.
4
4
 
5
- from failprint.lazy import lazy
5
+ import warnings
6
+ from typing import Any
6
7
 
8
+ from duty._internal.callables import twine as _twine
7
9
 
8
- def run(*args: str, version: bool = False, no_color: bool = False) -> None:
9
- """Run `twine`.
10
10
 
11
- Parameters:
12
- *args: CLI arguments.
13
- version: Show program's version number and exit.
14
- no_color: Disable colored output.
15
- """
16
- from twine.cli import dispatch as twine
17
-
18
- cli_args = []
19
-
20
- # --version and --no-color must appear first.
21
- if version:
22
- cli_args.append("--version")
23
-
24
- if no_color:
25
- cli_args.append("--no-color")
26
-
27
- cli_args.extend(args)
28
-
29
- twine(cli_args)
30
-
31
-
32
- @lazy(name="twine.check")
33
- def check(
34
- *dists: str,
35
- strict: bool = False,
36
- version: bool = False,
37
- no_color: bool = False,
38
- ) -> None:
39
- """Checks whether your distribution's long description will render correctly on PyPI.
40
-
41
- Parameters:
42
- dists: The distribution files to check, usually `dist/*`.
43
- strict: Fail on warnings.
44
- version: Show program's version number and exit.
45
- no_color: Disable colored output.
46
- """
47
- cli_args = list(dists)
48
-
49
- if strict is True:
50
- cli_args.append("--strict")
51
-
52
- run("check", *cli_args, version=version, no_color=no_color)
53
-
54
-
55
- @lazy(name="twine.register")
56
- def register(
57
- package: str,
58
- *,
59
- repository: str = "pypi",
60
- repository_url: str | None = None,
61
- attestations: bool = False,
62
- sign: bool = False,
63
- sign_with: str | None = None,
64
- identity: str | None = None,
65
- username: str | None = None,
66
- password: str | None = None,
67
- non_interactive: bool = False,
68
- comment: str | None = None,
69
- config_file: str | None = None,
70
- skip_existing: bool = False,
71
- cert: str | None = None,
72
- client_cert: str | None = None,
73
- verbose: bool = False,
74
- disable_progress_bar: bool = False,
75
- version: bool = False,
76
- no_color: bool = False,
77
- ) -> None:
78
- """Pre-register a name with a repository before uploading a distribution.
79
-
80
- Pre-registration is not supported on PyPI, so the register command
81
- is only necessary if you are using a different repository that requires it.
82
-
83
- Parameters:
84
- package: File from which we read the package metadata.
85
- repository: The repository (package index) to upload the package to.
86
- Should be a section in the config file (default: `pypi`).
87
- Can also be set via `TWINE_REPOSITORY` environment variable.
88
- repository_url: The repository (package index) URL to upload the package to. This overrides `--repository`.
89
- Can also be set via `TWINE_REPOSITORY_URL` environment variable.
90
- attestations: Upload each file's associated attestations.
91
- sign: Sign files to upload using GPG.
92
- sign_with: GPG program used to sign uploads (default: `gpg`).
93
- identity: GPG identity used to sign files.
94
- username: The username to authenticate to the repository (package index) as.
95
- Can also be set via `TWINE_USERNAME` environment variable.
96
- password: The password to authenticate to the repository (package index) with.
97
- Can also be set via `TWINE_PASSWORD` environment variable.
98
- non_interactive: Do not interactively prompt for username/password if the required credentials are missing.
99
- Can also be set via `TWINE_NON_INTERACTIVE` environment variable.
100
- comment: The comment to include with the distribution file.
101
- config_file: The `.pypirc` config file to use.
102
- skip_existing: Continue uploading files if one already exists.
103
- Only valid when uploading to PyPI. Other implementations may not support this.
104
- cert: Path to alternate CA bundle (can also be set via `TWINE_CERT` environment variable).
105
- client_cert: Path to SSL client certificate, a single file containing the private key and the certificate in PEM format.
106
- verbose: Show verbose output.
107
- disable_progress_bar: Disable the progress bar.
108
- version: Show program's version number and exit.
109
- no_color: Disable colored output.
110
- """
111
- cli_args = [package]
112
-
113
- if repository:
114
- cli_args.append("--repository")
115
- cli_args.append(repository)
116
-
117
- if repository_url:
118
- cli_args.append("--repository-url")
119
- cli_args.append(repository_url)
120
-
121
- if attestations:
122
- cli_args.append("--attestations")
123
-
124
- if sign:
125
- cli_args.append("--sign")
126
-
127
- if sign_with:
128
- cli_args.append("--sign-with")
129
- cli_args.append(sign_with)
130
-
131
- if identity:
132
- cli_args.append("--identity")
133
- cli_args.append(identity)
134
-
135
- if username:
136
- cli_args.append("--username")
137
- cli_args.append(username)
138
-
139
- if password:
140
- cli_args.append("--password")
141
- cli_args.append(password)
142
-
143
- if non_interactive:
144
- cli_args.append("--non-interactive")
145
-
146
- if comment:
147
- cli_args.append("--repository")
148
-
149
- if config_file:
150
- cli_args.append("--config-file")
151
- cli_args.append(config_file)
152
-
153
- if skip_existing:
154
- cli_args.append("--skip-existing")
155
-
156
- if cert:
157
- cli_args.append("--cert")
158
- cli_args.append(cert)
159
-
160
- if client_cert:
161
- cli_args.append("--client-cert")
162
- cli_args.append(client_cert)
163
-
164
- if verbose:
165
- cli_args.append("--verbose")
166
-
167
- if disable_progress_bar:
168
- cli_args.append("--disable-progress-bar")
169
-
170
- run("check", *cli_args, version=version, no_color=no_color)
171
-
172
-
173
- @lazy(name="twine.upload")
174
- def upload(
175
- *dists: str,
176
- repository: str = "pypi",
177
- repository_url: str | None = None,
178
- attestations: bool = False,
179
- sign: bool = False,
180
- sign_with: str | None = None,
181
- identity: str | None = None,
182
- username: str | None = None,
183
- password: str | None = None,
184
- non_interactive: bool = False,
185
- comment: str | None = None,
186
- config_file: str | None = None,
187
- skip_existing: bool = False,
188
- cert: str | None = None,
189
- client_cert: str | None = None,
190
- verbose: bool = False,
191
- disable_progress_bar: bool = False,
192
- version: bool = False,
193
- no_color: bool = False,
194
- ) -> None:
195
- """Uploads one or more distributions to a repository.
196
-
197
- Parameters:
198
- dists: The distribution files to check, usually `dist/*`.
199
- repository: The repository (package index) to upload the package to.
200
- Should be a section in the config file (default: `pypi`).
201
- Can also be set via `TWINE_REPOSITORY` environment variable.
202
- repository_url: The repository (package index) URL to upload the package to. This overrides `--repository`.
203
- Can also be set via `TWINE_REPOSITORY_URL` environment variable.
204
- attestations: Upload each file's associated attestations.
205
- sign: Sign files to upload using GPG.
206
- sign_with: GPG program used to sign uploads (default: `gpg`).
207
- identity: GPG identity used to sign files.
208
- username: The username to authenticate to the repository (package index) as.
209
- Can also be set via `TWINE_USERNAME` environment variable.
210
- password: The password to authenticate to the repository (package index) with.
211
- Can also be set via `TWINE_PASSWORD` environment variable.
212
- non_interactive: Do not interactively prompt for username/password if the required credentials are missing.
213
- Can also be set via `TWINE_NON_INTERACTIVE` environment variable.
214
- comment: The comment to include with the distribution file.
215
- config_file: The `.pypirc` config file to use.
216
- skip_existing: Continue uploading files if one already exists.
217
- Only valid when uploading to PyPI. Other implementations may not support this.
218
- cert: Path to alternate CA bundle (can also be set via `TWINE_CERT` environment variable).
219
- client_cert: Path to SSL client certificate, a single file containing the private key and the certificate in PEM format.
220
- verbose: Show verbose output.
221
- disable_progress_bar: Disable the progress bar.
222
- version: Show program's version number and exit.
223
- no_color: Disable colored output.
224
- """
225
- cli_args = list(dists)
226
-
227
- if repository:
228
- cli_args.append("--repository")
229
- cli_args.append(repository)
230
-
231
- if repository_url:
232
- cli_args.append("--repository-url")
233
- cli_args.append(repository_url)
234
-
235
- if attestations:
236
- cli_args.append("--attestations")
237
-
238
- if sign:
239
- cli_args.append("--sign")
240
-
241
- if sign_with:
242
- cli_args.append("--sign-with")
243
- cli_args.append(sign_with)
244
-
245
- if identity:
246
- cli_args.append("--identity")
247
- cli_args.append(identity)
248
-
249
- if username:
250
- cli_args.append("--username")
251
- cli_args.append(username)
252
-
253
- if password:
254
- cli_args.append("--password")
255
- cli_args.append(password)
256
-
257
- if non_interactive:
258
- cli_args.append("--non-interactive")
259
-
260
- if comment:
261
- cli_args.append("--repository")
262
-
263
- if config_file:
264
- cli_args.append("--config-file")
265
- cli_args.append(config_file)
266
-
267
- if skip_existing:
268
- cli_args.append("--skip-existing")
269
-
270
- if cert:
271
- cli_args.append("--cert")
272
- cli_args.append(cert)
273
-
274
- if client_cert:
275
- cli_args.append("--client-cert")
276
- cli_args.append(client_cert)
277
-
278
- if verbose:
279
- cli_args.append("--verbose")
280
-
281
- if disable_progress_bar:
282
- cli_args.append("--disable-progress-bar")
283
-
284
- run("upload", *cli_args, version=version, no_color=no_color)
11
+ def __getattr__(name: str) -> Any:
12
+ warnings.warn(
13
+ "Callables are deprecated in favor of tools, use `duty.tools.twine` instead of `duty.callables.twine`.",
14
+ DeprecationWarning,
15
+ stacklevel=2,
16
+ )
17
+ return getattr(_twine, name)
duty/cli.py CHANGED
@@ -1,323 +1,17 @@
1
- """Module that contains the command line application."""
1
+ """Deprecated. Import from `duty` directly."""
2
2
 
3
- # Why does this file exist, and why not put this in `__main__`?
4
- #
5
- # You might be tempted to import things from `__main__` later,
6
- # but that will cause problems: the code will get executed twice:
7
- #
8
- # - When you run `python -m duty` python will execute
9
- # `__main__.py` as a script. That means there won't be any
10
- # `duty.__main__` in `sys.modules`.
11
- # - When you import `__main__` it will get executed again (as a module) because
12
- # there's no `duty.__main__` in `sys.modules`.
3
+ # YORE: Bump 2: Remove file.
13
4
 
14
- from __future__ import annotations
15
-
16
- import argparse
17
- import inspect
18
- import sys
19
- import textwrap
20
- from pathlib import Path
5
+ import warnings
21
6
  from typing import Any
22
7
 
23
- from failprint.cli import ArgParser, add_flags
24
-
25
- from duty import debug
26
- from duty.collection import Collection, Duty
27
- from duty.exceptions import DutyFailure
28
- from duty.validation import validate
29
-
30
- empty = inspect.Signature.empty
31
-
32
-
33
- class _DebugInfo(argparse.Action):
34
- def __init__(self, nargs: int | str | None = 0, **kwargs: Any) -> None:
35
- super().__init__(nargs=nargs, **kwargs)
36
-
37
- def __call__(self, *args: Any, **kwargs: Any) -> None: # noqa: ARG002
38
- debug.print_debug_info()
39
- sys.exit(0)
40
-
41
-
42
- def get_parser() -> ArgParser:
43
- """Return the CLI argument parser.
44
-
45
- Returns:
46
- An argparse parser.
47
- """
48
- usage = "duty [GLOBAL_OPTS...] [DUTY [DUTY_OPTS...] [DUTY_PARAMS...]...]"
49
- description = "A simple task runner."
50
- parser = ArgParser(add_help=False, usage=usage, description=description)
51
-
52
- parser.add_argument(
53
- "-d",
54
- "--duties-file",
55
- default="duties.py",
56
- help="Python file where the duties are defined.",
57
- )
58
- parser.add_argument(
59
- "-l",
60
- "--list",
61
- action="store_true",
62
- dest="list",
63
- help="List the available duties.",
64
- )
65
- parser.add_argument(
66
- "-h",
67
- "--help",
68
- dest="help",
69
- nargs="*",
70
- metavar="DUTY",
71
- help="Show this help message and exit. Pass duties names to print their help.",
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
- )
85
- parser.add_argument("-V", "--version", action="version", version=f"%(prog)s {debug.get_version()}")
86
- parser.add_argument("--debug-info", action=_DebugInfo, help="Print debug information.")
87
-
88
- add_flags(parser, set_defaults=False)
89
- parser.add_argument("remainder", nargs=argparse.REMAINDER)
90
-
91
- parser._optionals.title = "Global options"
8
+ from duty._internal import cli
92
9
 
93
- return parser
94
10
 
95
-
96
- def split_args(args: list[str], names: list[str]) -> list[list[str]]:
97
- """Split command line arguments into duty commands.
98
-
99
- Parameters:
100
- args: The CLI arguments.
101
- names: The known duty names.
102
-
103
- Raises:
104
- ValueError: When a duty name is missing before an argument,
105
- or when the duty name is unknown.
106
-
107
- Returns:
108
- The split commands.
109
- """
110
- arg_lists = []
111
- current_arg_list: list[str] = []
112
-
113
- for arg in args:
114
- if arg in names:
115
- # We found a duty name.
116
- if current_arg_list:
117
- # Append the previous arg list to the result and reset it.
118
- arg_lists.append(current_arg_list)
119
- current_arg_list = []
120
- current_arg_list.append(arg)
121
- elif current_arg_list:
122
- # We found an argument.
123
- current_arg_list.append(arg)
124
- else:
125
- # We found an argument but no duty name.
126
- raise ValueError(f"> Missing duty name before argument '{arg}', or unknown duty name")
127
-
128
- # Don't forget the last arg list.
129
- if current_arg_list:
130
- arg_lists.append(current_arg_list)
131
-
132
- return arg_lists
133
-
134
-
135
- def get_duty_parser(duty: Duty) -> ArgParser:
136
- """Get a duty-specific options parser.
137
-
138
- Parameters:
139
- duty: The duty to parse for.
140
-
141
- Returns:
142
- A duty-specific parser.
143
- """
144
- parser = ArgParser(
145
- prog=f"duty {duty.name}",
146
- add_help=False,
147
- description=duty.description,
148
- formatter_class=argparse.RawDescriptionHelpFormatter,
11
+ def __getattr__(name: str) -> Any:
12
+ warnings.warn(
13
+ "Importing from `duty.cli` is deprecated. Import from `duty` directly.",
14
+ DeprecationWarning,
15
+ stacklevel=2,
149
16
  )
150
- add_flags(parser, set_defaults=False)
151
- return parser
152
-
153
-
154
- def specified_options(opts: argparse.Namespace, exclude: set[str] | None = None) -> dict:
155
- """Cast an argparse Namespace into a dictionary of options.
156
-
157
- Remove all options that were not specified (equal to None).
158
-
159
- Parameters:
160
- opts: The namespace to cast.
161
- exclude: Names of options to exclude from the result.
162
-
163
- Returns:
164
- A dictionary of specified-only options.
165
- """
166
- exclude = exclude or set()
167
- options = opts.__dict__.items()
168
- return {opt: value for opt, value in options if value is not None and opt not in exclude}
169
-
170
-
171
- def parse_options(duty: Duty, args: list[str]) -> tuple[dict, list[str]]:
172
- """Parse options for a duty.
173
-
174
- Parameters:
175
- duty: The duty to parse for.
176
- args: The CLI args passed for this duty.
177
-
178
- Returns:
179
- The parsed opts, and the remaining arguments.
180
- """
181
- parser = get_duty_parser(duty)
182
- opts, remainder = parser.parse_known_args(args)
183
- return specified_options(opts), remainder
184
-
185
-
186
- def parse_args(duty: Duty, args: list[str]) -> tuple:
187
- """Parse the positional and keyword arguments of a duty.
188
-
189
- Parameters:
190
- duty: The duty to parse for.
191
- args: The list of arguments.
192
-
193
- Returns:
194
- The positional and keyword arguments.
195
- """
196
- posargs = []
197
- kwargs = {}
198
-
199
- for arg in args:
200
- if "=" in arg:
201
- # we found a keyword argument
202
- arg_name, arg_value = arg.split("=", 1)
203
- kwargs[arg_name] = arg_value
204
- else:
205
- # we found a positional argument
206
- posargs.append(arg)
207
-
208
- return validate(duty.function, *posargs, **kwargs)
209
-
210
-
211
- def parse_commands(arg_lists: list[list[str]], global_opts: dict[str, Any], collection: Collection) -> list[tuple]:
212
- """Parse argument lists into ready-to-run duties.
213
-
214
- Parameters:
215
- arg_lists: Lists of arguments lists.
216
- global_opts: The global options.
217
- collection: The duties collection.
218
-
219
- Returns:
220
- A list of tuples composed of:
221
-
222
- - a duty
223
- - its positional arguments
224
- - its keyword arguments
225
- """
226
- commands = []
227
- for arg_list in arg_lists:
228
- duty = collection.get(arg_list[0])
229
- opts, remainder = parse_options(duty, arg_list[1:])
230
- if remainder and remainder[0] == "--":
231
- remainder = remainder[1:]
232
- duty.options_override = {**global_opts, **opts}
233
- commands.append((duty, *parse_args(duty, remainder)))
234
- return commands
235
-
236
-
237
- def print_help(parser: ArgParser, opts: argparse.Namespace, collection: Collection) -> None:
238
- """Print general help or duties help.
239
-
240
- Parameters:
241
- parser: The main parser.
242
- opts: The main parsed options.
243
- collection: A collection of duties.
244
- """
245
- if opts.help:
246
- for duty_name in opts.help:
247
- try:
248
- duty = collection.get(duty_name)
249
- except KeyError:
250
- print(f"> Unknown duty '{duty_name}'")
251
- else:
252
- print(get_duty_parser(duty).format_help())
253
- else:
254
- print(parser.format_help())
255
- print("Available duties:")
256
- print(textwrap.indent(collection.format_help(), prefix=" "))
257
-
258
-
259
- def main(args: list[str] | None = None) -> int:
260
- """Run the main program.
261
-
262
- This function is executed when you type `duty` or `python -m duty`.
263
-
264
- Parameters:
265
- args: Arguments passed from the command line.
266
-
267
- Returns:
268
- An exit code.
269
- """
270
- parser = get_parser()
271
- opts = parser.parse_args(args=args)
272
- remainder = opts.remainder
273
-
274
- collection = Collection(opts.duties_file)
275
- collection.load()
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
-
289
- if opts.help is not None:
290
- print_help(parser, opts, collection)
291
- return 0
292
-
293
- if opts.list:
294
- print(textwrap.indent(collection.format_help(), prefix=" "))
295
- return 0
296
-
297
- try:
298
- arg_lists = split_args(remainder, collection.names())
299
- except ValueError as error:
300
- print(error, file=sys.stderr)
301
- return 1
302
-
303
- if not arg_lists:
304
- print_help(parser, opts, collection)
305
- return 1
306
-
307
- global_opts = specified_options(
308
- opts,
309
- exclude={"duties_file", "list", "help", "remainder", "complete", "completion"},
310
- )
311
- try:
312
- commands = parse_commands(arg_lists, global_opts, collection)
313
- except TypeError as error:
314
- print(f"> {error}", file=sys.stderr)
315
- return 1
316
-
317
- for duty, posargs, kwargs in commands:
318
- try:
319
- duty.run(*posargs, **kwargs)
320
- except DutyFailure as failure:
321
- return failure.code
322
-
323
- return 0
17
+ return getattr(cli, name)