rdoc 6.17.0 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +193 -0
  3. data/LEGAL.rdoc +6 -0
  4. data/LICENSE.rdoc +4 -0
  5. data/README.md +132 -8
  6. data/RI.md +75 -75
  7. data/doc/markup_reference/markdown.md +659 -0
  8. data/doc/markup_reference/rdoc.rdoc +1169 -0
  9. data/exe/rdoc +2 -2
  10. data/lib/rdoc/code_object/alias.rb +70 -74
  11. data/lib/rdoc/code_object/any_method.rb +308 -293
  12. data/lib/rdoc/code_object/attr.rb +150 -142
  13. data/lib/rdoc/code_object/class_module.rb +802 -715
  14. data/lib/rdoc/code_object/constant.rb +181 -144
  15. data/lib/rdoc/code_object/context/section.rb +140 -130
  16. data/lib/rdoc/code_object/context.rb +925 -941
  17. data/lib/rdoc/code_object/extend.rb +7 -5
  18. data/lib/rdoc/code_object/include.rb +7 -5
  19. data/lib/rdoc/code_object/method_attr.rb +328 -315
  20. data/lib/rdoc/code_object/mixin.rb +97 -92
  21. data/lib/rdoc/code_object/normal_class.rb +77 -78
  22. data/lib/rdoc/code_object/normal_module.rb +61 -59
  23. data/lib/rdoc/code_object/require.rb +23 -39
  24. data/lib/rdoc/code_object/single_class.rb +21 -19
  25. data/lib/rdoc/code_object/top_level.rb +215 -202
  26. data/lib/rdoc/code_object.rb +305 -305
  27. data/lib/rdoc/comment.rb +274 -337
  28. data/lib/rdoc/cross_reference.rb +195 -206
  29. data/lib/rdoc/encoding.rb +105 -103
  30. data/lib/rdoc/erb_partial.rb +13 -11
  31. data/lib/rdoc/erbio.rb +29 -27
  32. data/lib/rdoc/generator/aliki.rb +196 -30
  33. data/lib/rdoc/generator/darkfish.rb +647 -629
  34. data/lib/rdoc/generator/json_index.rb +233 -229
  35. data/lib/rdoc/generator/markup.rb +165 -122
  36. data/lib/rdoc/generator/pot/message_extractor.rb +57 -51
  37. data/lib/rdoc/generator/pot/po.rb +52 -51
  38. data/lib/rdoc/generator/pot/po_entry.rb +138 -132
  39. data/lib/rdoc/generator/pot.rb +85 -81
  40. data/lib/rdoc/generator/ri.rb +23 -19
  41. data/lib/rdoc/generator/template/aliki/DESIGN.md +538 -0
  42. data/lib/rdoc/generator/template/aliki/_aside_toc.rhtml +1 -1
  43. data/lib/rdoc/generator/template/aliki/_footer.rhtml +2 -2
  44. data/lib/rdoc/generator/template/aliki/_head.rhtml +15 -10
  45. data/lib/rdoc/generator/template/aliki/_header.rhtml +29 -44
  46. data/lib/rdoc/generator/template/aliki/_sidebar_extends.rhtml +8 -6
  47. data/lib/rdoc/generator/template/aliki/_sidebar_includes.rhtml +8 -6
  48. data/lib/rdoc/generator/template/aliki/_sidebar_installed.rhtml +1 -1
  49. data/lib/rdoc/generator/template/aliki/_sidebar_pages.rhtml +3 -3
  50. data/lib/rdoc/generator/template/aliki/_sidebar_search.rhtml +3 -3
  51. data/lib/rdoc/generator/template/aliki/_sidebar_sections.rhtml +1 -1
  52. data/lib/rdoc/generator/template/aliki/_sidebar_toggle.rhtml +1 -1
  53. data/lib/rdoc/generator/template/aliki/class.rhtml +71 -61
  54. data/lib/rdoc/generator/template/aliki/css/rdoc.css +796 -323
  55. data/lib/rdoc/generator/template/aliki/index.rhtml +1 -1
  56. data/lib/rdoc/generator/template/aliki/js/aliki.js +119 -113
  57. data/lib/rdoc/generator/template/aliki/js/bash_highlighter.js +167 -0
  58. data/lib/rdoc/generator/template/aliki/js/c_highlighter.js +1 -1
  59. data/lib/rdoc/generator/template/aliki/js/{search.js → search_controller.js} +16 -7
  60. data/lib/rdoc/generator/template/aliki/js/search_navigation.js +105 -0
  61. data/lib/rdoc/generator/template/aliki/js/search_ranker.js +239 -0
  62. data/lib/rdoc/generator/template/aliki/page.rhtml +1 -1
  63. data/lib/rdoc/generator/template/aliki/servlet_not_found.rhtml +1 -1
  64. data/lib/rdoc/generator/template/aliki/servlet_root.rhtml +2 -2
  65. data/lib/rdoc/generator/template/darkfish/_footer.rhtml +1 -1
  66. data/lib/rdoc/generator/template/darkfish/_sidebar_extends.rhtml +8 -6
  67. data/lib/rdoc/generator/template/darkfish/_sidebar_includes.rhtml +8 -6
  68. data/lib/rdoc/generator/template/darkfish/_sidebar_installed.rhtml +1 -1
  69. data/lib/rdoc/generator/template/darkfish/_sidebar_pages.rhtml +2 -2
  70. data/lib/rdoc/generator/template/darkfish/_sidebar_sections.rhtml +1 -1
  71. data/lib/rdoc/generator/template/darkfish/_sidebar_table_of_contents.rhtml +5 -5
  72. data/lib/rdoc/generator/template/darkfish/class.rhtml +29 -32
  73. data/lib/rdoc/generator/template/darkfish/css/rdoc.css +19 -1
  74. data/lib/rdoc/generator/template/darkfish/table_of_contents.rhtml +3 -3
  75. data/lib/rdoc/generator.rb +48 -46
  76. data/lib/rdoc/i18n/locale.rb +99 -95
  77. data/lib/rdoc/i18n/text.rb +109 -105
  78. data/lib/rdoc/i18n.rb +7 -5
  79. data/lib/rdoc/markdown/byte_runtime.rb +80 -0
  80. data/lib/rdoc/markdown.kpeg +52 -33
  81. data/lib/rdoc/markdown.rb +365 -177
  82. data/lib/rdoc/markup/blank_line.rb +25 -23
  83. data/lib/rdoc/markup/block_quote.rb +12 -8
  84. data/lib/rdoc/markup/document.rb +127 -123
  85. data/lib/rdoc/markup/element.rb +21 -0
  86. data/lib/rdoc/markup/formatter.rb +220 -203
  87. data/lib/rdoc/markup/hard_break.rb +30 -27
  88. data/lib/rdoc/markup/heading.rb +163 -77
  89. data/lib/rdoc/markup/include.rb +33 -29
  90. data/lib/rdoc/markup/indented_paragraph.rb +37 -33
  91. data/lib/rdoc/markup/inline_parser.rb +316 -0
  92. data/lib/rdoc/markup/list.rb +80 -88
  93. data/lib/rdoc/markup/list_item.rb +73 -85
  94. data/lib/rdoc/markup/paragraph.rb +23 -19
  95. data/lib/rdoc/markup/parser.rb +501 -497
  96. data/lib/rdoc/markup/pre_process.rb +284 -305
  97. data/lib/rdoc/markup/raw.rb +52 -55
  98. data/lib/rdoc/markup/rule.rb +16 -12
  99. data/lib/rdoc/markup/table.rb +48 -40
  100. data/lib/rdoc/markup/to_ansi.rb +143 -92
  101. data/lib/rdoc/markup/to_bs.rb +86 -102
  102. data/lib/rdoc/markup/to_html.rb +608 -506
  103. data/lib/rdoc/markup/to_html_crossref.rb +219 -210
  104. data/lib/rdoc/markup/to_html_snippet.rb +210 -205
  105. data/lib/rdoc/markup/to_joined_paragraph.rb +40 -41
  106. data/lib/rdoc/markup/to_label.rb +64 -51
  107. data/lib/rdoc/markup/to_markdown.rb +212 -184
  108. data/lib/rdoc/markup/to_rdoc.rb +341 -277
  109. data/lib/rdoc/markup/to_table_of_contents.rb +66 -62
  110. data/lib/rdoc/markup/to_test.rb +62 -50
  111. data/lib/rdoc/markup/to_tt_only.rb +83 -92
  112. data/lib/rdoc/markup/verbatim.rb +62 -58
  113. data/lib/rdoc/markup.rb +196 -215
  114. data/lib/rdoc/options.rb +1063 -1076
  115. data/lib/rdoc/parser/c.rb +1039 -1036
  116. data/lib/rdoc/parser/changelog.rb +320 -287
  117. data/lib/rdoc/parser/markdown.rb +17 -13
  118. data/lib/rdoc/parser/rbs.rb +279 -0
  119. data/lib/rdoc/parser/rd.rb +17 -13
  120. data/lib/rdoc/parser/ruby.rb +1231 -2222
  121. data/lib/rdoc/parser/ruby_colorizer.rb +303 -0
  122. data/lib/rdoc/parser/simple.rb +31 -27
  123. data/lib/rdoc/parser/text.rb +12 -8
  124. data/lib/rdoc/parser.rb +230 -221
  125. data/lib/rdoc/rbs_helper.rb +186 -0
  126. data/lib/rdoc/rd/inline.rb +57 -53
  127. data/lib/rdoc/rd.rb +90 -88
  128. data/lib/rdoc/rdoc.rb +547 -366
  129. data/lib/rdoc/ri/driver.rb +1141 -1130
  130. data/lib/rdoc/ri/formatter.rb +7 -3
  131. data/lib/rdoc/ri/paths.rb +140 -136
  132. data/lib/rdoc/ri/servlet.rb +456 -0
  133. data/lib/rdoc/ri/store.rb +4 -2
  134. data/lib/rdoc/ri/task.rb +55 -51
  135. data/lib/rdoc/ri.rb +14 -11
  136. data/lib/rdoc/rubygems_hook.rb +194 -192
  137. data/lib/rdoc/server.rb +462 -0
  138. data/lib/rdoc/stats/normal.rb +46 -42
  139. data/lib/rdoc/stats/quiet.rb +39 -35
  140. data/lib/rdoc/stats/verbose.rb +35 -31
  141. data/lib/rdoc/stats.rb +363 -338
  142. data/lib/rdoc/store.rb +919 -725
  143. data/lib/rdoc/task.rb +260 -255
  144. data/lib/rdoc/text.rb +164 -240
  145. data/lib/rdoc/token_stream.rb +88 -106
  146. data/lib/rdoc/tom_doc.rb +203 -201
  147. data/lib/rdoc/version.rb +1 -1
  148. data/lib/rdoc.rb +35 -7
  149. data/lib/rubygems_plugin.rb +2 -11
  150. data/rdoc-logo.svg +43 -0
  151. data/rdoc.gemspec +9 -7
  152. metadata +46 -32
  153. data/CONTRIBUTING.rdoc +0 -219
  154. data/ExampleMarkdown.md +0 -39
  155. data/ExampleRDoc.rdoc +0 -210
  156. data/lib/rdoc/code_object/anon_class.rb +0 -10
  157. data/lib/rdoc/code_object/ghost_method.rb +0 -6
  158. data/lib/rdoc/code_object/meta_method.rb +0 -6
  159. data/lib/rdoc/markdown/literals.kpeg +0 -21
  160. data/lib/rdoc/markdown/literals.rb +0 -454
  161. data/lib/rdoc/markup/attr_changer.rb +0 -22
  162. data/lib/rdoc/markup/attr_span.rb +0 -35
  163. data/lib/rdoc/markup/attribute_manager.rb +0 -405
  164. data/lib/rdoc/markup/attributes.rb +0 -70
  165. data/lib/rdoc/markup/regexp_handling.rb +0 -40
  166. data/lib/rdoc/parser/prism_ruby.rb +0 -1100
  167. data/lib/rdoc/parser/ripper_state_lex.rb +0 -302
  168. data/lib/rdoc/parser/ruby_tools.rb +0 -163
  169. data/lib/rdoc/servlet.rb +0 -452
@@ -1,609 +1,711 @@
1
1
  # frozen_string_literal: true
2
2
  require 'cgi/escape'
3
3
  require 'cgi/util' unless defined?(CGI::EscapeExt)
4
+ require 'prism'
5
+
6
+ module RDoc
7
+ class Markup
8
+ ##
9
+ # Outputs RDoc markup as HTML.
10
+
11
+ class ToHtml < Markup::Formatter
12
+
13
+ include Text
14
+
15
+ # :section: Utilities
16
+
17
+ ##
18
+ # Maps RDoc::Markup::Parser::LIST_TOKENS types to HTML tags
19
+
20
+ LIST_TYPE_TO_HTML = {
21
+ :BULLET => ['<ul>', '</ul>'],
22
+ :LABEL => ['<dl class="rdoc-list label-list">', '</dl>'],
23
+ :LALPHA => ['<ol style="list-style-type: lower-alpha">', '</ol>'],
24
+ :NOTE => ['<dl class="rdoc-list note-list">', '</dl>'],
25
+ :NUMBER => ['<ol>', '</ol>'],
26
+ :UALPHA => ['<ol style="list-style-type: upper-alpha">', '</ol>'],
27
+ }
28
+
29
+ attr_reader :res # :nodoc:
30
+ attr_reader :in_list_entry # :nodoc:
31
+ attr_reader :list # :nodoc:
32
+
33
+ ##
34
+ # The RDoc::CodeObject HTML is being generated for. This is used to
35
+ # generate namespaced URI fragments
36
+
37
+ attr_accessor :code_object
38
+
39
+ ##
40
+ # Path to this document for relative links
41
+
42
+ attr_accessor :from_path
43
+
44
+ # :section:
45
+
46
+ # Maps an encoding to a Hash of characters properly transcoded for that
47
+ # encoding.
48
+ #
49
+ # See also encode_fallback.
50
+
51
+ TO_HTML_CHARACTERS = Hash.new do |h, encoding|
52
+ h[encoding] = {
53
+ close_dquote: encode_fallback('”', encoding, '"'),
54
+ close_squote: encode_fallback('’', encoding, '\''),
55
+ copyright: encode_fallback('©', encoding, '(c)'),
56
+ ellipsis: encode_fallback('…', encoding, '...'),
57
+ dot_ellipsis: encode_fallback('.…', encoding, '....'),
58
+ em_dash: encode_fallback('—', encoding, '---'),
59
+ en_dash: encode_fallback('–', encoding, '--'),
60
+ open_dquote: encode_fallback('“', encoding, '"'),
61
+ open_squote: encode_fallback('‘', encoding, '\''),
62
+ trademark: encode_fallback('®', encoding, '(r)'),
63
+ }
64
+ end
4
65
 
5
- ##
6
- # Outputs RDoc markup as HTML.
7
-
8
- class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
9
-
10
- include RDoc::Text
11
-
12
- # :section: Utilities
13
-
14
- ##
15
- # Maps RDoc::Markup::Parser::LIST_TOKENS types to HTML tags
16
-
17
- LIST_TYPE_TO_HTML = {
18
- :BULLET => ['<ul>', '</ul>'],
19
- :LABEL => ['<dl class="rdoc-list label-list">', '</dl>'],
20
- :LALPHA => ['<ol style="list-style-type: lower-alpha">', '</ol>'],
21
- :NOTE => ['<dl class="rdoc-list note-list">', '</dl>'],
22
- :NUMBER => ['<ol>', '</ol>'],
23
- :UALPHA => ['<ol style="list-style-type: upper-alpha">', '</ol>'],
24
- }
25
-
26
- attr_reader :res # :nodoc:
27
- attr_reader :in_list_entry # :nodoc:
28
- attr_reader :list # :nodoc:
29
-
30
- ##
31
- # The RDoc::CodeObject HTML is being generated for. This is used to
32
- # generate namespaced URI fragments
33
-
34
- attr_accessor :code_object
35
-
36
- ##
37
- # Path to this document for relative links
38
-
39
- attr_accessor :from_path
40
-
41
- # :section:
42
-
43
- ##
44
- # Creates a new formatter that will output HTML
45
-
46
- def initialize(options, markup = nil)
47
- super
48
-
49
- @code_object = nil
50
- @from_path = ''
51
- @in_list_entry = nil
52
- @list = nil
53
- @th = nil
54
- @hard_break = "<br>\n"
66
+ HTML_CHARACTER_ALIASES = {
67
+ '(c)' => :copyright,
68
+ '(C)' => :copyright,
69
+ '(r)' => :trademark,
70
+ '(R)' => :trademark,
71
+ '---' => :em_dash,
72
+ '--' => :en_dash,
73
+ '....' => :dot_ellipsis,
74
+ '...' => :ellipsis,
75
+ '``' => :open_dquote,
76
+ "''" => :close_dquote,
77
+ }
78
+ HTML_CHARACTERS_REGEXP = Regexp.union(HTML_CHARACTER_ALIASES.keys)
79
+
80
+ # Transcodes +character+ to +encoding+ with a +fallback+ character.
81
+
82
+ def self.encode_fallback(character, encoding, fallback)
83
+ character.encode(
84
+ encoding,
85
+ fallback: { character => fallback },
86
+ undef: :replace,
87
+ replace: fallback
88
+ )
89
+ end
55
90
 
56
- init_regexp_handlings
91
+ # Converts ascii quote pairs to multibyte quote characters
92
+ class QuoteConverter
93
+
94
+ def initialize
95
+ @in_dquote = false
96
+ @in_squote = false
97
+ end
98
+
99
+ def convert(quote, after_word:)
100
+ case quote
101
+ when '"'
102
+ type = @in_dquote ? :close_dquote : :open_dquote
103
+ @in_dquote = !@in_dquote
104
+ when "'"
105
+ if @in_squote
106
+ type = :close_squote
107
+ @in_squote = false
108
+ elsif after_word
109
+ # Mary's dog, my parents' house: do not start paired quotes
110
+ type = :close_squote
111
+ else
112
+ type = :open_squote
113
+ @in_squote = true
114
+ end
115
+ when '`'
116
+ # Opening quote of <tt>`quoted sentence'</tt>.
117
+ # This will conflict with code blocks <tt>`puts('hello')`</tt> in the future.
118
+ if !@in_squote && !after_word
119
+ type = :open_squote
120
+ @in_squote = true
121
+ end
122
+ end
123
+ TO_HTML_CHARACTERS[quote.encoding][type] if type
124
+ end
125
+ end
57
126
 
58
- init_tags
59
- end
127
+ ##
128
+ # Creates a new formatter that will output HTML
60
129
 
61
- # :section: Regexp Handling
62
- #
63
- # These methods are used by regexp handling markup added by RDoc::Markup#add_regexp_handling.
130
+ def initialize(pipe: false, output_decoration: true)
131
+ super()
64
132
 
65
- # :nodoc:
66
- URL_CHARACTERS_REGEXP_STR = /[A-Za-z0-9\-._~:\/\?#\[\]@!$&'\(\)*+,;%=]/.source
133
+ @pipe = pipe
134
+ @output_decoration = output_decoration
135
+ @code_object = nil
136
+ @from_path = ''
137
+ @in_list_entry = nil
138
+ @list = nil
139
+ @th = nil
140
+ @quote_converter = nil
141
+ @in_tidylink_label = false
142
+ @hard_break = "<br>\n"
67
143
 
68
- ##
69
- # Adds regexp handlings.
144
+ init_regexp_handlings
145
+ end
70
146
 
71
- def init_regexp_handlings
72
- # external links
73
- @markup.add_regexp_handling(/(?:link:|https?:|mailto:|ftp:|irc:|www\.)#{URL_CHARACTERS_REGEXP_STR}+\w/,
74
- :HYPERLINK)
75
- init_link_notation_regexp_handlings
76
- end
147
+ # :section: Regexp Handling
148
+ #
149
+ # These methods are used by regexp handling markup added by RDoc::Markup#add_regexp_handling.
77
150
 
78
- ##
79
- # Adds regexp handlings about link notations.
151
+ ##
152
+ # URL schemes that are not linked because they can run scripts or open
153
+ # local files. Links to them are rendered as their text only.
80
154
 
81
- def init_link_notation_regexp_handlings
82
- add_regexp_handling_RDOCLINK
83
- add_regexp_handling_TIDYLINK
84
- end
155
+ UNSAFE_URL_SCHEME_REGEXP = /\A(?:javascript|vbscript|data|file):/i
85
156
 
86
- def handle_RDOCLINK(url) # :nodoc:
87
- case url
88
- when /^rdoc-ref:/
89
- CGI.escapeHTML($')
90
- when /^rdoc-label:/
91
- text = $'
92
-
93
- text = case text
94
- when /\Alabel-/ then $'
95
- when /\Afootmark-/ then $'
96
- when /\Afoottext-/ then $'
97
- else text
98
- end
99
-
100
- gen_url CGI.escapeHTML(url), CGI.escapeHTML(text)
101
- when /^rdoc-image:/
102
- # Split the string after "rdoc-image:" into url and alt.
103
- # "path/to/image.jpg:alt text" => ["path/to/image.jpg", "alt text"]
104
- # "http://example.com/path/to/image.jpg:alt text" => ["http://example.com/path/to/image.jpg", "alt text"]
105
- url, alt = $'.split(/:(?!\/)/, 2)
106
- if alt && !alt.empty?
107
- %[<img src="#{CGI.escapeHTML(url)}" alt="#{CGI.escapeHTML(alt)}">]
108
- else
109
- %[<img src="#{CGI.escapeHTML(url)}">]
110
- end
111
- when /\Ardoc-[a-z]+:/
112
- CGI.escapeHTML($')
113
- end
114
- end
157
+ # :nodoc:
158
+ URL_CHARACTERS_REGEXP_STR = /[A-Za-z0-9\-._~:\/\?#\[\]@!$&'\(\)*+,;%=]/.source
115
159
 
116
- ##
117
- # +target+ is a <code><br></code>
160
+ ##
161
+ # Adds regexp handlings.
118
162
 
119
- def handle_regexp_HARD_BREAK(target)
120
- '<br>'
121
- end
163
+ def init_regexp_handlings
164
+ # external links
165
+ @markup.add_regexp_handling(/(?:link:|https?:|mailto:|ftp:|irc:|www\.)#{URL_CHARACTERS_REGEXP_STR}+\w/o,
166
+ :HYPERLINK)
122
167
 
123
- ##
124
- # +target+ is a potential link. The following schemes are handled:
125
- #
126
- # <tt>mailto:</tt>::
127
- # Inserted as-is.
128
- # <tt>http:</tt>::
129
- # Links are checked to see if they reference an image. If so, that image
130
- # gets inserted using an <tt><img></tt> tag. Otherwise a conventional
131
- # <tt><a href></tt> is used.
132
- # <tt>link:</tt>::
133
- # Reference to a local file relative to the output directory.
134
-
135
- def handle_regexp_HYPERLINK(target)
136
- url = CGI.escapeHTML(target.text)
137
-
138
- gen_url url, url
139
- end
168
+ # suppress crossref: \#method \::method \ClassName \method_with_underscores
169
+ @markup.add_regexp_handling(/\\(?:[#:A-Z]|[a-z]+_[a-z0-9])/, :SUPPRESSED_CROSSREF)
140
170
 
141
- ##
142
- # +target+ is an rdoc-schemed link that will be converted into a hyperlink.
143
- #
144
- # For the +rdoc-ref+ scheme the named reference will be returned without
145
- # creating a link.
146
- #
147
- # For the +rdoc-label+ scheme the footnote and label prefixes are stripped
148
- # when creating a link. All other contents will be linked verbatim.
149
-
150
- def handle_regexp_RDOCLINK(target)
151
- handle_RDOCLINK target.text
152
- end
171
+ @markup.add_regexp_handling(HTML_CHARACTERS_REGEXP, :HTML_CHARACTERS)
153
172
 
154
- ##
155
- # This +target+ is a link where the label is different from the URL
156
- # <tt>label[url]</tt> or <tt>{long label}[url]</tt>
173
+ @markup.add_regexp_handling(/\b['"`]/, :QUOTE_AFTER_WORD)
174
+ @markup.add_regexp_handling(/\B['"`]/, :QUOTE_NOT_AFTER_WORD)
157
175
 
158
- def handle_regexp_TIDYLINK(target)
159
- text = target.text
176
+ init_link_notation_regexp_handlings
177
+ end
160
178
 
161
- if tidy_link_capturing?
162
- return finish_tidy_link(text)
163
- end
179
+ ##
180
+ # Adds regexp handlings about link notations.
164
181
 
165
- if text.start_with?('{') && !text.include?('}')
166
- start_tidy_link text
167
- return ''
168
- end
182
+ def init_link_notation_regexp_handlings
183
+ add_regexp_handling_RDOCLINK
184
+ end
169
185
 
170
- convert_complete_tidy_link(text)
171
- end
186
+ def handle_RDOCLINK(url) # :nodoc:
187
+ case url
188
+ when /^rdoc-ref:/
189
+ CGI.escapeHTML($')
190
+ when /^rdoc-label:/
191
+ return CGI.escapeHTML(url) if in_tidylink_label?
192
+ text = $'
193
+
194
+ text = case text
195
+ when /\Alabel-/ then $'
196
+ when /\Afootmark-/ then $'
197
+ when /\Afoottext-/ then $'
198
+ else text
199
+ end
172
200
 
173
- # :section: Visitor
174
- #
175
- # These methods implement the HTML visitor.
201
+ gen_url CGI.escapeHTML(url), CGI.escapeHTML(text)
202
+ when /^rdoc-image:/
203
+ # Split the string after "rdoc-image:" into url and alt.
204
+ # "path/to/image.jpg:alt text" => ["path/to/image.jpg", "alt text"]
205
+ # "http://example.com/path/to/image.jpg:alt text" => ["http://example.com/path/to/image.jpg", "alt text"]
206
+ url, alt = $'.split(/:(?!\/)/, 2)
207
+ if alt && !alt.empty?
208
+ %[<img src="#{CGI.escapeHTML(url)}" alt="#{CGI.escapeHTML(alt)}">]
209
+ else
210
+ %[<img src="#{CGI.escapeHTML(url)}">]
211
+ end
212
+ when /\Ardoc-[a-z]+:/
213
+ CGI.escapeHTML($')
214
+ end
215
+ end
176
216
 
177
- ##
178
- # Prepares the visitor for HTML generation
217
+ def handle_PLAIN_TEXT(text)
218
+ emit_inline(convert_string(text))
219
+ end
179
220
 
180
- def start_accepting
181
- @res = []
182
- @in_list_entry = []
183
- @list = []
184
- end
221
+ def handle_REGEXP_HANDLING_TEXT(text)
222
+ emit_inline(text)
223
+ end
185
224
 
186
- ##
187
- # Returns the generated output
225
+ def handle_BOLD(nodes)
226
+ emit_inline('<strong>')
227
+ super
228
+ emit_inline('</strong>')
229
+ end
188
230
 
189
- def end_accepting
190
- @res.join
191
- end
231
+ def handle_EM(nodes)
232
+ emit_inline('<em>')
233
+ super
234
+ emit_inline('</em>')
235
+ end
192
236
 
193
- ##
194
- # Adds +block_quote+ to the output
237
+ def handle_BOLD_WORD(word)
238
+ emit_inline('<strong>')
239
+ super
240
+ emit_inline('</strong>')
241
+ end
195
242
 
196
- def accept_block_quote(block_quote)
197
- @res << "\n<blockquote>"
243
+ def handle_EM_WORD(word)
244
+ emit_inline('<em>')
245
+ super
246
+ emit_inline('</em>')
247
+ end
198
248
 
199
- block_quote.parts.each do |part|
200
- part.accept self
201
- end
249
+ def handle_TT(code)
250
+ emit_inline('<code>')
251
+ super
252
+ emit_inline('</code>')
253
+ end
202
254
 
203
- @res << "</blockquote>\n"
204
- end
255
+ def handle_STRIKE(nodes)
256
+ emit_inline('<del>')
257
+ super
258
+ emit_inline('</del>')
259
+ end
205
260
 
206
- ##
207
- # Adds +paragraph+ to the output
208
-
209
- def accept_paragraph(paragraph)
210
- @res << "\n<p>"
211
- text = paragraph.text @hard_break
212
- text = text.gsub(/(#{SPACE_SEPARATED_LETTER_CLASS})?\K\r?\n(?=(?(1)(#{SPACE_SEPARATED_LETTER_CLASS})?))/o) {
213
- defined?($2) && ' '
214
- }
215
- @res << to_html(text)
216
- @res << "</p>\n"
217
- end
261
+ def handle_HARD_BREAK
262
+ emit_inline('<br>')
263
+ end
218
264
 
219
- ##
220
- # Adds +verbatim+ to the output
265
+ def emit_inline(text)
266
+ @inline_output << text
267
+ end
221
268
 
222
- def accept_verbatim(verbatim)
223
- text = verbatim.text.rstrip
269
+ # Returns true if we are processing inside a tidy link label.
224
270
 
225
- klass = nil
271
+ def in_tidylink_label?
272
+ @in_tidylink_label
273
+ end
226
274
 
227
- content = if verbatim.ruby? or parseable? text then
228
- begin
229
- tokens = RDoc::Parser::RipperStateLex.parse text
230
- klass = ' class="ruby"'
275
+ # Special handling for tidy link labels.
276
+ # When a tidy link is <tt>{rdoc-image:path/to/image.jpg:alt text}[http://example.com]</tt>,
277
+ # label part is normally considered RDOCLINK <tt>rdoc-image:path/to/image.jpg:alt</tt> and a text <tt>" text"</tt>
278
+ # but RDoc's test code expects the whole label part to be treated as RDOCLINK only in tidy link label.
279
+ # When a tidy link is <tt>{^1}[url]</tt> or <tt>{*1}[url]</tt>, the label part needs to drop leading * or ^.
280
+ # TODO: reconsider this workaround.
231
281
 
232
- result = RDoc::TokenStream.to_html tokens
233
- result = result + "\n" unless "\n" == result[-1]
234
- result
235
- rescue
236
- CGI.escapeHTML text
237
- end
238
- else
239
- CGI.escapeHTML text
240
- end
241
-
242
- if @options.pipe then
243
- @res << "\n<pre><code>#{CGI.escapeHTML text}\n</code></pre>\n"
244
- else
245
- @res << "\n<pre#{klass}>#{content}</pre>\n"
246
- end
247
- end
282
+ def apply_tidylink_label_special_handling(label, url)
283
+ # ^1 *1 will be converted to just 1 in tidy link label.
284
+ return label[1..] if label.match?(/\A[*^]\d+\z/)
248
285
 
249
- ##
250
- # Adds +rule+ to the output
286
+ # rdoc-image in label specially allows spaces in alt text.
287
+ return handle_RDOCLINK(label) if label.start_with?('rdoc-image:')
288
+ end
251
289
 
252
- def accept_rule(rule)
253
- @res << "<hr>\n"
254
- end
290
+ def handle_TIDYLINK(label_part, url)
291
+ # When url is an image, ignore label part (maybe bug?) and just generate img tag.
292
+ if url.match?(/\Ahttps?:\/\/.+\.(png|gif|jpg|jpeg|bmp)\z/)
293
+ emit_inline("<img src=\"#{CGI.escapeHTML(url)}\" />")
294
+ return
295
+ elsif url.match?(/\Ardoc-image:/)
296
+ emit_inline(handle_RDOCLINK(url))
297
+ return
298
+ end
299
+
300
+ if label_part.size == 1 && String === label_part[0]
301
+ raw_label = label_part[0]
302
+
303
+ @in_tidylink_label = true
304
+ special = apply_tidylink_label_special_handling(raw_label, url)
305
+ @in_tidylink_label = false
306
+
307
+ if special
308
+ tag = gen_url(CGI.escapeHTML(url), special)
309
+ unless tag.empty?
310
+ emit_inline(tag)
311
+ return
312
+ end
313
+ end
314
+ end
315
+
316
+ tag = gen_url(CGI.escapeHTML(url), '')
317
+ open_tag, close_tag = tag.split(/(?=<\/a>)/, 2)
318
+ valid_tag = open_tag && close_tag
319
+ emit_inline(open_tag) if valid_tag
320
+ @in_tidylink_label = true
321
+ traverse_inline_nodes(label_part)
322
+ @in_tidylink_label = false
323
+ emit_inline(close_tag) if valid_tag
324
+ end
255
325
 
256
- ##
257
- # Prepares the visitor for consuming +list+
326
+ def handle_inline(text) # :nodoc:
327
+ @inline_output = +''
328
+ @quote_converter = QuoteConverter.new
329
+ super
330
+ out = @inline_output
331
+ @inline_output = nil
332
+ @quote_converter = nil
333
+ out
334
+ end
258
335
 
259
- def accept_list_start(list)
260
- @list << list.type
261
- @res << html_list_name(list.type, true)
262
- @in_list_entry.push false
263
- end
336
+ # Converts <tt>(c), (r), --, --- , ..., ...., ``, ''</tt> to HTML characters.
337
+ def handle_regexp_HTML_CHARACTERS(text)
338
+ name = HTML_CHARACTER_ALIASES[text]
339
+ TO_HTML_CHARACTERS[text.encoding][name] if name
340
+ end
264
341
 
265
- ##
266
- # Finishes consumption of +list+
342
+ def handle_regexp_QUOTE_NOT_AFTER_WORD(text)
343
+ @quote_converter.convert(text, after_word: false) || convert_string(text)
344
+ end
267
345
 
268
- def accept_list_end(list)
269
- @list.pop
270
- if tag = @in_list_entry.pop
271
- @res << tag
272
- end
273
- @res << html_list_name(list.type, false) << "\n"
274
- end
346
+ def handle_regexp_QUOTE_AFTER_WORD(text)
347
+ @quote_converter.convert(text, after_word: true) || convert_string(text)
348
+ end
275
349
 
276
- ##
277
- # Prepares the visitor for consuming +list_item+
350
+ # Converts suppressed cross-reference +text+ to HTML by removing the leading backslash.
278
351
 
279
- def accept_list_item_start(list_item)
280
- if tag = @in_list_entry.last
281
- @res << tag
282
- end
352
+ def handle_regexp_SUPPRESSED_CROSSREF(text)
353
+ convert_string(text.delete_prefix('\\'))
354
+ end
283
355
 
284
- @res << list_item_start(list_item, @list.last)
285
- end
356
+ ##
357
+ # +target+ is a potential link. The following schemes are handled:
358
+ #
359
+ # <tt>mailto:</tt>::
360
+ # Inserted as-is.
361
+ # <tt>http:</tt>::
362
+ # Links are checked to see if they reference an image. If so, that image
363
+ # gets inserted using an <tt><img></tt> tag. Otherwise a conventional
364
+ # <tt><a href></tt> is used.
365
+ # <tt>link:</tt>::
366
+ # Reference to a local file relative to the output directory.
367
+
368
+ def handle_regexp_HYPERLINK(text)
369
+ return convert_string(text) if in_tidylink_label?
370
+
371
+ url = CGI.escapeHTML(text)
372
+ gen_url url, url
373
+ end
286
374
 
287
- ##
288
- # Finishes consumption of +list_item+
375
+ ##
376
+ # +target+ is an rdoc-schemed link that will be converted into a hyperlink.
377
+ #
378
+ # For the +rdoc-ref+ scheme the named reference will be returned without
379
+ # creating a link.
380
+ #
381
+ # For the +rdoc-label+ scheme the footnote and label prefixes are stripped
382
+ # when creating a link. All other contents will be linked verbatim.
383
+
384
+ def handle_regexp_RDOCLINK(text)
385
+ handle_RDOCLINK text
386
+ end
289
387
 
290
- def accept_list_item_end(list_item)
291
- @in_list_entry[-1] = list_end_for(@list.last)
292
- end
388
+ # :section: Visitor
389
+ #
390
+ # These methods implement the HTML visitor.
293
391
 
294
- ##
295
- # Adds +blank_line+ to the output
392
+ ##
393
+ # Prepares the visitor for HTML generation
296
394
 
297
- def accept_blank_line(blank_line)
298
- # @res << annotate("<p />") << "\n"
299
- end
395
+ def start_accepting
396
+ @res = []
397
+ @in_list_entry = []
398
+ @list = []
399
+ @heading_ids = {}
400
+ end
300
401
 
301
- ##
302
- # Adds +heading+ to the output. The headings greater than 6 are trimmed to
303
- # level 6.
402
+ ##
403
+ # Returns the generated output
304
404
 
305
- def accept_heading(heading)
306
- level = [6, heading.level].min
405
+ def end_accepting
406
+ @res.join
407
+ end
307
408
 
308
- label = heading.label @code_object
409
+ ##
410
+ # Adds +block_quote+ to the output
309
411
 
310
- @res << if @options.output_decoration
311
- "\n<h#{level} id=\"#{label}\">"
312
- else
313
- "\n<h#{level}>"
314
- end
412
+ def accept_block_quote(block_quote)
413
+ @res << "\n<blockquote>"
315
414
 
316
- if @options.pipe
317
- @res << to_html(heading.text)
318
- else
319
- @res << "<a href=\"##{label}\">#{to_html(heading.text)}</a>"
320
- end
415
+ block_quote.parts.each do |part|
416
+ part.accept self
417
+ end
321
418
 
322
- @res << "</h#{level}>\n"
323
- end
419
+ @res << "</blockquote>\n"
420
+ end
324
421
 
325
- ##
326
- # Adds +raw+ to the output
422
+ ##
423
+ # Adds +paragraph+ to the output
424
+
425
+ def accept_paragraph(paragraph)
426
+ @res << "\n<p>"
427
+ text = paragraph.text @hard_break
428
+ text = text.gsub(/(#{SPACE_SEPARATED_LETTER_CLASS})?\K(?:\r?\n)+(?=(?(1)(#{SPACE_SEPARATED_LETTER_CLASS})?))/o) {
429
+ defined?($2) && ' '
430
+ }
431
+ @res << to_html(text)
432
+ @res << "</p>\n"
433
+ end
327
434
 
328
- def accept_raw(raw)
329
- @res << raw.parts.join("\n")
330
- end
435
+ # Generate syntax highlighted html for ruby-like text.
331
436
 
332
- ##
333
- # Adds +table+ to the output
437
+ def parsable_text_to_html(text)
438
+ tokens = ::RDoc::Parser::RubyColorizer.colorize(text)
439
+ result = TokenStream.to_html tokens
440
+ result = result + "\n" unless result.end_with?("\n")
441
+ result
442
+ end
334
443
 
335
- def accept_table(header, body, aligns)
336
- @res << "\n<table role=\"table\">\n<thead>\n<tr>\n"
337
- header.zip(aligns) do |text, align|
338
- @res << '<th'
339
- @res << ' align="' << align << '"' if align
340
- @res << '>' << to_html(text) << "</th>\n"
341
- end
342
- @res << "</tr>\n</thead>\n<tbody>\n"
343
- body.each do |row|
344
- @res << "<tr>\n"
345
- row.zip(aligns) do |text, align|
346
- @res << '<td'
347
- @res << ' align="' << align << '"' if align
348
- @res << '>' << to_html(text) << "</td>\n"
349
- end
350
- @res << "</tr>\n"
351
- end
352
- @res << "</tbody>\n</table>\n"
353
- end
444
+ ##
445
+ # Adds +verbatim+ to the output
446
+
447
+ def accept_verbatim(verbatim)
448
+ text = verbatim.text.rstrip
449
+ format = verbatim.format
450
+
451
+ # Apply Ruby syntax highlighting if
452
+ # - explicitly marked as Ruby (via ruby? which accepts :ruby or :rb)
453
+ # - no format specified but the text is parseable as Ruby
454
+ # Otherwise, add language class when applicable and skip Ruby highlighting
455
+ if verbatim.ruby? || (format.nil? && parseable?(text))
456
+ content = parsable_text_to_html(text)
457
+ klass = ' class="ruby"'
458
+ else
459
+ content = CGI.escapeHTML text
460
+ klass = " class=\"#{format}\"" if format
461
+ end
462
+
463
+ if @pipe
464
+ @res << "\n<pre><code>#{CGI.escapeHTML text}\n</code></pre>\n"
465
+ else
466
+ @res << "\n<pre#{klass}>#{content}</pre>\n"
467
+ end
468
+ end
354
469
 
355
- # :section: Utilities
470
+ ##
471
+ # Adds +rule+ to the output
356
472
 
357
- ##
358
- # CGI-escapes +text+
473
+ def accept_rule(rule)
474
+ @res << "<hr>\n"
475
+ end
359
476
 
360
- def convert_string(text)
361
- CGI.escapeHTML text
362
- end
477
+ ##
478
+ # Prepares the visitor for consuming +list+
363
479
 
364
- ##
365
- # Generate a link to +url+ with content +text+. Handles the special cases
366
- # for img: and link: described under handle_regexp_HYPERLINK
480
+ def accept_list_start(list)
481
+ @list << list.type
482
+ @res << html_list_name(list.type, true)
483
+ @in_list_entry.push false
484
+ end
367
485
 
368
- def gen_url(url, text)
369
- scheme, url, id = parse_url url
486
+ ##
487
+ # Finishes consumption of +list+
370
488
 
371
- if %w[http https link].include?(scheme) and
372
- url =~ /\.(gif|png|jpg|jpeg|bmp)$/ then
373
- "<img src=\"#{url}\" />"
374
- else
375
- if scheme != 'link' and %r%\A((?!https?:)(?:[^/#]*/)*+)([^/#]+)\.(rb|rdoc|md)(?=\z|#)%i =~ url
376
- url = "#$1#{$2.tr('.', '_')}_#$3.html#$'"
489
+ def accept_list_end(list)
490
+ @list.pop
491
+ if tag = @in_list_entry.pop
492
+ @res << tag
493
+ end
494
+ @res << html_list_name(list.type, false) << "\n"
377
495
  end
378
496
 
379
- text = text.sub %r%^#{scheme}:/*%i, ''
380
- text = text.sub %r%^[*\^](\d+)$%, '\1'
497
+ ##
498
+ # Prepares the visitor for consuming +list_item+
381
499
 
382
- link = "<a#{id} href=\"#{url}\">#{text}</a>"
500
+ def accept_list_item_start(list_item)
501
+ if tag = @in_list_entry.last
502
+ @res << tag
503
+ end
383
504
 
384
- link = "<sup>#{link}</sup>" if /"foot/ =~ id
505
+ @res << list_item_start(list_item, @list.last)
506
+ end
385
507
 
386
- link
387
- end
388
- end
508
+ ##
509
+ # Finishes consumption of +list_item+
389
510
 
390
- ##
391
- # Determines the HTML list element for +list_type+ and +open_tag+
511
+ def accept_list_item_end(list_item)
512
+ @in_list_entry[-1] = list_end_for(@list.last)
513
+ end
392
514
 
393
- def html_list_name(list_type, open_tag)
394
- tags = LIST_TYPE_TO_HTML[list_type]
395
- raise RDoc::Error, "Invalid list type: #{list_type.inspect}" unless tags
396
- tags[open_tag ? 0 : 1]
397
- end
515
+ ##
516
+ # Adds +blank_line+ to the output
398
517
 
399
- ##
400
- # Maps attributes to HTML tags
518
+ def accept_blank_line(blank_line)
519
+ # @res << annotate("<p />") << "\n"
520
+ end
401
521
 
402
- def init_tags
403
- add_tag :BOLD, "<strong>", "</strong>"
404
- add_tag :TT, "<code>", "</code>"
405
- add_tag :EM, "<em>", "</em>"
406
- end
522
+ ##
523
+ # Adds +heading+ to the output. The headings greater than 6 are trimmed to
524
+ # level 6.
407
525
 
408
- ##
409
- # Returns the HTML tag for +list_type+, possible using a label from
410
- # +list_item+
411
-
412
- def list_item_start(list_item, list_type)
413
- case list_type
414
- when :BULLET, :LALPHA, :NUMBER, :UALPHA then
415
- "<li>"
416
- when :LABEL, :NOTE then
417
- Array(list_item.label).map do |label|
418
- "<dt>#{to_html label}</dt>\n"
419
- end.join << "<dd>"
420
- else
421
- raise RDoc::Error, "Invalid list type: #{list_type.inspect}"
422
- end
423
- end
526
+ def accept_heading(heading)
527
+ level = [6, heading.level].min
424
528
 
425
- ##
426
- # Returns the HTML end-tag for +list_type+
427
-
428
- def list_end_for(list_type)
429
- case list_type
430
- when :BULLET, :LALPHA, :NUMBER, :UALPHA then
431
- "</li>"
432
- when :LABEL, :NOTE then
433
- "</dd>"
434
- else
435
- raise RDoc::Error, "Invalid list type: #{list_type.inspect}"
436
- end
437
- end
529
+ label = deduplicate_heading_id(heading.label(@code_object))
530
+ legacy_label = deduplicate_heading_id(heading.legacy_label(@code_object))
438
531
 
439
- ##
440
- # Returns true if text is valid ruby syntax
532
+ # Add legacy anchor before the heading for backward compatibility.
533
+ # This allows old links with label- prefix to still work.
534
+ if @output_decoration && !@pipe
535
+ @res << "\n<span id=\"#{legacy_label}\" class=\"legacy-anchor\"></span>"
536
+ end
441
537
 
442
- def parseable?(text)
443
- verbose, $VERBOSE = $VERBOSE, nil
444
- catch(:valid) do
445
- eval("BEGIN { throw :valid, true }\n#{text}")
446
- end
447
- rescue SyntaxError
448
- false
449
- ensure
450
- $VERBOSE = verbose
451
- end
538
+ @res << if @output_decoration
539
+ "\n<h#{level} id=\"#{label}\">"
540
+ else
541
+ "\n<h#{level}>"
542
+ end
452
543
 
453
- ##
454
- # Converts +item+ to HTML using RDoc::Text#to_html
544
+ if @pipe
545
+ @res << to_html(heading.text)
546
+ else
547
+ @res << "<a href=\"##{label}\">#{to_html(heading.text)}</a>"
548
+ end
455
549
 
456
- def to_html(item)
457
- super convert_flow @am.flow item
458
- end
550
+ @res << "</h#{level}>\n"
551
+ end
459
552
 
460
- private
553
+ ##
554
+ # Adds +raw+ to the output
461
555
 
462
- def convert_flow(flow_items)
463
- res = []
556
+ def accept_raw(raw)
557
+ @res << raw.parts.join("\n")
558
+ end
464
559
 
465
- flow_items.each do |item|
466
- case item
467
- when String
468
- append_flow_fragment res, convert_string(item)
469
- when RDoc::Markup::AttrChanger
470
- off_tags res, item
471
- on_tags res, item
472
- when RDoc::Markup::RegexpHandling
473
- append_flow_fragment res, convert_regexp_handling(item)
474
- else
475
- raise "Unknown flow element: #{item.inspect}"
560
+ ##
561
+ # Adds +table+ to the output
562
+
563
+ def accept_table(header, body, aligns)
564
+ @res << "\n<table role=\"table\">\n<thead>\n<tr>\n"
565
+ header.zip(aligns) do |text, align|
566
+ @res << '<th'
567
+ @res << ' align="' << align << '"' if align
568
+ @res << '>' << to_html(text) << "</th>\n"
569
+ end
570
+ @res << "</tr>\n</thead>\n<tbody>\n"
571
+ body.each do |row|
572
+ @res << "<tr>\n"
573
+ row.zip(aligns) do |text, align|
574
+ @res << '<td'
575
+ @res << ' align="' << align << '"' if align
576
+ @res << '>' << to_html(text) << "</td>\n"
577
+ end
578
+ @res << "</tr>\n"
579
+ end
580
+ @res << "</tbody>\n</table>\n"
476
581
  end
477
- end
478
582
 
479
- res.join
480
- end
583
+ # :section: Utilities
481
584
 
482
- def append_flow_fragment(res, fragment)
483
- return if fragment.nil? || fragment.empty?
585
+ ##
586
+ # Returns a unique heading ID, appending -1, -2, etc. for duplicates.
587
+ # Matches GitHub's behavior for duplicate heading anchors.
484
588
 
485
- emit_tidy_link_fragment(res, fragment)
486
- end
589
+ def deduplicate_heading_id(id)
590
+ if @heading_ids.key?(id)
591
+ @heading_ids[id] += 1
592
+ "#{id}-#{@heading_ids[id]}"
593
+ else
594
+ @heading_ids[id] = 0
595
+ id
596
+ end
597
+ end
487
598
 
488
- def append_to_tidy_label(fragment)
489
- @tidy_link_buffer << fragment
490
- end
599
+ ##
600
+ # CGI-escapes +text+
491
601
 
492
- ##
493
- # Matches an entire tidy link with a braced label "{label}[url]".
494
- #
495
- # Capture 1: label contents.
496
- # Capture 2: URL text.
497
- # Capture 3: trailing content.
498
- TIDY_LINK_WITH_BRACES = /\A\{(.*?)\}\[(.*?)\](.*)\z/
499
-
500
- ##
501
- # Matches the tail of a braced tidy link when the opening brace was
502
- # consumed earlier while accumulating the label text.
503
- #
504
- # Capture 1: remaining label content.
505
- # Capture 2: URL text.
506
- # Capture 3: trailing content.
507
- TIDY_LINK_WITH_BRACES_TAIL = /\A(.*?)\}\[(.*?)\](.*)\z/
508
-
509
- ##
510
- # Matches a tidy link with a single-word label "label[url]".
511
- #
512
- # Capture 1: the single-word label (no whitespace).
513
- # Capture 2: URL text between the brackets.
514
- TIDY_LINK_SINGLE_WORD = /\A(\S+)\[(.*?)\](.*)\z/
515
-
516
- def convert_complete_tidy_link(text)
517
- return text unless
518
- text =~ TIDY_LINK_WITH_BRACES or text =~ TIDY_LINK_SINGLE_WORD
519
-
520
- label = $1
521
- url = CGI.escapeHTML($2)
522
-
523
- label_html = if /^rdoc-image:/ =~ label
524
- handle_RDOCLINK(label)
525
- else
526
- render_tidy_link_label(label)
527
- end
602
+ def convert_string(text)
603
+ CGI.escapeHTML text
604
+ end
528
605
 
529
- gen_url url, label_html
530
- end
606
+ ##
607
+ # Generates an HTML link or image tag for the given +url+ and +text+.
608
+ #
609
+ # - Image URLs (http/https/link ending in .gif, .png, .jpg, .jpeg, .bmp)
610
+ # become <img> tags
611
+ # - File references (.rb, .rdoc, .md) are converted to .html paths
612
+ # - Anchor URLs (#foo) pass through unchanged for GitHub-style header linking
613
+ # - Footnote links get wrapped in <sup> tags
614
+ # - URLs with an unsafe scheme (javascript:, vbscript:, data:, file:) are not
615
+ # linked and only +text+ is returned
616
+
617
+ def gen_url(url, text)
618
+ scheme, url, id = parse_url url
619
+
620
+ # Check the parsed URL because link: URLs are used as href without it
621
+ return text if unsafe_url?(url)
622
+
623
+ if %w[http https link].include?(scheme) && url =~ /\.(gif|png|jpg|jpeg|bmp)\z/
624
+ "<img src=\"#{url}\" />"
625
+ else
626
+ if scheme != 'link' and %r%\A((?!https?:)(?:[^/#]*/)*+)([^/#]+)\.(rb|rdoc|md)(?=\z|#)%i =~ url
627
+ url = "#$1#{$2.tr('.', '_')}_#$3.html#$'"
628
+ end
629
+
630
+ text = text.sub %r%^#{scheme}:/*%i, ''
631
+ text = text.sub %r%^[*\^](\d+)$%, '\1'
632
+
633
+ link = "<a#{id} href=\"#{url}\">#{text}</a>"
634
+
635
+ if /"foot/.match?(id)
636
+ "<sup>#{link}</sup>"
637
+ else
638
+ link
639
+ end
640
+ end
641
+ end
531
642
 
532
- def emit_tidy_link_fragment(res, fragment)
533
- if tidy_link_capturing?
534
- append_to_tidy_label fragment
535
- else
536
- res << fragment
537
- end
538
- end
643
+ ##
644
+ # Does +url+ have a scheme in UNSAFE_URL_SCHEME_REGEXP?
645
+ #
646
+ # Browsers ignore leading C0 controls and spaces and all ASCII tabs and
647
+ # newlines in a URL, so they are removed before checking the scheme.
648
+ # See https://url.spec.whatwg.org/#concept-basic-url-parser
649
+ #
650
+ #: (String) -> bool
651
+ def unsafe_url?(url)
652
+ normalized = url.sub(/\A[\x00-\x20]+/, '').delete("\t\n\r")
653
+ UNSAFE_URL_SCHEME_REGEXP.match?(normalized)
654
+ end
539
655
 
540
- def finish_tidy_link(text)
541
- label_tail, url, trailing = extract_tidy_link_parts(text)
542
- append_to_tidy_label CGI.escapeHTML(label_tail) unless label_tail.empty?
656
+ ##
657
+ # Determines the HTML list element for +list_type+ and +open_tag+
543
658
 
544
- return '' unless url
659
+ def html_list_name(list_type, open_tag)
660
+ tags = LIST_TYPE_TO_HTML[list_type]
661
+ raise Error, "Invalid list type: #{list_type.inspect}" unless tags
662
+ tags[open_tag ? 0 : 1]
663
+ end
545
664
 
546
- label_html = @tidy_link_buffer
547
- @tidy_link_buffer = nil
548
- link = gen_url(url, label_html)
665
+ ##
666
+ # Returns the HTML tag for +list_type+, possible using a label from
667
+ # +list_item+
668
+
669
+ def list_item_start(list_item, list_type)
670
+ case list_type
671
+ when :BULLET, :LALPHA, :NUMBER, :UALPHA
672
+ "<li>"
673
+ when :LABEL, :NOTE
674
+ Array(list_item.label).map do |label|
675
+ "<dt>#{to_html label}</dt>\n"
676
+ end.join << "<dd>"
677
+ else
678
+ raise Error, "Invalid list type: #{list_type.inspect}"
679
+ end
680
+ end
549
681
 
550
- return link if trailing.empty?
682
+ ##
683
+ # Returns the HTML end-tag for +list_type+
684
+
685
+ def list_end_for(list_type)
686
+ case list_type
687
+ when :BULLET, :LALPHA, :NUMBER, :UALPHA
688
+ "</li>"
689
+ when :LABEL, :NOTE
690
+ "</dd>"
691
+ else
692
+ raise Error, "Invalid list type: #{list_type.inspect}"
693
+ end
694
+ end
551
695
 
552
- link + CGI.escapeHTML(trailing)
553
- end
696
+ ##
697
+ # Returns true if text is valid ruby syntax
554
698
 
555
- def extract_tidy_link_parts(text)
556
- if text =~ TIDY_LINK_WITH_BRACES
557
- [$1, CGI.escapeHTML($2), $3]
558
- elsif text =~ TIDY_LINK_WITH_BRACES_TAIL
559
- [$1, CGI.escapeHTML($2), $3]
560
- elsif text =~ TIDY_LINK_SINGLE_WORD
561
- [$1, CGI.escapeHTML($2), $3]
562
- else
563
- [text, nil, '']
564
- end
565
- end
699
+ def parseable?(text)
700
+ Prism.parse_success?(text)
701
+ end
566
702
 
567
- def on_tags(res, item)
568
- each_attr_tag(item.turn_on) do |tag|
569
- emit_tidy_link_fragment(res, annotate(tag.on))
570
- @in_tt += 1 if tt? tag
571
- end
572
- end
703
+ ##
704
+ # Converts +item+ to HTML using RDoc::Text#to_html
573
705
 
574
- def off_tags(res, item)
575
- each_attr_tag(item.turn_off, true) do |tag|
576
- emit_tidy_link_fragment(res, annotate(tag.off))
577
- @in_tt -= 1 if tt? tag
706
+ def to_html(item)
707
+ handle_inline(item)
708
+ end
578
709
  end
579
710
  end
580
-
581
- def start_tidy_link(text)
582
- @tidy_link_buffer = String.new
583
- append_to_tidy_label CGI.escapeHTML(text.delete_prefix('{'))
584
- end
585
-
586
- def tidy_link_capturing?
587
- !!@tidy_link_buffer
588
- end
589
-
590
- def render_tidy_link_label(label)
591
- RDoc::Markup::LinkLabelToHtml.render(label, @options, @from_path)
592
- end
593
- end
594
-
595
- ##
596
- # Formatter dedicated to rendering tidy link labels without mutating the
597
- # calling formatter's state.
598
-
599
- class RDoc::Markup::LinkLabelToHtml < RDoc::Markup::ToHtml
600
- def self.render(label, options, from_path)
601
- new(options, from_path).to_html(label)
602
- end
603
-
604
- def initialize(options, from_path = nil)
605
- super(options)
606
-
607
- self.from_path = from_path if from_path
608
- end
609
711
  end