Class: RDoc::Markup::ToHtmlCrossref

Inherits:
ToHtml show all
Defined in:
lib/rdoc/markup/to_html_crossref.rb

Overview

Subclass of the RDoc::Markup::ToHtml class that supports looking up method names, classes, etc to create links. RDoc::CrossReference is used to generate those links based on the current context.

Constant Summary collapse

ALL_CROSSREF_REGEXP =

:stopdoc:

RDoc::CrossReference::ALL_CROSSREF_REGEXP
CROSSREF_REGEXP =
RDoc::CrossReference::CROSSREF_REGEXP

Constants inherited from ToHtml

RDoc::Markup::ToHtml::HTML_CHARACTER_ALIASES, RDoc::Markup::ToHtml::LIST_TYPE_TO_HTML, RDoc::Markup::ToHtml::TO_HTML_CHARACTERS, RDoc::Markup::ToHtml::URL_CHARACTERS_REGEXP_STR

Constants included from Text

Text::MARKUP_FORMAT, Text::SPACE_SEPARATED_LETTER_CLASS

Instance Attribute Summary collapse

Attributes inherited from ToHtml

#code_object, #from_path, #in_list_entry, #list, #res

Attributes included from Text

#language

Instance Method Summary collapse

Methods inherited from ToHtml

#accept_blank_line, #accept_block_quote, #accept_heading, #accept_list_end, #accept_list_item_end, #accept_list_item_start, #accept_list_start, #accept_paragraph, #accept_raw, #accept_rule, #accept_table, #accept_verbatim, #convert_string, #deduplicate_heading_id, #emit_inline, encode_fallback, #end_accepting, #handle_BOLD, #handle_BOLD_WORD, #handle_EM, #handle_EM_WORD, #handle_HARD_BREAK, #handle_PLAIN_TEXT, #handle_RDOCLINK, #handle_REGEXP_HANDLING_TEXT, #handle_STRIKE, #handle_TIDYLINK, #handle_inline, #handle_regexp_HTML_CHARACTERS, #handle_regexp_QUOTE_AFTER_WORD, #handle_regexp_QUOTE_NOT_AFTER_WORD, #handle_regexp_SUPPRESSED_CROSSREF, #html_list_name, #in_tidylink_label?, #init_regexp_handlings, #list_end_for, #list_item_start, #parsable_text_to_html, #parseable?, #start_accepting, #to_html

Methods included from Text

decode_legacy_label, expand_tabs, #flush_left, #markup, #normalize_comment, #parse, #snippet, #strip_hashes, #strip_newlines, #strip_stars, to_anchor, #wrap

Methods inherited from Formatter

#accept_document, #add_regexp_handling_RDOCLINK, #annotate, #apply_regexp_handling, #convert, #convert_string, gen_relative_url, #handle_BOLD, #handle_BOLD_WORD, #handle_EM, #handle_EM_WORD, #handle_HARD_BREAK, #handle_PLAIN_TEXT, #handle_REGEXP_HANDLING_TEXT, #handle_STRIKE, #handle_TEXT, #handle_TIDYLINK, #handle_inline, #ignore, #parse_url, #traverse_inline_nodes, #tt?

Constructor Details

#initialize(from_path, context, pipe: false, output_decoration: true, hyperlink_all: false, show_hash: false, autolink_excluded_words: [], warn_missing_rdoc_ref: true) ⇒ ToHtmlCrossref

Creates a new crossref resolver that generates links relative to context which lives at from_path in the generated files. ‘#’ characters on references are removed unless show_hash is true. Only method names preceded by ‘#’ or ‘::’ are linked, unless hyperlink_all is true.

Raises:

  • (ArgumentError)


30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/rdoc/markup/to_html_crossref.rb', line 30

def initialize(from_path, context, pipe: false, output_decoration: true,
               hyperlink_all: false, show_hash: false,
               autolink_excluded_words: [], warn_missing_rdoc_ref: true)
  raise ArgumentError, 'from_path cannot be nil' if from_path.nil?

  super(pipe: pipe, output_decoration: output_decoration)

  @context       = context
  @from_path     = from_path
  @hyperlink_all = hyperlink_all
  @show_hash     = show_hash
  @autolink_excluded_words = autolink_excluded_words
  @warn_missing_rdoc_ref = warn_missing_rdoc_ref

  @cross_reference = RDoc::CrossReference.new @context
end

Instance Attribute Details

#contextObject

RDoc::CodeObject for generating references



17
18
19
# File 'lib/rdoc/markup/to_html_crossref.rb', line 17

def context
  @context
end

#show_hashObject

Should we show ‘#’ characters on method references?



22
23
24
# File 'lib/rdoc/markup/to_html_crossref.rb', line 22

def show_hash
  @show_hash
end

Instance Method Details

Applies additional special handling on top of the one defined in ToHtml. When a tidy link is {Foo}[rdoc-ref:Foo], the label part is surrounded by . TODO: reconsider this workaround.



230
231
232
233
234
235
236
# File 'lib/rdoc/markup/to_html_crossref.rb', line 230

def apply_tidylink_label_special_handling(label, url)
  if url == "rdoc-ref:#{label}" && cross_reference(label)&.include?('<code>')
    "<code>#{convert_string(label)}</code>"
  else
    super
  end
end

#cross_reference(name, text = nil, code = true, rdoc_ref: false) ⇒ Object

Creates a link to the reference name if the name exists. If text is given it is used as the link text, otherwise name is used. Returns nil if the link target could not be resolved.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/rdoc/markup/to_html_crossref.rb', line 62

def cross_reference(name, text = nil, code = true, rdoc_ref: false)
  # Strip '#' for link display text (e.g. #method shows as "method" in links)
  display = !@show_hash && name.start_with?('#') ? name[1..] : name

  if !display.end_with?('+@', '-@') && match = display.match(/(.*[^#:])?@(.*)/)
    context_name = match[1]
    label = convert_string(RDoc::Text.decode_legacy_label(match[2]))
    text ||= "#{label} at <code>#{convert_string(context_name)}</code>" if context_name
    text ||= label
    code = false
  else
    text ||= convert_string(display)
  end

  link(name, text, code, rdoc_ref: rdoc_ref)
end

#gen_url(url, text) ⇒ Object

Generates links for rdoc-ref: scheme URLs and allows RDoc::Markup::ToHtml to handle other schemes.



143
144
145
146
147
148
149
150
# File 'lib/rdoc/markup/to_html_crossref.rb', line 143

def gen_url(url, text)
  if url =~ /\Ardoc-ref:/
    name = $'
    cross_reference(name, text, name == text, rdoc_ref: true) || text
  else
    super
  end
end

#handle_regexp_CROSSREF(name) ⇒ Object

We’re invoked when any text matches the CROSSREF pattern. If we find the corresponding reference, generate a link. If the name we’re looking for contains no punctuation, we look for it up the module/class chain. For example, ToHtml is found, even without the RDoc::Markup:: prefix, because we look for it in module Markup first.



86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/rdoc/markup/to_html_crossref.rb', line 86

def handle_regexp_CROSSREF(name)
  return convert_string(name) if in_tidylink_label?
  return name if @autolink_excluded_words&.include?(name)

  return name if name =~ /@[\w-]+\.[\w-]/ # labels that look like emails

  unless @hyperlink_all then
    # This ensures that words entirely consisting of lowercase letters will
    # not have cross-references generated (to suppress lots of erroneous
    # cross-references to "new" in text, for instance)
    return name if name =~ /\A[a-z]*\z/
  end
  cross_reference(name, rdoc_ref: false) || convert_string(name)
end

Handles rdoc-ref: scheme links and allows RDoc::Markup::ToHtml to handle other schemes.



105
106
107
108
109
110
111
112
113
114
115
# File 'lib/rdoc/markup/to_html_crossref.rb', line 105

def handle_regexp_HYPERLINK(url)
  return convert_string(url) if in_tidylink_label?

  case url
  when /\Ardoc-ref:/
    ref = $'
    cross_reference(ref, rdoc_ref: true) || convert_string(ref)
  else
    super
  end
end

target is an rdoc-schemed link that will be converted into a hyperlink. For the rdoc-ref scheme the cross-reference will be looked up and the given name will be used.

All other contents are handled by the superclass



125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/rdoc/markup/to_html_crossref.rb', line 125

def handle_regexp_RDOCLINK(url)
  case url
  when /\Ardoc-ref:/
    if in_tidylink_label?
      convert_string(url)
    else
      ref = $'
      cross_reference(ref, rdoc_ref: true) || convert_string(ref)
    end
  else
    super
  end
end

#handle_TT(code) ⇒ Object



223
224
225
# File 'lib/rdoc/markup/to_html_crossref.rb', line 223

def handle_TT(code)
  emit_inline(tt_cross_reference(code) || "<code>#{convert_string(code)}</code>")
end

:nodoc:



48
49
50
51
52
53
54
55
# File 'lib/rdoc/markup/to_html_crossref.rb', line 48

def init_link_notation_regexp_handlings
  add_regexp_handling_RDOCLINK

  # The crossref must be linked before tidylink because Klass.method[:sym]
  # will be processed as a tidylink first and will be broken.
  crossref_re = @hyperlink_all ? ALL_CROSSREF_REGEXP : CROSSREF_REGEXP
  @markup.add_regexp_handling crossref_re, :CROSSREF
end

Creates an HTML link to name with the given html_string. html_string should be already escaped and may contain HTML tags. Returns the link HTML string, or nil if the reference could not be resolved.



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/rdoc/markup/to_html_crossref.rb', line 157

def link(name, html_string, code = true, rdoc_ref: false)
  if !(name.end_with?('+@', '-@')) and name =~ /(.*[^#:])?@/
    name = $1
    label = $'
  end

  ref = @cross_reference.resolve name if name

  # Non-text source files (C, Ruby, etc.) don't get HTML pages generated,
  # so don't auto-link to them. Explicit rdoc-ref: links are still allowed.
  if !rdoc_ref && RDoc::TopLevel === ref && !ref.text?
    return
  end

  if ref
    path = ref.as_href(@from_path)

    if code and RDoc::CodeObject === ref and !(RDoc::TopLevel === ref)
      html_string = "<code>#{html_string}</code>"
    end
  elsif name
    if rdoc_ref && @warn_missing_rdoc_ref
      puts "#{@from_path}: `rdoc-ref:#{name}` can't be resolved for `#{html_string}`"
    end
    return
  else
    # A bare label reference like @foo still produces a valid anchor link
    return unless label
    path = +""
  end

  if label
    # Decode legacy labels (e.g., "What-27s+Here" -> "What's Here")
    # then convert to GitHub-style anchor format
    decoded_label = RDoc::Text.decode_legacy_label(label)
    formatted_label = RDoc::Text.to_anchor(decoded_label)

    # Case 1: Path already has an anchor (e.g., method link)
    #   Input:  C1#method@label -> path="C1.html#method-i-m"
    #   Output: C1.html#method-i-m-label
    if path =~ /#/
      path << "-#{formatted_label}"

    # Case 2: Label matches a section title
    #   Input:  C1@Section -> path="C1.html", section "Section" exists
    #   Output: C1.html#section (uses section.aref for GitHub-style)
    elsif (section = ref&.sections&.find { |s| decoded_label == s.title })
      path << "##{section.aref}"

    # Case 3: Ref has an aref (class/module context)
    #   Input:  C1@heading -> path="C1.html", ref=C1 class
    #   Output: C1.html#class-c1-heading
    elsif ref.respond_to?(:aref)
      path << "##{ref.aref}-#{formatted_label}"

    # Case 4: No context, just the label (e.g., TopLevel/file)
    #   Input:  README@section -> path="README_md.html"
    #   Output: README_md.html#section
    else
      path << "##{formatted_label}"
    end
  end

  "<a href=\"#{path}\">#{html_string}</a>"
end

#tt_cross_reference(code) ⇒ Object

Handles cross-reference and suppressed-crossref inside tt tag. Returns nil if code is not an existing cross-reference nor a suppressed-crossref.



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/rdoc/markup/to_html_crossref.rb', line 240

def tt_cross_reference(code)
  return if in_tidylink_label?

  crossref_regexp = @hyperlink_all ? ALL_CROSSREF_REGEXP : CROSSREF_REGEXP
  # REGEXP sometimes matches a string that starts with a backslash but is not a
  # suppressed cross-reference (for example, `\+`), so the backslash-removed
  # part needs to be checked against crossref_regexp.
  match = crossref_regexp.match(code.delete_prefix('\\'))
  return unless match && match.begin(1).zero?
  return unless match.post_match.match?(/\A[[:punct:]\s]*\z/)

  # cross_reference(file_page) may return a link without code tag.
  # We need to check it because this method shouldn't return an html text without code tag.
  if code.start_with?('\\')
    # Remove leading backslash if crossref exists
    "<code>#{convert_string(code[1..])}</code>" if cross_reference(code[1..])&.include?('<code>')
  else
    html = cross_reference(code)
    html if html&.include?('<code>')
  end
end