11"""Contains code related to click."""
22from __future__ import annotations
33
4- import enum
54import inspect
5+ from enum import Enum
66from gettext import gettext as _
7+ from gettext import ngettext
78from typing import Any
89from typing import ClassVar
10+ from typing import TYPE_CHECKING
911
1012import click
1113from _pytask import __version__ as version
1214from _pytask .console import console
15+ from click import Choice
16+ from click import Command
17+ from click import Context
18+ from click import Parameter
1319from click .parser import split_opt
1420from click_default_group import DefaultGroup
1521from rich .highlighter import RegexHighlighter
1622from rich .panel import Panel
1723from rich .table import Table
1824from rich .text import Text
1925
26+ if TYPE_CHECKING :
27+ from collections .abc import Sequence
28+
2029
2130__all__ = ["ColoredCommand" , "ColoredGroup" , "EnumChoice" ]
2231
2332
24- class EnumChoice (click . Choice ):
33+ class EnumChoice (Choice ):
2534 """An enum-based choice type.
2635
2736 The implementation is copied from https://github.com/pallets/click/pull/2210 and
@@ -35,17 +44,15 @@ class EnumChoice(click.Choice):
3544
3645 """
3746
38- def __init__ (self , enum_type : type [enum . Enum ], case_sensitive : bool = True ) -> None :
47+ def __init__ (self , enum_type : type [Enum ], case_sensitive : bool = True ) -> None :
3948 super ().__init__ (
4049 choices = [element .value for element in enum_type ],
4150 case_sensitive = case_sensitive ,
4251 )
4352 self .enum_type = enum_type
4453
45- def convert (
46- self , value : Any , param : click .Parameter | None , ctx : click .Context | None
47- ) -> Any :
48- if isinstance (value , enum .Enum ):
54+ def convert (self , value : Any , param : Parameter | None , ctx : Context | None ) -> Any :
55+ if isinstance (value , Enum ):
4956 value = value .value
5057 value = super ().convert (value = value , param = param , ctx = ctx )
5158 if value is None :
@@ -68,7 +75,7 @@ class ColoredGroup(DefaultGroup):
6875
6976 def format_help (
7077 self : DefaultGroup ,
71- ctx : click . Context ,
78+ ctx : Context ,
7279 formatter : Any , # noqa: ARG002
7380 ) -> None :
7481 """Format the help text."""
@@ -114,12 +121,62 @@ def format_help(
114121 )
115122
116123
117- class ColoredCommand (click .Command ):
124+ def _iter_params_for_processing (
125+ invocation_order : Sequence [Parameter ], declaration_order : Sequence [Parameter ]
126+ ) -> list [Parameter ]:
127+ def sort_key (item : Parameter ) -> tuple [bool , float ]:
128+ # Hardcode the order of the config and paths parameters so that they are always
129+ # processed first even if other eager parameters are chosen. The rest follows
130+ # https://click.palletsprojects.com/en/8.1.x/advanced/#callback-evaluation-order.
131+ if item .name == "paths" :
132+ return False , - 3
133+
134+ if item .name == "config" :
135+ return False , - 2
136+
137+ if item .name == "hook_module" :
138+ return False , - 1
139+
140+ try :
141+ idx : float = invocation_order .index (item )
142+ except ValueError :
143+ idx = float ("inf" )
144+
145+ return not item .is_eager , idx
146+
147+ return sorted (declaration_order , key = sort_key )
148+
149+
150+ class ColoredCommand (Command ):
118151 """A command with colored help pages."""
119152
153+ def parse_args (self , ctx : Context , args : list [str ]) -> list [str ]:
154+ if not args and self .no_args_is_help and not ctx .resilient_parsing :
155+ click .echo (ctx .get_help (), color = ctx .color )
156+ ctx .exit ()
157+
158+ parser = self .make_parser (ctx )
159+ opts , args , param_order = parser .parse_args (args = args )
160+
161+ for param in _iter_params_for_processing (param_order , self .get_params (ctx )):
162+ value , args = param .handle_parse_result (ctx , opts , args )
163+
164+ if args and not ctx .allow_extra_args and not ctx .resilient_parsing :
165+ ctx .fail (
166+ ngettext (
167+ "Got unexpected extra argument ({args})" ,
168+ "Got unexpected extra arguments ({args})" ,
169+ len (args ),
170+ ).format (args = " " .join (map (str , args )))
171+ )
172+
173+ ctx .args = args
174+ ctx ._opt_prefixes .update (parser ._opt_prefixes )
175+ return args
176+
120177 def format_help (
121- self : click . Command ,
122- ctx : click . Context ,
178+ self : Command ,
179+ ctx : Context ,
123180 formatter : Any , # noqa: ARG002
124181 ) -> None :
125182 """Format the help text."""
@@ -142,9 +199,7 @@ def format_help(
142199 )
143200
144201
145- def _print_options (
146- group_or_command : click .Command | DefaultGroup , ctx : click .Context
147- ) -> None :
202+ def _print_options (group_or_command : Command | DefaultGroup , ctx : Context ) -> None :
148203 """Print options formatted with a table in a panel."""
149204 highlighter = _OptionHighlighter ()
150205
@@ -195,7 +250,7 @@ def _print_options(
195250
196251
197252def _format_help_text ( # noqa: C901, PLR0912, PLR0915
198- param : click . Parameter , ctx : click . Context
253+ param : Parameter , ctx : Context
199254) -> Text :
200255 """Format the help of a click parameter.
201256
@@ -264,7 +319,7 @@ def _format_help_text( # noqa: C901, PLR0912, PLR0915
264319 and not default_value
265320 ):
266321 default_string = ""
267- elif isinstance (default_value , enum . Enum ):
322+ elif isinstance (default_value , Enum ):
268323 default_string = str (default_value .value )
269324 else :
270325 default_string = str (default_value )
0 commit comments