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