Coverage for typer / core.py: 100%
554 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-09 00:15 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-09 00:15 +0000
1import errno 1afcdbeg
2import inspect 1afcdbeg
3import os 1afcdbeg
4import sys 1afcdbeg
5from collections.abc import Callable, Mapping, MutableMapping, Sequence 1afcdbeg
6from difflib import get_close_matches 1afcdbeg
7from enum import Enum 1afcdbeg
8from gettext import gettext as _ 1afcdbeg
9from typing import ( 1afcdbeg
10 Any,
11 TextIO,
12 Union,
13 cast,
14)
16from . import _click 1afcdbeg
17from ._click import types 1afcdbeg
18from ._click.parser import _OptionParser 1afcdbeg
19from ._click.shell_completion import CompletionItem 1afcdbeg
20from ._typing import Literal 1afcdbeg
21from .exceptions import Abort, Exit 1afcdbeg
22from .utils import parse_boolean_env_var 1afcdbeg
24MarkupMode = Literal["markdown", "rich", None] 1afcdbeg
25MARKUP_MODE_KEY = "TYPER_RICH_MARKUP_MODE" 1afcdbeg
27HAS_RICH = parse_boolean_env_var(os.getenv("TYPER_USE_RICH"), default=True) 1afcdbeg
29if HAS_RICH: 1afcdbeg
30 DEFAULT_MARKUP_MODE: MarkupMode = "rich" 1afcdbeg
31else:
32 DEFAULT_MARKUP_MODE = None 1afcdbeg
35# Copy from _click.parser._split_opt
36def _split_opt(opt: str) -> tuple[str, str]: 1afcdbeg
37 first = opt[:1] 1afcdbeg
38 if first.isalnum(): 1afcdbeg
39 return "", opt 1afcdbeg
40 if opt[1:2] == first: 1afcdbeg
41 return opt[:2], opt[2:] 1afcdbeg
42 return first, opt[1:] 1afcdbeg
45def _typer_param_setup_autocompletion_compat( 1afcdbeg
46 self: _click.Parameter,
47 *,
48 autocompletion: Callable[
49 [_click.Context, list[str], str], list[tuple[str, str] | str]
50 ]
51 | None = None,
52) -> None:
53 if self._custom_shell_complete is not None: 1afcdbeg
54 import warnings 1afcdbeg
56 warnings.warn( 1afcdbeg
57 "In Typer, only the parameter 'autocompletion' is supported. "
58 "The support for 'shell_complete' is deprecated and will be removed in upcoming versions. ",
59 DeprecationWarning,
60 stacklevel=2,
61 )
63 if autocompletion is not None: 1afcdbeg
65 def compat_autocompletion( 1afcdbeg
66 ctx: _click.Context, param: _click.core.Parameter, incomplete: str
67 ) -> list[CompletionItem]:
68 out = [] 1afcdbeg
70 for c in autocompletion(ctx, [], incomplete): 1afcdbeg
71 if isinstance(c, tuple): 1afcdbeg
72 use_completion = CompletionItem(c[0], help=c[1]) 1afcdbeg
73 else:
74 assert isinstance(c, str) 1afcdbeg
75 use_completion = CompletionItem(c) 1afcdbeg
77 if use_completion.value.startswith(incomplete): 1afcdbeg
78 out.append(use_completion) 1afcdbeg
80 return out 1afcdbeg
82 self._custom_shell_complete = compat_autocompletion 1afcdbeg
85def _get_default_string( 1afcdbeg
86 obj: Union["TyperArgument", "TyperOption"],
87 *,
88 ctx: _click.Context,
89 show_default_is_str: bool,
90 default_value: list[Any] | tuple[Any, ...] | str | Callable[..., Any] | Any,
91) -> str:
92 # Extracted from _click.core.Option.get_help_record() to be reused by
93 # rich_utils avoiding RegEx hacks
94 if show_default_is_str: 1afcdbeg
95 default_string = f"({obj.show_default})" 1afcdbeg
96 elif isinstance(default_value, (list, tuple)): 1afcdbeg
97 parts = [ 1afcdbeg
98 _get_default_string(
99 obj, ctx=ctx, show_default_is_str=show_default_is_str, default_value=d
100 )
101 for d in default_value
102 ]
103 if all(not part for part in parts): 1afcdbeg
104 default_string = "" 1afcdbeg
105 else:
106 default_string = ", ".join(parts) 1afcdbeg
107 elif isinstance(default_value, Enum): 1afcdbeg
108 default_string = str(default_value.value) 1afcdbeg
109 elif inspect.isfunction(default_value): 1afcdbeg
110 default_string = _("(dynamic)") 1afcdbeg
111 elif isinstance(obj, TyperOption) and obj.is_bool_flag and obj.secondary_opts: 1afcdbeg
112 # For boolean flags that have distinct True/False opts,
113 # use the opt without prefix instead of the value.
114 # Typer override, original commented
115 # default_string = _click.parser.split_opt(
116 # (self.opts if self.default else self.secondary_opts)[0]
117 # )[1]
118 if obj.default: 1afcdbeg
119 if obj.opts: 1afcdbeg
120 default_string = _split_opt(obj.opts[0])[1] 1afcdbeg
121 else:
122 default_string = str(default_value) 1afcdbeg
123 else:
124 default_string = _split_opt(obj.secondary_opts[0])[1] 1afcdbeg
125 # Typer override end
126 elif ( 1ab
127 isinstance(obj, TyperOption)
128 and obj.is_bool_flag
129 and not obj.secondary_opts
130 and not default_value
131 ):
132 default_string = "" 1afcdbeg
133 elif default_value is None: 1afcdbeg
134 default_string = "" 1afcdbeg
135 else:
136 default_string = str(default_value) 1afcdbeg
137 return default_string 1afcdbeg
140def _extract_default_help_str( 1afcdbeg
141 obj: Union["TyperArgument", "TyperOption"], *, ctx: _click.Context
142) -> Any | Callable[[], Any] | None:
143 # Extracted from _click.core.Option.get_help_record() to be reused by
144 # rich_utils avoiding RegEx hacks
145 # Temporarily enable resilient parsing to avoid type casting
146 # failing for the default. Might be possible to extend this to
147 # help formatting in general.
148 resilient = ctx.resilient_parsing 1afcdbeg
149 ctx.resilient_parsing = True 1afcdbeg
151 try: 1afcdbeg
152 default_value = obj.get_default(ctx, call=False) 1afcdbeg
153 finally:
154 ctx.resilient_parsing = resilient 1afcdbeg
155 return default_value 1afcdbeg
158def _main( 1afcdbeg
159 self: _click.Command,
160 *,
161 args: Sequence[str] | None = None,
162 prog_name: str | None = None,
163 complete_var: str | None = None,
164 standalone_mode: bool = True,
165 windows_expand_args: bool = True,
166 rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE,
167 **extra: Any,
168) -> Any:
169 # Originally copied from _click.main(), adopted to handle custom rich exceptions etc
170 if args is None: 1afcdbeg
171 args = sys.argv[1:] 1afcdbeg
173 if os.name == "nt" and windows_expand_args: # pragma: no cover 1afcdbeg
174 args = _click.utils._expand_args(args) 1cd
175 else:
176 args = list(args) 1afcdbeg
178 if prog_name is None: 1afcdbeg
179 prog_name = _click.utils._detect_program_name() 1afcdbeg
181 # Process shell completion requests and exit early.
182 self._main_shell_completion(extra, prog_name, complete_var) 1afcdbeg
184 try: 1afcdbeg
185 try: 1afcdbeg
186 with self.make_context(prog_name, args, **extra) as ctx: 1afcdbeg
187 rv = self.invoke(ctx) 1afcdbeg
188 if not standalone_mode: 1afcdbeg
189 return rv 1afcdbeg
190 # it's not safe to `ctx.exit(rv)` here!
191 # note that `rv` may actually contain data like "1" which
192 # has obvious effects
193 # more subtle case: `rv=[None, None]` can come out of
194 # chained commands which all returned `None` -- so it's not
195 # even always obvious that `rv` indicates success/failure
196 # by its truthiness/falsiness
197 ctx.exit() 1afcdbeg
198 except EOFError as e: 1afcdbeg
199 _click.echo(file=sys.stderr) 1afcdbeg
200 raise Abort() from e 1afcdbeg
201 except KeyboardInterrupt as e: 1afcdbeg
202 raise Exit(130) from e 1afcdbeg
203 except _click.exceptions.ClickException as e: 1afcdbeg
204 if not standalone_mode: 1afcdbeg
205 raise 1afcdbeg
206 if HAS_RICH and rich_markup_mode is not None: 1afcdbeg
207 from . import rich_utils 1afcdbeg
209 rich_utils.rich_format_error(e) 1afcdbeg
210 else:
211 e.show() 1afcdbeg
212 sys.exit(e.exit_code) 1afcdbeg
213 except OSError as e: 1afcdbeg
214 if e.errno == errno.EPIPE: 1afcdbeg
215 sys.stdout = cast(TextIO, _click.utils.PacifyFlushWrapper(sys.stdout)) 1afcdbeg
216 sys.stderr = cast(TextIO, _click.utils.PacifyFlushWrapper(sys.stderr)) 1afcdbeg
217 sys.exit(1) 1afcdbeg
218 else:
219 raise 1afcdbeg
220 except Exit as e: 1afcdbeg
221 if standalone_mode: 1afcdbeg
222 sys.exit(e.exit_code) 1afcdbeg
223 else:
224 # in non-standalone mode, return the exit code
225 # note that this is only reached if `self.invoke` above raises
226 # an Exit explicitly -- thus bypassing the check there which
227 # would return its result
228 # the results of non-standalone execution may therefore be
229 # somewhat ambiguous: if there are codepaths which lead to
230 # `ctx.exit(1)` and to `return 1`, the caller won't be able to
231 # tell the difference between the two
232 return e.exit_code 1afcdbeg
233 except Abort: 1afcdbeg
234 if not standalone_mode: 1afcdbeg
235 raise 1afcdbeg
236 if HAS_RICH and rich_markup_mode is not None: 1afcdbeg
237 from . import rich_utils 1afcdbeg
239 rich_utils.rich_abort_error() 1afcdbeg
240 else:
241 _click.echo(_("Aborted!"), file=sys.stderr) 1afcdbeg
242 sys.exit(1) 1afcdbeg
245class TyperArgument(_click.core.Parameter): 1afcdbeg
246 param_type_name = "argument" 1afcdbeg
248 def __init__( 1afcdbeg
249 self,
250 *,
251 # Parameter
252 param_decls: list[str],
253 type: Any | None = None,
254 required: bool = False,
255 default: Any | None = None,
256 callback: Callable[..., Any] | None = None,
257 nargs: int | None = None,
258 metavar: str | None = None,
259 expose_value: bool = True,
260 is_eager: bool = False,
261 envvar: str | list[str] | None = None,
262 # Note that shell_complete is not fully supported and will be removed in future versions
263 # TODO: Remove shell_complete in a future version (after 0.16.0)
264 shell_complete: Callable[
265 [_click.Context, _click.Parameter, str],
266 list[CompletionItem] | list[str],
267 ]
268 | None = None,
269 autocompletion: Callable[..., Any] | None = None,
270 # TyperArgument
271 show_default: bool | str = True,
272 show_choices: bool = True,
273 show_envvar: bool = True,
274 help: str | None = None,
275 hidden: bool = False,
276 # Rich settings
277 rich_help_panel: str | None = None,
278 ):
279 self.help = help 1afcdbeg
280 self.show_default = show_default 1afcdbeg
281 self.show_choices = show_choices 1afcdbeg
282 self.show_envvar = show_envvar 1afcdbeg
283 self.hidden = hidden 1afcdbeg
284 self.rich_help_panel = rich_help_panel 1afcdbeg
286 super().__init__( 1afcdbeg
287 param_decls=param_decls,
288 type=type,
289 required=required,
290 default=default,
291 callback=callback,
292 nargs=nargs,
293 metavar=metavar,
294 expose_value=expose_value,
295 is_eager=is_eager,
296 envvar=envvar,
297 shell_complete=shell_complete,
298 )
299 _typer_param_setup_autocompletion_compat(self, autocompletion=autocompletion) 1afcdbeg
301 @property 1afcdbeg
302 def human_readable_name(self) -> str: 1afcdbeg
303 if self.metavar is not None: 1afcdbeg
304 return self.metavar 1afcdbeg
305 assert self.name is not None, "self.name or self.metavar should be set" 1afcdbeg
306 return self.name 1afcdbeg
308 def _get_default_string( 1afcdbeg
309 self,
310 *,
311 ctx: _click.Context,
312 show_default_is_str: bool,
313 default_value: list[Any] | tuple[Any, ...] | str | Callable[..., Any] | Any,
314 ) -> str:
315 return _get_default_string( 1afcdbeg
316 self,
317 ctx=ctx,
318 show_default_is_str=show_default_is_str,
319 default_value=default_value,
320 )
322 def _extract_default_help_str( 1afcdbeg
323 self, *, ctx: _click.Context
324 ) -> Any | Callable[[], Any] | None:
325 return _extract_default_help_str(self, ctx=ctx) 1afcdbeg
327 def get_help_record(self, ctx: _click.Context) -> tuple[str, str] | None: 1afcdbeg
328 # Modified version of _click.core.Option.get_help_record()
329 # to support Arguments
330 if self.hidden: 1afcdbeg
331 return None 1afcdbeg
332 name = self.make_metavar(ctx=ctx) 1afcdbeg
333 help = self.help or "" 1afcdbeg
334 extra = [] 1afcdbeg
335 if self.show_envvar: 1afcdbeg
336 envvar = self.envvar 1afcdbeg
337 # allow_from_autoenv is currently not supported in Typer for CLI Arguments
338 if envvar is not None: 1afcdbeg
339 var_str = ( 1afcdbeg
340 ", ".join(str(d) for d in envvar)
341 if isinstance(envvar, (list, tuple))
342 else envvar
343 )
344 extra.append(f"env var: {var_str}") 1afcdbeg
346 # Typer override:
347 # Extracted to _extract_default_help_str() to allow re-using it in rich_utils
348 default_value = self._extract_default_help_str(ctx=ctx) 1afcdbeg
349 # Typer override end
351 show_default_is_str = isinstance(self.show_default, str) 1afcdbeg
353 if show_default_is_str or ( 1afcdbeg
354 default_value is not None and (self.show_default or ctx.show_default)
355 ):
356 # Typer override:
357 # Extracted to _get_default_string() to allow re-using it in rich_utils
358 default_string = self._get_default_string( 1afcdbeg
359 ctx=ctx,
360 show_default_is_str=show_default_is_str,
361 default_value=default_value,
362 )
363 # Typer override end
364 if default_string: 1afcdbeg
365 extra.append(_("default: {default}").format(default=default_string)) 1afcdbeg
366 if self.required: 1afcdbeg
367 extra.append(_("required")) 1afcdbeg
368 if extra: 1afcdbeg
369 extra_str = "; ".join(extra) 1afcdbeg
370 extra_str = f"[{extra_str}]" 1afcdbeg
371 rich_markup_mode = None 1afcdbeg
372 if hasattr(ctx, "obj") and isinstance(ctx.obj, dict): 1afcdbeg
373 rich_markup_mode = ctx.obj.get(MARKUP_MODE_KEY, None) 1afcdbeg
374 if HAS_RICH and rich_markup_mode == "rich": 1afcdbeg
375 # This is needed for when we want to export to HTML
376 from . import rich_utils 1afcdbeg
378 extra_str = rich_utils.escape_before_html_export(extra_str) 1afcdbeg
380 help = f"{help} {extra_str}" if help else f"{extra_str}" 1afcdbeg
381 return name, help 1afcdbeg
383 def make_metavar(self, ctx: _click.Context, *, usage: bool = False) -> str: 1afcdbeg
384 # Modified version of _click.core.Argument.make_metavar()
385 # to include Argument name
386 if self.metavar is not None: 1afcdbeg
387 var = self.metavar 1afcdbeg
388 if var.startswith("[") or not usage: 1afcdbeg
389 return var 1afcdbeg
390 if not self.required: 1afcdbeg
391 return f"[{var}]" 1afcdbeg
392 return f"{{{var}}}" 1afcdbeg
393 var = self.name or "" 1afcdbeg
394 if usage and not self.required: 1afcdbeg
395 var = f"[{var}]" 1afcdbeg
396 elif usage and self.required: 1afcdbeg
397 var = f"{{{var}}}" 1afcdbeg
398 type_var = self.type.get_metavar(self, ctx=ctx) 1afcdbeg
399 if type_var: 1afcdbeg
400 var += f":{type_var}" 1afcdbeg
401 if self.nargs != 1: 1afcdbeg
402 var += "..." 1afcdbeg
403 return var 1afcdbeg
405 def value_is_missing(self, value: Any) -> bool: 1afcdbeg
406 return _value_is_missing(self, value) 1afcdbeg
408 def _parse_decls( 1afcdbeg
409 self, decls: Sequence[str], expose_value: bool
410 ) -> tuple[str | None, list[str], list[str]]:
411 if not decls: 1afcdbeg
412 if not expose_value: 1afcdbeg
413 return None, [], [] 1afcdbeg
414 raise TypeError("Argument is marked as exposed, but does not have a name.") 1afcdbeg
415 if len(decls) == 1: 1afcdbeg
416 name = arg = decls[0] 1afcdbeg
417 name = name.replace("-", "_") 1afcdbeg
418 else:
419 raise TypeError( 1afcdbeg
420 "Arguments take exactly one parameter declaration, got"
421 f" {len(decls)}: {decls}."
422 )
423 return name, [arg], [] 1afcdbeg
425 def get_usage_pieces(self, ctx: _click.Context) -> list[str]: 1afcdbeg
426 return [self.make_metavar(ctx, usage=True)] 1afcdbeg
428 def get_error_hint(self, ctx: _click.Context) -> str: 1afcdbeg
429 return f"'{self.human_readable_name}'" 1afcdbeg
431 def add_to_parser(self, parser: _OptionParser, ctx: _click.Context) -> None: 1afcdbeg
432 parser.add_argument(dest=self.name, nargs=self.nargs, obj=self) 1afcdbeg
435class TyperOption(_click.Parameter): 1afcdbeg
436 param_type_name = "option" 1afcdbeg
438 _depr_flag_value: bool | None 1acbe
440 def __init__( 1afcdbeg
441 self,
442 *,
443 # Parameter
444 param_decls: list[str],
445 type: types.ParamType | Any | None = None,
446 required: bool = False,
447 default: Any | None = None,
448 callback: Callable[..., Any] | None = None,
449 nargs: int | None = None,
450 metavar: str | None = None,
451 expose_value: bool = True,
452 is_eager: bool = False,
453 envvar: str | list[str] | None = None,
454 # Note that shell_complete is not fully supported and will be removed in future versions
455 # TODO: Remove shell_complete in a future version (after 0.16.0)
456 shell_complete: Callable[
457 [_click.Context, _click.Parameter, str],
458 list[CompletionItem] | list[str],
459 ]
460 | None = None,
461 autocompletion: Callable[..., Any] | None = None,
462 # Option
463 show_default: bool | str = False,
464 prompt: bool | str = False,
465 confirmation_prompt: bool | str = False,
466 prompt_required: bool = True,
467 hide_input: bool = False,
468 is_flag: bool | None = None,
469 multiple: bool = False,
470 count: bool = False,
471 allow_from_autoenv: bool = True,
472 help: str | None = None,
473 hidden: bool = False,
474 show_choices: bool = True,
475 show_envvar: bool = False,
476 # Rich settings
477 rich_help_panel: str | None = None,
478 ):
479 if help: 1afcdbeg
480 help = inspect.cleandoc(help) 1afcdbeg
482 super().__init__( 1afcdbeg
483 param_decls,
484 type=type,
485 multiple=multiple,
486 required=required,
487 default=default,
488 callback=callback,
489 nargs=nargs,
490 metavar=metavar,
491 expose_value=expose_value,
492 is_eager=is_eager,
493 envvar=envvar,
494 shell_complete=shell_complete,
495 )
497 if prompt is True: 1afcdbeg
498 if self.name is None: 1afcdbeg
499 raise TypeError("'name' is required with 'prompt=True'.") 1afcdbeg
501 prompt_text: str | None = self.name.replace("_", " ").capitalize() 1afcdbeg
502 elif prompt is False: 1afcdbeg
503 prompt_text = None 1afcdbeg
504 else:
505 prompt_text = prompt 1afcdbeg
507 self.prompt = prompt_text 1afcdbeg
508 self.confirmation_prompt = confirmation_prompt 1afcdbeg
509 self.prompt_required = prompt_required 1afcdbeg
510 self.hide_input = hide_input 1afcdbeg
511 self.hidden = hidden 1afcdbeg
513 # TODO: revisit all of this flag stuff
514 if is_flag and type is None: 1afcdbeg
515 self.type: types.ParamType = types.BoolParamType() 1afcdbeg
517 self.is_flag: bool = bool(is_flag) 1afcdbeg
518 self.is_bool_flag: bool = bool( 1afcdbeg
519 is_flag and isinstance(self.type, types.BoolParamType)
520 )
522 if self.is_flag: 1afcdbeg
523 self._depr_flag_value = True 1afcdbeg
524 else:
525 self._depr_flag_value = None 1afcdbeg
527 # Counting. TODO: test or remove? Not currently in coverage.
528 self.count = count 1afcdbeg
529 if count and type is None: 1afcdbeg
530 self.type = types.IntRange(min=0) 1afcdbeg
532 self.allow_from_autoenv = allow_from_autoenv 1afcdbeg
533 self.help = help 1afcdbeg
534 self.show_default = show_default 1afcdbeg
535 self.show_choices = show_choices 1afcdbeg
536 self.show_envvar = show_envvar 1afcdbeg
538 _typer_param_setup_autocompletion_compat(self, autocompletion=autocompletion) 1afcdbeg
539 self.rich_help_panel = rich_help_panel 1afcdbeg
541 def get_error_hint(self, ctx: _click.Context) -> str: 1afcdbeg
542 result = super().get_error_hint(ctx) 1afcdbeg
543 if self.show_envvar and self.envvar is not None: 1afcdbeg
544 result += f" (env var: '{self.envvar}')" 1afcdbeg
545 return result 1afcdbeg
547 def _parse_decls( 1afcdbeg
548 self, decls: Sequence[str], expose_value: bool
549 ) -> tuple[str | None, list[str], list[str]]:
550 opts = [] 1afcdbeg
551 secondary_opts = [] 1afcdbeg
552 name = None 1afcdbeg
553 possible_names = [] 1afcdbeg
555 for decl in decls: 1afcdbeg
556 if decl.isidentifier(): 1afcdbeg
557 if name is not None: 1afcdbeg
558 raise TypeError(f"Name '{name}' defined twice") 1afcdbeg
559 name = decl 1afcdbeg
560 else:
561 split_char = ";" if decl[:1] == "/" else "/" 1afcdbeg
562 if split_char in decl: 1afcdbeg
563 first, second = decl.split(split_char, 1) 1afcdbeg
564 first = first.rstrip() 1afcdbeg
565 if first: 1afcdbeg
566 possible_names.append(_split_opt(first)) 1afcdbeg
567 opts.append(first) 1afcdbeg
568 second = second.lstrip() 1afcdbeg
569 if second: 1afcdbeg
570 secondary_opts.append(second.lstrip()) 1afcdbeg
571 if first == second: 1afcdbeg
572 raise ValueError( 1afcdbeg
573 f"Boolean option {decl!r} cannot use the"
574 " same flag for true/false."
575 )
576 else:
577 possible_names.append(_split_opt(decl)) 1afcdbeg
578 opts.append(decl) 1afcdbeg
580 if name is None and possible_names: 1afcdbeg
581 possible_names.sort(key=lambda x: -len(x[0])) # group long options first 1afcdbeg
582 name = possible_names[0][1].replace("-", "_") 1afcdbeg
583 if not name.isidentifier(): 1afcdbeg
584 name = None 1afcdbeg
586 return name, opts, secondary_opts 1afcdbeg
588 def add_to_parser(self, parser: _OptionParser, ctx: _click.Context) -> None: 1afcdbeg
589 if self.multiple: 1afcdbeg
590 action = "append" 1afcdbeg
591 elif self.count: 1afcdbeg
592 action = "count" 1afcdbeg
593 else:
594 action = "store" 1afcdbeg
596 if self.is_flag: 1afcdbeg
597 action = f"{action}_const" 1afcdbeg
599 if self.is_bool_flag and self.secondary_opts: 1afcdbeg
600 parser.add_option( 1afcdbeg
601 obj=self, opts=self.opts, dest=self.name, action=action, const=True
602 )
603 parser.add_option( 1afcdbeg
604 obj=self,
605 opts=self.secondary_opts,
606 dest=self.name,
607 action=action,
608 const=False,
609 )
610 else:
611 parser.add_option( 1afcdbeg
612 obj=self,
613 opts=self.opts,
614 dest=self.name,
615 action=action,
616 const=self._depr_flag_value,
617 )
618 else:
619 parser.add_option( 1afcdbeg
620 obj=self,
621 opts=self.opts,
622 dest=self.name,
623 action=action,
624 nargs=self.nargs,
625 )
627 def prompt_for_value(self, ctx: _click.Context) -> Any: 1afcdbeg
628 """This is an alternative flow that can be activated in the full
629 value processing if a value does not exist. It will prompt the
630 user until a valid value exists and then returns the processed
631 value as result.
632 """
633 assert self.prompt is not None 1afcdbeg
635 # Calculate the default before prompting anything to lock in the value before
636 # attempting any user interaction.
637 default = self.get_default(ctx) 1afcdbeg
639 # A boolean flag can use a simplified [y/n] confirmation prompt.
640 if self.is_bool_flag: 1afcdbeg
641 # Nothing prevent you to declare an option that is simultaneously:
642 # 1) auto-detected as a boolean flag,
643 # 2) allowed to prompt, and
644 # 3) still declare a non-boolean default.
645 # This forced casting into a boolean is necessary to align any non-boolean
646 # default to the prompt, which is going to be a [y/n]-style confirmation
647 # because the option is still a boolean flag. That way, instead of [y/n],
648 # we get [Y/n] or [y/N] depending on the truthy value of the default.
649 # Refs: https://github.com/pallets/click/pull/3030#discussion_r2289180249
650 if default is not None: 1afcdbeg
651 default = bool(default) 1afcdbeg
652 return _click.termui.confirm(self.prompt, default) 1afcdbeg
654 # If show_default is set to True/False, provide this to `prompt` as well. For
655 # non-bool values of `show_default`, we use `prompt`'s default behavior
656 prompt_kwargs: Any = {} 1afcdbeg
657 if isinstance(self.show_default, bool): 1afcdbeg
658 prompt_kwargs["show_default"] = self.show_default 1afcdbeg
660 return _click.termui.prompt( 1afcdbeg
661 self.prompt,
662 # Use ``None`` to inform the prompt() function to reiterate until a valid
663 # value is provided by the user if we have no default.
664 default=default,
665 type=self.type,
666 hide_input=self.hide_input,
667 show_choices=self.show_choices,
668 confirmation_prompt=self.confirmation_prompt,
669 value_proc=lambda x: self.process_value(ctx, x),
670 **prompt_kwargs,
671 )
673 def value_from_envvar(self, ctx: _click.Context) -> Any: 1afcdbeg
674 # TODO: clean up
675 rv = self.resolve_envvar_value(ctx) 1afcdbeg
677 # Absent environment variable or an empty string is interpreted as unset.
678 if rv is None: 1afcdbeg
679 return None 1afcdbeg
681 if self.nargs != 1 or self.multiple: 1afcdbeg
682 return self.type.split_envvar_value(rv) 1afcdbeg
684 return rv 1afcdbeg
686 def resolve_envvar_value(self, ctx: _click.Context) -> str | None: 1afcdbeg
687 rv = super().resolve_envvar_value(ctx) 1afcdbeg
689 if rv is not None: 1afcdbeg
690 return rv 1afcdbeg
692 if ( 1ab
693 self.allow_from_autoenv
694 and ctx.auto_envvar_prefix is not None
695 and self.name is not None
696 ):
697 envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}" 1afcdbeg
698 rv = os.environ.get(envvar) 1afcdbeg
700 if rv: 1afcdbeg
701 return rv 1afcdbeg
703 return None 1afcdbeg
705 def consume_value( 1afcdbeg
706 self, ctx: _click.Context, opts: Mapping[str, _click.Parameter]
707 ) -> tuple[Any, _click.core.ParameterSource]:
708 """For `Option`, the value can be collected from an interactive prompt
709 if the option is a flag that needs a value (and the `prompt` property is
710 set).
712 Additionally, this method handles flag option that are activated without a
713 value, in which case the `flag_value` is returned.
714 """
715 value, source = super().consume_value(ctx, opts) 1afcdbeg
717 # The value wasn't set, or used the param's default, prompt for one to the user
718 # if prompting is enabled.
719 if (
720 source in {None, _click.core.ParameterSource.DEFAULT}
721 and self.prompt is not None
722 and (self.required or self.prompt_required)
723 and not ctx.resilient_parsing
724 ):
725 value = self.prompt_for_value(ctx) 1afcdbeg
726 source = _click.core.ParameterSource.PROMPT 1afcdbeg
728 return value, source 1afcdbeg
730 def _get_default_string( 1afcdbeg
731 self,
732 *,
733 ctx: _click.Context,
734 show_default_is_str: bool,
735 default_value: list[Any] | tuple[Any, ...] | str | Callable[..., Any] | Any,
736 ) -> str:
737 return _get_default_string( 1afcdbeg
738 self,
739 ctx=ctx,
740 show_default_is_str=show_default_is_str,
741 default_value=default_value,
742 )
744 def _extract_default_help_str( 1afcdbeg
745 self, *, ctx: _click.Context
746 ) -> Any | Callable[[], Any] | None:
747 return _extract_default_help_str(self, ctx=ctx) 1afcdbeg
749 def make_metavar(self, ctx: _click.Context) -> str: 1afcdbeg
750 return super().make_metavar(ctx=ctx) 1afcdbeg
752 def get_help_record(self, ctx: _click.Context) -> tuple[str, str] | None: 1afcdbeg
753 # Duplicate all of Click's logic only to modify a single line, to allow boolean
754 # flags with only names for False values as it's currently supported by Typer
755 # Ref: https://typer.tiangolo.com/tutorial/parameter-types/bool/#only-names-for-false
756 if self.hidden: 1afcdbeg
757 return None 1afcdbeg
759 any_prefix_is_slash = False 1afcdbeg
761 def _write_opts(opts: Sequence[str]) -> str: 1afcdbeg
762 nonlocal any_prefix_is_slash
764 rv, any_slashes = _click.formatting.join_options(opts) 1afcdbeg
766 if any_slashes: 1afcdbeg
767 any_prefix_is_slash = True 1afcdbeg
769 if not self.is_flag and not self.count: 1afcdbeg
770 rv += f" {self.make_metavar(ctx=ctx)}" 1afcdbeg
772 return rv 1afcdbeg
774 rv = [_write_opts(self.opts)] 1afcdbeg
776 if self.secondary_opts: 1afcdbeg
777 rv.append(_write_opts(self.secondary_opts)) 1afcdbeg
779 help = self.help or "" 1afcdbeg
780 extra = [] 1afcdbeg
782 if self.show_envvar: 1afcdbeg
783 envvar = self.envvar 1afcdbeg
785 if envvar is None: 1afcdbeg
786 if ( 1ab
787 self.allow_from_autoenv
788 and ctx.auto_envvar_prefix is not None
789 and self.name is not None
790 ):
791 envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}" 1afcdbeg
793 if envvar is not None: 1afcdbeg
794 var_str = ( 1afcdbeg
795 envvar
796 if isinstance(envvar, str)
797 else ", ".join(str(d) for d in envvar)
798 )
799 extra.append(_("env var: {var}").format(var=var_str)) 1afcdbeg
801 # Typer override:
802 # Extracted to _extract_default() to allow re-using it in rich_utils
803 default_value = self._extract_default_help_str(ctx=ctx) 1afcdbeg
804 # Typer override end
806 show_default_is_str = isinstance(self.show_default, str) 1afcdbeg
808 if show_default_is_str or ( 1afcdbeg
809 default_value is not None and (self.show_default or ctx.show_default)
810 ):
811 # Typer override:
812 # Extracted to _get_default_string() to allow re-using it in rich_utils
813 default_string = self._get_default_string( 1afcdbeg
814 ctx=ctx,
815 show_default_is_str=show_default_is_str,
816 default_value=default_value,
817 )
818 # Typer override end
819 if default_string: 1afcdbeg
820 extra.append(_("default: {default}").format(default=default_string)) 1afcdbeg
822 if isinstance(self.type, types._NumberRangeBase): 1afcdbeg
823 range_str = self.type._describe_range() 1afcdbeg
825 if range_str: 1afcdbeg
826 extra.append(range_str) 1afcdbeg
828 if self.required: 1afcdbeg
829 extra.append(_("required")) 1afcdbeg
831 if extra: 1afcdbeg
832 extra_str = "; ".join(extra) 1afcdbeg
833 extra_str = f"[{extra_str}]" 1afcdbeg
834 rich_markup_mode = None 1afcdbeg
835 if hasattr(ctx, "obj") and isinstance(ctx.obj, dict): 1afcdbeg
836 rich_markup_mode = ctx.obj.get(MARKUP_MODE_KEY, None) 1afcdbeg
837 if HAS_RICH and rich_markup_mode == "rich": 1afcdbeg
838 # This is needed for when we want to export to HTML
839 from . import rich_utils 1afcdbeg
841 extra_str = rich_utils.escape_before_html_export(extra_str) 1afcdbeg
843 help = f"{help} {extra_str}" if help else f"{extra_str}" 1afcdbeg
845 return ("; " if any_prefix_is_slash else " / ").join(rv), help 1afcdbeg
847 def value_is_missing(self, value: Any) -> bool: 1afcdbeg
848 return _value_is_missing(self, value) 1afcdbeg
851def _value_is_missing(param: _click.Parameter, value: Any) -> bool: 1afcdbeg
852 if value is None: 1afcdbeg
853 return True 1afcdbeg
855 if (param.nargs != 1 or param.multiple) and value == (): 1afcdbeg
856 return True # pragma: no cover
858 return False 1afcdbeg
861def _typer_format_options( 1afcdbeg
862 self: _click.core.Command, *, ctx: _click.Context, formatter: _click.HelpFormatter
863) -> None:
864 args = [] 1afcdbeg
865 opts = [] 1afcdbeg
866 for param in self.get_params(ctx): 1afcdbeg
867 rv = param.get_help_record(ctx) 1afcdbeg
868 if rv is not None: 1afcdbeg
869 if param.param_type_name == "argument": 1afcdbeg
870 args.append(rv) 1afcdbeg
871 elif param.param_type_name == "option": 1afcdbeg
872 opts.append(rv) 1afcdbeg
874 if args: 1afcdbeg
875 with formatter.section(_("Arguments")): 1afcdbeg
876 formatter.write_dl(args) 1afcdbeg
877 if opts: 1afcdbeg
878 with formatter.section(_("Options")): 1afcdbeg
879 formatter.write_dl(opts) 1afcdbeg
882def _typer_main_shell_completion( 1afcdbeg
883 self: _click.core.Command,
884 *,
885 ctx_args: MutableMapping[str, Any],
886 prog_name: str,
887 complete_var: str | None = None,
888) -> None:
889 if complete_var is None: 1afcdbeg
890 complete_var = f"_{prog_name}_COMPLETE".replace("-", "_").upper() 1afcdbeg
892 instruction = os.environ.get(complete_var) 1afcdbeg
894 if not instruction: 1afcdbeg
895 return 1afcdbeg
897 from .completion import shell_complete 1afcdbeg
899 rv = shell_complete(self, ctx_args, prog_name, complete_var, instruction) 1afcdbeg
900 sys.exit(rv) 1afcdbeg
903class TyperCommand(_click.core.Command): 1afcdbeg
904 def __init__( 1afcdbeg
905 self,
906 name: str | None,
907 *,
908 context_settings: dict[str, Any] | None = None,
909 callback: Callable[..., Any] | None = None,
910 params: list[_click.Parameter] | None = None,
911 help: str | None = None,
912 epilog: str | None = None,
913 short_help: str | None = None,
914 options_metavar: str | None = "[OPTIONS]",
915 add_help_option: bool = True,
916 no_args_is_help: bool = False,
917 hidden: bool = False,
918 deprecated: bool = False,
919 # Rich settings
920 rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE,
921 rich_help_panel: str | None = None,
922 ) -> None:
923 super().__init__( 1afcdbeg
924 name=name,
925 context_settings=context_settings,
926 callback=callback,
927 params=params,
928 help=help,
929 epilog=epilog,
930 short_help=short_help,
931 options_metavar=options_metavar,
932 add_help_option=add_help_option,
933 no_args_is_help=no_args_is_help,
934 hidden=hidden,
935 deprecated=deprecated,
936 )
937 self.rich_markup_mode: MarkupMode = rich_markup_mode 1afcdbeg
938 self.rich_help_panel = rich_help_panel 1afcdbeg
940 def format_options( 1afcdbeg
941 self, ctx: _click.Context, formatter: _click.HelpFormatter
942 ) -> None:
943 _typer_format_options(self, ctx=ctx, formatter=formatter) 1afcdbeg
945 def _main_shell_completion( 1afcdbeg
946 self,
947 ctx_args: MutableMapping[str, Any],
948 prog_name: str,
949 complete_var: str | None = None,
950 ) -> None:
951 _typer_main_shell_completion( 1afcdbeg
952 self, ctx_args=ctx_args, prog_name=prog_name, complete_var=complete_var
953 )
955 def main( 1afcdbeg
956 self,
957 args: Sequence[str] | None = None,
958 prog_name: str | None = None,
959 complete_var: str | None = None,
960 standalone_mode: bool = True,
961 windows_expand_args: bool = True,
962 **extra: Any,
963 ) -> Any:
964 return _main( 1afcdbeg
965 self,
966 args=args,
967 prog_name=prog_name,
968 complete_var=complete_var,
969 standalone_mode=standalone_mode,
970 windows_expand_args=windows_expand_args,
971 rich_markup_mode=self.rich_markup_mode,
972 **extra,
973 )
975 def format_help(self, ctx: _click.Context, formatter: _click.HelpFormatter) -> None: 1afcdbeg
976 if not HAS_RICH or self.rich_markup_mode is None: 1afcdbeg
977 if not hasattr(ctx, "obj") or ctx.obj is None: 1afcdbeg
978 ctx.ensure_object(dict) 1afcdbeg
979 if isinstance(ctx.obj, dict): 1afcdbeg
980 ctx.obj[MARKUP_MODE_KEY] = self.rich_markup_mode 1afcdbeg
981 return super().format_help(ctx, formatter) 1afcdbeg
982 from . import rich_utils 1afcdbeg
984 return rich_utils.rich_format_help( 1afcdbeg
985 obj=self,
986 ctx=ctx,
987 markup_mode=self.rich_markup_mode,
988 )
991class TyperGroup(_click.Command): 1afcdbeg
992 allow_extra_args = True 1afcdbeg
993 allow_interspersed_args = False 1afcdbeg
994 command_class: type[_click.Command] | None = None 1afcdbeg
995 group_class: type["TyperGroup"] | type[type] | None = None 1afcdbeg
997 def __init__( 1afcdbeg
998 self,
999 *,
1000 name: str | None = None,
1001 commands: dict[str, _click.Command] | Sequence[_click.Command] | None = None,
1002 # Rich settings
1003 rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE,
1004 rich_help_panel: str | None = None,
1005 suggest_commands: bool = True,
1006 # Click settings
1007 invoke_without_command: bool = False,
1008 no_args_is_help: bool = False,
1009 subcommand_metavar: str | None = None,
1010 result_callback: Callable[..., Any] | None = None,
1011 **attrs: Any,
1012 ) -> None:
1013 super().__init__(name=name, **attrs) 1afcdbeg
1014 self.rich_markup_mode: MarkupMode = rich_markup_mode 1afcdbeg
1015 self.rich_help_panel = rich_help_panel 1afcdbeg
1016 self.suggest_commands = suggest_commands 1afcdbeg
1018 # copied from Click's init
1019 if commands is None: 1afcdbeg
1020 commands = {} 1afcdbeg
1021 elif isinstance(commands, Sequence): 1afcdbeg
1022 commands = { 1afcdbeg
1023 c.name: c
1024 for c in commands
1025 if isinstance(c, _click.Command) and c.name is not None
1026 }
1028 self.commands = cast(MutableMapping[str, _click.Command], commands) 1afcdbeg
1029 self.no_args_is_help = no_args_is_help 1afcdbeg
1030 self.invoke_without_command = invoke_without_command 1afcdbeg
1032 if subcommand_metavar is None: 1afcdbeg
1033 subcommand_metavar = "COMMAND [ARGS]..." 1afcdbeg
1035 self.subcommand_metavar = subcommand_metavar 1afcdbeg
1036 self._result_callback = result_callback 1afcdbeg
1038 def add_command(self, cmd: _click.Command, name: str | None = None) -> None: 1afcdbeg
1039 name = name or cmd.name 1afcdbeg
1040 if name is None: 1afcdbeg
1041 raise TypeError("Command has no name.") 1afcdbeg
1042 self.commands[name] = cmd 1afcdbeg
1044 def get_command(self, ctx: _click.Context, cmd_name: str) -> _click.Command | None: 1afcdbeg
1045 return self.commands.get(cmd_name) 1afcdbeg
1047 def collect_usage_pieces(self, ctx: _click.Context) -> list[str]: 1afcdbeg
1048 rv = super().collect_usage_pieces(ctx) 1afcdbeg
1049 rv.append(self.subcommand_metavar) 1afcdbeg
1050 return rv 1afcdbeg
1052 def format_commands( 1afcdbeg
1053 self, ctx: _click.Context, formatter: _click.HelpFormatter
1054 ) -> None:
1055 commands = [] 1afcdbeg
1056 for subcommand in self.list_commands(ctx): 1afcdbeg
1057 cmd = self.get_command(ctx, subcommand) 1afcdbeg
1058 if cmd is None or cmd.hidden: 1afcdbeg
1059 continue 1afcdbeg
1061 commands.append((subcommand, cmd)) 1afcdbeg
1063 # allow for 3 times the default spacing
1064 if len(commands): 1afcdbeg
1065 limit = formatter.width - 6 - max(len(cmd[0]) for cmd in commands) 1afcdbeg
1067 rows = [] 1afcdbeg
1068 for subcommand, cmd in commands: 1afcdbeg
1069 assert cmd is not None 1afcdbeg
1070 help = cmd.get_short_help_str(limit) 1afcdbeg
1071 rows.append((subcommand, help)) 1afcdbeg
1073 if rows: 1afcdbeg
1074 with formatter.section(_("Commands")): 1afcdbeg
1075 formatter.write_dl(rows) 1afcdbeg
1077 def parse_args(self, ctx: _click.Context, args: list[str]) -> list[str]: 1afcdbeg
1078 if not args and self.no_args_is_help and not ctx.resilient_parsing: 1afcdbeg
1079 raise _click.exceptions.NoArgsIsHelpError(ctx) 1afcdbeg
1081 rest = super().parse_args(ctx, args) 1afcdbeg
1083 if rest: 1afcdbeg
1084 ctx._protected_args, ctx.args = rest[:1], rest[1:] 1afcdbeg
1086 return ctx.args 1afcdbeg
1088 def invoke(self, ctx: _click.Context) -> Any: 1afcdbeg
1089 def _process_result(value: Any) -> Any: 1afcdbeg
1090 if self._result_callback is not None: 1afcdbeg
1091 value = ctx.invoke(self._result_callback, value, **ctx.params) 1afcdbeg
1092 return value 1afcdbeg
1094 if not ctx._protected_args: 1afcdbeg
1095 if self.invoke_without_command: 1afcdbeg
1096 # No subcommand was invoked, so the result callback is
1097 # invoked with the group return value for regular
1098 # groups, or an empty list for chained groups.
1099 with ctx: 1afcdbeg
1100 rv = super().invoke(ctx) 1afcdbeg
1101 # return _process_result([] if self.chain else rv)
1102 return _process_result(rv) 1afcdbeg
1103 ctx.fail(_("Missing command.")) 1afcdbeg
1105 # Fetch args back out
1106 args = [*ctx._protected_args, *ctx.args] 1afcdbeg
1107 ctx.args = [] 1afcdbeg
1108 ctx._protected_args = [] 1afcdbeg
1110 # Make sure the context is entered so we do not clean up
1111 # resources until the result processor has worked.
1112 with ctx: 1afcdbeg
1113 cmd_name, cmd, args = self.resolve_command(ctx, args) 1afcdbeg
1114 assert cmd is not None 1afcdbeg
1115 ctx.invoked_subcommand = cmd_name 1afcdbeg
1116 super().invoke(ctx) 1afcdbeg
1117 sub_ctx = cmd.make_context(cmd_name, args, parent=ctx) 1afcdbeg
1118 with sub_ctx: 1afcdbeg
1119 return _process_result(sub_ctx.command.invoke(sub_ctx)) 1afcdbeg
1121 def shell_complete( 1afcdbeg
1122 self, ctx: _click.Context, incomplete: str
1123 ) -> list[CompletionItem]:
1124 """Return a list of completions for the incomplete value. Looks
1125 at the names of options, subcommands, and chained
1126 multi-commands.
1127 """
1129 results = [ 1afcdbeg
1130 CompletionItem(name, help=command.get_short_help_str())
1131 for name, command in _click.core._complete_visible_commands(ctx, incomplete)
1132 ]
1133 results.extend(super().shell_complete(ctx, incomplete)) 1afcdbeg
1134 return results 1afcdbeg
1136 def format_options( 1afcdbeg
1137 self, ctx: _click.Context, formatter: _click.HelpFormatter
1138 ) -> None:
1139 _typer_format_options(self, ctx=ctx, formatter=formatter) 1afcdbeg
1140 self.format_commands(ctx, formatter) 1afcdbeg
1142 def _main_shell_completion( 1afcdbeg
1143 self,
1144 ctx_args: MutableMapping[str, Any],
1145 prog_name: str,
1146 complete_var: str | None = None,
1147 ) -> None:
1148 _typer_main_shell_completion( 1afcdbeg
1149 self, ctx_args=ctx_args, prog_name=prog_name, complete_var=complete_var
1150 )
1152 def _click_resolve_command( 1afcdbeg
1153 self, ctx: _click.Context, args: list[str]
1154 ) -> tuple[str | None, _click.Command | None, list[str]]:
1155 cmd_name = args[0] 1afcdbeg
1156 original_cmd_name = cmd_name 1afcdbeg
1158 # Get the command
1159 cmd = self.get_command(ctx, cmd_name) 1afcdbeg
1161 if cmd is None and ctx.token_normalize_func is not None: 1afcdbeg
1162 cmd_name = ctx.token_normalize_func(cmd_name) 1afcdbeg
1163 cmd = self.get_command(ctx, cmd_name) 1afcdbeg
1165 if cmd is None and not ctx.resilient_parsing: 1afcdbeg
1166 if _split_opt(cmd_name)[0]: 1afcdbeg
1167 self.parse_args(ctx, args) 1afcdbeg
1168 ctx.fail(_("No such command {name!r}.").format(name=original_cmd_name)) 1afcdbeg
1169 return cmd_name if cmd else None, cmd, args[1:] 1afcdbeg
1171 def resolve_command( 1afcdbeg
1172 self, ctx: _click.Context, args: list[str]
1173 ) -> tuple[str | None, _click.Command | None, list[str]]:
1174 try: 1afcdbeg
1175 return self._click_resolve_command(ctx, args) 1afcdbeg
1176 except _click.exceptions.UsageError as e: 1afcdbeg
1177 if self.suggest_commands: 1afcdbeg
1178 available_commands = list(self.commands.keys()) 1afcdbeg
1179 if available_commands and args: 1afcdbeg
1180 typo = args[0] 1afcdbeg
1181 matches = get_close_matches(typo, available_commands) 1afcdbeg
1182 if matches: 1afcdbeg
1183 suggestions = ", ".join(f"{m!r}" for m in matches) 1afcdbeg
1184 message = e.message.rstrip(".") 1afcdbeg
1185 e.message = f"{message}. Did you mean {suggestions}?" 1afcdbeg
1186 raise 1afcdbeg
1188 def main( 1afcdbeg
1189 self,
1190 args: Sequence[str] | None = None,
1191 prog_name: str | None = None,
1192 complete_var: str | None = None,
1193 standalone_mode: bool = True,
1194 windows_expand_args: bool = True,
1195 **extra: Any,
1196 ) -> Any:
1197 return _main( 1afcdbeg
1198 self,
1199 args=args,
1200 prog_name=prog_name,
1201 complete_var=complete_var,
1202 standalone_mode=standalone_mode,
1203 windows_expand_args=windows_expand_args,
1204 rich_markup_mode=self.rich_markup_mode,
1205 **extra,
1206 )
1208 def format_help(self, ctx: _click.Context, formatter: _click.HelpFormatter) -> None: 1afcdbeg
1209 if not HAS_RICH or self.rich_markup_mode is None: 1afcdbeg
1210 return super().format_help(ctx, formatter) 1afcdbeg
1211 from . import rich_utils 1afcdbeg
1213 return rich_utils.rich_format_help( 1afcdbeg
1214 obj=self,
1215 ctx=ctx,
1216 markup_mode=self.rich_markup_mode,
1217 )
1219 def list_commands(self, ctx: _click.Context) -> list[str]: 1afcdbeg
1220 """Returns a list of subcommand names, maintaining the original order of creation (cf Issue #933)"""
1221 return [n for n, c in self.commands.items()] 1afcdbeg