summaryrefslogtreecommitdiff
path: root/lib/rdoc/markup/to_html_crossref.rb
blob: 930218bb7e5eb10324a0e7ffff49662562648852 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
require 'rdoc/markup/to_html'

##
# Subclass of the RDoc::Markup::ToHtml class that supports looking up words in
# the AllReferences list. Those that are found (like AllReferences in this
# comment) will be hyperlinked

class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml

  attr_accessor :context

  # Regular expressions to match class and method references.
  #
  # 1.) There can be a '\' in front of text to suppress
  #     any cross-references (note, however, that the single '\'
  #     is written as '\\\\' in order to escape it twice, once
  #     in the Ruby String literal and once in the regexp).
  # 2.) There can be a '::' in front of class names to reference
  #     from the top-level namespace.
  # 3.) The method can be followed by parenthesis,
  #     which may or may not have things inside (this
  #     apparently is allowed for Fortran 95, but I also think that this
  #     is a good idea for Ruby, as it is very reasonable to want to
  #     reference a call with arguments).
  #
  # NOTE: In order to support Fortran 95 properly, the [A-Z] below
  # should be changed to [A-Za-z].  This slows down rdoc significantly,
  # however, and the Fortran 95 support is broken in any case due to
  # the return in handle_special_CROSSREF if the token consists
  # entirely of lowercase letters.
  #
  # The markup/cross-referencing engine needs a rewrite for
  # Fortran 95 to be supported properly.
  CLASS_REGEXP_STR = '\\\\?((?:\:{2})?[A-Z]\w*(?:\:\:\w+)*)'
  METHOD_REGEXP_STR = '(\w+[!?=]?)(?:\([\.\w+\*\/\+\-\=\<\>]*\))?'

  # Regular expressions matching text that should potentially have
  # cross-reference links generated are passed to add_special.
  # Note that these expressions are meant to pick up text for which
  # cross-references have been suppressed, since the suppression
  # characters are removed by the code that is triggered.
  CROSSREF_REGEXP = /(
                      # A::B::C.meth
                      #{CLASS_REGEXP_STR}[\.\#]#{METHOD_REGEXP_STR}

                      # Stand-alone method (proceeded by a #)
                      | \\?\##{METHOD_REGEXP_STR}

                      # A::B::C
                      # The stuff after CLASS_REGEXP_STR is a
                      # nasty hack.  CLASS_REGEXP_STR unfortunately matches
                      # words like dog and cat (these are legal "class"
                      # names in Fortran 95).  When a word is flagged as a
                      # potential cross-reference, limitations in the markup
                      # engine suppress other processing, such as typesetting.
                      # This is particularly noticeable for contractions.
                      # In order that words like "can't" not
                      # be flagged as potential cross-references, only
                      # flag potential class cross-references if the character
                      # after the cross-referece is a space or sentence
                      # punctuation.
                      | #{CLASS_REGEXP_STR}(?=[\s\)\.\?\!\,\;]|\z)

                      # Things that look like filenames
                      # The key thing is that there must be at least
                      # one special character (period, slash, or
                      # underscore).
                      | (?:\.\.\/)*[-\/\w]+[_\/\.][-\w\/\.]+

                      # Things that have markup suppressed
                      | \\[^\s]
                      )/x

  ##
  # We need to record the html path of our caller so we can generate
  # correct relative paths for any hyperlinks that we find

  def initialize(from_path, context, show_hash)
    raise ArgumentError, 'from_path cannot be nil' if from_path.nil?
    super()

    @markup.add_special(CROSSREF_REGEXP, :CROSSREF)

    @from_path = from_path
    @context = context
    @show_hash = show_hash

    @seen = {}
  end

  ##
  # We're invoked when any text matches the CROSSREF pattern
  # (defined in MarkUp). If we fine the corresponding reference,
  # generate a hyperlink. If the name we're looking for contains
  # no punctuation, we look for it up the module/class chain. For
  # example, HyperlinkHtml is found, even without the Generator::
  # prefix, because we look for it in module Generator first.

  def handle_special_CROSSREF(special)
    name = special.text

    # 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/

    return @seen[name] if @seen.include? name

    if name[0, 1] == '#' then
      lookup = name[1..-1]
      name = lookup unless @show_hash
    else
      lookup = name
    end


    # Find class, module, or method in class or module.
    #
    # Do not, however, use an if/elsif/else chain to do so.  Instead, test
    # each possible pattern until one matches.  The reason for this is that a
    # string like "YAML.txt" could be the txt() class method of class YAML (in
    # which case it would match the first pattern, which splits the string
    # into container and method components and looks up both) or a filename
    # (in which case it would match the last pattern, which just checks
    # whether the string as a whole is a known symbol).

    if /#{CLASS_REGEXP_STR}[\.\#]#{METHOD_REGEXP_STR}/ =~ lookup then
      container = $1
      method = $2
      ref = @context.find_symbol container, method
    end

    ref = @context.find_symbol lookup unless ref

    out = if lookup =~ /^\\/ then
            $'
          elsif ref and ref.document_self then
            "<a href=\"#{ref.as_href(@from_path)}\">#{name}</a>"
          else
            name
          end

    @seen[name] = out

    out
  end

end