diff options
author | shadchin <shadchin@yandex-team.ru> | 2022-02-10 16:44:39 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:44:39 +0300 |
commit | e9656aae26e0358d5378e5b63dcac5c8dbe0e4d0 (patch) | |
tree | 64175d5cadab313b3e7039ebaa06c5bc3295e274 /contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text | |
parent | 2598ef1d0aee359b4b6d5fdd1758916d5907d04f (diff) | |
download | ydb-e9656aae26e0358d5378e5b63dcac5c8dbe0e4d0.tar.gz |
Restoring authorship annotation for <shadchin@yandex-team.ru>. Commit 2 of 2.
Diffstat (limited to 'contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text')
6 files changed, 733 insertions, 733 deletions
diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/__init__.py index 7272539fcc..f0c92c96f9 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/__init__.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/__init__.py @@ -1,54 +1,54 @@ -""" -Many places in prompt_toolkit can take either plain text, or formatted text. -For instance the :func:`~prompt_toolkit.shortcuts.prompt` function takes either -plain text or formatted text for the prompt. The -:class:`~prompt_toolkit.layout.FormattedTextControl` can also take either plain -text or formatted text. - -In any case, there is an input that can either be just plain text (a string), -an :class:`.HTML` object, an :class:`.ANSI` object or a sequence of -`(style_string, text)` tuples. The :func:`.to_formatted_text` conversion -function takes any of these and turns all of them into such a tuple sequence. -""" -from .ansi import ANSI -from .base import ( - AnyFormattedText, - FormattedText, - StyleAndTextTuples, - Template, - is_formatted_text, - merge_formatted_text, - to_formatted_text, -) -from .html import HTML -from .pygments import PygmentsTokens -from .utils import ( - fragment_list_len, - fragment_list_to_text, - fragment_list_width, - split_lines, +""" +Many places in prompt_toolkit can take either plain text, or formatted text. +For instance the :func:`~prompt_toolkit.shortcuts.prompt` function takes either +plain text or formatted text for the prompt. The +:class:`~prompt_toolkit.layout.FormattedTextControl` can also take either plain +text or formatted text. + +In any case, there is an input that can either be just plain text (a string), +an :class:`.HTML` object, an :class:`.ANSI` object or a sequence of +`(style_string, text)` tuples. The :func:`.to_formatted_text` conversion +function takes any of these and turns all of them into such a tuple sequence. +""" +from .ansi import ANSI +from .base import ( + AnyFormattedText, + FormattedText, + StyleAndTextTuples, + Template, + is_formatted_text, + merge_formatted_text, + to_formatted_text, +) +from .html import HTML +from .pygments import PygmentsTokens +from .utils import ( + fragment_list_len, + fragment_list_to_text, + fragment_list_width, + split_lines, to_plain_text, -) - -__all__ = [ - # Base. - "AnyFormattedText", - "to_formatted_text", - "is_formatted_text", - "Template", - "merge_formatted_text", - "FormattedText", - "StyleAndTextTuples", - # HTML. - "HTML", - # ANSI. - "ANSI", - # Pygments. - "PygmentsTokens", - # Utils. - "fragment_list_len", - "fragment_list_width", - "fragment_list_to_text", - "split_lines", +) + +__all__ = [ + # Base. + "AnyFormattedText", + "to_formatted_text", + "is_formatted_text", + "Template", + "merge_formatted_text", + "FormattedText", + "StyleAndTextTuples", + # HTML. + "HTML", + # ANSI. + "ANSI", + # Pygments. + "PygmentsTokens", + # Utils. + "fragment_list_len", + "fragment_list_width", + "fragment_list_to_text", + "split_lines", "to_plain_text", -] +] diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/ansi.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/ansi.py index 2d8c5dac95..3d57063357 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/ansi.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/ansi.py @@ -1,117 +1,117 @@ -from typing import Generator, List, Optional - -from prompt_toolkit.output.vt100 import BG_ANSI_COLORS, FG_ANSI_COLORS -from prompt_toolkit.output.vt100 import _256_colors as _256_colors_table - -from .base import StyleAndTextTuples - -__all__ = [ - "ANSI", - "ansi_escape", -] - - -class ANSI: - """ - ANSI formatted text. - Take something ANSI escaped text, for use as a formatted string. E.g. - - :: - - ANSI('\\x1b[31mhello \\x1b[32mworld') - - Characters between ``\\001`` and ``\\002`` are supposed to have a zero width - when printed, but these are literally sent to the terminal output. This can - be used for instance, for inserting Final Term prompt commands. They will - be translated into a prompt_toolkit '[ZeroWidthEscape]' fragment. - """ - - def __init__(self, value: str) -> None: - self.value = value - self._formatted_text: StyleAndTextTuples = [] - - # Default style attributes. - self._color: Optional[str] = None - self._bgcolor: Optional[str] = None - self._bold = False - self._underline = False - self._strike = False - self._italic = False - self._blink = False - self._reverse = False - self._hidden = False - - # Process received text. - parser = self._parse_corot() - parser.send(None) # type: ignore - for c in value: - parser.send(c) - - def _parse_corot(self) -> Generator[None, str, None]: - """ - Coroutine that parses the ANSI escape sequences. - """ - style = "" - formatted_text = self._formatted_text - - while True: +from typing import Generator, List, Optional + +from prompt_toolkit.output.vt100 import BG_ANSI_COLORS, FG_ANSI_COLORS +from prompt_toolkit.output.vt100 import _256_colors as _256_colors_table + +from .base import StyleAndTextTuples + +__all__ = [ + "ANSI", + "ansi_escape", +] + + +class ANSI: + """ + ANSI formatted text. + Take something ANSI escaped text, for use as a formatted string. E.g. + + :: + + ANSI('\\x1b[31mhello \\x1b[32mworld') + + Characters between ``\\001`` and ``\\002`` are supposed to have a zero width + when printed, but these are literally sent to the terminal output. This can + be used for instance, for inserting Final Term prompt commands. They will + be translated into a prompt_toolkit '[ZeroWidthEscape]' fragment. + """ + + def __init__(self, value: str) -> None: + self.value = value + self._formatted_text: StyleAndTextTuples = [] + + # Default style attributes. + self._color: Optional[str] = None + self._bgcolor: Optional[str] = None + self._bold = False + self._underline = False + self._strike = False + self._italic = False + self._blink = False + self._reverse = False + self._hidden = False + + # Process received text. + parser = self._parse_corot() + parser.send(None) # type: ignore + for c in value: + parser.send(c) + + def _parse_corot(self) -> Generator[None, str, None]: + """ + Coroutine that parses the ANSI escape sequences. + """ + style = "" + formatted_text = self._formatted_text + + while True: # NOTE: CSI is a special token within a stream of characters that # introduces an ANSI control sequence used to set the # style attributes of the following characters. - csi = False - - c = yield - - # Everything between \001 and \002 should become a ZeroWidthEscape. - if c == "\001": - escaped_text = "" - while c != "\002": - c = yield - if c == "\002": - formatted_text.append(("[ZeroWidthEscape]", escaped_text)) - c = yield - break - else: - escaped_text += c - + csi = False + + c = yield + + # Everything between \001 and \002 should become a ZeroWidthEscape. + if c == "\001": + escaped_text = "" + while c != "\002": + c = yield + if c == "\002": + formatted_text.append(("[ZeroWidthEscape]", escaped_text)) + c = yield + break + else: + escaped_text += c + # Check for CSI - if c == "\x1b": - # Start of color escape sequence. - square_bracket = yield - if square_bracket == "[": - csi = True - else: - continue - elif c == "\x9b": - csi = True - - if csi: - # Got a CSI sequence. Color codes are following. - current = "" - params = [] - - while True: - char = yield + if c == "\x1b": + # Start of color escape sequence. + square_bracket = yield + if square_bracket == "[": + csi = True + else: + continue + elif c == "\x9b": + csi = True + + if csi: + # Got a CSI sequence. Color codes are following. + current = "" + params = [] + + while True: + char = yield # Construct number - if char.isdigit(): - current += char + if char.isdigit(): + current += char # Eval number - else: + else: # Limit and save number value - params.append(min(int(current or 0), 9999)) + params.append(min(int(current or 0), 9999)) # Get delimiter token if present - if char == ";": - current = "" + if char == ";": + current = "" # Check and evaluate color codes - elif char == "m": - # Set attributes and token. - self._select_graphic_rendition(params) - style = self._create_style_string() - break + elif char == "m": + # Set attributes and token. + self._select_graphic_rendition(params) + style = self._create_style_string() + break # Check and evaluate cursor forward elif char == "C": @@ -120,163 +120,163 @@ class ANSI: formatted_text.append((style, " ")) break - else: - # Ignore unsupported sequence. - break - else: - # Add current character. - # NOTE: At this point, we could merge the current character - # into the previous tuple if the style did not change, - # however, it's not worth the effort given that it will - # be "Exploded" once again when it's rendered to the - # output. - formatted_text.append((style, c)) - - def _select_graphic_rendition(self, attrs: List[int]) -> None: - """ - Taken a list of graphics attributes and apply changes. - """ - if not attrs: - attrs = [0] - else: - attrs = list(attrs[::-1]) - - while attrs: - attr = attrs.pop() - - if attr in _fg_colors: - self._color = _fg_colors[attr] - elif attr in _bg_colors: - self._bgcolor = _bg_colors[attr] - elif attr == 1: - self._bold = True + else: + # Ignore unsupported sequence. + break + else: + # Add current character. + # NOTE: At this point, we could merge the current character + # into the previous tuple if the style did not change, + # however, it's not worth the effort given that it will + # be "Exploded" once again when it's rendered to the + # output. + formatted_text.append((style, c)) + + def _select_graphic_rendition(self, attrs: List[int]) -> None: + """ + Taken a list of graphics attributes and apply changes. + """ + if not attrs: + attrs = [0] + else: + attrs = list(attrs[::-1]) + + while attrs: + attr = attrs.pop() + + if attr in _fg_colors: + self._color = _fg_colors[attr] + elif attr in _bg_colors: + self._bgcolor = _bg_colors[attr] + elif attr == 1: + self._bold = True # elif attr == 2: # self._faint = True - elif attr == 3: - self._italic = True - elif attr == 4: - self._underline = True - elif attr == 5: + elif attr == 3: + self._italic = True + elif attr == 4: + self._underline = True + elif attr == 5: self._blink = True # Slow blink - elif attr == 6: + elif attr == 6: self._blink = True # Fast blink - elif attr == 7: - self._reverse = True - elif attr == 8: - self._hidden = True - elif attr == 9: - self._strike = True - elif attr == 22: + elif attr == 7: + self._reverse = True + elif attr == 8: + self._hidden = True + elif attr == 9: + self._strike = True + elif attr == 22: self._bold = False # Normal intensity - elif attr == 23: - self._italic = False - elif attr == 24: - self._underline = False - elif attr == 25: - self._blink = False - elif attr == 27: - self._reverse = False + elif attr == 23: + self._italic = False + elif attr == 24: + self._underline = False + elif attr == 25: + self._blink = False + elif attr == 27: + self._reverse = False elif attr == 28: self._hidden = False - elif attr == 29: - self._strike = False - elif not attr: + elif attr == 29: + self._strike = False + elif not attr: # Reset all style attributes - self._color = None - self._bgcolor = None - self._bold = False - self._underline = False - self._strike = False - self._italic = False - self._blink = False - self._reverse = False - self._hidden = False - - elif attr in (38, 48) and len(attrs) > 1: - n = attrs.pop() - - # 256 colors. - if n == 5 and len(attrs) >= 1: - if attr == 38: - m = attrs.pop() - self._color = _256_colors.get(m) - elif attr == 48: - m = attrs.pop() - self._bgcolor = _256_colors.get(m) - - # True colors. - if n == 2 and len(attrs) >= 3: - try: - color_str = "#%02x%02x%02x" % ( - attrs.pop(), - attrs.pop(), - attrs.pop(), - ) - except IndexError: - pass - else: - if attr == 38: - self._color = color_str - elif attr == 48: - self._bgcolor = color_str - - def _create_style_string(self) -> str: - """ - Turn current style flags into a string for usage in a formatted text. - """ - result = [] - if self._color: - result.append(self._color) - if self._bgcolor: - result.append("bg:" + self._bgcolor) - if self._bold: - result.append("bold") - if self._underline: - result.append("underline") - if self._strike: - result.append("strike") - if self._italic: - result.append("italic") - if self._blink: - result.append("blink") - if self._reverse: - result.append("reverse") - if self._hidden: - result.append("hidden") - - return " ".join(result) - - def __repr__(self) -> str: - return "ANSI(%r)" % (self.value,) - - def __pt_formatted_text__(self) -> StyleAndTextTuples: - return self._formatted_text - - def format(self, *args: str, **kwargs: str) -> "ANSI": - """ - Like `str.format`, but make sure that the arguments are properly - escaped. (No ANSI escapes can be injected.) - """ - # Escape all the arguments. - args = tuple(ansi_escape(a) for a in args) - kwargs = {k: ansi_escape(v) for k, v in kwargs.items()} - - return ANSI(self.value.format(*args, **kwargs)) - - -# Mapping of the ANSI color codes to their names. -_fg_colors = {v: k for k, v in FG_ANSI_COLORS.items()} -_bg_colors = {v: k for k, v in BG_ANSI_COLORS.items()} - -# Mapping of the escape codes for 256colors to their 'ffffff' value. -_256_colors = {} - -for i, (r, g, b) in enumerate(_256_colors_table.colors): - _256_colors[i] = "#%02x%02x%02x" % (r, g, b) - - -def ansi_escape(text: str) -> str: - """ - Replace characters with a special meaning. - """ - return text.replace("\x1b", "?").replace("\b", "?") + self._color = None + self._bgcolor = None + self._bold = False + self._underline = False + self._strike = False + self._italic = False + self._blink = False + self._reverse = False + self._hidden = False + + elif attr in (38, 48) and len(attrs) > 1: + n = attrs.pop() + + # 256 colors. + if n == 5 and len(attrs) >= 1: + if attr == 38: + m = attrs.pop() + self._color = _256_colors.get(m) + elif attr == 48: + m = attrs.pop() + self._bgcolor = _256_colors.get(m) + + # True colors. + if n == 2 and len(attrs) >= 3: + try: + color_str = "#%02x%02x%02x" % ( + attrs.pop(), + attrs.pop(), + attrs.pop(), + ) + except IndexError: + pass + else: + if attr == 38: + self._color = color_str + elif attr == 48: + self._bgcolor = color_str + + def _create_style_string(self) -> str: + """ + Turn current style flags into a string for usage in a formatted text. + """ + result = [] + if self._color: + result.append(self._color) + if self._bgcolor: + result.append("bg:" + self._bgcolor) + if self._bold: + result.append("bold") + if self._underline: + result.append("underline") + if self._strike: + result.append("strike") + if self._italic: + result.append("italic") + if self._blink: + result.append("blink") + if self._reverse: + result.append("reverse") + if self._hidden: + result.append("hidden") + + return " ".join(result) + + def __repr__(self) -> str: + return "ANSI(%r)" % (self.value,) + + def __pt_formatted_text__(self) -> StyleAndTextTuples: + return self._formatted_text + + def format(self, *args: str, **kwargs: str) -> "ANSI": + """ + Like `str.format`, but make sure that the arguments are properly + escaped. (No ANSI escapes can be injected.) + """ + # Escape all the arguments. + args = tuple(ansi_escape(a) for a in args) + kwargs = {k: ansi_escape(v) for k, v in kwargs.items()} + + return ANSI(self.value.format(*args, **kwargs)) + + +# Mapping of the ANSI color codes to their names. +_fg_colors = {v: k for k, v in FG_ANSI_COLORS.items()} +_bg_colors = {v: k for k, v in BG_ANSI_COLORS.items()} + +# Mapping of the escape codes for 256colors to their 'ffffff' value. +_256_colors = {} + +for i, (r, g, b) in enumerate(_256_colors_table.colors): + _256_colors[i] = "#%02x%02x%02x" % (r, g, b) + + +def ansi_escape(text: str) -> str: + """ + Replace characters with a special meaning. + """ + return text.replace("\x1b", "?").replace("\b", "?") diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/base.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/base.py index 95b6cf716a..c1761f2640 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/base.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/base.py @@ -1,176 +1,176 @@ -from typing import TYPE_CHECKING, Any, Callable, Iterable, List, Tuple, Union, cast - -from prompt_toolkit.mouse_events import MouseEvent - -if TYPE_CHECKING: - from typing_extensions import Protocol - -__all__ = [ - "OneStyleAndTextTuple", - "StyleAndTextTuples", - "MagicFormattedText", - "AnyFormattedText", - "to_formatted_text", - "is_formatted_text", - "Template", - "merge_formatted_text", - "FormattedText", -] - -OneStyleAndTextTuple = Union[ - Tuple[str, str], Tuple[str, str, Callable[[MouseEvent], None]] -] - -# List of (style, text) tuples. -StyleAndTextTuples = List[OneStyleAndTextTuple] - - -if TYPE_CHECKING: - from typing_extensions import TypeGuard - - class MagicFormattedText(Protocol): - """ - Any object that implements ``__pt_formatted_text__`` represents formatted - text. - """ - - def __pt_formatted_text__(self) -> StyleAndTextTuples: - ... - - -AnyFormattedText = Union[ - str, - "MagicFormattedText", - StyleAndTextTuples, - # Callable[[], 'AnyFormattedText'] # Recursive definition not supported by mypy. - Callable[[], Any], - None, -] - - -def to_formatted_text( - value: AnyFormattedText, style: str = "", auto_convert: bool = False -) -> "FormattedText": - """ - Convert the given value (which can be formatted text) into a list of text - fragments. (Which is the canonical form of formatted text.) The outcome is - always a `FormattedText` instance, which is a list of (style, text) tuples. - - It can take a plain text string, an `HTML` or `ANSI` object, anything that - implements `__pt_formatted_text__` or a callable that takes no arguments and - returns one of those. - - :param style: An additional style string which is applied to all text - fragments. - :param auto_convert: If `True`, also accept other types, and convert them - to a string first. - """ - result: Union[FormattedText, StyleAndTextTuples] - - if value is None: - result = [] - elif isinstance(value, str): - result = [("", value)] - elif isinstance(value, list): - result = value # StyleAndTextTuples - elif hasattr(value, "__pt_formatted_text__"): - result = cast("MagicFormattedText", value).__pt_formatted_text__() - elif callable(value): - return to_formatted_text(value(), style=style) - elif auto_convert: - result = [("", "{}".format(value))] - else: - raise ValueError( - "No formatted text. Expecting a unicode object, " - "HTML, ANSI or a FormattedText instance. Got %r" % (value,) - ) - - # Apply extra style. - if style: - result = cast( - StyleAndTextTuples, - [(style + " " + item_style, *rest) for item_style, *rest in result], - ) - - # Make sure the result is wrapped in a `FormattedText`. Among other - # reasons, this is important for `print_formatted_text` to work correctly - # and distinguish between lists and formatted text. - if isinstance(result, FormattedText): - return result - else: - return FormattedText(result) - - -def is_formatted_text(value: object) -> "TypeGuard[AnyFormattedText]": - """ - Check whether the input is valid formatted text (for use in assert - statements). - In case of a callable, it doesn't check the return type. - """ - if callable(value): - return True - if isinstance(value, (str, list)): - return True - if hasattr(value, "__pt_formatted_text__"): - return True - return False - - -class FormattedText(StyleAndTextTuples): - """ - A list of ``(style, text)`` tuples. - - (In some situations, this can also be ``(style, text, mouse_handler)`` - tuples.) - """ - - def __pt_formatted_text__(self) -> StyleAndTextTuples: - return self - - def __repr__(self) -> str: - return "FormattedText(%s)" % super().__repr__() - - -class Template: - """ - Template for string interpolation with formatted text. - - Example:: - - Template(' ... {} ... ').format(HTML(...)) - - :param text: Plain text. - """ - - def __init__(self, text: str) -> None: - assert "{0}" not in text - self.text = text - - def format(self, *values: AnyFormattedText) -> AnyFormattedText: - def get_result() -> AnyFormattedText: - # Split the template in parts. - parts = self.text.split("{}") - assert len(parts) - 1 == len(values) - - result = FormattedText() - for part, val in zip(parts, values): - result.append(("", part)) - result.extend(to_formatted_text(val)) - result.append(("", parts[-1])) - return result - - return get_result - - -def merge_formatted_text(items: Iterable[AnyFormattedText]) -> AnyFormattedText: - """ - Merge (Concatenate) several pieces of formatted text together. - """ - - def _merge_formatted_text() -> AnyFormattedText: - result = FormattedText() - for i in items: - result.extend(to_formatted_text(i)) - return result - - return _merge_formatted_text +from typing import TYPE_CHECKING, Any, Callable, Iterable, List, Tuple, Union, cast + +from prompt_toolkit.mouse_events import MouseEvent + +if TYPE_CHECKING: + from typing_extensions import Protocol + +__all__ = [ + "OneStyleAndTextTuple", + "StyleAndTextTuples", + "MagicFormattedText", + "AnyFormattedText", + "to_formatted_text", + "is_formatted_text", + "Template", + "merge_formatted_text", + "FormattedText", +] + +OneStyleAndTextTuple = Union[ + Tuple[str, str], Tuple[str, str, Callable[[MouseEvent], None]] +] + +# List of (style, text) tuples. +StyleAndTextTuples = List[OneStyleAndTextTuple] + + +if TYPE_CHECKING: + from typing_extensions import TypeGuard + + class MagicFormattedText(Protocol): + """ + Any object that implements ``__pt_formatted_text__`` represents formatted + text. + """ + + def __pt_formatted_text__(self) -> StyleAndTextTuples: + ... + + +AnyFormattedText = Union[ + str, + "MagicFormattedText", + StyleAndTextTuples, + # Callable[[], 'AnyFormattedText'] # Recursive definition not supported by mypy. + Callable[[], Any], + None, +] + + +def to_formatted_text( + value: AnyFormattedText, style: str = "", auto_convert: bool = False +) -> "FormattedText": + """ + Convert the given value (which can be formatted text) into a list of text + fragments. (Which is the canonical form of formatted text.) The outcome is + always a `FormattedText` instance, which is a list of (style, text) tuples. + + It can take a plain text string, an `HTML` or `ANSI` object, anything that + implements `__pt_formatted_text__` or a callable that takes no arguments and + returns one of those. + + :param style: An additional style string which is applied to all text + fragments. + :param auto_convert: If `True`, also accept other types, and convert them + to a string first. + """ + result: Union[FormattedText, StyleAndTextTuples] + + if value is None: + result = [] + elif isinstance(value, str): + result = [("", value)] + elif isinstance(value, list): + result = value # StyleAndTextTuples + elif hasattr(value, "__pt_formatted_text__"): + result = cast("MagicFormattedText", value).__pt_formatted_text__() + elif callable(value): + return to_formatted_text(value(), style=style) + elif auto_convert: + result = [("", "{}".format(value))] + else: + raise ValueError( + "No formatted text. Expecting a unicode object, " + "HTML, ANSI or a FormattedText instance. Got %r" % (value,) + ) + + # Apply extra style. + if style: + result = cast( + StyleAndTextTuples, + [(style + " " + item_style, *rest) for item_style, *rest in result], + ) + + # Make sure the result is wrapped in a `FormattedText`. Among other + # reasons, this is important for `print_formatted_text` to work correctly + # and distinguish between lists and formatted text. + if isinstance(result, FormattedText): + return result + else: + return FormattedText(result) + + +def is_formatted_text(value: object) -> "TypeGuard[AnyFormattedText]": + """ + Check whether the input is valid formatted text (for use in assert + statements). + In case of a callable, it doesn't check the return type. + """ + if callable(value): + return True + if isinstance(value, (str, list)): + return True + if hasattr(value, "__pt_formatted_text__"): + return True + return False + + +class FormattedText(StyleAndTextTuples): + """ + A list of ``(style, text)`` tuples. + + (In some situations, this can also be ``(style, text, mouse_handler)`` + tuples.) + """ + + def __pt_formatted_text__(self) -> StyleAndTextTuples: + return self + + def __repr__(self) -> str: + return "FormattedText(%s)" % super().__repr__() + + +class Template: + """ + Template for string interpolation with formatted text. + + Example:: + + Template(' ... {} ... ').format(HTML(...)) + + :param text: Plain text. + """ + + def __init__(self, text: str) -> None: + assert "{0}" not in text + self.text = text + + def format(self, *values: AnyFormattedText) -> AnyFormattedText: + def get_result() -> AnyFormattedText: + # Split the template in parts. + parts = self.text.split("{}") + assert len(parts) - 1 == len(values) + + result = FormattedText() + for part, val in zip(parts, values): + result.append(("", part)) + result.extend(to_formatted_text(val)) + result.append(("", parts[-1])) + return result + + return get_result + + +def merge_formatted_text(items: Iterable[AnyFormattedText]) -> AnyFormattedText: + """ + Merge (Concatenate) several pieces of formatted text together. + """ + + def _merge_formatted_text() -> AnyFormattedText: + result = FormattedText() + for i in items: + result.extend(to_formatted_text(i)) + return result + + return _merge_formatted_text diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/html.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/html.py index 58d4c08c82..06c6020f54 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/html.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/html.py @@ -1,138 +1,138 @@ -import xml.dom.minidom as minidom -from typing import Any, List, Tuple, Union - -from .base import FormattedText, StyleAndTextTuples - -__all__ = ["HTML"] - - -class HTML: - """ - HTML formatted text. - Take something HTML-like, for use as a formatted string. - - :: - - # Turn something into red. - HTML('<style fg="ansired" bg="#00ff44">...</style>') - - # Italic, bold, underline and strike. - HTML('<i>...</i>') - HTML('<b>...</b>') - HTML('<u>...</u>') - HTML('<s>...</s>') - - All HTML elements become available as a "class" in the style sheet. - E.g. ``<username>...</username>`` can be styled, by setting a style for - ``username``. - """ - - def __init__(self, value: str) -> None: - self.value = value - document = minidom.parseString("<html-root>%s</html-root>" % (value,)) - - result: StyleAndTextTuples = [] - name_stack: List[str] = [] - fg_stack: List[str] = [] - bg_stack: List[str] = [] - - def get_current_style() -> str: - "Build style string for current node." - parts = [] - if name_stack: - parts.append("class:" + ",".join(name_stack)) - - if fg_stack: - parts.append("fg:" + fg_stack[-1]) - if bg_stack: - parts.append("bg:" + bg_stack[-1]) - return " ".join(parts) - - def process_node(node: Any) -> None: - "Process node recursively." - for child in node.childNodes: - if child.nodeType == child.TEXT_NODE: - result.append((get_current_style(), child.data)) - else: - add_to_name_stack = child.nodeName not in ( - "#document", - "html-root", - "style", - ) - fg = bg = "" - - for k, v in child.attributes.items(): - if k == "fg": - fg = v - if k == "bg": - bg = v - if k == "color": - fg = v # Alias for 'fg'. - - # Check for spaces in attributes. This would result in - # invalid style strings otherwise. - if " " in fg: - raise ValueError('"fg" attribute contains a space.') - if " " in bg: - raise ValueError('"bg" attribute contains a space.') - - if add_to_name_stack: - name_stack.append(child.nodeName) - if fg: - fg_stack.append(fg) - if bg: - bg_stack.append(bg) - - process_node(child) - - if add_to_name_stack: - name_stack.pop() - if fg: - fg_stack.pop() - if bg: - bg_stack.pop() - - process_node(document) - - self.formatted_text = FormattedText(result) - - def __repr__(self) -> str: - return "HTML(%r)" % (self.value,) - - def __pt_formatted_text__(self) -> StyleAndTextTuples: - return self.formatted_text - - def format(self, *args: object, **kwargs: object) -> "HTML": - """ - Like `str.format`, but make sure that the arguments are properly - escaped. - """ - # Escape all the arguments. - escaped_args = [html_escape(a) for a in args] - escaped_kwargs = {k: html_escape(v) for k, v in kwargs.items()} - - return HTML(self.value.format(*escaped_args, **escaped_kwargs)) - - def __mod__(self, value: Union[object, Tuple[object, ...]]) -> "HTML": - """ - HTML('<b>%s</b>') % value - """ - if not isinstance(value, tuple): - value = (value,) - - value = tuple(html_escape(i) for i in value) - return HTML(self.value % value) - - -def html_escape(text: object) -> str: - # The string interpolation functions also take integers and other types. - # Convert to string first. - if not isinstance(text, str): - text = "{}".format(text) - - return ( - text.replace("&", "&") - .replace("<", "<") - .replace(">", ">") - .replace('"', """) - ) +import xml.dom.minidom as minidom +from typing import Any, List, Tuple, Union + +from .base import FormattedText, StyleAndTextTuples + +__all__ = ["HTML"] + + +class HTML: + """ + HTML formatted text. + Take something HTML-like, for use as a formatted string. + + :: + + # Turn something into red. + HTML('<style fg="ansired" bg="#00ff44">...</style>') + + # Italic, bold, underline and strike. + HTML('<i>...</i>') + HTML('<b>...</b>') + HTML('<u>...</u>') + HTML('<s>...</s>') + + All HTML elements become available as a "class" in the style sheet. + E.g. ``<username>...</username>`` can be styled, by setting a style for + ``username``. + """ + + def __init__(self, value: str) -> None: + self.value = value + document = minidom.parseString("<html-root>%s</html-root>" % (value,)) + + result: StyleAndTextTuples = [] + name_stack: List[str] = [] + fg_stack: List[str] = [] + bg_stack: List[str] = [] + + def get_current_style() -> str: + "Build style string for current node." + parts = [] + if name_stack: + parts.append("class:" + ",".join(name_stack)) + + if fg_stack: + parts.append("fg:" + fg_stack[-1]) + if bg_stack: + parts.append("bg:" + bg_stack[-1]) + return " ".join(parts) + + def process_node(node: Any) -> None: + "Process node recursively." + for child in node.childNodes: + if child.nodeType == child.TEXT_NODE: + result.append((get_current_style(), child.data)) + else: + add_to_name_stack = child.nodeName not in ( + "#document", + "html-root", + "style", + ) + fg = bg = "" + + for k, v in child.attributes.items(): + if k == "fg": + fg = v + if k == "bg": + bg = v + if k == "color": + fg = v # Alias for 'fg'. + + # Check for spaces in attributes. This would result in + # invalid style strings otherwise. + if " " in fg: + raise ValueError('"fg" attribute contains a space.') + if " " in bg: + raise ValueError('"bg" attribute contains a space.') + + if add_to_name_stack: + name_stack.append(child.nodeName) + if fg: + fg_stack.append(fg) + if bg: + bg_stack.append(bg) + + process_node(child) + + if add_to_name_stack: + name_stack.pop() + if fg: + fg_stack.pop() + if bg: + bg_stack.pop() + + process_node(document) + + self.formatted_text = FormattedText(result) + + def __repr__(self) -> str: + return "HTML(%r)" % (self.value,) + + def __pt_formatted_text__(self) -> StyleAndTextTuples: + return self.formatted_text + + def format(self, *args: object, **kwargs: object) -> "HTML": + """ + Like `str.format`, but make sure that the arguments are properly + escaped. + """ + # Escape all the arguments. + escaped_args = [html_escape(a) for a in args] + escaped_kwargs = {k: html_escape(v) for k, v in kwargs.items()} + + return HTML(self.value.format(*escaped_args, **escaped_kwargs)) + + def __mod__(self, value: Union[object, Tuple[object, ...]]) -> "HTML": + """ + HTML('<b>%s</b>') % value + """ + if not isinstance(value, tuple): + value = (value,) + + value = tuple(html_escape(i) for i in value) + return HTML(self.value % value) + + +def html_escape(text: object) -> str: + # The string interpolation functions also take integers and other types. + # Convert to string first. + if not isinstance(text, str): + text = "{}".format(text) + + return ( + text.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace('"', """) + ) diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/pygments.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/pygments.py index 34197840e5..dd16f0efbe 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/pygments.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/pygments.py @@ -1,30 +1,30 @@ -from typing import TYPE_CHECKING, List, Tuple - -from prompt_toolkit.styles.pygments import pygments_token_to_classname - -from .base import StyleAndTextTuples - -if TYPE_CHECKING: - from pygments.token import Token - -__all__ = [ - "PygmentsTokens", -] - - -class PygmentsTokens: - """ - Turn a pygments token list into a list of prompt_toolkit text fragments - (``(style_str, text)`` tuples). - """ - - def __init__(self, token_list: List[Tuple["Token", str]]) -> None: - self.token_list = token_list - - def __pt_formatted_text__(self) -> StyleAndTextTuples: - result: StyleAndTextTuples = [] - - for token, text in self.token_list: - result.append(("class:" + pygments_token_to_classname(token), text)) - - return result +from typing import TYPE_CHECKING, List, Tuple + +from prompt_toolkit.styles.pygments import pygments_token_to_classname + +from .base import StyleAndTextTuples + +if TYPE_CHECKING: + from pygments.token import Token + +__all__ = [ + "PygmentsTokens", +] + + +class PygmentsTokens: + """ + Turn a pygments token list into a list of prompt_toolkit text fragments + (``(style_str, text)`` tuples). + """ + + def __init__(self, token_list: List[Tuple["Token", str]]) -> None: + self.token_list = token_list + + def __pt_formatted_text__(self) -> StyleAndTextTuples: + result: StyleAndTextTuples = [] + + for token, text in self.token_list: + result.append(("class:" + pygments_token_to_classname(token), text)) + + return result diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/utils.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/utils.py index 126a6e0b39..cda4233e06 100644 --- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/utils.py +++ b/contrib/python/prompt-toolkit/py3/prompt_toolkit/formatted_text/utils.py @@ -1,29 +1,29 @@ -""" -Utilities for manipulating formatted text. - -When ``to_formatted_text`` has been called, we get a list of ``(style, text)`` -tuples. This file contains functions for manipulating such a list. -""" -from typing import Iterable, cast - -from prompt_toolkit.utils import get_cwidth - +""" +Utilities for manipulating formatted text. + +When ``to_formatted_text`` has been called, we get a list of ``(style, text)`` +tuples. This file contains functions for manipulating such a list. +""" +from typing import Iterable, cast + +from prompt_toolkit.utils import get_cwidth + from .base import ( AnyFormattedText, OneStyleAndTextTuple, StyleAndTextTuples, to_formatted_text, ) - -__all__ = [ + +__all__ = [ "to_plain_text", - "fragment_list_len", - "fragment_list_width", - "fragment_list_to_text", - "split_lines", -] - - + "fragment_list_len", + "fragment_list_width", + "fragment_list_to_text", + "split_lines", +] + + def to_plain_text(value: AnyFormattedText) -> str: """ Turn any kind of formatted text back into plain text. @@ -31,68 +31,68 @@ def to_plain_text(value: AnyFormattedText) -> str: return fragment_list_to_text(to_formatted_text(value)) -def fragment_list_len(fragments: StyleAndTextTuples) -> int: - """ - Return the amount of characters in this text fragment list. - - :param fragments: List of ``(style_str, text)`` or - ``(style_str, text, mouse_handler)`` tuples. - """ - ZeroWidthEscape = "[ZeroWidthEscape]" - return sum(len(item[1]) for item in fragments if ZeroWidthEscape not in item[0]) - - -def fragment_list_width(fragments: StyleAndTextTuples) -> int: - """ - Return the character width of this text fragment list. - (Take double width characters into account.) - - :param fragments: List of ``(style_str, text)`` or - ``(style_str, text, mouse_handler)`` tuples. - """ - ZeroWidthEscape = "[ZeroWidthEscape]" - return sum( - get_cwidth(c) - for item in fragments - for c in item[1] - if ZeroWidthEscape not in item[0] - ) - - -def fragment_list_to_text(fragments: StyleAndTextTuples) -> str: - """ - Concatenate all the text parts again. - - :param fragments: List of ``(style_str, text)`` or - ``(style_str, text, mouse_handler)`` tuples. - """ - ZeroWidthEscape = "[ZeroWidthEscape]" - return "".join(item[1] for item in fragments if ZeroWidthEscape not in item[0]) - - -def split_lines(fragments: StyleAndTextTuples) -> Iterable[StyleAndTextTuples]: - """ - Take a single list of (style_str, text) tuples and yield one such list for each - line. Just like str.split, this will yield at least one item. - - :param fragments: List of (style_str, text) or (style_str, text, mouse_handler) - tuples. - """ - line: StyleAndTextTuples = [] - - for style, string, *mouse_handler in fragments: - parts = string.split("\n") - - for part in parts[:-1]: - if part: - line.append(cast(OneStyleAndTextTuple, (style, part, *mouse_handler))) - yield line - line = [] - - line.append(cast(OneStyleAndTextTuple, (style, parts[-1], *mouse_handler))) - - # Always yield the last line, even when this is an empty line. This ensures - # that when `fragments` ends with a newline character, an additional empty - # line is yielded. (Otherwise, there's no way to differentiate between the - # cases where `fragments` does and doesn't end with a newline.) - yield line +def fragment_list_len(fragments: StyleAndTextTuples) -> int: + """ + Return the amount of characters in this text fragment list. + + :param fragments: List of ``(style_str, text)`` or + ``(style_str, text, mouse_handler)`` tuples. + """ + ZeroWidthEscape = "[ZeroWidthEscape]" + return sum(len(item[1]) for item in fragments if ZeroWidthEscape not in item[0]) + + +def fragment_list_width(fragments: StyleAndTextTuples) -> int: + """ + Return the character width of this text fragment list. + (Take double width characters into account.) + + :param fragments: List of ``(style_str, text)`` or + ``(style_str, text, mouse_handler)`` tuples. + """ + ZeroWidthEscape = "[ZeroWidthEscape]" + return sum( + get_cwidth(c) + for item in fragments + for c in item[1] + if ZeroWidthEscape not in item[0] + ) + + +def fragment_list_to_text(fragments: StyleAndTextTuples) -> str: + """ + Concatenate all the text parts again. + + :param fragments: List of ``(style_str, text)`` or + ``(style_str, text, mouse_handler)`` tuples. + """ + ZeroWidthEscape = "[ZeroWidthEscape]" + return "".join(item[1] for item in fragments if ZeroWidthEscape not in item[0]) + + +def split_lines(fragments: StyleAndTextTuples) -> Iterable[StyleAndTextTuples]: + """ + Take a single list of (style_str, text) tuples and yield one such list for each + line. Just like str.split, this will yield at least one item. + + :param fragments: List of (style_str, text) or (style_str, text, mouse_handler) + tuples. + """ + line: StyleAndTextTuples = [] + + for style, string, *mouse_handler in fragments: + parts = string.split("\n") + + for part in parts[:-1]: + if part: + line.append(cast(OneStyleAndTextTuple, (style, part, *mouse_handler))) + yield line + line = [] + + line.append(cast(OneStyleAndTextTuple, (style, parts[-1], *mouse_handler))) + + # Always yield the last line, even when this is an empty line. This ensures + # that when `fragments` ends with a newline character, an additional empty + # line is yielded. (Otherwise, there's no way to differentiate between the + # cases where `fragments` does and doesn't end with a newline.) + yield line |