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
@@ -0,0 +1,462 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+ require 'json'
5
+ require 'erb'
6
+ require 'set'
7
+ require 'uri'
8
+
9
+ module RDoc
10
+ ##
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
21
+
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
30
+ <script>
31
+ (function() {
32
+ var lastChange = #{last_change_time.to_json};
33
+ setInterval(function() {
34
+ fetch('/__status').then(function(r) { return r.json(); }).then(function(data) {
35
+ if (data.last_change > lastChange) location.reload();
36
+ lastChange = data.last_change;
37
+ }).catch(function() {});
38
+ }, 1000);
39
+ })();
40
+ </script>
41
+ JS
42
+ end
43
+
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
68
+
69
+ def record_changed(file)
70
+ reload_rbs_signatures_if_needed file
71
+ changed_files << file
72
+ end
73
+
74
+ def record_removed(file)
75
+ reload_rbs_signatures_if_needed file
76
+ removed_files << file
77
+ end
78
+
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
86
+
87
+ private
88
+
89
+ def reload_rbs_signatures_if_needed(file)
90
+ @reload_rbs_signatures = true if @rdoc.auto_discovered_rbs_signature_file?(file)
91
+ end
92
+ end
93
+
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.
100
+
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
116
+
117
+ ##
118
+ # Starts the server. Blocks until interrupted.
119
+
120
+ def start
121
+ @tcp_server = TCPServer.new('127.0.0.1', @port)
122
+ @running = true
123
+
124
+ @watcher_thread = start_watcher(@rdoc.watch_files)
125
+
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)
140
+ end
141
+
142
+ private
143
+
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
149
+
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
157
+
158
+ ##
159
+ # Reads an HTTP request from +client+ and dispatches to the router.
160
+
161
+ def handle_client(client)
162
+ client.binmode
163
+
164
+ return unless IO.select([client], nil, nil, 5)
165
+
166
+ request_line = client.gets("\n")
167
+ return unless request_line
168
+
169
+ method, request_uri, = request_line.split(' ', 3)
170
+ return write_response(client, 400, 'text/plain', 'Bad Request') unless request_uri
171
+
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
177
+
178
+ while (line = client.gets("\n"))
179
+ break if line.strip.empty?
180
+ end
181
+
182
+ unless method == 'GET'
183
+ return write_response(client, 405, 'text/plain', 'Method Not Allowed')
184
+ end
185
+
186
+ if path.start_with?('/__') || %r{\A/(?:css|js)/}.match?(path)
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)"
193
+ end
194
+ write_response(client, status, content_type, body)
195
+ rescue => e
196
+ write_response(client, 500, 'text/html', <<~HTML)
197
+ <!DOCTYPE html>
198
+ <html><body>
199
+ <h1>Internal Server Error</h1>
200
+ <pre>#{ERB::Util.html_escape e.message}\n#{ERB::Util.html_escape e.backtrace.join("\n")}</pre>
201
+ </body></html>
202
+ HTML
203
+ ensure
204
+ client.close rescue nil
205
+ end
206
+
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
223
+ end
224
+
225
+ ##
226
+ # Writes an HTTP/1.1 response to +client+.
227
+
228
+ def write_response(client, status, content_type, body)
229
+ body_bytes = body.b
230
+
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"
236
+
237
+ client.write(header)
238
+ client.write(body_bytes)
239
+ client.flush
240
+ rescue Errno::EPIPE
241
+ # Client disconnected before we finished writing — harmless.
242
+ end
243
+
244
+ ##
245
+ # Serves a static asset (CSS, JS) from the Aliki template directory.
246
+
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)
251
+
252
+ unless real_asset.start_with?("#{@template_dir}/") && File.file?(real_asset)
253
+ return [404, 'text/plain', "Asset not found: #{rel_path}"]
254
+ end
255
+
256
+ ext = File.extname(rel_path)
257
+ content_type = CONTENT_TYPES[ext] || 'application/octet-stream'
258
+ [200, content_type, File.read(real_asset)]
259
+ end
260
+
261
+ ##
262
+ # Serves an HTML page, rendering from the generator or returning a cached
263
+ # version.
264
+
265
+ def serve_page(path)
266
+ name = path.delete_prefix("/")
267
+ name = 'index.html' if name.empty?
268
+
269
+ html = render_page(name)
270
+
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
278
+
279
+ ext = File.extname(name)
280
+ content_type = CONTENT_TYPES[ext] || 'text/html'
281
+ [200, content_type, html]
282
+ end
283
+
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
296
+ end
297
+ end
298
+
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
321
+
322
+ ##
323
+ # Injects the live-reload polling script before +</body>+.
324
+
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
344
+ end
345
+ end
346
+ end
347
+
348
+ def file_mtimes_for(files)
349
+ files.each_with_object({}) do |f, h|
350
+ h[f] = ::RDoc.safe_mtime(f)
351
+ end
352
+ end
353
+
354
+ ##
355
+ # Checks for modified, new, and deleted files. Returns true if any
356
+ # changes were found and processed.
357
+
358
+ def check_for_changes
359
+ changes = FileChanges.new @rdoc
360
+ current_files = current_watch_files
361
+ current_file_set = current_files.to_set
362
+
363
+ @file_mtimes.each_key do |file|
364
+ changes.record_removed file unless current_file_set.include? file
365
+ end
366
+
367
+ current_files.each do |file|
368
+ next unless file_changed? file
369
+
370
+ @file_mtimes[file] = nil unless @file_mtimes.key? file
371
+ changes.record_changed file
372
+ end
373
+
374
+ return false unless changes.source_files_changed?
375
+
376
+ reparse_and_refresh changes
377
+ true
378
+ end
379
+
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
396
+ end
397
+
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
405
+
406
+ def file_changed?(file)
407
+ return true unless @file_mtimes.key? file
408
+
409
+ old_mtime = @file_mtimes[file]
410
+ return true unless old_mtime
411
+
412
+ current_mtime = ::RDoc.safe_mtime(file)
413
+ current_mtime && current_mtime > old_mtime
414
+ end
415
+
416
+ def remove_files(files)
417
+ return if files.empty?
418
+
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
426
+ end
427
+
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
435
+ end
436
+ $stderr.puts "Reloaded RBS signatures (#{duration_ms}ms)"
437
+ end
438
+
439
+ def reparse_files(files)
440
+ return if files.empty?
441
+
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
454
+ end
455
+
456
+ @store.cleanup_stale_contributions
457
+ end
458
+ $stderr.puts "Re-parsed #{changed_file_names.join(', ')} (#{duration_ms}ms)"
459
+ end
460
+
461
+ end
462
+ end
@@ -6,53 +6,57 @@ rescue LoadError
6
6
  require 'io/console'
7
7
  end
8
8
 
9
- ##
10
- # Stats printer that prints just the files being documented with a progress
11
- # bar
9
+ module RDoc
10
+ class Stats
11
+ ##
12
+ # Stats printer that prints just the files being documented with a progress
13
+ # bar
12
14
 
13
- class RDoc::Stats::Normal < RDoc::Stats::Quiet
15
+ class Normal < Stats::Quiet
14
16
 
15
- def begin_adding # :nodoc:
16
- puts "Parsing sources..."
17
- @last_width = 0
18
- end
17
+ def begin_adding # :nodoc:
18
+ puts "Parsing sources..."
19
+ @last_width = 0
20
+ end
19
21
 
20
- ##
21
- # Prints a file with a progress bar
22
-
23
- def print_file(files_so_far, filename)
24
- progress_bar = sprintf("%3d%% [%2d/%2d] ",
25
- 100 * files_so_far / @num_files,
26
- files_so_far,
27
- @num_files)
28
-
29
- if $stdout.tty?
30
- # Print a progress bar, but make sure it fits on a single line. Filename
31
- # will be truncated if necessary.
32
- size = IO.respond_to?(:console_size) ? IO.console_size : IO.console.winsize
33
- terminal_width = size[1].to_i.nonzero? || 80
34
- max_filename_size = (terminal_width - progress_bar.size) - 1
35
-
36
- if filename.size > max_filename_size then
37
- # Turn "some_long_filename.rb" to "...ong_filename.rb"
38
- filename = filename[(filename.size - max_filename_size) .. -1]
39
- filename[0..2] = "..."
22
+ ##
23
+ # Prints a file with a progress bar
24
+
25
+ def print_file(files_so_far, filename)
26
+ progress_bar = sprintf("%3d%% [%2d/%2d] ",
27
+ 100 * files_so_far / @num_files,
28
+ files_so_far,
29
+ @num_files)
30
+
31
+ if $stdout.tty?
32
+ # Print a progress bar, but make sure it fits on a single line. Filename
33
+ # will be truncated if necessary.
34
+ size = IO.respond_to?(:console_size) ? IO.console_size : IO.console.winsize
35
+ terminal_width = size[1].to_i.nonzero? || 80
36
+ max_filename_size = (terminal_width - progress_bar.size) - 1
37
+
38
+ if filename.size > max_filename_size
39
+ # Turn "some_long_filename.rb" to "...ong_filename.rb"
40
+ filename = filename[(filename.size - max_filename_size) .. -1]
41
+ filename[0..2] = "..."
42
+ end
43
+
44
+ # Clean the line with whitespaces so that leftover output from the
45
+ # previous line doesn't show up.
46
+ $stdout.print("\r\e[K") if @last_width && @last_width > 0
47
+ @last_width = progress_bar.size + filename.size
48
+ term = "\r"
49
+ else
50
+ term = "\n"
51
+ end
52
+ $stdout.print(progress_bar, filename, term)
53
+ $stdout.flush
40
54
  end
41
55
 
42
- # Clean the line with whitespaces so that leftover output from the
43
- # previous line doesn't show up.
44
- $stdout.print("\r\e[K") if @last_width && @last_width > 0
45
- @last_width = progress_bar.size + filename.size
46
- term = "\r"
47
- else
48
- term = "\n"
49
- end
50
- $stdout.print(progress_bar, filename, term)
51
- $stdout.flush
52
- end
56
+ def done_adding # :nodoc:
57
+ puts
58
+ end
53
59
 
54
- def done_adding # :nodoc:
55
- puts
60
+ end
56
61
  end
57
-
58
62
  end
@@ -1,59 +1,63 @@
1
1
  # frozen_string_literal: true
2
- ##
3
- # Stats printer that prints nothing
2
+ module RDoc
3
+ class Stats
4
+ ##
5
+ # Stats printer that prints nothing
4
6
 
5
- class RDoc::Stats::Quiet
7
+ class Quiet
6
8
 
7
- ##
8
- # Creates a new Quiet that will print nothing
9
+ ##
10
+ # Creates a new Quiet that will print nothing
9
11
 
10
- def initialize(num_files)
11
- @num_files = num_files
12
- end
12
+ def initialize(num_files)
13
+ @num_files = num_files
14
+ end
13
15
 
14
- ##
15
- # Prints a message at the beginning of parsing
16
+ ##
17
+ # Prints a message at the beginning of parsing
16
18
 
17
- def begin_adding(*) end
19
+ def begin_adding(*) end
18
20
 
19
- ##
20
- # Prints when an alias is added
21
+ ##
22
+ # Prints when an alias is added
21
23
 
22
- def print_alias(*) end
24
+ def print_alias(*) end
23
25
 
24
- ##
25
- # Prints when an attribute is added
26
+ ##
27
+ # Prints when an attribute is added
26
28
 
27
- def print_attribute(*) end
29
+ def print_attribute(*) end
28
30
 
29
- ##
30
- # Prints when a class is added
31
+ ##
32
+ # Prints when a class is added
31
33
 
32
- def print_class(*) end
34
+ def print_class(*) end
33
35
 
34
- ##
35
- # Prints when a constant is added
36
+ ##
37
+ # Prints when a constant is added
36
38
 
37
- def print_constant(*) end
39
+ def print_constant(*) end
38
40
 
39
- ##
40
- # Prints when a file is added
41
+ ##
42
+ # Prints when a file is added
41
43
 
42
- def print_file(*) end
44
+ def print_file(*) end
43
45
 
44
- ##
45
- # Prints when a method is added
46
+ ##
47
+ # Prints when a method is added
46
48
 
47
- def print_method(*) end
49
+ def print_method(*) end
48
50
 
49
- ##
50
- # Prints when a module is added
51
+ ##
52
+ # Prints when a module is added
51
53
 
52
- def print_module(*) end
54
+ def print_module(*) end
53
55
 
54
- ##
55
- # Prints when RDoc is done
56
+ ##
57
+ # Prints when RDoc is done
56
58
 
57
- def done_adding(*) end
59
+ def done_adding(*) end
58
60
 
61
+ end
62
+ end
59
63
  end