Skip to content
Open
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
10 changes: 6 additions & 4 deletions humanfriendly/terminal/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,12 @@ def close(self):
the input but a style is still active. This is intended to prevent
malformed HTML from messing up terminal output.
"""
if any(self.stack):
self.output.write(ANSI_RESET)
self.stack = []
HTMLParser.close(self)
try:
HTMLParser.close(self)
finally:
if any(self.stack):
self.output.write(ANSI_RESET)
self.stack = []

def emit_style(self, style=None):
"""
Expand Down
33 changes: 33 additions & 0 deletions humanfriendly/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1010,6 +1010,39 @@ def test_ansi_wrap(self):
# Make sure ansi_wrap() ends the text by resetting the ANSI styles.
assert ansi_wrap(text, bold=True).endswith(ANSI_RESET)

def test_html_buffered_tail_keeps_style(self):
"""Flush buffered character references before closing active styles."""
for tag in ('b', 'em', 'code', 'span style="color: red"'):
for tail in ('&', '&amp'):
result = html_to_ansi('<' + tag + '>tail ' + tail)
self.assertTrue(result.endswith('tail &' + ANSI_RESET))
self.assertEqual(html_to_ansi('tail &'), 'tail &')

def test_html_streaming_close_keeps_style(self):
"""Explicit close also styles data buffered across feed calls."""
from humanfriendly.terminal.html import HTMLConverter
converter = HTMLConverter()
converter.feed('<b>tail ')
converter.feed('&amp')
converter.close()
self.assertEqual(converter.output.getvalue(), '\x1b[0m\x1b[1mtail &' + ANSI_RESET)
value = converter.output.getvalue()
converter.close()
self.assertEqual(converter.output.getvalue(), value)

def test_html_close_resets_style_after_callback_error(self):
"""A callback failure during flushing still closes terminal styles."""
from humanfriendly.terminal.html import HTMLConverter

def callback(text):
raise ValueError('callback failed')

converter = HTMLConverter(callback=callback)
converter.feed('<b>tail &')
with self.assertRaises(ValueError):
converter.close()
self.assertTrue(converter.output.getvalue().endswith(ANSI_RESET))

def test_html_to_ansi(self):
"""Test the :func:`humanfriendly.terminal.html_to_ansi()` function."""
assert html_to_ansi("Just some plain text") == "Just some plain text"
Expand Down