radubulimac's picture
fix import issue
2d876d1
raw
history blame
11.2 kB
"""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()