|
10 | 10 | from argparse import SUPPRESS, Action, ArgumentDefaultsHelpFormatter, ArgumentError, ArgumentParser, Namespace |
11 | 11 | from pathlib import Path |
12 | 12 | from types import UnionType |
13 | | -from typing import TYPE_CHECKING, Any, Literal, TypeVar, cast |
| 13 | +from typing import TYPE_CHECKING, Any, Literal, TypeVar, cast, overload |
14 | 14 |
|
15 | 15 | from colorama import Fore |
16 | 16 |
|
|
26 | 26 | from typing_extensions import Self |
27 | 27 |
|
28 | 28 | if TYPE_CHECKING: |
29 | | - from collections.abc import Callable, Sequence |
| 29 | + from collections.abc import Callable, Iterable, Sequence |
30 | 30 |
|
31 | 31 | from tox.session.state import State |
32 | 32 |
|
| 33 | +_N = TypeVar("_N", bound=Namespace) |
| 34 | + |
33 | 35 |
|
34 | 36 | class ArgumentParserWithEnvAndConfig(ArgumentParser): |
35 | 37 | """Argument parser which updates its defaults by checking the configuration files and environmental variables.""" |
@@ -80,20 +82,29 @@ def get_type(action: Action) -> type[Any]: |
80 | 82 | raise TypeError(action) |
81 | 83 | return of_type |
82 | 84 |
|
83 | | - def parse_args( # avoid defining all overloads |
| 85 | + @overload |
| 86 | + def parse_args(self, args: Iterable[str] | None = None, namespace: None = None) -> Namespace: ... |
| 87 | + |
| 88 | + @overload |
| 89 | + def parse_args(self, args: Iterable[str] | None, namespace: _N) -> _N: ... |
| 90 | + |
| 91 | + @overload |
| 92 | + def parse_args(self, *, namespace: _N) -> _N: ... |
| 93 | + |
| 94 | + def parse_args( |
84 | 95 | self, |
85 | | - args: Sequence[str] | None = None, |
86 | | - namespace: Namespace | None = None, |
87 | | - ) -> Namespace: |
88 | | - res, argv = self.parse_known_args(args, namespace) |
| 96 | + args: Iterable[str] | None = None, |
| 97 | + namespace: _N | None = None, |
| 98 | + ) -> _N: |
| 99 | + res, argv = self.parse_known_args(list(args) if args is not None else None, namespace) |
89 | 100 | if argv: |
90 | 101 | self.error( |
91 | 102 | f"unrecognized arguments: {' '.join(argv)}\n" |
92 | 103 | "hint: if you tried to pass arguments to a command use -- to separate them from tox ones", |
93 | 104 | ) |
94 | 105 | if getattr(res, "no_capture", False) and getattr(res, "result_json", None): |
95 | 106 | self.error("argument -i/--no-capture: not allowed with argument --result-json") |
96 | | - return cast("Namespace", res) |
| 107 | + return cast("_N", res) |
97 | 108 |
|
98 | 109 |
|
99 | 110 | class HelpFormatter(ArgumentDefaultsHelpFormatter): |
@@ -374,29 +385,41 @@ def _add_base_options(self) -> None: |
374 | 385 | add_core_arguments(self) |
375 | 386 | self.fix_defaults() |
376 | 387 |
|
| 388 | + @overload |
| 389 | + def parse_known_args( |
| 390 | + self, args: Iterable[str] | None = None, namespace: None = None |
| 391 | + ) -> tuple[Parsed, list[str]]: ... |
| 392 | + |
| 393 | + @overload |
| 394 | + def parse_known_args(self, args: Iterable[str] | None, namespace: _N) -> tuple[_N, list[str]]: ... |
| 395 | + |
| 396 | + @overload |
| 397 | + def parse_known_args(self, *, namespace: _N) -> tuple[_N, list[str]]: ... |
| 398 | + |
377 | 399 | def parse_known_args( |
378 | 400 | self, |
379 | | - args: Sequence[str] | None = None, |
380 | | - namespace: Parsed | None = None, |
381 | | - ) -> tuple[Parsed, list[str]]: |
382 | | - if args is None: |
383 | | - args = sys.argv[1:] |
| 401 | + args: Iterable[str] | None = None, |
| 402 | + namespace: _N | None = None, |
| 403 | + ) -> tuple[_N, list[str]]: |
| 404 | + args_list: list[str] = list(args) if args is not None else sys.argv[1:] |
384 | 405 | cmd_at: int | None = None |
385 | | - if self._cmd is not None and args: |
386 | | - for at, arg in enumerate(args): |
| 406 | + if self._cmd is not None and args_list: |
| 407 | + for at, arg in enumerate(args_list): |
387 | 408 | if arg in self._cmd.choices: |
388 | 409 | cmd_at = at |
389 | 410 | break |
390 | 411 | else: |
391 | 412 | cmd_at = None |
392 | 413 | if cmd_at is not None: # if we found a command move it to the start |
393 | | - args = args[cmd_at], *args[:cmd_at], *args[cmd_at + 1 :] |
394 | | - elif tuple(args) not in {("--help",), ("-h",)} and (self._cmd is not None and "legacy" in self._cmd.choices): |
| 414 | + args_list = [args_list[cmd_at], *args_list[:cmd_at], *args_list[cmd_at + 1 :]] |
| 415 | + elif tuple(args_list) not in {("--help",), ("-h",)} and ( |
| 416 | + self._cmd is not None and "legacy" in self._cmd.choices |
| 417 | + ): |
395 | 418 | # on help no mangling needed, and we also want to insert once we have legacy to insert |
396 | | - args = "legacy", *args |
| 419 | + args_list = ["legacy", *args_list] |
397 | 420 | result = Parsed() if namespace is None else namespace |
398 | | - _, args = super().parse_known_args(args, namespace=result) |
399 | | - return result, args |
| 421 | + _, remainder = super().parse_known_args(args_list, namespace=result) |
| 422 | + return cast("tuple[_N, list[str]]", (result, remainder)) |
400 | 423 |
|
401 | 424 |
|
402 | 425 | def add_core_arguments(parser: ArgumentParser) -> None: |
|
0 commit comments