| """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() | |