rdoc 8.0.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 (114) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +1 -3
  3. data/RI.md +75 -75
  4. data/exe/rdoc +2 -2
  5. data/lib/rdoc/code_object/alias.rb +71 -69
  6. data/lib/rdoc/code_object/any_method.rb +305 -303
  7. data/lib/rdoc/code_object/attr.rb +150 -148
  8. data/lib/rdoc/code_object/class_module.rb +798 -792
  9. data/lib/rdoc/code_object/constant.rb +175 -173
  10. data/lib/rdoc/code_object/context/section.rb +142 -138
  11. data/lib/rdoc/code_object/context.rb +926 -958
  12. data/lib/rdoc/code_object/extend.rb +7 -5
  13. data/lib/rdoc/code_object/include.rb +7 -5
  14. data/lib/rdoc/code_object/method_attr.rb +326 -319
  15. data/lib/rdoc/code_object/mixin.rb +97 -95
  16. data/lib/rdoc/code_object/normal_class.rb +77 -78
  17. data/lib/rdoc/code_object/normal_module.rb +61 -59
  18. data/lib/rdoc/code_object/require.rb +23 -39
  19. data/lib/rdoc/code_object/single_class.rb +21 -19
  20. data/lib/rdoc/code_object/top_level.rb +212 -219
  21. data/lib/rdoc/code_object.rb +305 -303
  22. data/lib/rdoc/comment.rb +275 -273
  23. data/lib/rdoc/cross_reference.rb +192 -190
  24. data/lib/rdoc/encoding.rb +105 -103
  25. data/lib/rdoc/erb_partial.rb +13 -11
  26. data/lib/rdoc/erbio.rb +29 -27
  27. data/lib/rdoc/generator/aliki.rb +161 -153
  28. data/lib/rdoc/generator/darkfish.rb +645 -635
  29. data/lib/rdoc/generator/json_index.rb +233 -229
  30. data/lib/rdoc/generator/markup.rb +164 -146
  31. data/lib/rdoc/generator/pot/message_extractor.rb +57 -51
  32. data/lib/rdoc/generator/pot/po.rb +52 -51
  33. data/lib/rdoc/generator/pot/po_entry.rb +138 -132
  34. data/lib/rdoc/generator/pot.rb +85 -81
  35. data/lib/rdoc/generator/ri.rb +23 -19
  36. data/lib/rdoc/generator/template/aliki/DESIGN.md +6 -4
  37. data/lib/rdoc/generator/template/aliki/_footer.rhtml +1 -1
  38. data/lib/rdoc/generator/template/aliki/_head.rhtml +10 -10
  39. data/lib/rdoc/generator/template/aliki/_header.rhtml +29 -44
  40. data/lib/rdoc/generator/template/aliki/_sidebar_search.rhtml +4 -4
  41. data/lib/rdoc/generator/template/aliki/css/rdoc.css +207 -178
  42. data/lib/rdoc/generator/template/aliki/js/aliki.js +60 -84
  43. data/lib/rdoc/generator/template/darkfish/_footer.rhtml +1 -1
  44. data/lib/rdoc/generator.rb +48 -46
  45. data/lib/rdoc/i18n/locale.rb +99 -95
  46. data/lib/rdoc/i18n/text.rb +109 -105
  47. data/lib/rdoc/i18n.rb +7 -5
  48. data/lib/rdoc/markdown/byte_runtime.rb +80 -0
  49. data/lib/rdoc/markdown.kpeg +15 -11
  50. data/lib/rdoc/markdown.rb +40 -47
  51. data/lib/rdoc/markup/block_quote.rb +12 -8
  52. data/lib/rdoc/markup/document.rb +127 -123
  53. data/lib/rdoc/markup/formatter.rb +219 -215
  54. data/lib/rdoc/markup/include.rb +33 -29
  55. data/lib/rdoc/markup/indented_paragraph.rb +37 -33
  56. data/lib/rdoc/markup/inline_parser.rb +281 -277
  57. data/lib/rdoc/markup/list.rb +80 -88
  58. data/lib/rdoc/markup/list_item.rb +73 -85
  59. data/lib/rdoc/markup/paragraph.rb +23 -19
  60. data/lib/rdoc/markup/parser.rb +501 -497
  61. data/lib/rdoc/markup/pre_process.rb +283 -279
  62. data/lib/rdoc/markup/raw.rb +2 -2
  63. data/lib/rdoc/markup/rule.rb +16 -12
  64. data/lib/rdoc/markup/to_ansi.rb +143 -139
  65. data/lib/rdoc/markup/to_bs.rb +72 -68
  66. data/lib/rdoc/markup/to_html.rb +594 -565
  67. data/lib/rdoc/markup/to_html_crossref.rb +234 -230
  68. data/lib/rdoc/markup/to_html_snippet.rb +232 -227
  69. data/lib/rdoc/markup/to_joined_paragraph.rb +36 -32
  70. data/lib/rdoc/markup/to_label.rb +63 -59
  71. data/lib/rdoc/markup/to_markdown.rb +212 -208
  72. data/lib/rdoc/markup/to_rdoc.rb +336 -332
  73. data/lib/rdoc/markup/to_table_of_contents.rb +66 -62
  74. data/lib/rdoc/markup/to_test.rb +60 -56
  75. data/lib/rdoc/markup/to_tt_only.rb +84 -80
  76. data/lib/rdoc/markup/verbatim.rb +62 -58
  77. data/lib/rdoc/markup.rb +198 -196
  78. data/lib/rdoc/options.rb +1063 -1061
  79. data/lib/rdoc/parser/c.rb +1039 -1037
  80. data/lib/rdoc/parser/changelog.rb +319 -315
  81. data/lib/rdoc/parser/markdown.rb +17 -13
  82. data/lib/rdoc/parser/rbs.rb +239 -235
  83. data/lib/rdoc/parser/rd.rb +17 -13
  84. data/lib/rdoc/parser/ruby.rb +1245 -1124
  85. data/lib/rdoc/parser/ruby_colorizer.rb +263 -213
  86. data/lib/rdoc/parser/simple.rb +31 -27
  87. data/lib/rdoc/parser/text.rb +12 -8
  88. data/lib/rdoc/parser.rb +228 -220
  89. data/lib/rdoc/rbs_helper.rb +1 -1
  90. data/lib/rdoc/rd/inline.rb +57 -53
  91. data/lib/rdoc/rd.rb +90 -88
  92. data/lib/rdoc/rdoc.rb +500 -491
  93. data/lib/rdoc/ri/driver.rb +1140 -1135
  94. data/lib/rdoc/ri/formatter.rb +7 -3
  95. data/lib/rdoc/ri/paths.rb +140 -136
  96. data/lib/rdoc/ri/servlet.rb +354 -350
  97. data/lib/rdoc/ri/store.rb +4 -2
  98. data/lib/rdoc/ri/task.rb +55 -51
  99. data/lib/rdoc/ri.rb +14 -12
  100. data/lib/rdoc/rubygems_hook.rb +183 -181
  101. data/lib/rdoc/server.rb +349 -347
  102. data/lib/rdoc/stats/normal.rb +46 -42
  103. data/lib/rdoc/stats/quiet.rb +39 -35
  104. data/lib/rdoc/stats/verbose.rb +35 -31
  105. data/lib/rdoc/stats.rb +365 -363
  106. data/lib/rdoc/store.rb +888 -902
  107. data/lib/rdoc/task.rb +260 -256
  108. data/lib/rdoc/text.rb +135 -133
  109. data/lib/rdoc/token_stream.rb +101 -93
  110. data/lib/rdoc/tom_doc.rb +203 -201
  111. data/lib/rdoc/version.rb +1 -1
  112. metadata +4 -5
  113. data/lib/rdoc/markdown/literals.kpeg +0 -21
  114. data/lib/rdoc/markdown/literals.rb +0 -454
data/lib/rdoc/server.rb CHANGED
@@ -6,26 +6,27 @@ require 'erb'
6
6
  require 'set'
7
7
  require 'uri'
8
8
 
9
- ##
10
- # A minimal HTTP server for live-reloading RDoc documentation.
11
- #
12
- # Uses Ruby's built-in +TCPServer+ (no external dependencies).
13
- #
14
- # Used by <tt>rdoc --server</tt> to let developers preview documentation
15
- # while editing source files. Parses sources once on startup, watches for
16
- # file changes, re-parses only the changed files, and auto-refreshes the
17
- # browser via a simple polling script.
18
-
19
- class RDoc::Server
20
-
9
+ module RDoc
21
10
  ##
22
- # Returns a live-reload polling script with the given +last_change_time+
23
- # embedded so the browser knows the exact timestamp of the content it
24
- # received. This avoids a race where a change that occurs between page
25
- # generation and the first poll would be silently skipped.
11
+ # A minimal HTTP server for live-reloading RDoc documentation.
12
+ #
13
+ # Uses Ruby's built-in +TCPServer+ (no external dependencies).
14
+ #
15
+ # Used by <tt>rdoc --server</tt> to let developers preview documentation
16
+ # while editing source files. Parses sources once on startup, watches for
17
+ # file changes, re-parses only the changed files, and auto-refreshes the
18
+ # browser via a simple polling script.
19
+
20
+ class Server
26
21
 
27
- def self.live_reload_script(last_change_time)
28
- <<~JS
22
+ ##
23
+ # Returns a live-reload polling script with the given +last_change_time+
24
+ # embedded so the browser knows the exact timestamp of the content it
25
+ # received. This avoids a race where a change that occurs between page
26
+ # generation and the first poll would be silently skipped.
27
+
28
+ def self.live_reload_script(last_change_time)
29
+ <<~JS
29
30
  <script>
30
31
  (function() {
31
32
  var lastChange = #{last_change_time.to_json};
@@ -38,423 +39,424 @@ class RDoc::Server
38
39
  })();
39
40
  </script>
40
41
  JS
41
- end
42
-
43
- CONTENT_TYPES = {
44
- '.html' => 'text/html',
45
- '.css' => 'text/css',
46
- '.js' => 'application/javascript',
47
- '.json' => 'application/json',
48
- }.freeze
49
-
50
- STATUS_TEXTS = {
51
- 200 => 'OK',
52
- 400 => 'Bad Request',
53
- 404 => 'Not Found',
54
- 405 => 'Method Not Allowed',
55
- 500 => 'Internal Server Error',
56
- }.freeze
57
-
58
- class FileChanges # :nodoc:
59
- attr_reader :changed_files, :removed_files
60
-
61
- def initialize(rdoc)
62
- @rdoc = rdoc
63
- @changed_files = []
64
- @removed_files = []
65
- @reload_rbs_signatures = false
66
42
  end
67
43
 
68
- def record_changed(file)
69
- reload_rbs_signatures_if_needed file
70
- changed_files << file
71
- end
44
+ CONTENT_TYPES = {
45
+ '.html' => 'text/html',
46
+ '.css' => 'text/css',
47
+ '.js' => 'application/javascript',
48
+ '.json' => 'application/json',
49
+ }.freeze
50
+
51
+ STATUS_TEXTS = {
52
+ 200 => 'OK',
53
+ 400 => 'Bad Request',
54
+ 404 => 'Not Found',
55
+ 405 => 'Method Not Allowed',
56
+ 500 => 'Internal Server Error',
57
+ }.freeze
58
+
59
+ class FileChanges # :nodoc:
60
+ attr_reader :changed_files, :removed_files
61
+
62
+ def initialize(rdoc)
63
+ @rdoc = rdoc
64
+ @changed_files = []
65
+ @removed_files = []
66
+ @reload_rbs_signatures = false
67
+ end
72
68
 
73
- def record_removed(file)
74
- reload_rbs_signatures_if_needed file
75
- removed_files << file
76
- end
69
+ def record_changed(file)
70
+ reload_rbs_signatures_if_needed file
71
+ changed_files << file
72
+ end
77
73
 
78
- def reload_rbs_signatures?
79
- @reload_rbs_signatures
80
- end
74
+ def record_removed(file)
75
+ reload_rbs_signatures_if_needed file
76
+ removed_files << file
77
+ end
81
78
 
82
- def source_files_changed?
83
- !changed_files.empty? || !removed_files.empty?
84
- end
79
+ def reload_rbs_signatures?
80
+ @reload_rbs_signatures
81
+ end
82
+
83
+ def source_files_changed?
84
+ !changed_files.empty? || !removed_files.empty?
85
+ end
85
86
 
86
- private
87
+ private
87
88
 
88
- def reload_rbs_signatures_if_needed(file)
89
- @reload_rbs_signatures = true if @rdoc.auto_discovered_rbs_signature_file?(file)
89
+ def reload_rbs_signatures_if_needed(file)
90
+ @reload_rbs_signatures = true if @rdoc.auto_discovered_rbs_signature_file?(file)
91
+ end
90
92
  end
91
- end
92
93
 
93
- ##
94
- # Creates a new server.
95
- #
96
- # +rdoc+ is the RDoc::RDoc instance that has already parsed the source
97
- # files.
98
- # +port+ is the TCP port to listen on.
99
-
100
- def initialize(rdoc, port)
101
- @rdoc = rdoc
102
- @options = rdoc.options
103
- @store = rdoc.store
104
- @port = port
105
-
106
- # Silence stats output — the server prints its own timing.
107
- @rdoc.stats.verbosity = 0
108
- @generator = create_generator
109
- @template_dir = File.expand_path(@generator.template_dir)
110
- @page_cache = {}
111
- @last_change_time = Time.now.to_f
112
- @mutex = Mutex.new
113
- @running = false
114
- end
94
+ ##
95
+ # Creates a new server.
96
+ #
97
+ # +rdoc+ is the RDoc::RDoc instance that has already parsed the source
98
+ # files.
99
+ # +port+ is the TCP port to listen on.
115
100
 
116
- ##
117
- # Starts the server. Blocks until interrupted.
101
+ def initialize(rdoc, port)
102
+ @rdoc = rdoc
103
+ @options = rdoc.options
104
+ @store = rdoc.store
105
+ @port = port
106
+
107
+ # Silence stats output — the server prints its own timing.
108
+ @rdoc.stats.verbosity = 0
109
+ @generator = create_generator
110
+ @template_dir = File.expand_path(@generator.template_dir)
111
+ @page_cache = {}
112
+ @last_change_time = Time.now.to_f
113
+ @mutex = Mutex.new
114
+ @running = false
115
+ end
118
116
 
119
- def start
120
- @tcp_server = TCPServer.new('127.0.0.1', @port)
121
- @running = true
117
+ ##
118
+ # Starts the server. Blocks until interrupted.
122
119
 
123
- @watcher_thread = start_watcher(@rdoc.watch_files)
120
+ def start
121
+ @tcp_server = TCPServer.new('127.0.0.1', @port)
122
+ @running = true
124
123
 
125
- url = "http://localhost:#{@port}"
126
- $stderr.puts "\nServing documentation at: \e]8;;#{url}\e\\#{url}\e]8;;\e\\"
127
- $stderr.puts "Press Ctrl+C to stop.\n\n"
124
+ @watcher_thread = start_watcher(@rdoc.watch_files)
128
125
 
129
- loop do
130
- client = @tcp_server.accept
131
- Thread.new(client) { |c| handle_client(c) }
126
+ url = "http://localhost:#{@port}"
127
+ $stderr.puts "\nServing documentation at: \e]8;;#{url}\e\\#{url}\e]8;;\e\\"
128
+ $stderr.puts "Press Ctrl+C to stop.\n\n"
129
+
130
+ loop do
131
+ client = @tcp_server.accept
132
+ Thread.new(client) { |c| handle_client(c) }
133
+ end
134
+ rescue Interrupt
135
+ # Ctrl+C
136
+ ensure
137
+ @running = false
138
+ @tcp_server&.close
139
+ @watcher_thread&.join(2)
132
140
  end
133
- rescue Interrupt
134
- # Ctrl+C
135
- ensure
136
- @running = false
137
- @tcp_server&.close
138
- @watcher_thread&.join(2)
139
- end
140
141
 
141
142
  private
142
143
 
143
- def measure
144
- start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
145
- yield
146
- ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round(1)
147
- end
144
+ def measure
145
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
146
+ yield
147
+ ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round(1)
148
+ end
148
149
 
149
- def create_generator
150
- gen = RDoc::Generator::Aliki.new(@store, @options)
151
- gen.file_output = false
152
- gen.asset_rel_path = ''
153
- gen.setup
154
- gen
155
- end
150
+ def create_generator
151
+ gen = Generator::Aliki.new(@store, @options)
152
+ gen.file_output = false
153
+ gen.asset_rel_path = ''
154
+ gen.setup
155
+ gen
156
+ end
156
157
 
157
- ##
158
- # Reads an HTTP request from +client+ and dispatches to the router.
158
+ ##
159
+ # Reads an HTTP request from +client+ and dispatches to the router.
159
160
 
160
- def handle_client(client)
161
- client.binmode
161
+ def handle_client(client)
162
+ client.binmode
162
163
 
163
- return unless IO.select([client], nil, nil, 5)
164
+ return unless IO.select([client], nil, nil, 5)
164
165
 
165
- request_line = client.gets("\n")
166
- return unless request_line
166
+ request_line = client.gets("\n")
167
+ return unless request_line
167
168
 
168
- method, request_uri, = request_line.split(' ', 3)
169
- return write_response(client, 400, 'text/plain', 'Bad Request') unless request_uri
169
+ method, request_uri, = request_line.split(' ', 3)
170
+ return write_response(client, 400, 'text/plain', 'Bad Request') unless request_uri
170
171
 
171
- begin
172
- path = URI.parse(request_uri).path
173
- rescue URI::InvalidURIError
174
- return write_response(client, 400, 'text/plain', 'Bad Request')
175
- end
172
+ begin
173
+ path = URI.parse(request_uri).path
174
+ rescue URI::InvalidURIError
175
+ return write_response(client, 400, 'text/plain', 'Bad Request')
176
+ end
176
177
 
177
- while (line = client.gets("\n"))
178
- break if line.strip.empty?
179
- end
178
+ while (line = client.gets("\n"))
179
+ break if line.strip.empty?
180
+ end
180
181
 
181
- unless method == 'GET'
182
- return write_response(client, 405, 'text/plain', 'Method Not Allowed')
183
- end
182
+ unless method == 'GET'
183
+ return write_response(client, 405, 'text/plain', 'Method Not Allowed')
184
+ end
184
185
 
185
- if path.start_with?('/__') || %r{\A/(?:css|js)/}.match?(path)
186
- status, content_type, body = route(path)
187
- else
188
- duration_ms = measure do
186
+ if path.start_with?('/__') || %r{\A/(?:css|js)/}.match?(path)
189
187
  status, content_type, body = route(path)
188
+ else
189
+ duration_ms = measure do
190
+ status, content_type, body = route(path)
191
+ end
192
+ $stderr.puts "#{status} #{path} (#{duration_ms}ms)"
190
193
  end
191
- $stderr.puts "#{status} #{path} (#{duration_ms}ms)"
192
- end
193
- write_response(client, status, content_type, body)
194
- rescue => e
195
- write_response(client, 500, 'text/html', <<~HTML)
194
+ write_response(client, status, content_type, body)
195
+ rescue => e
196
+ write_response(client, 500, 'text/html', <<~HTML)
196
197
  <!DOCTYPE html>
197
198
  <html><body>
198
199
  <h1>Internal Server Error</h1>
199
200
  <pre>#{ERB::Util.html_escape e.message}\n#{ERB::Util.html_escape e.backtrace.join("\n")}</pre>
200
201
  </body></html>
201
202
  HTML
202
- ensure
203
- client.close rescue nil
204
- end
203
+ ensure
204
+ client.close rescue nil
205
+ end
205
206
 
206
- ##
207
- # Routes a request path and returns [status, content_type, body].
208
-
209
- def route(path)
210
- case path
211
- when '/__status'
212
- t = @mutex.synchronize { @last_change_time }
213
- [200, 'application/json', JSON.generate(last_change: t)]
214
- when '/js/search_data.js'
215
- # Search data is dynamically generated, not a static asset
216
- serve_page(path)
217
- when %r{\A/(?:css|js)/}
218
- serve_asset(path)
219
- else
220
- serve_page(path)
207
+ ##
208
+ # Routes a request path and returns [status, content_type, body].
209
+
210
+ def route(path)
211
+ case path
212
+ when '/__status'
213
+ t = @mutex.synchronize { @last_change_time }
214
+ [200, 'application/json', JSON.generate(last_change: t)]
215
+ when '/js/search_data.js'
216
+ # Search data is dynamically generated, not a static asset
217
+ serve_page(path)
218
+ when %r{\A/(?:css|js)/}
219
+ serve_asset(path)
220
+ else
221
+ serve_page(path)
222
+ end
221
223
  end
222
- end
223
224
 
224
- ##
225
- # Writes an HTTP/1.1 response to +client+.
226
-
227
- def write_response(client, status, content_type, body)
228
- body_bytes = body.b
229
-
230
- header = +"HTTP/1.1 #{status} #{STATUS_TEXTS[status] || 'Unknown'}\r\n"
231
- header << "Content-Type: #{content_type}\r\n"
232
- header << "Content-Length: #{body_bytes.bytesize}\r\n"
233
- header << "Connection: close\r\n"
234
- header << "\r\n"
235
-
236
- client.write(header)
237
- client.write(body_bytes)
238
- client.flush
239
- rescue Errno::EPIPE
240
- # Client disconnected before we finished writing — harmless.
241
- end
225
+ ##
226
+ # Writes an HTTP/1.1 response to +client+.
242
227
 
243
- ##
244
- # Serves a static asset (CSS, JS) from the Aliki template directory.
228
+ def write_response(client, status, content_type, body)
229
+ body_bytes = body.b
245
230
 
246
- def serve_asset(path)
247
- rel_path = path.delete_prefix("/")
248
- asset_path = File.join(@generator.template_dir, rel_path)
249
- real_asset = File.expand_path(asset_path)
231
+ header = +"HTTP/1.1 #{status} #{STATUS_TEXTS[status] || 'Unknown'}\r\n"
232
+ header << "Content-Type: #{content_type}\r\n"
233
+ header << "Content-Length: #{body_bytes.bytesize}\r\n"
234
+ header << "Connection: close\r\n"
235
+ header << "\r\n"
250
236
 
251
- unless real_asset.start_with?("#{@template_dir}/") && File.file?(real_asset)
252
- return [404, 'text/plain', "Asset not found: #{rel_path}"]
237
+ client.write(header)
238
+ client.write(body_bytes)
239
+ client.flush
240
+ rescue Errno::EPIPE
241
+ # Client disconnected before we finished writing — harmless.
253
242
  end
254
243
 
255
- ext = File.extname(rel_path)
256
- content_type = CONTENT_TYPES[ext] || 'application/octet-stream'
257
- [200, content_type, File.read(real_asset)]
258
- end
244
+ ##
245
+ # Serves a static asset (CSS, JS) from the Aliki template directory.
259
246
 
260
- ##
261
- # Serves an HTML page, rendering from the generator or returning a cached
262
- # version.
247
+ def serve_asset(path)
248
+ rel_path = path.delete_prefix("/")
249
+ asset_path = File.join(@generator.template_dir, rel_path)
250
+ real_asset = File.expand_path(asset_path)
263
251
 
264
- def serve_page(path)
265
- name = path.delete_prefix("/")
266
- name = 'index.html' if name.empty?
267
-
268
- html = render_page(name)
252
+ unless real_asset.start_with?("#{@template_dir}/") && File.file?(real_asset)
253
+ return [404, 'text/plain', "Asset not found: #{rel_path}"]
254
+ end
269
255
 
270
- unless html
271
- not_found = @generator.generate_servlet_not_found(
272
- "The page <kbd>#{ERB::Util.html_escape path}</kbd> was not found"
273
- )
274
- t = @mutex.synchronize { @last_change_time }
275
- return [404, 'text/html', inject_live_reload(not_found || '', t)]
256
+ ext = File.extname(rel_path)
257
+ content_type = CONTENT_TYPES[ext] || 'application/octet-stream'
258
+ [200, content_type, File.read(real_asset)]
276
259
  end
277
260
 
278
- ext = File.extname(name)
279
- content_type = CONTENT_TYPES[ext] || 'text/html'
280
- [200, content_type, html]
281
- end
261
+ ##
262
+ # Serves an HTML page, rendering from the generator or returning a cached
263
+ # version.
282
264
 
283
- ##
284
- # Renders a page through the Aliki generator and caches the result.
265
+ def serve_page(path)
266
+ name = path.delete_prefix("/")
267
+ name = 'index.html' if name.empty?
285
268
 
286
- def render_page(name)
287
- @mutex.synchronize do
288
- return @page_cache[name] if @page_cache[name]
269
+ html = render_page(name)
289
270
 
290
- result = generate_page(name)
291
- return nil unless result
271
+ unless html
272
+ not_found = @generator.generate_servlet_not_found(
273
+ "The page <kbd>#{ERB::Util.html_escape path}</kbd> was not found"
274
+ )
275
+ t = @mutex.synchronize { @last_change_time }
276
+ return [404, 'text/html', inject_live_reload(not_found || '', t)]
277
+ end
292
278
 
293
- result = inject_live_reload(result, @last_change_time) if name.end_with?('.html')
294
- @page_cache[name] = result
279
+ ext = File.extname(name)
280
+ content_type = CONTENT_TYPES[ext] || 'text/html'
281
+ [200, content_type, html]
295
282
  end
296
- end
297
283
 
298
- ##
299
- # Dispatches to the appropriate generator method based on the page name.
300
-
301
- def generate_page(name)
302
- case name
303
- when 'index.html'
304
- @generator.generate_index
305
- when 'table_of_contents.html'
306
- @generator.generate_table_of_contents
307
- when 'js/search_data.js'
308
- "var search_data = #{JSON.generate(index: @generator.build_search_index)};"
309
- else
310
- text_name = name.chomp('.html')
311
- class_name = text_name.gsub('/', '::')
312
-
313
- if klass = @store.find_class_or_module(class_name)
314
- @generator.generate_class(klass)
315
- elsif page = @store.find_text_page(text_name.sub(/_([^_]*)\z/, '.\1'))
316
- @generator.generate_page(page)
284
+ ##
285
+ # Renders a page through the Aliki generator and caches the result.
286
+
287
+ def render_page(name)
288
+ @mutex.synchronize do
289
+ return @page_cache[name] if @page_cache[name]
290
+
291
+ result = generate_page(name)
292
+ return nil unless result
293
+
294
+ result = inject_live_reload(result, @last_change_time) if name.end_with?('.html')
295
+ @page_cache[name] = result
317
296
  end
318
297
  end
319
- end
320
298
 
321
- ##
322
- # Injects the live-reload polling script before +</body>+.
299
+ ##
300
+ # Dispatches to the appropriate generator method based on the page name.
301
+
302
+ def generate_page(name)
303
+ case name
304
+ when 'index.html'
305
+ @generator.generate_index
306
+ when 'table_of_contents.html'
307
+ @generator.generate_table_of_contents
308
+ when 'js/search_data.js'
309
+ "var search_data = #{JSON.generate(index: @generator.build_search_index)};"
310
+ else
311
+ text_name = name.chomp('.html')
312
+ class_name = text_name.gsub('/', '::')
313
+
314
+ if klass = @store.find_class_or_module(class_name)
315
+ @generator.generate_class(klass)
316
+ elsif page = @store.find_text_page(text_name.sub(/_([^_]*)\z/, '.\1'))
317
+ @generator.generate_page(page)
318
+ end
319
+ end
320
+ end
323
321
 
324
- def inject_live_reload(html, last_change_time)
325
- html.sub('</body>', "#{self.class.live_reload_script(last_change_time)}</body>")
326
- end
322
+ ##
323
+ # Injects the live-reload polling script before +</body>+.
327
324
 
328
- ##
329
- # Starts a background thread that polls source file mtimes and triggers
330
- # re-parsing when changes are detected.
331
-
332
- def start_watcher(source_files)
333
- @file_mtimes = file_mtimes_for(source_files)
334
-
335
- Thread.new do
336
- while @running
337
- begin
338
- sleep 1
339
- check_for_changes
340
- rescue => e
341
- $stderr.puts "RDoc server watcher error: #{e.message}"
325
+ def inject_live_reload(html, last_change_time)
326
+ html.sub('</body>', "#{self.class.live_reload_script(last_change_time)}</body>")
327
+ end
328
+
329
+ ##
330
+ # Starts a background thread that polls source file mtimes and triggers
331
+ # re-parsing when changes are detected.
332
+
333
+ def start_watcher(source_files)
334
+ @file_mtimes = file_mtimes_for(source_files)
335
+
336
+ Thread.new do
337
+ while @running
338
+ begin
339
+ sleep 1
340
+ check_for_changes
341
+ rescue => e
342
+ $stderr.puts "RDoc server watcher error: #{e.message}"
343
+ end
342
344
  end
343
345
  end
344
346
  end
345
- end
346
347
 
347
- def file_mtimes_for(files)
348
- files.each_with_object({}) do |f, h|
349
- h[f] = RDoc.safe_mtime(f)
348
+ def file_mtimes_for(files)
349
+ files.each_with_object({}) do |f, h|
350
+ h[f] = ::RDoc.safe_mtime(f)
351
+ end
350
352
  end
351
- end
352
353
 
353
- ##
354
- # Checks for modified, new, and deleted files. Returns true if any
355
- # changes were found and processed.
354
+ ##
355
+ # Checks for modified, new, and deleted files. Returns true if any
356
+ # changes were found and processed.
356
357
 
357
- def check_for_changes
358
- changes = FileChanges.new @rdoc
359
- current_files = current_watch_files
360
- current_file_set = current_files.to_set
358
+ def check_for_changes
359
+ changes = FileChanges.new @rdoc
360
+ current_files = current_watch_files
361
+ current_file_set = current_files.to_set
361
362
 
362
- @file_mtimes.each_key do |file|
363
- changes.record_removed file unless current_file_set.include? file
364
- end
363
+ @file_mtimes.each_key do |file|
364
+ changes.record_removed file unless current_file_set.include? file
365
+ end
365
366
 
366
- current_files.each do |file|
367
- next unless file_changed? file
367
+ current_files.each do |file|
368
+ next unless file_changed? file
368
369
 
369
- @file_mtimes[file] = nil unless @file_mtimes.key? file
370
- changes.record_changed file
371
- end
370
+ @file_mtimes[file] = nil unless @file_mtimes.key? file
371
+ changes.record_changed file
372
+ end
372
373
 
373
- return false unless changes.source_files_changed?
374
+ return false unless changes.source_files_changed?
374
375
 
375
- reparse_and_refresh changes
376
- true
377
- end
376
+ reparse_and_refresh changes
377
+ true
378
+ end
378
379
 
379
- ##
380
- # Re-parses changed files, removes deleted files from the store,
381
- # refreshes the generator, and invalidates caches.
382
-
383
- def reparse_and_refresh(changes)
384
- @mutex.synchronize do
385
- remove_files changes.removed_files
386
- reparse_files changes.changed_files
387
- reload_rbs_signatures if changes.reload_rbs_signatures?
388
- @store.complete(@options.visibility)
389
- @store.invalidate_type_name_lookup if changes.source_files_changed?
390
-
391
- @generator.refresh_store_data
392
- @page_cache.clear
393
- @last_change_time = Time.now.to_f
380
+ ##
381
+ # Re-parses changed files, removes deleted files from the store,
382
+ # refreshes the generator, and invalidates caches.
383
+
384
+ def reparse_and_refresh(changes)
385
+ @mutex.synchronize do
386
+ remove_files changes.removed_files
387
+ reparse_files changes.changed_files
388
+ reload_rbs_signatures if changes.reload_rbs_signatures?
389
+ @store.complete(@options.visibility)
390
+ @store.invalidate_type_name_lookup if changes.source_files_changed?
391
+
392
+ @generator.refresh_store_data
393
+ @page_cache.clear
394
+ @last_change_time = Time.now.to_f
395
+ end
394
396
  end
395
- end
396
397
 
397
- def current_watch_files
398
- file_list = @rdoc.normalized_file_list(
399
- @options.files.empty? ? [@options.root.to_s] : @options.files,
400
- true, @options.exclude
401
- )
402
- @rdoc.remove_unparseable(file_list).keys | @rdoc.auto_discovered_rbs_signature_files
403
- end
398
+ def current_watch_files
399
+ file_list = @rdoc.normalized_file_list(
400
+ @options.files.empty? ? [@options.root.to_s] : @options.files,
401
+ true, @options.exclude
402
+ )
403
+ @rdoc.remove_duplicate_files(@rdoc.remove_unparseable(file_list)).keys | @rdoc.auto_discovered_rbs_signature_files
404
+ end
404
405
 
405
- def file_changed?(file)
406
- return true unless @file_mtimes.key? file
406
+ def file_changed?(file)
407
+ return true unless @file_mtimes.key? file
407
408
 
408
- old_mtime = @file_mtimes[file]
409
- return true unless old_mtime
409
+ old_mtime = @file_mtimes[file]
410
+ return true unless old_mtime
410
411
 
411
- current_mtime = RDoc.safe_mtime(file)
412
- current_mtime && current_mtime > old_mtime
413
- end
412
+ current_mtime = ::RDoc.safe_mtime(file)
413
+ current_mtime && current_mtime > old_mtime
414
+ end
414
415
 
415
- def remove_files(files)
416
- return if files.empty?
416
+ def remove_files(files)
417
+ return if files.empty?
417
418
 
418
- $stderr.puts "Removed: #{files.join(', ')}"
419
- files.each do |f|
420
- @file_mtimes.delete(f)
421
- relative = @rdoc.relative_path_for(f)
422
- @store.clear_file_contributions(relative)
423
- @store.remove_file(relative)
419
+ $stderr.puts "Removed: #{files.join(', ')}"
420
+ files.each do |f|
421
+ @file_mtimes.delete(f)
422
+ relative = @rdoc.relative_path_for(f)
423
+ @store.clear_file_contributions(relative)
424
+ @store.remove_file(relative)
425
+ end
424
426
  end
425
- end
426
427
 
427
- def reload_rbs_signatures
428
- duration_ms = measure do
429
- @rdoc.load_auto_discovered_rbs_signatures
430
- @rdoc.record_auto_discovered_rbs_signature_mtimes
431
- @rdoc.auto_discovered_rbs_signature_files.each do |file|
432
- @file_mtimes[file] = RDoc.safe_mtime(file)
428
+ def reload_rbs_signatures
429
+ duration_ms = measure do
430
+ @rdoc.load_auto_discovered_rbs_signatures
431
+ @rdoc.record_auto_discovered_rbs_signature_mtimes
432
+ @rdoc.auto_discovered_rbs_signature_files.each do |file|
433
+ @file_mtimes[file] = ::RDoc.safe_mtime(file)
434
+ end
433
435
  end
436
+ $stderr.puts "Reloaded RBS signatures (#{duration_ms}ms)"
434
437
  end
435
- $stderr.puts "Reloaded RBS signatures (#{duration_ms}ms)"
436
- end
437
438
 
438
- def reparse_files(files)
439
- return if files.empty?
439
+ def reparse_files(files)
440
+ return if files.empty?
440
441
 
441
- changed_file_names = []
442
- duration_ms = measure do
443
- files.each do |f|
444
- relative = @rdoc.relative_path_for(f)
445
- changed_file_names << relative
446
- begin
447
- @store.clear_file_contributions(relative, keep_position: true)
448
- @rdoc.parse_file(f)
449
- @file_mtimes[f] = RDoc.safe_mtime(f)
450
- rescue => e
451
- $stderr.puts "Error parsing #{f}: #{e.message}"
442
+ changed_file_names = []
443
+ duration_ms = measure do
444
+ files.each do |f|
445
+ relative = @rdoc.relative_path_for(f)
446
+ changed_file_names << relative
447
+ begin
448
+ @store.clear_file_contributions(relative, keep_position: true)
449
+ @rdoc.parse_file(f)
450
+ @file_mtimes[f] = ::RDoc.safe_mtime(f)
451
+ rescue => e
452
+ $stderr.puts "Error parsing #{f}: #{e.message}"
453
+ end
452
454
  end
453
- end
454
455
 
455
- @store.cleanup_stale_contributions
456
+ @store.cleanup_stale_contributions
457
+ end
458
+ $stderr.puts "Re-parsed #{changed_file_names.join(', ')} (#{duration_ms}ms)"
456
459
  end
457
- $stderr.puts "Re-parsed #{changed_file_names.join(', ')} (#{duration_ms}ms)"
458
- end
459
460
 
461
+ end
460
462
  end