Spaces:
Sleeping
Sleeping
"""Contains the content control class :class:`.InquirerPyUIListControl`.""" | |
from abc import abstractmethod | |
from dataclasses import asdict, dataclass | |
from typing import Any, Callable, Dict, List, Optional, Tuple, cast | |
from prompt_toolkit.layout.controls import FormattedTextControl | |
from InquirerPy.exceptions import InvalidArgument, RequiredKeyNotFound | |
from InquirerPy.separator import Separator | |
from InquirerPy.utils import InquirerPyListChoices, InquirerPySessionResult | |
__all__ = ["Choice", "InquirerPyUIListControl"] | |
class Choice: | |
"""Class to create choices for list type prompts. | |
A simple dataclass that can be used as an alternate to using :class:`dict` | |
when working with choices. | |
Args: | |
value: The value of the choice when user selects this choice. | |
name: The value that should be presented to the user prior/after selection of the choice. | |
This value is optional, if not provided, it will fallback to the string representation of `value`. | |
enabled: Indicates if the choice should be pre-selected. | |
This only has effects when the prompt has `multiselect` enabled. | |
""" | |
value: Any | |
name: Optional[str] = None | |
enabled: bool = False | |
def __post_init__(self): | |
"""Assign strinify value to name if not present.""" | |
if self.name is None: | |
self.name = str(self.value) | |
class InquirerPyUIListControl(FormattedTextControl): | |
"""A base class to create :class:`~prompt_toolkit.layout.UIControl` to display list type contents. | |
Args: | |
choices(InquirerPyListChoices): List of choices to display as the content. | |
Can also be a callable or async callable that returns a list of choices. | |
default: Default value, this will affect the cursor position. | |
multiselect: Indicate if the current prompt has `multiselect` enabled. | |
session_result: Current session result. | |
""" | |
def __init__( | |
self, | |
choices: InquirerPyListChoices, | |
default: Any = None, | |
multiselect: bool = False, | |
session_result: Optional[InquirerPySessionResult] = None, | |
) -> None: | |
self._session_result = session_result or {} | |
self._selected_choice_index: int = 0 | |
self._choice_func = None | |
self._multiselect = multiselect | |
self._default = ( | |
default | |
if not isinstance(default, Callable) | |
else cast(Callable, default)(self._session_result) | |
) | |
self._raw_choices = ( | |
choices | |
if not isinstance(choices, Callable) | |
else cast(Callable, choices)(self._session_result) | |
) | |
self._choices = self._get_choices(self._raw_choices, self._default) | |
self._safety_check() | |
self._format_choices() | |
super().__init__(self._get_formatted_choices) | |
def _get_choices(self, choices: List[Any], default: Any) -> List[Dict[str, Any]]: | |
"""Process the raw user input choices and format it into dictionary. | |
Args: | |
choices: List of chices to display. | |
default: Default value, this will affect the :attr:`.InquirerPyUIListControl.selected_choice_index` | |
Returns: | |
List of choices. | |
Raises: | |
RequiredKeyNotFound: When the provided choice is missing the `name` or `value` key. | |
""" | |
processed_choices: List[Dict[str, Any]] = [] | |
try: | |
for index, choice in enumerate(choices, start=0): | |
if isinstance(choice, dict): | |
if choice["value"] == default: | |
self.selected_choice_index = index | |
processed_choices.append( | |
{ | |
"name": str(choice["name"]), | |
"value": choice["value"], | |
"enabled": choice.get("enabled", False) | |
if self._multiselect | |
else False, | |
} | |
) | |
elif isinstance(choice, Separator): | |
if self.selected_choice_index == index: | |
self.selected_choice_index = ( | |
self.selected_choice_index + 1 | |
) % len(choices) | |
processed_choices.append( | |
{"name": str(choice), "value": choice, "enabled": False} | |
) | |
elif isinstance(choice, Choice): | |
dict_choice = asdict(choice) | |
if dict_choice["value"] == default: | |
self.selected_choice_index = index | |
if not self._multiselect: | |
dict_choice["enabled"] = False | |
processed_choices.append(dict_choice) | |
else: | |
if choice == default: | |
self.selected_choice_index = index | |
processed_choices.append( | |
{"name": str(choice), "value": choice, "enabled": False} | |
) | |
except KeyError: | |
raise RequiredKeyNotFound( | |
"dictionary type of choice require a 'name' key and a 'value' key" | |
) | |
return processed_choices | |
def selected_choice_index(self) -> int: | |
"""int: Current highlighted index.""" | |
return self._selected_choice_index | |
def selected_choice_index(self, value: int) -> None: | |
self._selected_choice_index = value | |
def choices(self) -> List[Dict[str, Any]]: | |
"""List[Dict[str, Any]]: Get all processed choices.""" | |
return self._choices | |
def choices(self, value: List[Dict[str, Any]]) -> None: | |
self._choices = value | |
def _safety_check(self) -> None: | |
"""Validate processed choices. | |
Check if the choices are empty or if it only contains :class:`~InquirerPy.separator.Separator`. | |
""" | |
if not self.choices: | |
raise InvalidArgument("argument choices cannot be empty") | |
should_proceed: bool = False | |
for choice in self.choices: | |
if not isinstance(choice["value"], Separator): | |
should_proceed = True | |
break | |
if not should_proceed: | |
raise InvalidArgument( | |
"argument choices should contain choices other than separator" | |
) | |
def _get_formatted_choices(self) -> List[Tuple[str, str]]: | |
"""Get all choices in formatted text format. | |
Returns: | |
List of choices in formatted text form. | |
""" | |
display_choices = [] | |
for index, choice in enumerate(self.choices): | |
if index == self.selected_choice_index: | |
display_choices += self._get_hover_text(choice) | |
else: | |
display_choices += self._get_normal_text(choice) | |
display_choices.append(("", "\n")) | |
if display_choices: | |
display_choices.pop() | |
return display_choices | |
def _format_choices(self) -> None: | |
"""Perform post processing on the choices. | |
Additional customisation to the choices after :meth:`.InquirerPyUIListControl._get_choices` call. | |
""" | |
pass | |
def _get_hover_text(self, choice) -> List[Tuple[str, str]]: | |
"""Generate the formatted text for hovered choice. | |
Returns: | |
Formatted text in list of tuple format. | |
""" | |
pass | |
def _get_normal_text(self, choice) -> List[Tuple[str, str]]: | |
"""Generate the formatted text for non-hovered choices. | |
Returns: | |
Formatted text in list of tuple format. | |
""" | |
pass | |
def choice_count(self) -> int: | |
"""int: Total count of choices.""" | |
return len(self.choices) | |
def selection(self) -> Dict[str, Any]: | |
"""Dict[str, Any]: Current selected choice.""" | |
return self.choices[self.selected_choice_index] | |
def loading(self) -> bool: | |
"""bool: Indicate if the content control is loading.""" | |
return self._loading | |
def loading(self, value: bool) -> None: | |
self._loading = value | |