rdoc 6.3.4.1 → 8.0.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 (205) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +195 -0
  3. data/History.rdoc +65 -63
  4. data/LEGAL.rdoc +7 -1
  5. data/LICENSE.rdoc +6 -0
  6. data/README.md +236 -0
  7. data/RI.md +842 -0
  8. data/TODO.rdoc +8 -7
  9. data/doc/markup_reference/markdown.md +659 -0
  10. data/doc/markup_reference/rdoc.rdoc +1169 -0
  11. data/exe/rdoc +0 -1
  12. data/lib/rdoc/{alias.rb → code_object/alias.rb} +4 -18
  13. data/lib/rdoc/{any_method.rb → code_object/any_method.rb} +50 -24
  14. data/lib/rdoc/{attr.rb → code_object/attr.rb} +16 -14
  15. data/lib/rdoc/{class_module.rb → code_object/class_module.rb} +225 -58
  16. data/lib/rdoc/{constant.rb → code_object/constant.rb} +44 -10
  17. data/lib/rdoc/code_object/context/section.rb +182 -0
  18. data/lib/rdoc/{context.rb → code_object/context.rb} +78 -106
  19. data/lib/rdoc/{extend.rb → code_object/extend.rb} +0 -1
  20. data/lib/rdoc/{include.rb → code_object/include.rb} +0 -1
  21. data/lib/rdoc/{method_attr.rb → code_object/method_attr.rb} +46 -48
  22. data/lib/rdoc/{mixin.rb → code_object/mixin.rb} +6 -4
  23. data/lib/rdoc/{normal_class.rb → code_object/normal_class.rb} +2 -3
  24. data/lib/rdoc/{normal_module.rb → code_object/normal_module.rb} +2 -3
  25. data/lib/rdoc/{require.rb → code_object/require.rb} +1 -2
  26. data/lib/rdoc/{single_class.rb → code_object/single_class.rb} +5 -1
  27. data/lib/rdoc/{top_level.rb → code_object/top_level.rb} +60 -63
  28. data/lib/rdoc/code_object.rb +11 -39
  29. data/lib/rdoc/code_objects.rb +1 -2
  30. data/lib/rdoc/comment.rb +202 -99
  31. data/lib/rdoc/cross_reference.rb +75 -62
  32. data/lib/rdoc/encoding.rb +8 -24
  33. data/lib/rdoc/erb_partial.rb +1 -2
  34. data/lib/rdoc/erbio.rb +3 -8
  35. data/lib/rdoc/generator/aliki.rb +200 -0
  36. data/lib/rdoc/generator/darkfish.rb +156 -122
  37. data/lib/rdoc/generator/json_index.rb +8 -24
  38. data/lib/rdoc/generator/markup.rb +70 -34
  39. data/lib/rdoc/generator/pot/message_extractor.rb +7 -7
  40. data/lib/rdoc/generator/pot/po.rb +1 -1
  41. data/lib/rdoc/generator/pot/po_entry.rb +8 -8
  42. data/lib/rdoc/generator/pot.rb +1 -5
  43. data/lib/rdoc/generator/ri.rb +1 -2
  44. data/lib/rdoc/generator/template/aliki/DESIGN.md +536 -0
  45. data/lib/rdoc/generator/template/aliki/_aside_toc.rhtml +8 -0
  46. data/lib/rdoc/generator/template/aliki/_footer.rhtml +23 -0
  47. data/lib/rdoc/generator/template/aliki/_head.rhtml +163 -0
  48. data/lib/rdoc/generator/template/aliki/_header.rhtml +56 -0
  49. data/lib/rdoc/generator/template/aliki/_icons.rhtml +208 -0
  50. data/lib/rdoc/generator/template/aliki/_sidebar_ancestors.rhtml +16 -0
  51. data/lib/rdoc/generator/template/aliki/_sidebar_classes.rhtml +15 -0
  52. data/lib/rdoc/generator/template/aliki/_sidebar_extends.rhtml +27 -0
  53. data/lib/rdoc/generator/template/aliki/_sidebar_includes.rhtml +27 -0
  54. data/lib/rdoc/generator/template/aliki/_sidebar_installed.rhtml +16 -0
  55. data/lib/rdoc/generator/template/aliki/_sidebar_methods.rhtml +41 -0
  56. data/lib/rdoc/generator/template/aliki/_sidebar_pages.rhtml +67 -0
  57. data/lib/rdoc/generator/template/aliki/_sidebar_search.rhtml +15 -0
  58. data/lib/rdoc/generator/template/aliki/_sidebar_sections.rhtml +21 -0
  59. data/lib/rdoc/generator/template/aliki/_sidebar_toggle.rhtml +3 -0
  60. data/lib/rdoc/generator/template/aliki/class.rhtml +230 -0
  61. data/lib/rdoc/generator/template/aliki/css/rdoc.css +2181 -0
  62. data/lib/rdoc/generator/template/aliki/index.rhtml +22 -0
  63. data/lib/rdoc/generator/template/aliki/js/aliki.js +513 -0
  64. data/lib/rdoc/generator/template/aliki/js/bash_highlighter.js +167 -0
  65. data/lib/rdoc/generator/template/aliki/js/c_highlighter.js +299 -0
  66. data/lib/rdoc/generator/template/aliki/js/search_controller.js +129 -0
  67. data/lib/rdoc/generator/template/aliki/js/search_navigation.js +105 -0
  68. data/lib/rdoc/generator/template/aliki/js/search_ranker.js +239 -0
  69. data/lib/rdoc/generator/template/aliki/js/theme-toggle.js +112 -0
  70. data/lib/rdoc/generator/template/aliki/page.rhtml +18 -0
  71. data/lib/rdoc/generator/template/aliki/servlet_not_found.rhtml +14 -0
  72. data/lib/rdoc/generator/template/aliki/servlet_root.rhtml +65 -0
  73. data/lib/rdoc/generator/template/darkfish/_footer.rhtml +3 -3
  74. data/lib/rdoc/generator/template/darkfish/_head.rhtml +35 -14
  75. data/lib/rdoc/generator/template/darkfish/_sidebar_classes.rhtml +1 -5
  76. data/lib/rdoc/generator/template/darkfish/_sidebar_extends.rhtml +11 -9
  77. data/lib/rdoc/generator/template/darkfish/_sidebar_includes.rhtml +11 -9
  78. data/lib/rdoc/generator/template/darkfish/_sidebar_installed.rhtml +7 -6
  79. data/lib/rdoc/generator/template/darkfish/_sidebar_methods.rhtml +20 -11
  80. data/lib/rdoc/generator/template/darkfish/_sidebar_pages.rhtml +25 -5
  81. data/lib/rdoc/generator/template/darkfish/_sidebar_parent.rhtml +4 -9
  82. data/lib/rdoc/generator/template/darkfish/_sidebar_search.rhtml +2 -1
  83. data/lib/rdoc/generator/template/darkfish/_sidebar_sections.rhtml +4 -4
  84. data/lib/rdoc/generator/template/darkfish/_sidebar_table_of_contents.rhtml +28 -7
  85. data/lib/rdoc/generator/template/darkfish/_sidebar_toggle.rhtml +3 -0
  86. data/lib/rdoc/generator/template/darkfish/class.rhtml +138 -90
  87. data/lib/rdoc/generator/template/darkfish/css/rdoc.css +420 -358
  88. data/lib/rdoc/generator/template/darkfish/fonts/SourceCodePro-Bold.ttf +0 -0
  89. data/lib/rdoc/generator/template/darkfish/fonts/SourceCodePro-Regular.ttf +0 -0
  90. data/lib/rdoc/generator/template/darkfish/index.rhtml +14 -12
  91. data/lib/rdoc/generator/template/darkfish/js/darkfish.js +59 -3
  92. data/lib/rdoc/generator/template/darkfish/js/search.js +15 -5
  93. data/lib/rdoc/generator/template/darkfish/page.rhtml +7 -6
  94. data/lib/rdoc/generator/template/darkfish/servlet_not_found.rhtml +12 -9
  95. data/lib/rdoc/generator/template/darkfish/servlet_root.rhtml +24 -21
  96. data/lib/rdoc/generator/template/darkfish/table_of_contents.rhtml +35 -21
  97. data/lib/rdoc/generator/template/json_index/js/navigation.js +8 -8
  98. data/lib/rdoc/generator/template/json_index/js/searcher.js +48 -6
  99. data/lib/rdoc/generator.rb +6 -5
  100. data/lib/rdoc/i18n/text.rb +3 -3
  101. data/lib/rdoc/i18n.rb +1 -1
  102. data/lib/rdoc/known_classes.rb +5 -4
  103. data/lib/rdoc/markdown/entities.rb +0 -1
  104. data/lib/rdoc/markdown/literals.kpeg +0 -2
  105. data/lib/rdoc/markdown/literals.rb +0 -1
  106. data/lib/rdoc/markdown.kpeg +140 -62
  107. data/lib/rdoc/markdown.rb +1015 -873
  108. data/lib/rdoc/markup/blank_line.rb +25 -24
  109. data/lib/rdoc/markup/block_quote.rb +1 -2
  110. data/lib/rdoc/markup/document.rb +10 -11
  111. data/lib/rdoc/markup/element.rb +21 -0
  112. data/lib/rdoc/markup/formatter.rb +134 -115
  113. data/lib/rdoc/markup/hard_break.rb +30 -28
  114. data/lib/rdoc/markup/heading.rb +163 -72
  115. data/lib/rdoc/markup/include.rb +3 -4
  116. data/lib/rdoc/markup/indented_paragraph.rb +4 -5
  117. data/lib/rdoc/markup/inline_parser.rb +312 -0
  118. data/lib/rdoc/markup/list.rb +6 -7
  119. data/lib/rdoc/markup/list_item.rb +6 -7
  120. data/lib/rdoc/markup/paragraph.rb +2 -3
  121. data/lib/rdoc/markup/parser.rb +29 -19
  122. data/lib/rdoc/markup/pre_process.rb +39 -18
  123. data/lib/rdoc/markup/raw.rb +52 -56
  124. data/lib/rdoc/markup/rule.rb +2 -3
  125. data/lib/rdoc/markup/table.rb +49 -32
  126. data/lib/rdoc/markup/to_ansi.rb +58 -8
  127. data/lib/rdoc/markup/to_bs.rb +47 -38
  128. data/lib/rdoc/markup/to_html.rb +343 -105
  129. data/lib/rdoc/markup/to_html_crossref.rb +143 -58
  130. data/lib/rdoc/markup/to_html_snippet.rb +78 -76
  131. data/lib/rdoc/markup/to_joined_paragraph.rb +3 -9
  132. data/lib/rdoc/markup/to_label.rb +34 -25
  133. data/lib/rdoc/markup/to_markdown.rb +74 -51
  134. data/lib/rdoc/markup/to_rdoc.rb +141 -82
  135. data/lib/rdoc/markup/to_table_of_contents.rb +4 -5
  136. data/lib/rdoc/markup/to_test.rb +10 -3
  137. data/lib/rdoc/markup/to_tt_only.rb +17 -31
  138. data/lib/rdoc/markup/verbatim.rb +5 -6
  139. data/lib/rdoc/markup.rb +59 -707
  140. data/lib/rdoc/options.rb +228 -101
  141. data/lib/rdoc/parser/c.rb +186 -184
  142. data/lib/rdoc/parser/changelog.rb +57 -13
  143. data/lib/rdoc/parser/markdown.rb +0 -2
  144. data/lib/rdoc/parser/rbs.rb +275 -0
  145. data/lib/rdoc/parser/rd.rb +0 -1
  146. data/lib/rdoc/parser/ruby.rb +963 -2024
  147. data/lib/rdoc/parser/ruby_colorizer.rb +253 -0
  148. data/lib/rdoc/parser/simple.rb +6 -23
  149. data/lib/rdoc/parser/text.rb +0 -1
  150. data/lib/rdoc/parser.rb +35 -14
  151. data/lib/rdoc/rbs_helper.rb +186 -0
  152. data/lib/rdoc/rd/block_parser.rb +659 -11
  153. data/lib/rdoc/rd/block_parser.ry +13 -9
  154. data/lib/rdoc/rd/inline.rb +4 -5
  155. data/lib/rdoc/rd/inline_parser.rb +646 -2
  156. data/lib/rdoc/rd.rb +4 -5
  157. data/lib/rdoc/rdoc.rb +234 -90
  158. data/lib/rdoc/ri/driver.rb +172 -172
  159. data/lib/rdoc/ri/paths.rb +3 -3
  160. data/lib/rdoc/{servlet.rb → ri/servlet.rb} +28 -27
  161. data/lib/rdoc/ri/store.rb +0 -1
  162. data/lib/rdoc/ri/task.rb +3 -3
  163. data/lib/rdoc/ri.rb +5 -5
  164. data/lib/rdoc/rubygems_hook.rb +106 -25
  165. data/lib/rdoc/server.rb +460 -0
  166. data/lib/rdoc/stats/normal.rb +1 -1
  167. data/lib/rdoc/stats/quiet.rb +1 -2
  168. data/lib/rdoc/stats/verbose.rb +3 -5
  169. data/lib/rdoc/stats.rb +158 -136
  170. data/lib/rdoc/store.rb +267 -48
  171. data/lib/rdoc/task.rb +54 -28
  172. data/lib/rdoc/text.rb +60 -120
  173. data/lib/rdoc/token_stream.rb +30 -45
  174. data/lib/rdoc/tom_doc.rb +9 -15
  175. data/lib/rdoc/version.rb +3 -1
  176. data/lib/rdoc.rb +90 -52
  177. data/lib/rubygems_plugin.rb +14 -0
  178. data/man/ri.1 +2 -0
  179. data/rdoc-logo.svg +43 -0
  180. data/rdoc.gemspec +25 -200
  181. metadata +120 -53
  182. data/CONTRIBUTING.rdoc +0 -220
  183. data/ExampleMarkdown.md +0 -37
  184. data/ExampleRDoc.rdoc +0 -208
  185. data/Gemfile +0 -12
  186. data/README.rdoc +0 -129
  187. data/RI.rdoc +0 -57
  188. data/Rakefile +0 -101
  189. data/bin/console +0 -7
  190. data/bin/setup +0 -6
  191. data/lib/rdoc/anon_class.rb +0 -11
  192. data/lib/rdoc/context/section.rb +0 -232
  193. data/lib/rdoc/generator/template/darkfish/.document +0 -0
  194. data/lib/rdoc/generator/template/darkfish/_sidebar_VCS_info.rhtml +0 -19
  195. data/lib/rdoc/generator/template/darkfish/_sidebar_in_files.rhtml +0 -9
  196. data/lib/rdoc/generator/template/json_index/.document +0 -1
  197. data/lib/rdoc/ghost_method.rb +0 -7
  198. data/lib/rdoc/markup/attr_changer.rb +0 -23
  199. data/lib/rdoc/markup/attr_span.rb +0 -36
  200. data/lib/rdoc/markup/attribute_manager.rb +0 -409
  201. data/lib/rdoc/markup/attributes.rb +0 -71
  202. data/lib/rdoc/markup/regexp_handling.rb +0 -41
  203. data/lib/rdoc/meta_method.rb +0 -7
  204. data/lib/rdoc/parser/ripper_state_lex.rb +0 -590
  205. data/lib/rdoc/parser/ruby_tools.rb +0 -167
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
- require 'cgi'
2
+ require 'cgi/escape'
3
+ require 'cgi/util' unless defined?(CGI::EscapeExt)
4
+ require 'prism'
3
5
 
4
6
  ##
5
7
  # Outputs RDoc markup as HTML.
@@ -39,35 +41,129 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
39
41
 
40
42
  # :section:
41
43
 
44
+ # Maps an encoding to a Hash of characters properly transcoded for that
45
+ # encoding.
46
+ #
47
+ # See also encode_fallback.
48
+
49
+ TO_HTML_CHARACTERS = Hash.new do |h, encoding|
50
+ h[encoding] = {
51
+ close_dquote: encode_fallback('”', encoding, '"'),
52
+ close_squote: encode_fallback('’', encoding, '\''),
53
+ copyright: encode_fallback('©', encoding, '(c)'),
54
+ ellipsis: encode_fallback('…', encoding, '...'),
55
+ dot_ellipsis: encode_fallback('.…', encoding, '....'),
56
+ em_dash: encode_fallback('—', encoding, '---'),
57
+ en_dash: encode_fallback('–', encoding, '--'),
58
+ open_dquote: encode_fallback('“', encoding, '"'),
59
+ open_squote: encode_fallback('‘', encoding, '\''),
60
+ trademark: encode_fallback('®', encoding, '(r)'),
61
+ }
62
+ end
63
+
64
+ HTML_CHARACTER_ALIASES = {
65
+ '(c)' => :copyright,
66
+ '(C)' => :copyright,
67
+ '(r)' => :trademark,
68
+ '(R)' => :trademark,
69
+ '---' => :em_dash,
70
+ '--' => :en_dash,
71
+ '....' => :dot_ellipsis,
72
+ '...' => :ellipsis,
73
+ '``' => :open_dquote,
74
+ "''" => :close_dquote,
75
+ }
76
+
77
+ # Transcodes +character+ to +encoding+ with a +fallback+ character.
78
+
79
+ def self.encode_fallback(character, encoding, fallback)
80
+ character.encode(
81
+ encoding,
82
+ fallback: { character => fallback },
83
+ undef: :replace,
84
+ replace: fallback
85
+ )
86
+ end
87
+
88
+ # Converts ascii quote pairs to multibyte quote characters
89
+ class QuoteConverter
90
+
91
+ def initialize
92
+ @in_dquote = false
93
+ @in_squote = false
94
+ end
95
+
96
+ def convert(quote, after_word:)
97
+ case quote
98
+ when '"'
99
+ type = @in_dquote ? :close_dquote : :open_dquote
100
+ @in_dquote = !@in_dquote
101
+ when "'"
102
+ if @in_squote
103
+ type = :close_squote
104
+ @in_squote = false
105
+ elsif after_word
106
+ # Mary's dog, my parents' house: do not start paired quotes
107
+ type = :close_squote
108
+ else
109
+ type = :open_squote
110
+ @in_squote = true
111
+ end
112
+ when '`'
113
+ # Opening quote of <tt>`quoted sentence'</tt>.
114
+ # This will conflict with code blocks <tt>`puts('hello')`</tt> in the future.
115
+ if !@in_squote && !after_word
116
+ type = :open_squote
117
+ @in_squote = true
118
+ end
119
+ end
120
+ TO_HTML_CHARACTERS[quote.encoding][type] if type
121
+ end
122
+ end
123
+
42
124
  ##
43
125
  # Creates a new formatter that will output HTML
44
126
 
45
- def initialize options, markup = nil
46
- super
127
+ def initialize(pipe: false, output_decoration: true)
128
+ super()
47
129
 
130
+ @pipe = pipe
131
+ @output_decoration = output_decoration
48
132
  @code_object = nil
49
133
  @from_path = ''
50
134
  @in_list_entry = nil
51
135
  @list = nil
52
136
  @th = nil
137
+ @quote_converter = nil
138
+ @in_tidylink_label = false
53
139
  @hard_break = "<br>\n"
54
140
 
55
141
  init_regexp_handlings
56
-
57
- init_tags
58
142
  end
59
143
 
60
144
  # :section: Regexp Handling
61
145
  #
62
146
  # These methods are used by regexp handling markup added by RDoc::Markup#add_regexp_handling.
63
147
 
148
+ # :nodoc:
149
+ URL_CHARACTERS_REGEXP_STR = /[A-Za-z0-9\-._~:\/\?#\[\]@!$&'\(\)*+,;%=]/.source
150
+
64
151
  ##
65
152
  # Adds regexp handlings.
66
153
 
67
154
  def init_regexp_handlings
68
155
  # external links
69
- @markup.add_regexp_handling(/(?:link:|https?:|mailto:|ftp:|irc:|www\.)\S+\w/,
156
+ @markup.add_regexp_handling(/(?:link:|https?:|mailto:|ftp:|irc:|www\.)#{URL_CHARACTERS_REGEXP_STR}+\w/,
70
157
  :HYPERLINK)
158
+
159
+ # suppress crossref: \#method \::method \ClassName \method_with_underscores
160
+ @markup.add_regexp_handling(/\\(?:[#:A-Z]|[a-z]+_[a-z0-9])/, :SUPPRESSED_CROSSREF)
161
+
162
+ @markup.add_regexp_handling(Regexp.union(HTML_CHARACTER_ALIASES.keys), :HTML_CHARACTERS)
163
+
164
+ @markup.add_regexp_handling(/\b['"`]/, :QUOTE_AFTER_WORD)
165
+ @markup.add_regexp_handling(/\B['"`]/, :QUOTE_NOT_AFTER_WORD)
166
+
71
167
  init_link_notation_regexp_handlings
72
168
  end
73
169
 
@@ -76,14 +172,14 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
76
172
 
77
173
  def init_link_notation_regexp_handlings
78
174
  add_regexp_handling_RDOCLINK
79
- add_regexp_handling_TIDYLINK
80
175
  end
81
176
 
82
- def handle_RDOCLINK url # :nodoc:
177
+ def handle_RDOCLINK(url) # :nodoc:
83
178
  case url
84
179
  when /^rdoc-ref:/
85
- $'
180
+ CGI.escapeHTML($')
86
181
  when /^rdoc-label:/
182
+ return CGI.escapeHTML(url) if in_tidylink_label?
87
183
  text = $'
88
184
 
89
185
  text = case text
@@ -93,21 +189,159 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
93
189
  else text
94
190
  end
95
191
 
96
- gen_url url, text
192
+ gen_url CGI.escapeHTML(url), CGI.escapeHTML(text)
97
193
  when /^rdoc-image:/
98
- "<img src=\"#{$'}\">"
99
- else
100
- url =~ /\Ardoc-[a-z]+:/
194
+ # Split the string after "rdoc-image:" into url and alt.
195
+ # "path/to/image.jpg:alt text" => ["path/to/image.jpg", "alt text"]
196
+ # "http://example.com/path/to/image.jpg:alt text" => ["http://example.com/path/to/image.jpg", "alt text"]
197
+ url, alt = $'.split(/:(?!\/)/, 2)
198
+ if alt && !alt.empty?
199
+ %[<img src="#{CGI.escapeHTML(url)}" alt="#{CGI.escapeHTML(alt)}">]
200
+ else
201
+ %[<img src="#{CGI.escapeHTML(url)}">]
202
+ end
203
+ when /\Ardoc-[a-z]+:/
204
+ CGI.escapeHTML($')
205
+ end
206
+ end
207
+
208
+ def handle_PLAIN_TEXT(text)
209
+ emit_inline(convert_string(text))
210
+ end
211
+
212
+ def handle_REGEXP_HANDLING_TEXT(text)
213
+ emit_inline(text)
214
+ end
215
+
216
+ def handle_BOLD(nodes)
217
+ emit_inline('<strong>')
218
+ super
219
+ emit_inline('</strong>')
220
+ end
221
+
222
+ def handle_EM(nodes)
223
+ emit_inline('<em>')
224
+ super
225
+ emit_inline('</em>')
226
+ end
227
+
228
+ def handle_BOLD_WORD(word)
229
+ emit_inline('<strong>')
230
+ super
231
+ emit_inline('</strong>')
232
+ end
233
+
234
+ def handle_EM_WORD(word)
235
+ emit_inline('<em>')
236
+ super
237
+ emit_inline('</em>')
238
+ end
239
+
240
+ def handle_TT(code)
241
+ emit_inline('<code>')
242
+ super
243
+ emit_inline('</code>')
244
+ end
245
+
246
+ def handle_STRIKE(nodes)
247
+ emit_inline('<del>')
248
+ super
249
+ emit_inline('</del>')
250
+ end
251
+
252
+ def handle_HARD_BREAK
253
+ emit_inline('<br>')
254
+ end
255
+
256
+ def emit_inline(text)
257
+ @inline_output << text
258
+ end
259
+
260
+ # Returns true if we are processing inside a tidy link label.
261
+
262
+ def in_tidylink_label?
263
+ @in_tidylink_label
264
+ end
265
+
266
+ # Special handling for tidy link labels.
267
+ # When a tidy link is <tt>{rdoc-image:path/to/image.jpg:alt text}[http://example.com]</tt>,
268
+ # label part is normally considered RDOCLINK <tt>rdoc-image:path/to/image.jpg:alt</tt> and a text <tt>" text"</tt>
269
+ # but RDoc's test code expects the whole label part to be treated as RDOCLINK only in tidy link label.
270
+ # When a tidy link is <tt>{^1}[url]</tt> or <tt>{*1}[url]</tt>, the label part needs to drop leading * or ^.
271
+ # TODO: reconsider this workaround.
101
272
 
102
- $'
273
+ def apply_tidylink_label_special_handling(label, url)
274
+ # ^1 *1 will be converted to just 1 in tidy link label.
275
+ return label[1..] if label.match?(/\A[*^]\d+\z/)
276
+
277
+ # rdoc-image in label specially allows spaces in alt text.
278
+ return handle_RDOCLINK(label) if label.start_with?('rdoc-image:')
279
+ end
280
+
281
+ def handle_TIDYLINK(label_part, url)
282
+ # When url is an image, ignore label part (maybe bug?) and just generate img tag.
283
+ if url.match?(/\Ahttps?:\/\/.+\.(png|gif|jpg|jpeg|bmp)\z/)
284
+ emit_inline("<img src=\"#{CGI.escapeHTML(url)}\" />")
285
+ return
286
+ elsif url.match?(/\Ardoc-image:/)
287
+ emit_inline(handle_RDOCLINK(url))
288
+ return
289
+ end
290
+
291
+ if label_part.size == 1 && String === label_part[0]
292
+ raw_label = label_part[0]
293
+
294
+ @in_tidylink_label = true
295
+ special = apply_tidylink_label_special_handling(raw_label, url)
296
+ @in_tidylink_label = false
297
+
298
+ if special
299
+ tag = gen_url(CGI.escapeHTML(url), special)
300
+ unless tag.empty?
301
+ emit_inline(tag)
302
+ return
303
+ end
304
+ end
103
305
  end
306
+
307
+ tag = gen_url(CGI.escapeHTML(url), '')
308
+ open_tag, close_tag = tag.split(/(?=<\/a>)/, 2)
309
+ valid_tag = open_tag && close_tag
310
+ emit_inline(open_tag) if valid_tag
311
+ @in_tidylink_label = true
312
+ traverse_inline_nodes(label_part)
313
+ @in_tidylink_label = false
314
+ emit_inline(close_tag) if valid_tag
104
315
  end
105
316
 
106
- ##
107
- # +target+ is a <code><br></code>
317
+ def handle_inline(text) # :nodoc:
318
+ @inline_output = +''
319
+ @quote_converter = QuoteConverter.new
320
+ super
321
+ out = @inline_output
322
+ @inline_output = nil
323
+ @quote_converter = nil
324
+ out
325
+ end
326
+
327
+ # Converts <tt>(c), (r), --, --- , ..., ...., ``, ''</tt> to HTML characters.
328
+ def handle_regexp_HTML_CHARACTERS(text)
329
+ name = HTML_CHARACTER_ALIASES[text]
330
+ TO_HTML_CHARACTERS[text.encoding][name] if name
331
+ end
108
332
 
109
- def handle_regexp_HARD_BREAK target
110
- '<br>'
333
+ def handle_regexp_QUOTE_NOT_AFTER_WORD(text)
334
+ @quote_converter.convert(text, after_word: false) || convert_string(text)
335
+ end
336
+
337
+ def handle_regexp_QUOTE_AFTER_WORD(text)
338
+ @quote_converter.convert(text, after_word: true) || convert_string(text)
339
+ end
340
+
341
+ # Converts suppressed cross-reference +text+ to HTML by removing the leading backslash.
342
+
343
+ def handle_regexp_SUPPRESSED_CROSSREF(text)
344
+ convert_string(text.delete_prefix('\\'))
111
345
  end
112
346
 
113
347
  ##
@@ -122,9 +356,10 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
122
356
  # <tt>link:</tt>::
123
357
  # Reference to a local file relative to the output directory.
124
358
 
125
- def handle_regexp_HYPERLINK(target)
126
- url = target.text
359
+ def handle_regexp_HYPERLINK(text)
360
+ return convert_string(text) if in_tidylink_label?
127
361
 
362
+ url = CGI.escapeHTML(text)
128
363
  gen_url url, url
129
364
  end
130
365
 
@@ -137,26 +372,8 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
137
372
  # For the +rdoc-label+ scheme the footnote and label prefixes are stripped
138
373
  # when creating a link. All other contents will be linked verbatim.
139
374
 
140
- def handle_regexp_RDOCLINK target
141
- handle_RDOCLINK target.text
142
- end
143
-
144
- ##
145
- # This +target+ is a link where the label is different from the URL
146
- # <tt>label[url]</tt> or <tt>{long label}[url]</tt>
147
-
148
- def handle_regexp_TIDYLINK(target)
149
- text = target.text
150
-
151
- return text unless
152
- text =~ /^\{(.*)\}\[(.*?)\]$/ or text =~ /^(\S+)\[(.*?)\]$/
153
-
154
- label = $1
155
- url = $2
156
-
157
- label = handle_RDOCLINK label if /^rdoc-image:/ =~ label
158
-
159
- gen_url url, label
375
+ def handle_regexp_RDOCLINK(text)
376
+ handle_RDOCLINK text
160
377
  end
161
378
 
162
379
  # :section: Visitor
@@ -170,6 +387,7 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
170
387
  @res = []
171
388
  @in_list_entry = []
172
389
  @list = []
390
+ @heading_ids = {}
173
391
  end
174
392
 
175
393
  ##
@@ -182,7 +400,7 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
182
400
  ##
183
401
  # Adds +block_quote+ to the output
184
402
 
185
- def accept_block_quote block_quote
403
+ def accept_block_quote(block_quote)
186
404
  @res << "\n<blockquote>"
187
405
 
188
406
  block_quote.parts.each do |part|
@@ -195,38 +413,45 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
195
413
  ##
196
414
  # Adds +paragraph+ to the output
197
415
 
198
- def accept_paragraph paragraph
416
+ def accept_paragraph(paragraph)
199
417
  @res << "\n<p>"
200
418
  text = paragraph.text @hard_break
201
- text = text.gsub(/\r?\n/, ' ')
419
+ text = text.gsub(/(#{SPACE_SEPARATED_LETTER_CLASS})?\K(?:\r?\n)+(?=(?(1)(#{SPACE_SEPARATED_LETTER_CLASS})?))/o) {
420
+ defined?($2) && ' '
421
+ }
202
422
  @res << to_html(text)
203
423
  @res << "</p>\n"
204
424
  end
205
425
 
426
+ # Generate syntax highlighted html for ruby-like text.
427
+
428
+ def parsable_text_to_html(text)
429
+ tokens = RDoc::Parser::RubyColorizer.colorize(text)
430
+ result = RDoc::TokenStream.to_html tokens
431
+ result = result + "\n" unless result.end_with?("\n")
432
+ result
433
+ end
434
+
206
435
  ##
207
436
  # Adds +verbatim+ to the output
208
437
 
209
- def accept_verbatim verbatim
438
+ def accept_verbatim(verbatim)
210
439
  text = verbatim.text.rstrip
440
+ format = verbatim.format
441
+
442
+ # Apply Ruby syntax highlighting if
443
+ # - explicitly marked as Ruby (via ruby? which accepts :ruby or :rb)
444
+ # - no format specified but the text is parseable as Ruby
445
+ # Otherwise, add language class when applicable and skip Ruby highlighting
446
+ if verbatim.ruby? || (format.nil? && parseable?(text))
447
+ content = parsable_text_to_html(text)
448
+ klass = ' class="ruby"'
449
+ else
450
+ content = CGI.escapeHTML text
451
+ klass = " class=\"#{format}\"" if format
452
+ end
211
453
 
212
- klass = nil
213
-
214
- content = if verbatim.ruby? or parseable? text then
215
- begin
216
- tokens = RDoc::Parser::RipperStateLex.parse text
217
- klass = ' class="ruby"'
218
-
219
- result = RDoc::TokenStream.to_html tokens
220
- result = result + "\n" unless "\n" == result[-1]
221
- result
222
- rescue
223
- CGI.escapeHTML text
224
- end
225
- else
226
- CGI.escapeHTML text
227
- end
228
-
229
- if @options.pipe then
454
+ if @pipe
230
455
  @res << "\n<pre><code>#{CGI.escapeHTML text}\n</code></pre>\n"
231
456
  else
232
457
  @res << "\n<pre#{klass}>#{content}</pre>\n"
@@ -236,7 +461,7 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
236
461
  ##
237
462
  # Adds +rule+ to the output
238
463
 
239
- def accept_rule rule
464
+ def accept_rule(rule)
240
465
  @res << "<hr>\n"
241
466
  end
242
467
 
@@ -289,40 +514,49 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
289
514
  # Adds +heading+ to the output. The headings greater than 6 are trimmed to
290
515
  # level 6.
291
516
 
292
- def accept_heading heading
517
+ def accept_heading(heading)
293
518
  level = [6, heading.level].min
294
519
 
295
- label = heading.label @code_object
520
+ label = deduplicate_heading_id(heading.label(@code_object))
521
+ legacy_label = deduplicate_heading_id(heading.legacy_label(@code_object))
296
522
 
297
- @res << if @options.output_decoration
523
+ # Add legacy anchor before the heading for backward compatibility.
524
+ # This allows old links with label- prefix to still work.
525
+ if @output_decoration && !@pipe
526
+ @res << "\n<span id=\"#{legacy_label}\" class=\"legacy-anchor\"></span>"
527
+ end
528
+
529
+ @res << if @output_decoration
298
530
  "\n<h#{level} id=\"#{label}\">"
299
531
  else
300
532
  "\n<h#{level}>"
301
533
  end
302
- @res << to_html(heading.text)
303
- unless @options.pipe then
304
- @res << "<span><a href=\"##{label}\">&para;</a>"
305
- @res << " <a href=\"#top\">&uarr;</a></span>"
534
+
535
+ if @pipe
536
+ @res << to_html(heading.text)
537
+ else
538
+ @res << "<a href=\"##{label}\">#{to_html(heading.text)}</a>"
306
539
  end
540
+
307
541
  @res << "</h#{level}>\n"
308
542
  end
309
543
 
310
544
  ##
311
545
  # Adds +raw+ to the output
312
546
 
313
- def accept_raw raw
547
+ def accept_raw(raw)
314
548
  @res << raw.parts.join("\n")
315
549
  end
316
550
 
317
551
  ##
318
552
  # Adds +table+ to the output
319
553
 
320
- def accept_table header, body, aligns
554
+ def accept_table(header, body, aligns)
321
555
  @res << "\n<table role=\"table\">\n<thead>\n<tr>\n"
322
556
  header.zip(aligns) do |text, align|
323
557
  @res << '<th'
324
558
  @res << ' align="' << align << '"' if align
325
- @res << '>' << CGI.escapeHTML(text) << "</th>\n"
559
+ @res << '>' << to_html(text) << "</th>\n"
326
560
  end
327
561
  @res << "</tr>\n</thead>\n<tbody>\n"
328
562
  body.each do |row|
@@ -330,7 +564,7 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
330
564
  row.zip(aligns) do |text, align|
331
565
  @res << '<td'
332
566
  @res << ' align="' << align << '"' if align
333
- @res << '>' << CGI.escapeHTML(text) << "</td>\n"
567
+ @res << '>' << to_html(text) << "</td>\n"
334
568
  end
335
569
  @res << "</tr>\n"
336
570
  end
@@ -339,6 +573,20 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
339
573
 
340
574
  # :section: Utilities
341
575
 
576
+ ##
577
+ # Returns a unique heading ID, appending -1, -2, etc. for duplicates.
578
+ # Matches GitHub's behavior for duplicate heading anchors.
579
+
580
+ def deduplicate_heading_id(id)
581
+ if @heading_ids.key?(id)
582
+ @heading_ids[id] += 1
583
+ "#{id}-#{@heading_ids[id]}"
584
+ else
585
+ @heading_ids[id] = 0
586
+ id
587
+ end
588
+ end
589
+
342
590
  ##
343
591
  # CGI-escapes +text+
344
592
 
@@ -347,18 +595,22 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
347
595
  end
348
596
 
349
597
  ##
350
- # Generate a link to +url+ with content +text+. Handles the special cases
351
- # for img: and link: described under handle_regexp_HYPERLINK
598
+ # Generates an HTML link or image tag for the given +url+ and +text+.
599
+ #
600
+ # - Image URLs (http/https/link ending in .gif, .png, .jpg, .jpeg, .bmp)
601
+ # become <img> tags
602
+ # - File references (.rb, .rdoc, .md) are converted to .html paths
603
+ # - Anchor URLs (#foo) pass through unchanged for GitHub-style header linking
604
+ # - Footnote links get wrapped in <sup> tags
352
605
 
353
- def gen_url url, text
606
+ def gen_url(url, text)
354
607
  scheme, url, id = parse_url url
355
608
 
356
- if %w[http https link].include?(scheme) and
357
- url =~ /\.(gif|png|jpg|jpeg|bmp)$/ then
609
+ if %w[http https link].include?(scheme) && url =~ /\.(gif|png|jpg|jpeg|bmp)\z/
358
610
  "<img src=\"#{url}\" />"
359
611
  else
360
- if scheme != 'link' and /\.(?:rb|rdoc|md)\z/i =~ url
361
- url = url.sub(%r%\A([./]*)(.*)\z%) { "#$1#{$2.tr('.', '_')}.html" }
612
+ if scheme != 'link' and %r%\A((?!https?:)(?:[^/#]*/)*+)([^/#]+)\.(rb|rdoc|md)(?=\z|#)%i =~ url
613
+ url = "#$1#{$2.tr('.', '_')}_#$3.html#$'"
362
614
  end
363
615
 
364
616
  text = text.sub %r%^#{scheme}:/*%i, ''
@@ -366,9 +618,11 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
366
618
 
367
619
  link = "<a#{id} href=\"#{url}\">#{text}</a>"
368
620
 
369
- link = "<sup>#{link}</sup>" if /"foot/ =~ id
370
-
371
- link
621
+ if /"foot/.match?(id)
622
+ "<sup>#{link}</sup>"
623
+ else
624
+ link
625
+ end
372
626
  end
373
627
  end
374
628
 
@@ -381,15 +635,6 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
381
635
  tags[open_tag ? 0 : 1]
382
636
  end
383
637
 
384
- ##
385
- # Maps attributes to HTML tags
386
-
387
- def init_tags
388
- add_tag :BOLD, "<strong>", "</strong>"
389
- add_tag :TT, "<code>", "</code>"
390
- add_tag :EM, "<em>", "</em>"
391
- end
392
-
393
638
  ##
394
639
  # Returns the HTML tag for +list_type+, possible using a label from
395
640
  # +list_item+
@@ -400,7 +645,7 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
400
645
  "<li>"
401
646
  when :LABEL, :NOTE then
402
647
  Array(list_item.label).map do |label|
403
- "<dt>#{to_html label}\n"
648
+ "<dt>#{to_html label}</dt>\n"
404
649
  end.join << "<dd>"
405
650
  else
406
651
  raise RDoc::Error, "Invalid list type: #{list_type.inspect}"
@@ -424,21 +669,14 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
424
669
  ##
425
670
  # Returns true if text is valid ruby syntax
426
671
 
427
- def parseable? text
428
- verbose, $VERBOSE = $VERBOSE, nil
429
- eval("BEGIN {return true}\n#{text}")
430
- rescue SyntaxError
431
- false
432
- ensure
433
- $VERBOSE = verbose
672
+ def parseable?(text)
673
+ Prism.parse_success?(text)
434
674
  end
435
675
 
436
676
  ##
437
677
  # Converts +item+ to HTML using RDoc::Text#to_html
438
678
 
439
- def to_html item
440
- super convert_flow @am.flow item
679
+ def to_html(item)
680
+ handle_inline(item)
441
681
  end
442
-
443
682
  end
444
-