|
2 | 2 |
|
3 | 3 | from __future__ import annotations
|
4 | 4 |
|
| 5 | +import importlib.metadata |
5 | 6 | import inspect
|
6 | 7 | from enum import Enum
|
7 | 8 | from gettext import gettext as _
|
8 | 9 | from gettext import ngettext
|
9 | 10 | from typing import TYPE_CHECKING
|
10 | 11 | from typing import Any
|
11 | 12 | from typing import ClassVar
|
| 13 | +from typing import TypeVar |
12 | 14 |
|
13 | 15 | import click
|
14 | 16 | from click import Choice
|
15 | 17 | from click import Command
|
16 | 18 | from click import Context
|
17 | 19 | from click import Parameter
|
18 |
| -from click.parser import split_opt |
19 | 20 | from click_default_group import DefaultGroup
|
20 | 21 | from rich.highlighter import RegexHighlighter
|
| 22 | +from rich.padding import Padding |
21 | 23 | from rich.panel import Panel
|
22 | 24 | from rich.table import Table
|
23 | 25 | from rich.text import Text
|
|
27 | 29 | from _pytask.console import create_panel_title
|
28 | 30 |
|
29 | 31 | if TYPE_CHECKING:
|
| 32 | + from collections.abc import Iterable |
30 | 33 | from collections.abc import Sequence
|
31 | 34 |
|
32 | 35 |
|
33 | 36 | __all__ = ["ColoredCommand", "ColoredGroup", "EnumChoice"]
|
34 | 37 |
|
35 | 38 |
|
36 |
| -class EnumChoice(Choice): |
37 |
| - """An enum-based choice type. |
| 39 | +if importlib.metadata.version("click") < "8.2": |
| 40 | + from click.parser import split_opt |
38 | 41 |
|
39 |
| - The implementation is copied from https://github.com/pallets/click/pull/2210 and |
40 |
| - related discussion can be found in https://github.com/pallets/click/issues/605. |
| 42 | + class EnumChoice(Choice): # type: ignore[type-arg] |
| 43 | + """An enum-based choice type. |
41 | 44 |
|
42 |
| - In contrast to using :class:`click.Choice`, using this type ensures that the error |
43 |
| - message does not show the enum members. |
| 45 | + The implementation is copied from https://github.com/pallets/click/pull/2210 and |
| 46 | + related discussion can be found in https://github.com/pallets/click/issues/605. |
44 | 47 |
|
45 |
| - In contrast to the proposed implementation in the PR, this implementation does not |
46 |
| - use the members than rather the values of the enum. |
| 48 | + In contrast to using :class:`click.Choice`, using this type ensures that the |
| 49 | + error message does not show the enum members. |
47 | 50 |
|
48 |
| - """ |
| 51 | + In contrast to the proposed implementation in the PR, this implementation does |
| 52 | + not use the members than rather the values of the enum. |
49 | 53 |
|
50 |
| - def __init__(self, enum_type: type[Enum], case_sensitive: bool = True) -> None: |
51 |
| - super().__init__( |
52 |
| - choices=[element.value for element in enum_type], |
53 |
| - case_sensitive=case_sensitive, |
54 |
| - ) |
55 |
| - self.enum_type = enum_type |
| 54 | + """ |
| 55 | + |
| 56 | + def __init__(self, enum_type: type[Enum], case_sensitive: bool = True) -> None: |
| 57 | + super().__init__( |
| 58 | + choices=[element.value for element in enum_type], |
| 59 | + case_sensitive=case_sensitive, |
| 60 | + ) |
| 61 | + self.enum_type = enum_type |
| 62 | + |
| 63 | + def convert( |
| 64 | + self, value: Any, param: Parameter | None, ctx: Context | None |
| 65 | + ) -> Any: |
| 66 | + if isinstance(value, Enum): |
| 67 | + value = value.value |
| 68 | + value = super().convert(value=value, param=param, ctx=ctx) |
| 69 | + if value is None: |
| 70 | + return None |
| 71 | + return self.enum_type(value) |
| 72 | + |
| 73 | +else: |
| 74 | + from click.parser import _split_opt as split_opt |
| 75 | + |
| 76 | + ParamTypeValue = TypeVar("ParamTypeValue") |
56 | 77 |
|
57 |
| - def convert(self, value: Any, param: Parameter | None, ctx: Context | None) -> Any: |
58 |
| - if isinstance(value, Enum): |
59 |
| - value = value.value |
60 |
| - value = super().convert(value=value, param=param, ctx=ctx) |
61 |
| - if value is None: |
62 |
| - return None |
63 |
| - return self.enum_type(value) |
| 78 | + class EnumChoice(Choice): |
| 79 | + def __init__( |
| 80 | + self, choices: Iterable[ParamTypeValue], case_sensitive: bool = False |
| 81 | + ) -> None: |
| 82 | + super().__init__(choices=choices, case_sensitive=case_sensitive) |
64 | 83 |
|
65 | 84 |
|
66 | 85 | class _OptionHighlighter(RegexHighlighter):
|
@@ -119,7 +138,10 @@ def format_help(
|
119 | 138 | _print_options(self, ctx)
|
120 | 139 |
|
121 | 140 | console.print(
|
122 |
| - "[bold #FF0000]♥[/] [#f2f2f2]https://pytask-dev.readthedocs.io[/]", |
| 141 | + Padding( |
| 142 | + "[bold #FF0000]♥[/] [#f2f2f2]https://pytask-dev.readthedocs.io[/]", |
| 143 | + (0, 3, 0, 0), |
| 144 | + ), |
123 | 145 | justify="right",
|
124 | 146 | )
|
125 | 147 |
|
@@ -197,7 +219,10 @@ def format_help(
|
197 | 219 | _print_options(self, ctx)
|
198 | 220 |
|
199 | 221 | console.print(
|
200 |
| - "[bold #FF0000]♥[/] [#f2f2f2]https://pytask-dev.readthedocs.io[/]", |
| 222 | + Padding( |
| 223 | + "[bold #FF0000]♥[/] [#f2f2f2]https://pytask-dev.readthedocs.io[/]", |
| 224 | + (0, 3, 0, 0), |
| 225 | + ), |
201 | 226 | justify="right",
|
202 | 227 | )
|
203 | 228 |
|
@@ -235,7 +260,7 @@ def _print_options(group_or_command: Command | DefaultGroup, ctx: Context) -> No
|
235 | 260 | if param.metavar:
|
236 | 261 | opt2 += Text(f" {param.metavar}", style="metavar")
|
237 | 262 | elif isinstance(param.type, click.Choice):
|
238 |
| - choices = "[" + "|".join(param.type.choices) + "]" |
| 263 | + choices = "[" + "|".join([str(c.value) for c in param.type.choices]) + "]" |
239 | 264 | opt2 += Text(f" {choices}", style="metavar", overflow="fold")
|
240 | 265 |
|
241 | 266 | help_text = _format_help_text(param, ctx)
|
@@ -313,7 +338,7 @@ def _format_help_text( # noqa: C901, PLR0912, PLR0915
|
313 | 338 | elif param.is_bool_flag and param.secondary_opts: # type: ignore[attr-defined]
|
314 | 339 | # For boolean flags that have distinct True/False opts,
|
315 | 340 | # use the opt without prefix instead of the value.
|
316 |
| - default_string = split_opt( |
| 341 | + default_string = split_opt( # type: ignore[operator] |
317 | 342 | (param.opts if param.default else param.secondary_opts)[0]
|
318 | 343 | )[1]
|
319 | 344 | elif (
|
|
0 commit comments