1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
|
from __future__ import annotations
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
from prompt_toolkit.key_binding.key_bindings import NotImplementedOrNone
__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], "NotImplementedOrNone"]]
]
# 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: 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 = [("", f"{value}")]
else:
raise ValueError(
"No formatted text. Expecting a unicode object, "
f"HTML, ANSI or a FormattedText instance. Got {value!r}"
)
# 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 f"FormattedText({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
|