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
data/lib/rdoc/rdoc.rb CHANGED
@@ -5,192 +5,194 @@ require 'find'
5
5
  require 'fileutils'
6
6
  require 'pathname'
7
7
  require 'time'
8
+ require_relative 'rbs_helper'
8
9
 
9
- ##
10
- # This is the driver for generating RDoc output. It handles file parsing and
11
- # generation of output.
12
- #
13
- # To use this class to generate RDoc output via the API, the recommended way
14
- # is:
15
- #
16
- # rdoc = RDoc::RDoc.new
17
- # options = RDoc::Options.load_options # returns an RDoc::Options instance
18
- # # set extra options
19
- # rdoc.document options
20
- #
21
- # You can also generate output like the +rdoc+ executable:
22
- #
23
- # rdoc = RDoc::RDoc.new
24
- # rdoc.document argv
25
- #
26
- # Where +argv+ is an array of strings, each corresponding to an argument you'd
27
- # give rdoc on the command line. See <tt>rdoc --help</tt> for details.
28
-
29
- class RDoc::RDoc
30
-
31
- @current = nil
32
-
10
+ module RDoc
33
11
  ##
34
- # This is the list of supported output generators
12
+ # This is the driver for generating RDoc output. It handles file parsing and
13
+ # generation of output.
14
+ #
15
+ # To use this class to generate RDoc output via the API, the recommended way
16
+ # is:
17
+ #
18
+ # rdoc = RDoc::RDoc.new
19
+ # options = RDoc::Options.load_options # returns an RDoc::Options instance
20
+ # # set extra options
21
+ # rdoc.document options
22
+ #
23
+ # You can also generate output like the +rdoc+ executable:
24
+ #
25
+ # rdoc = RDoc::RDoc.new
26
+ # rdoc.document argv
27
+ #
28
+ # Where +argv+ is an array of strings, each corresponding to an argument you'd
29
+ # give rdoc on the command line. See <tt>rdoc --help</tt> for details.
35
30
 
36
- GENERATORS = {}
31
+ class RDoc
37
32
 
38
- ##
39
- # List of directory names always skipped
33
+ @current = nil
40
34
 
41
- UNCONDITIONALLY_SKIPPED_DIRECTORIES = %w[CVS .svn .git].freeze
35
+ ##
36
+ # This is the list of supported output generators
42
37
 
43
- ##
44
- # List of directory names skipped if test suites should be skipped
38
+ GENERATORS = {}
45
39
 
46
- TEST_SUITE_DIRECTORY_NAMES = %w[spec test].freeze
40
+ ##
41
+ # List of directory names always skipped
47
42
 
43
+ UNCONDITIONALLY_SKIPPED_DIRECTORIES = %w[CVS .svn .git].freeze
48
44
 
49
- ##
50
- # Generator instance used for creating output
45
+ ##
46
+ # List of directory names skipped if test suites should be skipped
51
47
 
52
- attr_accessor :generator
48
+ TEST_SUITE_DIRECTORY_NAMES = %w[spec test].freeze
53
49
 
54
- ##
55
- # Hash of files and their last modified times.
56
50
 
57
- attr_reader :last_modified
51
+ ##
52
+ # Generator instance used for creating output
58
53
 
59
- ##
60
- # RDoc options
54
+ attr_accessor :generator
61
55
 
62
- attr_accessor :options
56
+ ##
57
+ # Hash of files and their last modified times.
63
58
 
64
- ##
65
- # Accessor for statistics. Available after each call to parse_files
59
+ attr_reader :last_modified
66
60
 
67
- attr_reader :stats
61
+ ##
62
+ # RDoc options
68
63
 
69
- ##
70
- # The current documentation store
64
+ attr_accessor :options
71
65
 
72
- attr_accessor :store
66
+ ##
67
+ # Accessor for statistics. Available after each call to parse_files
73
68
 
74
- ##
75
- # Add +klass+ that can generate output after parsing
69
+ attr_reader :stats
76
70
 
77
- def self.add_generator(klass)
78
- name = klass.name.sub(/^RDoc::Generator::/, '').downcase
79
- GENERATORS[name] = klass
80
- end
71
+ ##
72
+ # The current documentation store
81
73
 
82
- ##
83
- # Active RDoc::RDoc instance
74
+ attr_accessor :store
84
75
 
85
- def self.current
86
- @current
87
- end
76
+ ##
77
+ # Add +klass+ that can generate output after parsing
88
78
 
89
- ##
90
- # Sets the active RDoc::RDoc instance
79
+ def self.add_generator(klass)
80
+ name = klass.name.sub(/^RDoc::Generator::/, '').downcase
81
+ GENERATORS[name] = klass
82
+ end
91
83
 
92
- def self.current=(rdoc)
93
- @current = rdoc
94
- end
84
+ ##
85
+ # Active RDoc::RDoc instance
95
86
 
96
- ##
97
- # Creates a new RDoc::RDoc instance. Call #document to parse files and
98
- # generate documentation.
99
-
100
- def initialize
101
- @current = nil
102
- @generator = nil
103
- @last_modified = {}
104
- @old_siginfo = nil
105
- @options = nil
106
- @stats = nil
107
- @store = nil
108
- end
87
+ def self.current
88
+ @current
89
+ end
109
90
 
110
- ##
111
- # Report an error message and exit
91
+ ##
92
+ # Sets the active RDoc::RDoc instance
112
93
 
113
- def error(msg)
114
- raise RDoc::Error, msg
115
- end
94
+ def self.current=(rdoc)
95
+ @current = rdoc
96
+ end
116
97
 
117
- ##
118
- # Gathers a set of parseable files from the files and directories listed in
119
- # +files+.
98
+ ##
99
+ # Creates a new RDoc::RDoc instance. Call #document to parse files and
100
+ # generate documentation.
101
+
102
+ def initialize
103
+ @current = nil
104
+ @generator = nil
105
+ @last_modified = {}
106
+ @old_siginfo = nil
107
+ @options = nil
108
+ @stats = nil
109
+ @store = nil
110
+ end
120
111
 
121
- def gather_files(files)
122
- files = [@options.root.to_s] if files.empty?
112
+ ##
113
+ # Report an error message and exit
123
114
 
124
- file_list = normalized_file_list files, true, @options.exclude
115
+ def error(msg)
116
+ raise Error, msg
117
+ end
118
+
119
+ ##
120
+ # Gathers a set of parseable files from the files and directories listed in
121
+ # +files+.
122
+
123
+ def gather_files(files)
124
+ files = [@options.root.to_s] if files.empty?
125
125
 
126
- file_list = remove_unparseable(file_list)
126
+ file_list = normalized_file_list files, true, @options.exclude
127
127
 
128
- if file_list.count {|name, mtime|
129
- file_list[name] = @last_modified[name] unless mtime
130
- mtime
131
- } > 0
132
- @last_modified.replace file_list
133
- file_list.keys.sort
134
- else
135
- []
128
+ file_list = remove_duplicate_files(remove_unparseable(file_list))
129
+
130
+ if file_list.count {|name, mtime|
131
+ file_list[name] = @last_modified[name] unless mtime
132
+ mtime
133
+ } > 0
134
+ @last_modified.replace file_list
135
+ file_list.keys.sort
136
+ else
137
+ []
138
+ end
136
139
  end
137
- end
138
140
 
139
- ##
140
- # Turns RDoc from stdin into HTML
141
+ ##
142
+ # Turns RDoc from stdin into HTML
141
143
 
142
- def handle_pipe
143
- @html = RDoc::Markup::ToHtml.new @options
144
+ def handle_pipe
145
+ @html = Markup::ToHtml.new(pipe: @options.pipe, output_decoration: @options.output_decoration)
144
146
 
145
- parser = RDoc::Text::MARKUP_FORMAT[@options.markup]
147
+ parser = Text::MARKUP_FORMAT[@options.markup]
146
148
 
147
- document = parser.parse $stdin.read
149
+ document = parser.parse $stdin.read
148
150
 
149
- out = @html.convert document
151
+ out = @html.convert document
150
152
 
151
- $stdout.write out
152
- end
153
+ $stdout.write out
154
+ end
153
155
 
154
- ##
155
- # Installs a siginfo handler that prints the current filename.
156
+ ##
157
+ # Installs a siginfo handler that prints the current filename.
156
158
 
157
- def install_siginfo_handler
158
- return unless Signal.list.include? 'INFO'
159
+ def install_siginfo_handler
160
+ return unless Signal.list.include? 'INFO'
159
161
 
160
- @old_siginfo = trap 'INFO' do
161
- puts @current if @current
162
+ @old_siginfo = trap 'INFO' do
163
+ puts @current if @current
164
+ end
162
165
  end
163
- end
164
166
 
165
- ##
166
- # Create an output dir if it doesn't exist. If it does exist, but doesn't
167
- # contain the flag file <tt>created.rid</tt> then we refuse to use it, as
168
- # we may clobber some manually generated documentation
167
+ ##
168
+ # Create an output dir if it doesn't exist. If it does exist, but doesn't
169
+ # contain the flag file <tt>created.rid</tt> then we refuse to use it, as
170
+ # we may clobber some manually generated documentation
169
171
 
170
- def setup_output_dir(dir, force)
171
- flag_file = output_flag_file dir
172
+ def setup_output_dir(dir, force)
173
+ flag_file = output_flag_file dir
172
174
 
173
- last = {}
175
+ last = {}
174
176
 
175
- if @options.dry_run then
176
- # do nothing
177
- elsif File.exist? dir then
178
- error "#{dir} exists and is not a directory" unless File.directory? dir
177
+ if @options.dry_run
178
+ # do nothing
179
+ elsif File.exist? dir
180
+ error "#{dir} exists and is not a directory" unless File.directory? dir
179
181
 
180
- begin
181
- File.open flag_file do |io|
182
- unless force then
183
- Time.parse io.gets
184
-
185
- io.each do |line|
186
- file, time = line.split "\t", 2
187
- time = Time.parse(time) rescue next
188
- last[file] = time
182
+ begin
183
+ File.open flag_file do |io|
184
+ unless force
185
+ Time.parse io.gets
186
+
187
+ io.each do |line|
188
+ file, time = line.split "\t", 2
189
+ time = Time.parse(time) rescue next
190
+ last[file] = time
191
+ end
189
192
  end
190
193
  end
191
- end
192
- rescue SystemCallError, TypeError
193
- error <<-ERROR
194
+ rescue SystemCallError, TypeError
195
+ error <<-ERROR
194
196
 
195
197
  Directory #{dir} already exists, but it looks like it isn't an RDoc directory.
196
198
 
@@ -199,183 +201,182 @@ you'll need to specify a different output directory name (using the --op <dir>
199
201
  option)
200
202
 
201
203
  ERROR
202
- end unless @options.force_output
203
- else
204
- FileUtils.mkdir_p dir
205
- FileUtils.touch flag_file
204
+ end unless @options.force_output
205
+ else
206
+ FileUtils.mkdir_p dir
207
+ FileUtils.touch flag_file
208
+ end
209
+
210
+ last
206
211
  end
207
212
 
208
- last
209
- end
213
+ ##
214
+ # Update the flag file in an output directory.
210
215
 
211
- ##
212
- # Update the flag file in an output directory.
213
-
214
- def update_output_dir(op_dir, time, last = {})
215
- return if @options.dry_run or not @options.update_output_dir
216
- unless ENV['SOURCE_DATE_EPOCH'].nil?
217
- time = Time.at(ENV['SOURCE_DATE_EPOCH'].to_i).gmtime
218
- end
216
+ def update_output_dir(op_dir, time, last = {})
217
+ return if @options.dry_run or not @options.update_output_dir
218
+ unless ENV['SOURCE_DATE_EPOCH'].nil?
219
+ time = Time.at(ENV['SOURCE_DATE_EPOCH'].to_i).gmtime
220
+ end
219
221
 
220
- File.open output_flag_file(op_dir), "w" do |f|
221
- f.puts time.rfc2822
222
- last.each do |n, t|
223
- f.puts "#{n}\t#{t.rfc2822}"
222
+ File.open output_flag_file(op_dir), "w" do |f|
223
+ f.puts time.rfc2822
224
+ last.each do |n, t|
225
+ f.puts "#{n}\t#{t.rfc2822}"
226
+ end
224
227
  end
225
228
  end
226
- end
227
229
 
228
- ##
229
- # Return the path name of the flag file in an output directory.
230
+ ##
231
+ # Return the path name of the flag file in an output directory.
230
232
 
231
- def output_flag_file(op_dir)
232
- File.join op_dir, "created.rid"
233
- end
233
+ def output_flag_file(op_dir)
234
+ File.join op_dir, "created.rid"
235
+ end
234
236
 
235
- ##
236
- # The .document file contains a list of file and directory name patterns,
237
- # representing candidates for documentation. It may also contain comments
238
- # (starting with '#')
237
+ ##
238
+ # The .document file contains a list of file and directory name patterns,
239
+ # representing candidates for documentation. It may also contain comments
240
+ # (starting with '#')
239
241
 
240
- def parse_dot_doc_file(in_dir, filename)
241
- # read and strip comments
242
- patterns = File.read(filename).gsub(/#.*/, '')
242
+ def parse_dot_doc_file(in_dir, filename)
243
+ # read and strip comments
244
+ patterns = File.read(filename).gsub(/#.*/, '')
243
245
 
244
- result = {}
246
+ result = {}
245
247
 
246
- patterns.split(' ').each do |patt|
247
- candidates = Dir.glob(File.join(in_dir, patt))
248
- result.update normalized_file_list(candidates, false, @options.exclude)
249
- end
248
+ patterns.split(' ').each do |patt|
249
+ candidates = Dir.glob(File.join(in_dir, patt))
250
+ result.update normalized_file_list(candidates, false, @options.exclude)
251
+ end
250
252
 
251
- result
252
- end
253
+ result
254
+ end
253
255
 
254
- ##
255
- # Given a list of files and directories, create a list of all the Ruby
256
- # files they contain.
257
- #
258
- # If +force_doc+ is true we always add the given files, if false, only
259
- # add files that we guarantee we can parse. It is true when looking at
260
- # files given on the command line, false when recursing through
261
- # subdirectories.
262
- #
263
- # The effect of this is that if you want a file with a non-standard
264
- # extension parsed, you must name it explicitly.
265
-
266
- def normalized_file_list(relative_files, force_doc = false,
267
- exclude_pattern = nil)
268
- file_list = {}
269
-
270
- relative_files.each do |rel_file_name|
271
- rel_file_name = rel_file_name.sub(/^\.\//, '')
272
- next if rel_file_name.end_with? 'created.rid'
273
- next if exclude_pattern && exclude_pattern =~ rel_file_name
274
- stat = File.stat rel_file_name rescue next
275
-
276
- case type = stat.ftype
277
- when "file" then
278
- mtime = (stat.mtime unless (last_modified = @last_modified[rel_file_name] and
279
- stat.mtime.to_i <= last_modified.to_i))
280
-
281
- if force_doc or RDoc::Parser.can_parse(rel_file_name) then
282
- file_list[rel_file_name] = mtime
283
- end
284
- when "directory" then
285
- next if UNCONDITIONALLY_SKIPPED_DIRECTORIES.include?(rel_file_name)
256
+ ##
257
+ # Given a list of files and directories, create a list of all the Ruby
258
+ # files they contain.
259
+ #
260
+ # If +force_doc+ is true we always add the given files, if false, only
261
+ # add files that we guarantee we can parse. It is true when looking at
262
+ # files given on the command line, false when recursing through
263
+ # subdirectories.
264
+ #
265
+ # The effect of this is that if you want a file with a non-standard
266
+ # extension parsed, you must name it explicitly.
267
+
268
+ def normalized_file_list(relative_files, force_doc = false,
269
+ exclude_pattern = nil)
270
+ file_list = {}
271
+
272
+ relative_files.each do |rel_file_name|
273
+ rel_file_name = rel_file_name.sub(/^\.\//, '')
274
+ next if rel_file_name.end_with? 'created.rid'
275
+ next if exclude_pattern && exclude_pattern =~ rel_file_name
276
+ stat = File.stat rel_file_name rescue next
277
+
278
+ case type = stat.ftype
279
+ when "file"
280
+ mtime = (stat.mtime unless (last_modified = @last_modified[rel_file_name] and
281
+ stat.mtime.to_i <= last_modified.to_i))
282
+
283
+ if force_doc or Parser.can_parse(rel_file_name)
284
+ file_list[rel_file_name] = mtime
285
+ end
286
+ when "directory"
287
+ next if UNCONDITIONALLY_SKIPPED_DIRECTORIES.include?(rel_file_name)
286
288
 
287
- basename = File.basename(rel_file_name)
288
- next if options.skip_tests && TEST_SUITE_DIRECTORY_NAMES.include?(basename)
289
+ basename = File.basename(rel_file_name)
290
+ next if options.skip_tests && TEST_SUITE_DIRECTORY_NAMES.include?(basename)
289
291
 
290
- created_rid = File.join rel_file_name, "created.rid"
291
- next if File.file? created_rid
292
+ created_rid = File.join rel_file_name, "created.rid"
293
+ next if File.file? created_rid
292
294
 
293
- dot_doc = File.join rel_file_name, RDoc::DOT_DOC_FILENAME
295
+ dot_doc = File.join rel_file_name, DOT_DOC_FILENAME
294
296
 
295
- if File.file? dot_doc then
296
- file_list.update(parse_dot_doc_file(rel_file_name, dot_doc))
297
+ if File.file? dot_doc
298
+ file_list.update(parse_dot_doc_file(rel_file_name, dot_doc))
299
+ else
300
+ file_list.update(list_files_in_directory(rel_file_name))
301
+ end
297
302
  else
298
- file_list.update(list_files_in_directory(rel_file_name))
303
+ warn "rdoc can't parse the #{type} #{rel_file_name}"
299
304
  end
300
- else
301
- warn "rdoc can't parse the #{type} #{rel_file_name}"
302
305
  end
303
- end
304
306
 
305
- file_list
306
- end
307
-
308
- ##
309
- # Return a list of the files to be processed in a directory. We know that
310
- # this directory doesn't have a .document file, so we're looking for real
311
- # files. However we may well contain subdirectories which must be tested
312
- # for .document files.
313
-
314
- def list_files_in_directory(dir)
315
- files = Dir.glob File.join(dir, "*")
307
+ file_list
308
+ end
316
309
 
317
- normalized_file_list files, false, @options.exclude
318
- end
310
+ ##
311
+ # Return a list of the files to be processed in a directory. We know that
312
+ # this directory doesn't have a .document file, so we're looking for real
313
+ # files. However we may well contain subdirectories which must be tested
314
+ # for .document files.
319
315
 
320
- ##
321
- # Parses +filename+ and returns an RDoc::TopLevel
316
+ def list_files_in_directory(dir)
317
+ files = Dir.glob File.join(dir, "*")
322
318
 
323
- def parse_file(filename)
324
- encoding = @options.encoding
325
- filename = filename.encode encoding
319
+ normalized_file_list files, false, @options.exclude
320
+ end
326
321
 
327
- @stats.add_file filename
322
+ ##
323
+ # Parses +filename+ and returns an RDoc::TopLevel
328
324
 
329
- return if RDoc::Parser.binary? filename
325
+ def parse_file(filename)
326
+ encoding = @options.encoding
327
+ filename = filename.encode encoding
330
328
 
331
- content = RDoc::Encoding.read_file filename, encoding
329
+ @stats.add_file filename
332
330
 
333
- return unless content
331
+ return if Parser.binary? filename
334
332
 
335
- filename_path = Pathname(filename).expand_path
336
- begin
337
- relative_path = filename_path.relative_path_from @options.root
338
- rescue ArgumentError
339
- relative_path = filename_path
340
- end
333
+ content = Encoding.read_file filename, encoding
341
334
 
342
- if @options.page_dir and
343
- relative_path.to_s.start_with? @options.page_dir.to_s then
344
- relative_path =
345
- relative_path.relative_path_from @options.page_dir
346
- end
335
+ return unless content
347
336
 
348
- top_level = @store.add_file filename, relative_name: relative_path.to_s
337
+ top_level = @store.add_file filename, relative_name: relative_path_for(filename)
349
338
 
350
- parser = RDoc::Parser.for top_level, content, @options, @stats
339
+ parser = Parser.for top_level, content, @options, @stats
351
340
 
352
- return unless parser
341
+ return unless parser
353
342
 
354
- parser.scan
343
+ parser.scan
355
344
 
356
- # restart documentation for the classes & modules found
357
- top_level.classes_or_modules.each do |cm|
358
- cm.done_documenting = false
359
- end
345
+ # restart documentation for the classes & modules found
346
+ top_level.classes_or_modules.each do |cm|
347
+ cm.done_documenting = false
348
+ end
360
349
 
361
- top_level
350
+ top_level
362
351
 
363
- rescue Errno::EACCES => e
364
- $stderr.puts <<-EOF
352
+ rescue Errno::EACCES => e
353
+ $stderr.puts <<-EOF
365
354
  Unable to read #{filename}, #{e.message}
366
355
 
367
356
  Please check the permissions for this file. Perhaps you do not have access to
368
357
  it or perhaps the original author's permissions are to restrictive. If the
369
358
  this is not your library please report a bug to the author.
370
359
  EOF
371
- rescue => e
372
- $stderr.puts <<-EOF
360
+ rescue => e
361
+ syntax_check_command = syntax_check_command_for filename, parser&.class
362
+ syntax_check_message = if syntax_check_command
363
+ <<~MESSAGE
373
364
  Before reporting this, could you check that the file you're documenting
374
365
  has proper syntax:
375
366
 
376
- #{Gem.ruby} -c #{filename}
367
+ #{syntax_check_command}
368
+ MESSAGE
369
+ else
370
+ <<~MESSAGE
371
+ Before reporting this, could you check that the file you're documenting
372
+ has proper syntax for its language?
373
+ MESSAGE
374
+ end
377
375
 
378
- RDoc is not a full Ruby parser and will fail when fed invalid ruby programs.
376
+ $stderr.puts <<-EOF
377
+ #{syntax_check_message}
378
+ RDoc's parsers are not full language parsers and may fail when fed invalid
379
+ source files.
379
380
 
380
381
  The internal error was:
381
382
 
@@ -383,158 +384,338 @@ The internal error was:
383
384
 
384
385
  EOF
385
386
 
386
- $stderr.puts e.backtrace.join("\n\t") if $DEBUG_RDOC
387
+ $stderr.puts e.backtrace.join("\n\t") if $DEBUG_RDOC
387
388
 
388
- raise e
389
- end
389
+ raise e
390
+ end
390
391
 
391
- ##
392
- # Parse each file on the command line, recursively entering directories.
392
+ def syntax_check_command_for(filename, parser_class = Parser.can_parse_by_name(filename))
393
+ if parser_class == Parser::Ruby
394
+ "#{Gem.ruby} -c #{filename}"
395
+ elsif parser_class == Parser::C
396
+ cc = ENV['CC']
397
+ cc = 'cc' if cc.nil? || cc.empty?
398
+ "#{cc} -fsyntax-only #{filename}"
399
+ end
400
+ end
393
401
 
394
- def parse_files(files)
395
- file_list = gather_files files
396
- @stats = RDoc::Stats.new @store, file_list.length, @options.verbosity
402
+ ##
403
+ # Returns the relative path for +filename+ against +options.root+ (and
404
+ # +options.page_dir+ when set). This is the key used by RDoc::Store to
405
+ # identify files.
397
406
 
398
- return [] if file_list.empty?
407
+ def relative_path_for(filename)
408
+ filename_path = Pathname(filename).expand_path
409
+ begin
410
+ relative_path = filename_path.relative_path_from @options.root
411
+ rescue ArgumentError
412
+ relative_path = filename_path
413
+ end
399
414
 
400
- # This workaround can be removed after the :main: directive is removed
401
- original_options = @options.dup
402
- @stats.begin_adding
415
+ if @options.page_dir &&
416
+ relative_path.to_s.start_with?(@options.page_dir.to_s)
417
+ relative_path =
418
+ relative_path.relative_path_from @options.page_dir
419
+ end
403
420
 
404
- file_info = file_list.map do |filename|
405
- @current = filename
406
- parse_file filename
407
- end.compact
421
+ relative_path.to_s
422
+ end
408
423
 
409
- @store.resolve_c_superclasses
424
+ ##
425
+ # Parse each file on the command line, recursively entering directories.
410
426
 
411
- @stats.done_adding
412
- @options = original_options
427
+ def parse_files(files)
428
+ file_list = gather_files files
429
+ @stats = Stats.new @store, file_list.length, @options.verbosity
413
430
 
414
- file_info
415
- end
431
+ return [] if file_list.empty?
416
432
 
417
- ##
418
- # Removes file extensions known to be unparseable from +files+ and TAGS
419
- # files for emacs and vim.
433
+ # This workaround can be removed after the :main: directive is removed
434
+ original_options = @options.dup
435
+ @stats.begin_adding
436
+
437
+ file_info = file_list.map do |filename|
438
+ @current = filename
439
+ parse_file filename
440
+ end.compact
441
+
442
+ @store.resolve_c_superclasses
420
443
 
421
- def remove_unparseable(files)
422
- files.reject do |file, *|
423
- file =~ /\.(?:class|eps|erb|scpt\.txt|svg|ttf|yml)$/i or
424
- (file =~ /tags$/i and
425
- /\A(\f\n[^,]+,\d+$|!_TAG_)/.match?(File.binread(file, 100)))
444
+ @stats.done_adding
445
+ @options = original_options
446
+
447
+ file_info
426
448
  end
427
- end
428
449
 
429
- ##
430
- # Generates documentation or a coverage report depending upon the settings
431
- # in +options+.
432
- #
433
- # +options+ can be either an RDoc::Options instance or an array of strings
434
- # equivalent to the strings that would be passed on the command line like
435
- # <tt>%w[-q -o doc -t My\ Doc\ Title]</tt>. #document will automatically
436
- # call RDoc::Options#finish if an options instance was given.
437
- #
438
- # For a list of options, see either RDoc::Options or <tt>rdoc --help</tt>.
439
- #
440
- # By default, output will be stored in a directory called "doc" below the
441
- # current directory, so make sure you're somewhere writable before invoking.
450
+ ##
451
+ # Removes file extensions known to be unparseable from +files+ and TAGS
452
+ # files for emacs and vim.
442
453
 
443
- def document(options)
444
- if RDoc::Options === options then
445
- @options = options
446
- else
447
- @options = RDoc::Options.load_options
448
- @options.parse options
454
+ def remove_unparseable(files)
455
+ files.reject do |file, *|
456
+ file =~ /\.(?:class|eps|erb|scpt\.txt|svg|ttf|yml)\z/i or
457
+ (file =~ /tags\z/i and
458
+ /\A(\f\n[^,]+,\d+$|!_TAG_)/.match?(File.binread(file, 100)))
459
+ end
449
460
  end
450
- @options.finish
451
461
 
452
- @store = RDoc::Store.new(@options)
462
+ ##
463
+ # Removes duplicate canonical paths while preserving the first path found.
453
464
 
454
- if @options.pipe then
455
- handle_pipe
456
- exit
465
+ def remove_duplicate_files(files)
466
+ files.uniq { |file,| File.realpath(file) }.to_h
457
467
  end
458
468
 
459
- unless @options.coverage_report then
460
- @last_modified = setup_output_dir @options.op_dir, @options.force_update
469
+ ##
470
+ # Generates documentation or a coverage report depending upon the settings
471
+ # in +options+.
472
+ #
473
+ # +options+ can be either an RDoc::Options instance or an array of strings
474
+ # equivalent to the strings that would be passed on the command line like
475
+ # <tt>%w[-q -o doc -t My\ Doc\ Title]</tt>. #document will automatically
476
+ # call RDoc::Options#finish if an options instance was given.
477
+ #
478
+ # For a list of options, see either RDoc::Options or <tt>rdoc --help</tt>.
479
+ #
480
+ # By default, output will be stored in a directory called "doc" below the
481
+ # current directory, so make sure you're somewhere writable before invoking.
482
+
483
+ def document(options)
484
+ if Options === options
485
+ @options = options
486
+ else
487
+ @options = Options.load_options
488
+ @options.parse options
489
+ end
490
+ @options.finish
491
+
492
+ @store = Store.new(@options)
493
+
494
+ if @options.pipe
495
+ handle_pipe
496
+ exit
497
+ end
498
+
499
+ if @options.server_port
500
+ @store.load_cache
501
+
502
+ parse_files @options.files
503
+ record_auto_discovered_rbs_signature_mtimes
504
+
505
+ @options.default_title = "RDoc Documentation"
506
+
507
+ load_auto_discovered_rbs_signatures
508
+ @store.complete @options.visibility
509
+
510
+ start_server
511
+ exit
512
+ end
513
+
514
+ unless @options.coverage_report
515
+ @last_modified = setup_output_dir @options.op_dir, @options.force_update
516
+ end
517
+
518
+ @start_time = Time.now
519
+
520
+ @store.load_cache
521
+
522
+ auto_discovered_rbs_signatures_changed = auto_discovered_rbs_signatures_changed?
523
+ # When only auto-discovered RBS signatures changed, no Ruby file would be
524
+ # reparsed under normal mtime checks. The store cache holds class metadata
525
+ # but not live RDoc::Context objects, so the generator would have nothing
526
+ # to iterate. Force a full reparse so updated signatures show up in the
527
+ # rendered output.
528
+ @last_modified.clear if auto_discovered_rbs_signatures_changed
529
+
530
+ file_info = parse_files @options.files
531
+ record_auto_discovered_rbs_signature_mtimes
532
+
533
+ @options.default_title = "RDoc Documentation"
534
+
535
+ load_auto_discovered_rbs_signatures
536
+
537
+ @store.complete @options.visibility
538
+
539
+ @stats.coverage_level = @options.coverage_report
540
+
541
+ if @options.coverage_report
542
+ puts
543
+
544
+ puts @stats.report
545
+ elsif file_info.empty? && !auto_discovered_rbs_signatures_changed
546
+ $stderr.puts "\nNo newer files." unless @options.quiet
547
+ else
548
+ gen_klass = @options.generator
549
+
550
+ @generator = gen_klass.new @store, @options
551
+
552
+ generate
553
+ end
554
+
555
+ if @stats and (@options.coverage_report or not @options.quiet)
556
+ puts
557
+ puts @stats.summary
558
+ end
559
+
560
+ exit @stats.fully_documented? if @options.coverage_report
461
561
  end
462
562
 
463
- @start_time = Time.now
563
+ ##
564
+ # Generates documentation for +file_info+ (from #parse_files) into the
565
+ # output dir using the generator selected
566
+ # by the RDoc options
464
567
 
465
- @store.load_cache
568
+ def generate
569
+ if @options.dry_run
570
+ # do nothing
571
+ @generator.generate
572
+ else
573
+ Dir.chdir @options.op_dir do
574
+ unless @options.quiet
575
+ $stderr.puts "\nGenerating #{@generator.class.name.sub(/^.*::/, '')} format into #{Dir.pwd}..."
576
+ uri = "file://#{Dir.pwd}/index.html"
577
+ ref = $stderr.tty? ? "\e]8;;#{uri}\e\\#{uri}\e]8;;\e\\" : uri
578
+ $stderr.puts "\nYou can visit the home page at: #{ref}"
579
+ end
466
580
 
467
- file_info = parse_files @options.files
581
+ @generator.generate
582
+ update_output_dir '.', @start_time, @last_modified
583
+ end
584
+ end
585
+ end
468
586
 
469
- @options.default_title = "RDoc Documentation"
587
+ ##
588
+ # Loads RBS type signatures from the project's +sig+ directory and RBS
589
+ # stdlib, then merges them into the store's code objects.
590
+
591
+ def load_auto_discovered_rbs_signatures
592
+ sig_dirs = []
593
+ sig_dir = File.join(@options.root.to_s, 'sig')
594
+ sig_dirs << sig_dir if File.directory?(sig_dir)
595
+ signatures = RbsHelper.load_signatures(*sig_dirs)
596
+ @store.merge_rbs_signatures(signatures)
597
+ rescue RBS::BaseError, Errno::ENOENT, LoadError => e
598
+ # In server mode, a previous successful load may have populated the store;
599
+ # drop those signatures so a now-broken sig file doesn't keep showing
600
+ # stale types alongside the warning.
601
+ @store.clear_rbs_signatures
602
+ @options.warn "Failed to load RBS type signatures: #{e.message}"
603
+ end
470
604
 
471
- @store.complete @options.visibility
605
+ ##
606
+ # Returns RBS files that RDoc auto-discovers for signature loading.
472
607
 
473
- @stats.coverage_level = @options.coverage_report
608
+ def auto_discovered_rbs_signature_files
609
+ Dir[File.join(@options.root.to_s, 'sig', '**', '*.rbs')].sort
610
+ end
474
611
 
475
- if @options.coverage_report then
476
- puts
612
+ ##
613
+ # Returns true if any auto-discovered RBS signature file has changed since
614
+ # the last run.
477
615
 
478
- puts @stats.report.accept RDoc::Markup::ToRdoc.new
479
- elsif file_info.empty? then
480
- $stderr.puts "\nNo newer files." unless @options.quiet
481
- else
482
- gen_klass = @options.generator
616
+ def auto_discovered_rbs_signatures_changed?
617
+ current = auto_discovered_rbs_signature_mtimes
618
+ previous = @last_modified.select { |file, _| auto_discovered_rbs_signature_file?(file) }
483
619
 
484
- @generator = gen_klass.new @store, @options
620
+ return true unless (previous.keys - current.keys).empty?
485
621
 
486
- generate
622
+ current.any? do |file, mtime|
623
+ last_modified = @last_modified[file]
624
+ last_modified.nil? || mtime.to_i > last_modified.to_i
625
+ end
487
626
  end
488
627
 
489
- if @stats and (@options.coverage_report or not @options.quiet) then
490
- puts
491
- puts @stats.summary.accept RDoc::Markup::ToRdoc.new
628
+ ##
629
+ # Records auto-discovered RBS signature file mtimes so normal generation
630
+ # freshness checks and the live server watcher can see signature-only edits.
631
+
632
+ def record_auto_discovered_rbs_signature_mtimes
633
+ @last_modified.reject! { |file, _| auto_discovered_rbs_signature_file?(file) }
634
+ @last_modified.merge! auto_discovered_rbs_signature_mtimes
492
635
  end
493
636
 
494
- exit @stats.fully_documented? if @options.coverage_report
495
- end
637
+ ##
638
+ # Files watched by the live preview server.
496
639
 
497
- ##
498
- # Generates documentation for +file_info+ (from #parse_files) into the
499
- # output dir using the generator selected
500
- # by the RDoc options
501
-
502
- def generate
503
- if @options.dry_run then
504
- # do nothing
505
- @generator.generate
506
- else
507
- Dir.chdir @options.op_dir do
508
- unless @options.quiet then
509
- $stderr.puts "\nGenerating #{@generator.class.name.sub(/^.*::/, '')} format into #{Dir.pwd}..."
510
- $stderr.puts "\nYou can visit the home page at: \e]8;;file://#{Dir.pwd}/index.html\e\\file://#{Dir.pwd}/index.html\e]8;;\e\\"
511
- end
640
+ def watch_files
641
+ (@last_modified.keys + auto_discovered_rbs_signature_files).uniq
642
+ end
512
643
 
513
- @generator.generate
514
- update_output_dir '.', @start_time, @last_modified
644
+ ##
645
+ # Returns true for project RBS files that are auto-discovered for signature
646
+ # loading. RDoc parses any selected .rbs file as documentation input, but
647
+ # only +sig/**/*.rbs+ files are loaded through RBS::EnvironmentLoader for
648
+ # type signature merging and live-reload bookkeeping.
649
+
650
+ def auto_discovered_rbs_signature_file?(file) # :nodoc:
651
+ return false unless File.extname(file) == '.rbs'
652
+
653
+ root = Pathname(@options.root.to_s).expand_path
654
+ relative_path = Pathname(file).expand_path.relative_path_from root
655
+ relative_path.each_filename.first == 'sig'
656
+ rescue ArgumentError
657
+ # file and root may be on different drives on Windows
658
+ false
659
+ end
660
+
661
+ ##
662
+ # Returns mtimes for auto-discovered RBS signature files.
663
+
664
+ def auto_discovered_rbs_signature_mtimes # :nodoc:
665
+ auto_discovered_rbs_signature_files.each_with_object({}) do |file, mtimes|
666
+ mtime = ::RDoc.safe_mtime(file)
667
+ mtimes[file] = mtime if mtime
515
668
  end
516
669
  end
517
- end
518
670
 
519
- ##
520
- # Removes a siginfo handler and replaces the previous
671
+ ##
672
+ # Starts a live-reloading HTTP server for previewing documentation.
673
+ # Called from #document when <tt>--server</tt> is given.
521
674
 
522
- def remove_siginfo_handler
523
- return unless Signal.list.key? 'INFO'
675
+ def start_server
676
+ server = Server.new(self, @options.server_port)
677
+ server.start
678
+ end
524
679
 
525
- handler = @old_siginfo || 'DEFAULT'
680
+ ##
681
+ # Removes a siginfo handler and replaces the previous
526
682
 
527
- trap 'INFO', handler
528
- end
683
+ def remove_siginfo_handler
684
+ return unless Signal.list.key? 'INFO'
685
+
686
+ handler = @old_siginfo || 'DEFAULT'
529
687
 
688
+ trap 'INFO', handler
689
+ end
690
+
691
+ ##
692
+ # Returns true when +extension+ is the RBS gem's RDoc discovery hook.
693
+ # Released RBS gems install their plugin through this hook, so skip it to
694
+ # avoid replacing the built-in parser during discovery.
695
+
696
+ def self.rbs_discovery_extension?(extension) # :nodoc:
697
+ extension = File.expand_path(extension)
698
+
699
+ Gem::Specification.find_all_by_name('rbs').any? do |spec|
700
+ File.expand_path('lib/rdoc/discover.rb', spec.full_gem_path) == extension
701
+ end
702
+ end
703
+
704
+ end
530
705
  end
531
706
 
707
+ # Load built-in parser registrations before RubyGems discovery, then skip the
708
+ # RBS gem's plugin hook so it cannot replace RDoc::Parser::RBS.
709
+ require_relative 'parser'
710
+
532
711
  begin
533
712
  require 'rubygems'
534
713
 
535
714
  rdoc_extensions = Gem.find_latest_files 'rdoc/discover'
536
715
 
537
716
  rdoc_extensions.each do |extension|
717
+ next if RDoc::RDoc.rbs_discovery_extension?(extension)
718
+
538
719
  begin
539
720
  load extension
540
721
  rescue => e