Spaces:
Sleeping
Sleeping
"""Module contains shared utility functions and typing aliases.""" | |
import math | |
import os | |
import shutil | |
from typing import ( | |
TYPE_CHECKING, | |
Any, | |
Callable, | |
Dict, | |
List, | |
NamedTuple, | |
Optional, | |
Tuple, | |
Union, | |
) | |
from prompt_toolkit import print_formatted_text | |
from prompt_toolkit.application import run_in_terminal | |
from prompt_toolkit.application.current import get_app | |
from prompt_toolkit.formatted_text import FormattedText | |
from prompt_toolkit.styles import Style | |
from prompt_toolkit.validation import Validator | |
from InquirerPy.exceptions import InvalidArgument | |
if TYPE_CHECKING: | |
from prompt_toolkit.filters.base import FilterOrBool | |
from InquirerPy.base.control import Choice | |
__all__ = [ | |
"get_style", | |
"calculate_height", | |
"InquirerPyStyle", | |
"patched_print", | |
"color_print", | |
] | |
class InquirerPyStyle(NamedTuple): | |
"""`InquirerPy` Style class. | |
Used as a helper class to enforce the method `get_style` to be used | |
while also avoiding :class:`dict` to be passed into prompts. | |
Note: | |
The class is an instance of :class:`typing.NamedTuple`. | |
Warning: | |
You should not directly be using this class besides for type hinting | |
purposes. Obtain an instance of this class using :func:`.get_style`. | |
""" | |
dict: Dict[str, str] | |
InquirerPySessionResult = Dict[Union[str, int], Optional[Union[str, bool, List[Any]]]] | |
InquirerPyChoice = Union[List[Any], List["Choice"], List[Dict[str, Any]]] | |
InquirerPyListChoices = Union[ | |
Callable[["InquirerPySessionResult"], InquirerPyChoice], | |
InquirerPyChoice, | |
] | |
InquirerPyValidate = Union[Callable[[Any], bool], "Validator"] | |
InquirerPyQuestions = Union[List[Dict[str, Any]], Dict[str, Any]] | |
InquirerPyMessage = Union[str, Callable[["InquirerPySessionResult"], str]] | |
InquirerPyDefault = Union[Any, Callable[["InquirerPySessionResult"], Any]] | |
InquirerPyKeybindings = Dict[ | |
str, List[Dict[str, Union[str, "FilterOrBool", List[str]]]] | |
] | |
def get_style( | |
style: Optional[Dict[str, str]] = None, style_override: bool = True | |
) -> InquirerPyStyle: | |
"""Obtain an :class:`.InquirerPyStyle` instance which can be consumed by the `style` parameter in prompts. | |
Tip: | |
This function supports ENV variables. | |
For all the color ENV variable names, refer to the :ref:`ENV <pages/env:Style>` documentation. | |
Note: | |
If no style is provided, then a default theme based on `one dark <https://github.com/joshdick/onedark.vim#color-reference>`_ | |
color palette is applied. | |
Note: | |
Priority: style parameter -> ENV variable -> default style | |
Args: | |
style: The dictionary of style classes and their colors, If nothing is passed, the style will be resolved to the :ref:`pages/style:Default Style`. | |
style_override: A boolean to determine if the supplied `style` parameter should be merged with the :ref:`pages/style:Default Style` or override them. | |
By default, the supplied style will overwrite the :ref:`pages/style:Default Style`. | |
Returns: | |
An instance of :class:`.InquirerPyStyle`. | |
Examples: | |
>>> from InquirerPy import get_style | |
>>> from InquirerPy import inquirer | |
>>> style = get_style({"questionmark": "#ffffff", "answer": "#000000"}, style_override=False) | |
>>> result = inquirer.confirm(message="Confirm?", style=style).execute() | |
""" | |
if not style_override or style is None: | |
if not style: | |
style = {} | |
result = { | |
"questionmark": os.getenv("INQUIRERPY_STYLE_QUESTIONMARK", "#e5c07b"), | |
"answermark": os.getenv("INQUIRERPY_STYLE_ANSWERMARK", "#e5c07b"), | |
"answer": os.getenv("INQUIRERPY_STYLE_ANSWER", "#61afef"), | |
"input": os.getenv("INQUIRERPY_STYLE_INPUT", "#98c379"), | |
"question": os.getenv("INQUIRERPY_STYLE_QUESTION", ""), | |
"answered_question": os.getenv("INQUIRERPY_STYLE_ANSWERED_QUESTION", ""), | |
"instruction": os.getenv("INQUIRERPY_STYLE_INSTRUCTION", "#abb2bf"), | |
"long_instruction": os.getenv( | |
"INQUIRERPY_STYLE_LONG_INSTRUCTION", "#abb2bf" | |
), | |
"pointer": os.getenv("INQUIRERPY_STYLE_POINTER", "#61afef"), | |
"checkbox": os.getenv("INQUIRERPY_STYLE_CHECKBOX", "#98c379"), | |
"separator": os.getenv("INQUIRERPY_STYLE_SEPARATOR", ""), | |
"skipped": os.getenv("INQUIRERPY_STYLE_SKIPPED", "#5c6370"), | |
"validator": os.getenv("INQUIRERPY_STYLE_VALIDATOR", ""), | |
"marker": os.getenv("INQUIRERPY_STYLE_MARKER", "#e5c07b"), | |
"fuzzy_prompt": os.getenv("INQUIRERPY_STYLE_FUZZY_PROMPT", "#c678dd"), | |
"fuzzy_info": os.getenv("INQUIRERPY_STYLE_FUZZY_INFO", "#abb2bf"), | |
"fuzzy_border": os.getenv("INQUIRERPY_STYLE_FUZZY_BORDER", "#4b5263"), | |
"fuzzy_match": os.getenv("INQUIRERPY_STYLE_FUZZY_MATCH", "#c678dd"), | |
"spinner_pattern": os.getenv("INQUIRERPY_STYLE_SPINNER_PATTERN", "#e5c07b"), | |
"spinner_text": os.getenv("INQUIRERPY_STYLE_SPINNER_TEXT", ""), | |
**style, | |
} | |
else: | |
result = { | |
"questionmark": os.getenv("INQUIRERPY_STYLE_QUESTIONMARK", ""), | |
"answermark": os.getenv("INQUIRERPY_STYLE_ANSWERMARK", ""), | |
"answer": os.getenv("INQUIRERPY_STYLE_ANSWER", ""), | |
"input": os.getenv("INQUIRERPY_STYLE_INPUT", ""), | |
"question": os.getenv("INQUIRERPY_STYLE_QUESTION", ""), | |
"answered_question": os.getenv("INQUIRERPY_STYLE_ANSWERED_QUESTION", ""), | |
"instruction": os.getenv("INQUIRERPY_STYLE_INSTRUCTION", ""), | |
"long_instruction": os.getenv("INQUIRERPY_STYLE_LONG_INSTRUCTION", ""), | |
"pointer": os.getenv("INQUIRERPY_STYLE_POINTER", ""), | |
"checkbox": os.getenv("INQUIRERPY_STYLE_CHECKBOX", ""), | |
"separator": os.getenv("INQUIRERPY_STYLE_SEPARATOR", ""), | |
"skipped": os.getenv("INQUIRERPY_STYLE_SKIPPED", ""), | |
"validator": os.getenv("INQUIRERPY_STYLE_VALIDATOR", ""), | |
"marker": os.getenv("INQUIRERPY_STYLE_MARKER", ""), | |
"fuzzy_prompt": os.getenv("INQUIRERPY_STYLE_FUZZY_PROMPT", ""), | |
"fuzzy_info": os.getenv("INQUIRERPY_STYLE_FUZZY_INFO", ""), | |
"fuzzy_border": os.getenv("INQUIRERPY_STYLE_FUZZY_BORDER", ""), | |
"fuzzy_match": os.getenv("INQUIRERPY_STYLE_FUZZY_MATCH", ""), | |
"spinner_pattern": os.getenv("INQUIRERPY_STYLE_SPINNER_PATTERN", ""), | |
"spinner_text": os.getenv("INQUIRERPY_STYLE_SPINNER_TEXT", ""), | |
**style, | |
} | |
if result.get("fuzzy_border"): | |
result["frame.border"] = result.pop("fuzzy_border") | |
if result.get("validator"): | |
result["validation-toolbar"] = result.pop("validator") | |
result["bottom-toolbar"] = "noreverse" | |
return InquirerPyStyle(result) | |
def calculate_height( | |
height: Optional[Union[int, str]], | |
max_height: Optional[Union[int, str]], | |
height_offset: int = 2, | |
) -> Tuple[Optional[int], int]: | |
"""Calculate the `height` and `max_height` for the main question contents. | |
Tip: | |
The parameter `height`/`max_height` can be specified by either a :class:`string` or :class:`int`. | |
When `height`/`max_height` is :class:`str`: | |
It will set the height to a percentage based on the value provided. | |
You can optionally add the '%' sign which will be ignored while processing. | |
Example: "60%" or "60" (60% of the current terminal visible lines) | |
When `height`/`max_height` is :class:`int`: | |
It will set the height to exact number of lines based on the value provided. | |
Example: 20 (20 lines in terminal) | |
Note: | |
If `max_height` is not provided or is None, the default `max_height` will be configured to `70%` for | |
best visual presentation in the terminal. | |
Args: | |
height: The desired height in either percentage as string or exact value as int. | |
max_height: Maximum acceptable height in either percentage as string or exact value as int. | |
height_offset: Height offset to apply to the height. | |
Returns: | |
A :class:`tuple` with the first value being the desired height and the second value being | |
the maximum height. | |
Raises: | |
InvalidArgument: The provided `height`/`max_height` is not able to to be converted to int. | |
Examples: | |
>>> calculate_height(height="60%", max_height="100%") | |
""" | |
try: | |
_, term_lines = shutil.get_terminal_size() | |
term_lines = term_lines | |
if not height: | |
dimmension_height = None | |
else: | |
if isinstance(height, str): | |
height = height.replace("%", "") | |
height = int(height) | |
dimmension_height = ( | |
math.floor(term_lines * (height / 100)) - height_offset | |
) | |
else: | |
dimmension_height = height | |
if not max_height: | |
max_height = "70%" if not height else "100%" | |
if isinstance(max_height, str): | |
max_height = max_height.replace("%", "") | |
max_height = int(max_height) | |
dimmension_max_height = ( | |
math.floor(term_lines * (max_height / 100)) - height_offset | |
) | |
else: | |
dimmension_max_height = max_height | |
if dimmension_height and dimmension_height > dimmension_max_height: | |
dimmension_height = dimmension_max_height | |
if dimmension_height and dimmension_height <= 0: | |
dimmension_height = 1 | |
if dimmension_max_height <= 0: | |
dimmension_max_height = 1 | |
return dimmension_height, dimmension_max_height | |
except ValueError: | |
raise InvalidArgument( | |
"prompt argument height/max_height needs to be type of an int or str" | |
) | |
def patched_print(*values) -> None: | |
"""Patched :func:`print` that can print values without interrupting the prompt. | |
See Also: | |
:func:`print` | |
:func:`~prompt_toolkit.application.run_in_terminal` | |
Args: | |
*values: Refer to :func:`print`. | |
Examples: | |
>>> patched_print("Hello World") | |
""" | |
def _print(): | |
print(*values) | |
run_in_terminal(_print) | |
def color_print( | |
formatted_text: List[Tuple[str, str]], style: Optional[Dict[str, str]] = None | |
) -> None: | |
"""Print colored text leveraging :func:`~prompt_toolkit.shortcuts.print_formatted_text`. | |
This function automatically handles printing the text without interrupting the | |
current prompt. | |
Args: | |
formatted_text: A list of formatted_text. | |
style: Style to apply to `formatted_text` in :class:`dictionary` form. | |
Example: | |
>>> color_print(formatted_text=[("class:aa", "hello "), ("class:bb", "world")], style={"aa": "red", "bb": "blue"}) | |
>>> color_print([("red", "yes"), ("", " "), ("blue", "no")]) | |
""" | |
def _print(): | |
print_formatted_text( | |
FormattedText(formatted_text), | |
style=Style.from_dict(style) if style else None, | |
) | |
if get_app().is_running: | |
run_in_terminal(_print) | |
else: | |
_print() | |