Skip to content
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
166 changes: 156 additions & 10 deletions lib/rdoc/markup/to_html.rb
Original file line number Diff line number Diff line change
Expand Up @@ -158,19 +158,16 @@ def handle_regexp_RDOCLINK(target)
def handle_regexp_TIDYLINK(target)
text = target.text

return text unless
text =~ /^\{(.*)\}\[(.*?)\]$/ or text =~ /^(\S+)\[(.*?)\]$/

label = $1
url = CGI.escapeHTML($2)
if tidy_link_capturing?
return finish_tidy_link(text)
end

if /^rdoc-image:/ =~ label
label = handle_RDOCLINK(label)
else
label = CGI.escapeHTML(label)
if text.start_with?('{') && !text.include?('}')
start_tidy_link text
return ''
end

gen_url url, label
convert_complete_tidy_link(text)
end

# :section: Visitor
Expand Down Expand Up @@ -458,4 +455,153 @@ def to_html(item)
super convert_flow @am.flow item
end

private

def convert_flow(flow_items)
res = []

flow_items.each do |item|
case item
when String
append_flow_fragment res, convert_string(item)
when RDoc::Markup::AttrChanger
off_tags res, item
on_tags res, item
when RDoc::Markup::RegexpHandling
append_flow_fragment res, convert_regexp_handling(item)
else
raise "Unknown flow element: #{item.inspect}"
end
end

res.join
end

def append_flow_fragment(res, fragment)
return if fragment.nil? || fragment.empty?

emit_tidy_link_fragment(res, fragment)
end

def append_to_tidy_label(fragment)
@tidy_link_buffer << fragment
end

##
# Matches an entire tidy link with a braced label "{label}[url]".
#
# Capture 1: label contents.
# Capture 2: URL text.
# Capture 3: trailing content.
TIDY_LINK_WITH_BRACES = /\A\{(.*?)\}\[(.*?)\](.*)\z/

##
# Matches the tail of a braced tidy link when the opening brace was
# consumed earlier while accumulating the label text.
#
# Capture 1: remaining label content.
# Capture 2: URL text.
# Capture 3: trailing content.
TIDY_LINK_WITH_BRACES_TAIL = /\A(.*?)\}\[(.*?)\](.*)\z/

##
# Matches a tidy link with a single-word label "label[url]".
#
# Capture 1: the single-word label (no whitespace).
# Capture 2: URL text between the brackets.
TIDY_LINK_SINGLE_WORD = /\A(\S+)\[(.*?)\](.*)\z/

def convert_complete_tidy_link(text)
return text unless
text =~ TIDY_LINK_WITH_BRACES or text =~ TIDY_LINK_SINGLE_WORD

label = $1
url = CGI.escapeHTML($2)

label_html = if /^rdoc-image:/ =~ label
handle_RDOCLINK(label)
else
render_tidy_link_label(label)
end

gen_url url, label_html
end

def emit_tidy_link_fragment(res, fragment)
if tidy_link_capturing?
append_to_tidy_label fragment
else
res << fragment
end
end

def finish_tidy_link(text)
label_tail, url, trailing = extract_tidy_link_parts(text)
append_to_tidy_label CGI.escapeHTML(label_tail) unless label_tail.empty?

return '' unless url

label_html = @tidy_link_buffer
@tidy_link_buffer = nil
link = gen_url(url, label_html)

return link if trailing.empty?

link + CGI.escapeHTML(trailing)
end

def extract_tidy_link_parts(text)
if text =~ TIDY_LINK_WITH_BRACES
[$1, CGI.escapeHTML($2), $3]
elsif text =~ TIDY_LINK_WITH_BRACES_TAIL
[$1, CGI.escapeHTML($2), $3]
elsif text =~ TIDY_LINK_SINGLE_WORD
[$1, CGI.escapeHTML($2), $3]
else
[text, nil, '']
end
end

def on_tags(res, item)
each_attr_tag(item.turn_on) do |tag|
emit_tidy_link_fragment(res, annotate(tag.on))
@in_tt += 1 if tt? tag
end
end

def off_tags(res, item)
each_attr_tag(item.turn_off, true) do |tag|
emit_tidy_link_fragment(res, annotate(tag.off))
@in_tt -= 1 if tt? tag
end
end

def start_tidy_link(text)
@tidy_link_buffer = String.new
append_to_tidy_label CGI.escapeHTML(text.delete_prefix('{'))
end

def tidy_link_capturing?
!!@tidy_link_buffer
end

def render_tidy_link_label(label)
RDoc::Markup::LinkLabelToHtml.render(label, @options, @from_path)
end
end

##
# Formatter dedicated to rendering tidy link labels without mutating the
# calling formatter's state.

class RDoc::Markup::LinkLabelToHtml < RDoc::Markup::ToHtml
def self.render(label, options, from_path)
new(options, from_path).to_html(label)
end

def initialize(options, from_path = nil)
super(options)

self.from_path = from_path if from_path
end
end
8 changes: 4 additions & 4 deletions lib/rdoc/markup/to_html_crossref.rb
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,9 @@ def convert_flow(flow_items, &block)

case item
when RDoc::Markup::AttrChanger
if (text = convert_tt_crossref(flow_items, i))
if !tidy_link_capturing? && (text = convert_tt_crossref(flow_items, i))
text = block.call(text, res) if block
res << text
append_flow_fragment res, text
i += 3
next
end
Expand All @@ -206,12 +206,12 @@ def convert_flow(flow_items, &block)
when String
text = convert_string(item)
text = block.call(text, res) if block
res << text
append_flow_fragment res, text
i += 1
when RDoc::Markup::RegexpHandling
text = convert_regexp_handling(item)
text = block.call(text, res) if block
res << text
append_flow_fragment res, text
i += 1
else
raise "Unknown flow element: #{item.inspect}"
Expand Down
20 changes: 20 additions & 0 deletions test/rdoc/rdoc_markdown_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1263,6 +1263,26 @@ def test_gfm_table_with_backslashes_in_code_spans
assert_equal expected, doc
end

def test_markdown_link_with_styled_label
markdown = <<~MD
[Link to Foo](https://example.com)

[Link to `Foo`](https://example.com)

[Link to **Foo**](https://example.com)

[Link to `Foo` and `\Bar` and `Baz`](https://example.com)
MD

doc = parse markdown
html = @to_html.convert doc

assert_includes html, '<a href="https://example.com">Link to Foo</a>'
assert_includes html, '<a href="https://example.com">Link to <code>Foo</code></a>'
assert_includes html, '<a href="https://example.com">Link to <strong>Foo</strong></a>'
assert_includes html, '<a href="https://example.com">Link to <code>Foo</code> and <code>Bar</code> and <code>Baz</code></a>'
end

def parse(text)
@parser.parse text
end
Expand Down
9 changes: 9 additions & 0 deletions test/rdoc/rdoc_markup_to_html_crossref_test.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# frozen_string_literal: true
require_relative 'xref_test_case'
require 'rdoc/markdown'

class RDocMarkupToHtmlCrossrefTest < XrefTestCase

Expand Down Expand Up @@ -288,6 +289,14 @@ def test_handle_regexp_TIDYLINK_label
link, 'C1#m@foo'
end

def test_convert_TIDYLINK_markdown_with_crossrefs
markdown = RDoc::Markdown.parse('[Link to `C1` and `Foo` and `\\C1` and `Bar`](https://example.com)')

result = markdown.accept(@to)

assert_equal para('<a href="https://example.com">Link to <code>C1</code> and <code>Foo</code> and <code>\\C1</code> and <code>Bar</code></a>'), result
end

def test_to_html_CROSSREF_email
@options.hyperlink_all = false

Expand Down
22 changes: 22 additions & 0 deletions test/rdoc/rdoc_markup_to_html_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,28 @@ def test_convert_TIDYLINK_multiple
assert_equal expected, result
end

def test_convert_TIDYLINK_with_code_label
result = @to.convert '{Link to +Foo+}[https://example.com]'

expected = "\n<p><a href=\"https://example.com\">Link to <code>Foo</code></a></p>\n"

assert_equal expected, result

result = @to.convert '{Link to +Foo+ and +Bar+ and +Baz+}[https://example.com]'

expected = "\n<p><a href=\"https://example.com\">Link to <code>Foo</code> and <code>Bar</code> and <code>Baz</code></a></p>\n"

assert_equal expected, result
end

def test_convert_TIDYLINK_with_bold_label
result = @to.convert '{Link to *Foo*}[https://example.com]'

expected = "\n<p><a href=\"https://example.com\">Link to <strong>Foo</strong></a></p>\n"

assert_equal expected, result
end

def test_convert_TIDYLINK_image
result =
@to.convert '{rdoc-image:path/to/image.jpg}[http://example.com]'
Expand Down
Loading