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
7 changes: 5 additions & 2 deletions humanfriendly/terminal/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ def handle_data(self, data):
if self.link_url:
# Link text is captured literally so that we can reliably check
# whether the text and the URL of the link are the same string.
self.link_text = data
self.link_text += data
elif self.callback and self.preformatted_text_level == 0:
# Text that is not part of a link and not preformatted text is
# passed to the user defined callback to allow for arbitrary
Expand All @@ -227,7 +227,7 @@ def handle_endtag(self, tag):
self.stack.pop(-1)
new_style = self.current_style
if tag == 'a':
if self.urls_match(self.link_text, self.link_url):
if not self.link_url or self.urls_match(self.link_text, self.link_url):
# Don't render the URL when it's part of the link text.
self.emit_style(new_style)
else:
Expand All @@ -237,6 +237,8 @@ def handle_endtag(self, tag):
self.output.write(self.render_url(self.link_url))
self.emit_style(new_style)
self.output.write(')')
self.link_url = None
self.link_text = None
else:
self.emit_style(new_style)
if tag in ('code', 'pre'):
Expand Down Expand Up @@ -270,6 +272,7 @@ def handle_starttag(self, tag, attrs):
# this is the most intuitive way to present a link in a plain text
# interface).
self.link_url = next((v for n, v in attrs if n == 'href'), '')
self.link_text = ''
elif tag == 'b' or tag == 'strong':
self.push_styles(bold=True)
elif tag == 'br':
Expand Down
18 changes: 18 additions & 0 deletions humanfriendly/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1078,6 +1078,24 @@ def callback(text):
title="VPS hosting" rel="external"&gt;True</a>
''')

def test_html_callback_after_link(self):
"""Text callbacks resume after each hyperlink closes."""
result = html_to_ansi('before <a href="url">link</a> after', callback=lambda text: text.upper())
self.assertEqual(ansi_strip(result), 'BEFORE link (url) AFTER')

def test_html_link_text_in_multiple_fragments(self):
"""Nested markup contributes all link text to URL comparison."""
result = html_to_ansi('<a href="https://example.com">https://<b>example</b>.com</a>')
self.assertEqual(ansi_strip(result), 'https://example.com')

def test_html_empty_links(self):
"""Empty anchors do not reuse previous link text or raise errors."""
result = html_to_ansi('<a href="first">first</a><a href="second"></a>')
self.assertEqual(ansi_strip(result), 'first (second)')
self.assertEqual(ansi_strip(html_to_ansi('<a href="url"></a>')), ' (url)')
self.assertEqual(ansi_strip(html_to_ansi('<a>text</a>')), 'text')
self.assertEqual(ansi_strip(html_to_ansi('text</a>')), 'text')

def test_generate_output(self):
"""Test the :func:`humanfriendly.terminal.output()` function."""
text = "Standard output generated by output()"
Expand Down