File: //opt/saltstack/salt/lib/python3.10/site-packages/typer/_types.py
from collections.abc import Iterable, Mapping, Sequence
from enum import Enum
from typing import Any, Generic, TypeVar
from . import _click
from ._click import types
from ._click.shell_completion import CompletionItem
ParamTypeValue = TypeVar("ParamTypeValue")
class TyperChoice(types.ParamType, Generic[ParamTypeValue]):
# Code adapted from Click 8.3.1, with Typer using enum values in normalize_choice
name = "choice"
def __init__(
self, choices: Iterable[ParamTypeValue], case_sensitive: bool = True
) -> None:
self.choices: Sequence[ParamTypeValue] = tuple(choices)
self.case_sensitive = case_sensitive
def _normalized_mapping(
self, ctx: _click.Context | None = None
) -> Mapping[ParamTypeValue, str]:
"""
Returns mapping where keys are the original choices and the values are
the normalized values that are accepted via the command line.
"""
return {
choice: self.normalize_choice(
choice=choice,
ctx=ctx,
)
for choice in self.choices
}
def normalize_choice(
self, choice: ParamTypeValue, ctx: _click.Context | None
) -> str:
normed_value = str(choice.value) if isinstance(choice, Enum) else str(choice)
if ctx is not None and ctx.token_normalize_func is not None:
normed_value = ctx.token_normalize_func(normed_value)
if not self.case_sensitive:
normed_value = normed_value.casefold()
return normed_value
def get_metavar(self, param: _click.Parameter, ctx: _click.Context) -> str | None:
if param.param_type_name == "option" and not param.show_choices: # type: ignore
choice_metavars = [
types.convert_type(type(choice)).name.upper() for choice in self.choices
]
choices_str = "|".join([*dict.fromkeys(choice_metavars)])
else:
choices_str = "|".join(
[str(i) for i in self._normalized_mapping(ctx=ctx).values()]
)
# Use curly braces to indicate a required argument.
if param.required and param.param_type_name == "argument":
return f"{{{choices_str}}}"
# Use square braces to indicate an option or optional argument.
return f"[{choices_str}]"
def get_missing_message(
self, param: _click.Parameter, ctx: _click.Context | None
) -> str:
"""Message shown when no choice is passed."""
choices = ",\n\t".join(self._normalized_mapping(ctx=ctx).values())
return f"Choose from:\n\t{choices}"
def convert(
self, value: Any, param: _click.Parameter | None, ctx: _click.Context | None
) -> ParamTypeValue:
"""
For a given value from the parser, normalize it and find its
matching normalized value in the list of choices. Then return the
matched "original" choice.
"""
normed_value = self.normalize_choice(choice=value, ctx=ctx)
normalized_mapping = self._normalized_mapping(ctx=ctx)
try:
return next(
original
for original, normalized in normalized_mapping.items()
if normalized == normed_value
)
except StopIteration:
self.fail(
self.get_invalid_choice_message(value=value, ctx=ctx),
param=param,
ctx=ctx,
)
def get_invalid_choice_message(self, value: Any, ctx: _click.Context | None) -> str:
"""Get the error message when the given choice is invalid."""
choices_str = ", ".join(map(repr, self._normalized_mapping(ctx=ctx).values()))
return f"{value!r} is not one of {choices_str}."
def __repr__(self) -> str:
return f"Choice({list(self.choices)})"
def shell_complete(
self, ctx: _click.Context, param: _click.Parameter, incomplete: str
) -> list[CompletionItem]:
"""Complete choices that start with the incomplete value."""
str_choices = map(str, self.choices)
if self.case_sensitive:
matched = (c for c in str_choices if c.startswith(incomplete))
else:
incomplete = incomplete.lower()
matched = (c for c in str_choices if c.lower().startswith(incomplete))
return [CompletionItem(c) for c in matched]