Skip to content

merge html formatting updates into ansi #1568

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 22 additions & 7 deletions prompt_toolkit/formatted_text/ansi.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import Generator, List, Optional
from string import Formatter
from typing import Generator, List, Optional, Tuple, Union

from prompt_toolkit.output.vt100 import BG_ANSI_COLORS, FG_ANSI_COLORS
from prompt_toolkit.output.vt100 import _256_colors as _256_colors_table
Expand Down Expand Up @@ -257,11 +258,17 @@ 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(FORMATTER.vformat(self.value, args, kwargs))

return ANSI(self.value.format(*args, **kwargs))
def __mod__(self, value: object) -> "ANSI":
"""
ANSI('<b>%s</b>') % value
"""
if not isinstance(value, tuple):
value = (value,)

value = tuple(ansi_escape(i) for i in value)
return ANSI(self.value % value)


# Mapping of the ANSI color codes to their names.
Expand All @@ -275,8 +282,16 @@ def format(self, *args: str, **kwargs: str) -> "ANSI":
_256_colors[i] = "#%02x%02x%02x" % (r, g, b)


def ansi_escape(text: str) -> str:
def ansi_escape(text: object) -> str:
"""
Replace characters with a special meaning.
"""
return text.replace("\x1b", "?").replace("\b", "?")
return str(text).replace("\x1b", "?").replace("\b", "?")


class ANSIFormatter(Formatter):
def format_field(self, value: object, format_spec: str) -> str:
return ansi_escape(format(value, format_spec))


FORMATTER = ANSIFormatter()
2 changes: 1 addition & 1 deletion prompt_toolkit/formatted_text/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def format(self, *args: object, **kwargs: object) -> "HTML":
"""
return HTML(FORMATTER.vformat(self.value, args, kwargs))

def __mod__(self, value: Union[object, Tuple[object, ...]]) -> "HTML":
def __mod__(self, value: object) -> "HTML":
"""
HTML('<b>%s</b>') % value
"""
Expand Down
81 changes: 73 additions & 8 deletions tests/test_formatted_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,71 @@ def test_ansi_true_color():
]


def test_ansi_interpolation():
# %-style interpolation.
value = ANSI("\x1b[1m%s\x1b[0m") % "hello\x1b"
assert to_formatted_text(value) == [
("bold", "h"),
("bold", "e"),
("bold", "l"),
("bold", "l"),
("bold", "o"),
("bold", "?"),
]

value = ANSI("\x1b[1m%s\x1b[0m") % ("\x1bhello",)
assert to_formatted_text(value) == [
("bold", "?"),
("bold", "h"),
("bold", "e"),
("bold", "l"),
("bold", "l"),
("bold", "o"),
]

value = ANSI("\x1b[32m%s\x1b[45m%s") % ("He", "\x1bllo")
assert to_formatted_text(value) == [
("ansigreen", "H"),
("ansigreen", "e"),
("ansigreen bg:ansimagenta", "?"),
("ansigreen bg:ansimagenta", "l"),
("ansigreen bg:ansimagenta", "l"),
("ansigreen bg:ansimagenta", "o"),
]

# Format function.
value = ANSI("\x1b[32m{0}\x1b[45m{1}").format("He\x1b", "llo")
assert to_formatted_text(value) == [
("ansigreen", "H"),
("ansigreen", "e"),
("ansigreen", "?"),
("ansigreen bg:ansimagenta", "l"),
("ansigreen bg:ansimagenta", "l"),
("ansigreen bg:ansimagenta", "o"),
]

value = ANSI("\x1b[32m{a}\x1b[45m{b}").format(a="\x1bHe", b="llo")
assert to_formatted_text(value) == [
("ansigreen", "?"),
("ansigreen", "H"),
("ansigreen", "e"),
("ansigreen bg:ansimagenta", "l"),
("ansigreen bg:ansimagenta", "l"),
("ansigreen bg:ansimagenta", "o"),
]

value = ANSI("\x1b[32m{:02d}\x1b[45m{:.3f}").format(3, 3.14159)
assert to_formatted_text(value) == [
("ansigreen", "0"),
("ansigreen", "3"),
("ansigreen bg:ansimagenta", "3"),
("ansigreen bg:ansimagenta", "."),
("ansigreen bg:ansimagenta", "1"),
("ansigreen bg:ansimagenta", "4"),
("ansigreen bg:ansimagenta", "2"),
]


def test_interpolation():
value = Template(" {} ").format(HTML("<b>hello</b>"))

Expand All @@ -125,18 +190,18 @@ def test_interpolation():

def test_html_interpolation():
# %-style interpolation.
value = HTML("<b>%s</b>") % "hello"
assert to_formatted_text(value) == [("class:b", "hello")]
value = HTML("<b>%s</b>") % "&hello"
assert to_formatted_text(value) == [("class:b", "&hello")]

value = HTML("<b>%s</b>") % ("hello",)
assert to_formatted_text(value) == [("class:b", "hello")]
value = HTML("<b>%s</b>") % ("<hello>",)
assert to_formatted_text(value) == [("class:b", "<hello>")]

value = HTML("<b>%s</b><u>%s</u>") % ("hello", "world")
assert to_formatted_text(value) == [("class:b", "hello"), ("class:u", "world")]
value = HTML("<b>%s</b><u>%s</u>") % ("<hello>", "</world>")
assert to_formatted_text(value) == [("class:b", "<hello>"), ("class:u", "</world>")]

# Format function.
value = HTML("<b>{0}</b><u>{1}</u>").format("hello", "world")
assert to_formatted_text(value) == [("class:b", "hello"), ("class:u", "world")]
value = HTML("<b>{0}</b><u>{1}</u>").format("'hello'", '"world"')
assert to_formatted_text(value) == [("class:b", "'hello'"), ("class:u", '"world"')]

value = HTML("<b>{a}</b><u>{b}</u>").format(a="hello", b="world")
assert to_formatted_text(value) == [("class:b", "hello"), ("class:u", "world")]
Expand Down