rdoc 6.14.2 → 7.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 (107) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +196 -0
  3. data/History.rdoc +1 -1
  4. data/LEGAL.rdoc +6 -0
  5. data/README.md +195 -0
  6. data/RI.md +1 -1
  7. data/doc/markup_reference/markdown.md +558 -0
  8. data/doc/markup_reference/rdoc.rdoc +1169 -0
  9. data/lib/rdoc/code_object/any_method.rb +15 -7
  10. data/lib/rdoc/code_object/class_module.rb +62 -11
  11. data/lib/rdoc/code_object/constant.rb +9 -0
  12. data/lib/rdoc/code_object/context/section.rb +20 -1
  13. data/lib/rdoc/code_object/method_attr.rb +13 -1
  14. data/lib/rdoc/code_object/top_level.rb +31 -26
  15. data/lib/rdoc/comment.rb +190 -8
  16. data/lib/rdoc/cross_reference.rb +31 -22
  17. data/lib/rdoc/generator/aliki.rb +183 -0
  18. data/lib/rdoc/generator/darkfish.rb +8 -2
  19. data/lib/rdoc/generator/template/aliki/_aside_toc.rhtml +8 -0
  20. data/lib/rdoc/generator/template/aliki/_footer.rhtml +23 -0
  21. data/lib/rdoc/generator/template/aliki/_head.rhtml +158 -0
  22. data/lib/rdoc/generator/template/aliki/_header.rhtml +56 -0
  23. data/lib/rdoc/generator/template/aliki/_icons.rhtml +208 -0
  24. data/lib/rdoc/generator/template/aliki/_sidebar_ancestors.rhtml +16 -0
  25. data/lib/rdoc/generator/template/aliki/_sidebar_classes.rhtml +15 -0
  26. data/lib/rdoc/generator/template/aliki/_sidebar_extends.rhtml +25 -0
  27. data/lib/rdoc/generator/template/aliki/_sidebar_includes.rhtml +25 -0
  28. data/lib/rdoc/generator/template/aliki/_sidebar_installed.rhtml +16 -0
  29. data/lib/rdoc/generator/template/aliki/_sidebar_methods.rhtml +41 -0
  30. data/lib/rdoc/generator/template/aliki/_sidebar_pages.rhtml +67 -0
  31. data/lib/rdoc/generator/template/aliki/_sidebar_search.rhtml +15 -0
  32. data/lib/rdoc/generator/template/aliki/_sidebar_sections.rhtml +21 -0
  33. data/lib/rdoc/generator/template/aliki/_sidebar_toggle.rhtml +3 -0
  34. data/lib/rdoc/generator/template/aliki/class.rhtml +220 -0
  35. data/lib/rdoc/generator/template/aliki/css/rdoc.css +1963 -0
  36. data/lib/rdoc/generator/template/aliki/index.rhtml +22 -0
  37. data/lib/rdoc/generator/template/aliki/js/aliki.js +505 -0
  38. data/lib/rdoc/generator/template/aliki/js/c_highlighter.js +299 -0
  39. data/lib/rdoc/generator/template/aliki/js/search_controller.js +129 -0
  40. data/lib/rdoc/generator/template/aliki/js/search_navigation.js +105 -0
  41. data/lib/rdoc/generator/template/aliki/js/search_ranker.js +239 -0
  42. data/lib/rdoc/generator/template/aliki/js/theme-toggle.js +112 -0
  43. data/lib/rdoc/generator/template/aliki/page.rhtml +18 -0
  44. data/lib/rdoc/generator/template/aliki/servlet_not_found.rhtml +14 -0
  45. data/lib/rdoc/generator/template/aliki/servlet_root.rhtml +65 -0
  46. data/lib/rdoc/generator/template/darkfish/_footer.rhtml +3 -3
  47. data/lib/rdoc/generator/template/darkfish/_head.rhtml +14 -19
  48. data/lib/rdoc/generator/template/darkfish/_sidebar_extends.rhtml +8 -8
  49. data/lib/rdoc/generator/template/darkfish/_sidebar_includes.rhtml +8 -8
  50. data/lib/rdoc/generator/template/darkfish/_sidebar_installed.rhtml +7 -6
  51. data/lib/rdoc/generator/template/darkfish/_sidebar_methods.rhtml +6 -6
  52. data/lib/rdoc/generator/template/darkfish/_sidebar_pages.rhtml +18 -18
  53. data/lib/rdoc/generator/template/darkfish/_sidebar_parent.rhtml +2 -2
  54. data/lib/rdoc/generator/template/darkfish/_sidebar_search.rhtml +1 -0
  55. data/lib/rdoc/generator/template/darkfish/_sidebar_sections.rhtml +3 -3
  56. data/lib/rdoc/generator/template/darkfish/_sidebar_table_of_contents.rhtml +14 -14
  57. data/lib/rdoc/generator/template/darkfish/class.rhtml +70 -68
  58. data/lib/rdoc/generator/template/darkfish/css/rdoc.css +19 -0
  59. data/lib/rdoc/generator/template/darkfish/index.rhtml +4 -3
  60. data/lib/rdoc/generator/template/darkfish/js/darkfish.js +21 -1
  61. data/lib/rdoc/generator/template/darkfish/js/search.js +11 -1
  62. data/lib/rdoc/generator/template/darkfish/page.rhtml +2 -1
  63. data/lib/rdoc/generator/template/darkfish/servlet_not_found.rhtml +2 -1
  64. data/lib/rdoc/generator/template/darkfish/servlet_root.rhtml +19 -19
  65. data/lib/rdoc/generator/template/darkfish/table_of_contents.rhtml +19 -17
  66. data/lib/rdoc/generator/template/json_index/js/searcher.js +48 -6
  67. data/lib/rdoc/generator.rb +1 -0
  68. data/lib/rdoc/markdown.kpeg +29 -22
  69. data/lib/rdoc/markdown.rb +366 -549
  70. data/lib/rdoc/markup/attribute_manager.rb +28 -1
  71. data/lib/rdoc/markup/blank_line.rb +25 -23
  72. data/lib/rdoc/markup/element.rb +21 -0
  73. data/lib/rdoc/markup/hard_break.rb +30 -27
  74. data/lib/rdoc/markup/heading.rb +166 -77
  75. data/lib/rdoc/markup/pre_process.rb +34 -10
  76. data/lib/rdoc/markup/raw.rb +52 -55
  77. data/lib/rdoc/markup/table.rb +48 -40
  78. data/lib/rdoc/markup/to_ansi.rb +4 -0
  79. data/lib/rdoc/markup/to_bs.rb +4 -0
  80. data/lib/rdoc/markup/to_html.rb +193 -25
  81. data/lib/rdoc/markup/to_html_crossref.rb +77 -28
  82. data/lib/rdoc/markup/to_label.rb +11 -1
  83. data/lib/rdoc/markup/to_rdoc.rb +11 -3
  84. data/lib/rdoc/markup/verbatim.rb +1 -1
  85. data/lib/rdoc/markup.rb +3 -2
  86. data/lib/rdoc/options.rb +22 -11
  87. data/lib/rdoc/parser/c.rb +15 -46
  88. data/lib/rdoc/parser/changelog.rb +8 -0
  89. data/lib/rdoc/parser/prism_ruby.rb +121 -113
  90. data/lib/rdoc/parser/ruby.rb +8 -8
  91. data/lib/rdoc/parser/ruby_tools.rb +5 -7
  92. data/lib/rdoc/parser/simple.rb +4 -21
  93. data/lib/rdoc/rdoc.rb +1 -0
  94. data/lib/rdoc/ri/task.rb +2 -2
  95. data/lib/rdoc/rubygems_hook.rb +3 -3
  96. data/lib/rdoc/store.rb +2 -2
  97. data/lib/rdoc/task.rb +4 -4
  98. data/lib/rdoc/text.rb +16 -1
  99. data/lib/rdoc/token_stream.rb +17 -9
  100. data/lib/rdoc/tom_doc.rb +1 -1
  101. data/lib/rdoc/version.rb +1 -1
  102. data/rdoc.gemspec +6 -5
  103. metadata +53 -12
  104. data/CONTRIBUTING.rdoc +0 -219
  105. data/ExampleMarkdown.md +0 -39
  106. data/ExampleRDoc.rdoc +0 -210
  107. data/README.rdoc +0 -144
@@ -351,7 +351,6 @@ class RDoc::AnyMethod < RDoc::MethodAttr
351
351
  return call_seq unless is_alias_for || !aliases.empty?
352
352
 
353
353
  method_name = self.name
354
- method_name = method_name[0, 1] if method_name =~ /\A\[/
355
354
 
356
355
  entries = call_seq.split "\n"
357
356
 
@@ -360,15 +359,24 @@ class RDoc::AnyMethod < RDoc::MethodAttr
360
359
  ignore << is_alias_for.name
361
360
  ignore.concat is_alias_for.aliases.map(&:name)
362
361
  end
363
- ignore.map! { |n| n =~ /\A\[/ ? /\[.*\]/ : n}
362
+
364
363
  ignore.delete(method_name)
365
- ignore = Regexp.union(ignore)
364
+ ignore_bracket_methods, ignore_other_methods = ignore.partition {|i| i.start_with?('[') }
366
365
 
367
- matching = entries.reject do |entry|
368
- entry =~ /^\w*\.?#{ignore}[$\(\s]/ or
369
- entry =~ /\s#{ignore}\s/
366
+ if ignore_other_methods.any?
367
+ ignore_union = Regexp.union(ignore_other_methods)
368
+ entries.reject! do |entry|
369
+ /\A(?:\w*\.)?#{ignore_union}(?:[(\s]|\z)/.match?(entry) ||
370
+ /\s#{ignore_union}\s/.match?(entry)
371
+ end
372
+ end
373
+ if ignore_bracket_methods.any?
374
+ entries.reject! do |entry|
375
+ # Ignore `receiver[arg] -> return_type` `[](arg)` `[]`
376
+ /\A\w*\[.*?\](?:[(\s]|\z)/.match?(entry)
377
+ end
370
378
  end
371
379
 
372
- matching.empty? ? nil : matching.join("\n")
380
+ entries.empty? ? nil : entries.join("\n")
373
381
  end
374
382
  end
@@ -30,7 +30,22 @@ class RDoc::ClassModule < RDoc::Context
30
30
  attr_accessor :constant_aliases
31
31
 
32
32
  ##
33
- # Comment and the location it came from. Use #add_comment to add comments
33
+ # An array of `[comment, location]` pairs documenting this class/module.
34
+ # Use #add_comment to add comments.
35
+ #
36
+ # Before marshalling:
37
+ # - +comment+ is a String
38
+ # - +location+ is an RDoc::TopLevel
39
+ #
40
+ # After unmarshalling:
41
+ # - +comment+ is an RDoc::Markup::Document
42
+ # - +location+ is a filename String
43
+ #
44
+ # These type changes are acceptable (for now) because:
45
+ # - +comment+: Both String and Document respond to #empty?, and #parse
46
+ # returns Document as-is (see RDoc::Text#parse)
47
+ # - +location+: Only used by #parse to set Document#file, which accepts
48
+ # both TopLevel (extracts relative_name) and String
34
49
 
35
50
  attr_accessor :comment_location
36
51
 
@@ -110,7 +125,7 @@ class RDoc::ClassModule < RDoc::Context
110
125
  @is_alias_for = nil
111
126
  @name = name
112
127
  @superclass = superclass
113
- @comment_location = [] # [[comment, location]]
128
+ @comment_location = [] # Array of [comment, location] pairs
114
129
 
115
130
  super()
116
131
  end
@@ -173,10 +188,26 @@ class RDoc::ClassModule < RDoc::Context
173
188
  end
174
189
 
175
190
  ##
176
- # HTML fragment reference for this module or class. See
177
- # RDoc::NormalClass#aref and RDoc::NormalModule#aref
191
+ # HTML fragment reference for this module or class using GitHub-style
192
+ # anchor format (lowercase, :: replaced with -).
193
+ #
194
+ # Examples:
195
+ # Foo -> class-foo
196
+ # Foo::Bar -> class-foo-bar
178
197
 
179
198
  def aref
199
+ "#{aref_prefix}-#{full_name.downcase.gsub('::', '-')}"
200
+ end
201
+
202
+ ##
203
+ # Legacy HTML fragment reference for backward compatibility.
204
+ # Returns the old RDoc-style anchor format.
205
+ #
206
+ # Examples:
207
+ # Foo -> class-Foo
208
+ # Foo::Bar -> class-Foo::Bar
209
+
210
+ def legacy_aref
180
211
  "#{aref_prefix}-#{full_name}"
181
212
  end
182
213
 
@@ -379,10 +410,10 @@ class RDoc::ClassModule < RDoc::Context
379
410
 
380
411
  @comment = RDoc::Comment.from_document document
381
412
 
382
- @comment_location = if RDoc::Markup::Document === document.parts.first then
383
- document
413
+ @comment_location = if document.parts.first.is_a?(RDoc::Markup::Document)
414
+ document.parts.map { |doc| [doc, doc.file] }
384
415
  else
385
- RDoc::Markup::Document.new document
416
+ [[document, document.file]]
386
417
  end
387
418
 
388
419
  array[5].each do |name, rw, visibility, singleton, file|
@@ -462,7 +493,12 @@ class RDoc::ClassModule < RDoc::Context
462
493
  document = document.merge other_document
463
494
 
464
495
  @comment = RDoc::Comment.from_document(document)
465
- @comment_location = document
496
+
497
+ @comment_location = if document.parts.first.is_a?(RDoc::Markup::Document)
498
+ document.parts.map { |doc| [doc, doc.file] }
499
+ else
500
+ [[document, document.file]]
501
+ end
466
502
  end
467
503
 
468
504
  cm = class_module
@@ -689,6 +725,9 @@ class RDoc::ClassModule < RDoc::Context
689
725
 
690
726
  ##
691
727
  # Search record used by RDoc::Generator::JsonIndex
728
+ #
729
+ # TODO: Remove this method after dropping the darkfish theme and JsonIndex generator.
730
+ # Use #search_snippet instead for getting documentation snippets.
692
731
 
693
732
  def search_record
694
733
  [
@@ -702,6 +741,16 @@ class RDoc::ClassModule < RDoc::Context
702
741
  ]
703
742
  end
704
743
 
744
+ ##
745
+ # Returns an HTML snippet of the first comment for search results.
746
+
747
+ def search_snippet
748
+ first_comment = @comment_location.first&.first
749
+ return '' unless first_comment && !first_comment.empty?
750
+
751
+ snippet(first_comment)
752
+ end
753
+
705
754
  ##
706
755
  # Sets the store for this class or module and its contained code objects.
707
756
 
@@ -794,11 +843,13 @@ class RDoc::ClassModule < RDoc::Context
794
843
  cm_alias = cm.dup
795
844
  cm_alias.name = const.name
796
845
 
797
- # Don't move top-level aliases under Object, they look ugly there
798
- unless RDoc::TopLevel === cm_alias.parent then
846
+ if full_name == 'Object'
847
+ # Don't move top-level aliases under Object, they look ugly there
848
+ cm_alias.parent = top_level
849
+ else
799
850
  cm_alias.parent = self
800
- cm_alias.full_name = nil # force update for new parent
801
851
  end
852
+ cm_alias.full_name = nil # force update for new parent
802
853
 
803
854
  cm_alias.aliases.clear
804
855
  cm_alias.is_alias_for = cm
@@ -154,6 +154,15 @@ class RDoc::Constant < RDoc::CodeObject
154
154
  "#{@parent.path}##{@name}"
155
155
  end
156
156
 
157
+ ##
158
+ # Returns an HTML snippet of the comment for search results.
159
+
160
+ def search_snippet
161
+ return '' if comment.empty?
162
+
163
+ snippet(comment)
164
+ end
165
+
157
166
  def pretty_print(q) # :nodoc:
158
167
  q.group 2, "[#{self.class.name} #{full_name}", "]" do
159
168
  unless comment.empty? then
@@ -70,11 +70,30 @@ class RDoc::Context::Section
70
70
  end
71
71
 
72
72
  ##
73
- # Anchor reference for linking to this section
73
+ # Anchor reference for linking to this section using GitHub-style format.
74
+ #
75
+ # Examples:
76
+ # "Section" -> "section"
77
+ # "One Two" -> "one-two"
78
+ # "[untitled]" -> "untitled"
74
79
 
75
80
  def aref
76
81
  title = @title || '[untitled]'
77
82
 
83
+ RDoc::Text.to_anchor(title)
84
+ end
85
+
86
+ ##
87
+ # Legacy anchor reference for backward compatibility.
88
+ #
89
+ # Examples:
90
+ # "Section" -> "section"
91
+ # "One Two" -> "one+two"
92
+ # "[untitled]" -> "5Buntitled-5D"
93
+
94
+ def legacy_aref
95
+ title = @title || '[untitled]'
96
+
78
97
  CGI.escape(title).gsub('%', '-').sub(/^-/, '')
79
98
  end
80
99
 
@@ -375,6 +375,9 @@ class RDoc::MethodAttr < RDoc::CodeObject
375
375
  ##
376
376
  # Used by RDoc::Generator::JsonIndex to create a record for the search
377
377
  # engine.
378
+ #
379
+ # TODO: Remove this method after dropping the darkfish theme and JsonIndex generator.
380
+ # Use #search_snippet instead for getting documentation snippets.
378
381
 
379
382
  def search_record
380
383
  [
@@ -384,10 +387,19 @@ class RDoc::MethodAttr < RDoc::CodeObject
384
387
  @parent.full_name,
385
388
  path,
386
389
  params,
387
- snippet(@comment),
390
+ search_snippet,
388
391
  ]
389
392
  end
390
393
 
394
+ ##
395
+ # Returns an HTML snippet of the comment for search results.
396
+
397
+ def search_snippet
398
+ return '' if @comment.empty?
399
+
400
+ snippet(@comment)
401
+ end
402
+
391
403
  def to_s # :nodoc:
392
404
  if @is_alias_for
393
405
  "#{self.class.name}: #{full_name} -> #{is_alias_for}"
@@ -16,6 +16,16 @@ class RDoc::TopLevel < RDoc::Context
16
16
 
17
17
  attr_accessor :absolute_name
18
18
 
19
+ ##
20
+ # Base name of this file
21
+
22
+ attr_reader :base_name
23
+
24
+ ##
25
+ # Base name of this file without the extension
26
+
27
+ attr_reader :page_name
28
+
19
29
  ##
20
30
  # All the classes or modules that were declared in
21
31
  # this file. These are assigned to either +#classes_hash+
@@ -40,6 +50,14 @@ class RDoc::TopLevel < RDoc::Context
40
50
  @relative_name = relative_name
41
51
  @parser = nil
42
52
 
53
+ if relative_name
54
+ @base_name = File.basename(relative_name)
55
+ @page_name = @base_name.sub(/\.(rb|rdoc|txt|md)\z/i, '')
56
+ else
57
+ @base_name = nil
58
+ @page_name = nil
59
+ end
60
+
43
61
  @classes_or_modules = []
44
62
  end
45
63
 
@@ -105,23 +123,8 @@ class RDoc::TopLevel < RDoc::Context
105
123
  @classes_or_modules << mod
106
124
  end
107
125
 
108
- ##
109
- # Base name of this file
110
-
111
- def base_name
112
- File.basename @relative_name
113
- end
114
-
115
126
  alias name base_name
116
127
 
117
- ##
118
- # Only a TopLevel that contains text file) will be displayed. See also
119
- # RDoc::CodeObject#display?
120
-
121
- def display?
122
- text? and super
123
- end
124
-
125
128
  ##
126
129
  # See RDoc::TopLevel::find_class_or_module
127
130
  #--
@@ -212,16 +215,6 @@ class RDoc::TopLevel < RDoc::Context
212
215
  end
213
216
  end
214
217
 
215
- ##
216
- # Base name of this file without the extension
217
-
218
- def page_name
219
- basename = File.basename @relative_name
220
- basename =~ /\.(rb|rdoc|txt|md)$/i
221
-
222
- $` || basename
223
- end
224
-
225
218
  ##
226
219
  # Path to this file for use with HTML generator output.
227
220
 
@@ -244,6 +237,9 @@ class RDoc::TopLevel < RDoc::Context
244
237
 
245
238
  ##
246
239
  # Search record used by RDoc::Generator::JsonIndex
240
+ #
241
+ # TODO: Remove this method after dropping the darkfish theme and JsonIndex generator.
242
+ # Use #search_snippet instead for getting documentation snippets.
247
243
 
248
244
  def search_record
249
245
  return unless @parser < RDoc::Parser::Text
@@ -255,10 +251,19 @@ class RDoc::TopLevel < RDoc::Context
255
251
  '',
256
252
  path,
257
253
  '',
258
- snippet(@comment),
254
+ search_snippet,
259
255
  ]
260
256
  end
261
257
 
258
+ ##
259
+ # Returns an HTML snippet of the comment for search results.
260
+
261
+ def search_snippet
262
+ return '' if @comment.empty?
263
+
264
+ snippet(@comment)
265
+ end
266
+
262
267
  ##
263
268
  # Is this TopLevel from a text file instead of a source code file?
264
269
 
data/lib/rdoc/comment.rb CHANGED
@@ -162,6 +162,12 @@ class RDoc::Comment
162
162
  self
163
163
  end
164
164
 
165
+ # Change normalized, when creating already normalized comment.
166
+
167
+ def normalized=(value)
168
+ @normalized = value
169
+ end
170
+
165
171
  ##
166
172
  # Was this text normalized?
167
173
 
@@ -223,14 +229,190 @@ class RDoc::Comment
223
229
  @format == 'tomdoc'
224
230
  end
225
231
 
226
- ##
227
- # Create a new parsed comment from a document
232
+ MULTILINE_DIRECTIVES = %w[call-seq].freeze # :nodoc:
228
233
 
229
- def self.from_document(document) # :nodoc:
230
- comment = RDoc::Comment.new('')
231
- comment.document = document
232
- comment.location = RDoc::TopLevel.new(document.file) if document.file
233
- comment
234
- end
234
+ # There are more, but already handled by RDoc::Parser::C
235
+ COLON_LESS_DIRECTIVES = %w[call-seq Document-method].freeze # :nodoc:
236
+
237
+ DIRECTIVE_OR_ESCAPED_DIRECTIV_REGEXP = /\A(?<colon>\\?:|:?)(?<directive>[\w-]+):(?<param>.*)/
238
+
239
+ private_constant :MULTILINE_DIRECTIVES, :COLON_LESS_DIRECTIVES, :DIRECTIVE_OR_ESCAPED_DIRECTIV_REGEXP
240
+
241
+ class << self
242
+
243
+ ##
244
+ # Create a new parsed comment from a document
235
245
 
246
+ def from_document(document) # :nodoc:
247
+ comment = RDoc::Comment.new('')
248
+ comment.document = document
249
+ comment.location = RDoc::TopLevel.new(document.file) if document.file
250
+ comment
251
+ end
252
+
253
+ # Parse comment, collect directives as an attribute and return [normalized_comment_text, directives_hash]
254
+ # This method expands include and removes everything not needed in the document text, such as
255
+ # private section, directive line, comment characters `# /* * */` and indent spaces.
256
+ #
257
+ # RDoc comment consists of include, directive, multiline directive, private section and comment text.
258
+ #
259
+ # Include
260
+ # # :include: filename
261
+ #
262
+ # Directive
263
+ # # :directive-without-value:
264
+ # # :directive-with-value: value
265
+ #
266
+ # Multiline directive (only :call-seq:)
267
+ # # :multiline-directive:
268
+ # # value1
269
+ # # value2
270
+ #
271
+ # Private section
272
+ # #--
273
+ # # private comment
274
+ # #++
275
+
276
+ def parse(text, filename, line_no, type, &include_callback)
277
+ case type
278
+ when :ruby
279
+ text = text.gsub(/^#+/, '') if text.start_with?('#')
280
+ private_start_regexp = /^-{2,}$/
281
+ private_end_regexp = /^\+{2}$/
282
+ indent_regexp = /^\s*/
283
+ when :c
284
+ private_start_regexp = /^(\s*\*)?-{2,}$/
285
+ private_end_regexp = /^(\s*\*)?\+{2}$/
286
+ indent_regexp = /^\s*(\/\*+|\*)?\s*/
287
+ text = text.gsub(/\s*\*+\/\s*\z/, '')
288
+ when :simple
289
+ # Unlike other types, this implementation only looks for two dashes at
290
+ # the beginning of the line. Three or more dashes are considered to be
291
+ # a rule and ignored.
292
+ private_start_regexp = /^-{2}$/
293
+ private_end_regexp = /^\+{2}$/
294
+ indent_regexp = /^\s*/
295
+ end
296
+
297
+ directives = {}
298
+ lines = text.split("\n")
299
+ in_private = false
300
+ comment_lines = []
301
+ until lines.empty?
302
+ line = lines.shift
303
+ read_lines = 1
304
+ if in_private
305
+ # If `++` appears in a private section that starts with `--`, private section ends.
306
+ in_private = false if line.match?(private_end_regexp)
307
+ line_no += read_lines
308
+ next
309
+ elsif line.match?(private_start_regexp)
310
+ # If `--` appears in a line, private section starts.
311
+ in_private = true
312
+ line_no += read_lines
313
+ next
314
+ end
315
+
316
+ prefix = line[indent_regexp]
317
+ prefix_indent = ' ' * prefix.size
318
+ line = line.byteslice(prefix.bytesize..)
319
+
320
+ if (directive_match = DIRECTIVE_OR_ESCAPED_DIRECTIV_REGEXP.match(line))
321
+ colon = directive_match[:colon]
322
+ directive = directive_match[:directive]
323
+ raw_param = directive_match[:param]
324
+ param = raw_param.strip
325
+ else
326
+ colon = directive = raw_param = param = nil
327
+ end
328
+
329
+ if !directive
330
+ comment_lines << prefix_indent + line
331
+ elsif colon == '\\:'
332
+ # If directive is escaped, unescape it
333
+ comment_lines << prefix_indent + line.sub('\\:', ':')
334
+ elsif raw_param.start_with?(':') || (colon.empty? && !COLON_LESS_DIRECTIVES.include?(directive))
335
+ # Something like `:toto::` is not a directive
336
+ # Only few directives allows to start without a colon
337
+ comment_lines << prefix_indent + line
338
+ elsif directive == 'include'
339
+ filename_to_include = param
340
+ include_callback.call(filename_to_include, prefix_indent).lines.each { |l| comment_lines << l.chomp }
341
+ elsif MULTILINE_DIRECTIVES.include?(directive)
342
+ value_lines = take_multiline_directive_value_lines(directive, filename, line_no, lines, prefix_indent.size, indent_regexp, !param.empty?)
343
+ read_lines += value_lines.size
344
+ lines.shift(value_lines.size)
345
+ unless param.empty?
346
+ # Accept `:call-seq: first-line\n second-line` for now
347
+ value_lines.unshift(param)
348
+ end
349
+ value = value_lines.join("\n")
350
+ directives[directive] = [value.empty? ? nil : value, line_no]
351
+ else
352
+ directives[directive] = [param.empty? ? nil : param, line_no]
353
+ end
354
+ line_no += read_lines
355
+ end
356
+
357
+ normalized_comment = String.new(encoding: text.encoding) << normalize_comment_lines(comment_lines).join("\n")
358
+ [normalized_comment, directives]
359
+ end
360
+
361
+ # Remove preceding indent spaces and blank lines from the comment lines
362
+
363
+ private def normalize_comment_lines(lines)
364
+ blank_line_regexp = /\A\s*\z/
365
+ lines = lines.dup
366
+ lines.shift while lines.first&.match?(blank_line_regexp)
367
+ lines.pop while lines.last&.match?(blank_line_regexp)
368
+
369
+ min_spaces = lines.map do |l|
370
+ l.match(/\A *(?=\S)/)&.end(0)
371
+ end.compact.min
372
+ if min_spaces && min_spaces > 0
373
+ lines.map { |l| l[min_spaces..] || '' }
374
+ else
375
+ lines
376
+ end
377
+ end
378
+
379
+ # Take value lines of multiline directive
380
+
381
+ private def take_multiline_directive_value_lines(directive, filename, line_no, lines, base_indent_size, indent_regexp, has_param)
382
+ return [] if lines.empty?
383
+
384
+ first_indent_size = lines.first.match(indent_regexp).end(0)
385
+
386
+ # Blank line or unindented line is not part of multiline-directive value
387
+ return [] if first_indent_size <= base_indent_size
388
+
389
+ if has_param
390
+ # :multiline-directive: line1
391
+ # line2
392
+ # line3
393
+ #
394
+ value_lines = lines.take_while do |l|
395
+ l.rstrip.match(indent_regexp).end(0) > base_indent_size
396
+ end
397
+ min_indent = value_lines.map { |l| l.match(indent_regexp).end(0) }.min
398
+ value_lines.map { |l| l[min_indent..] }
399
+ else
400
+ # Take indented lines accepting blank lines between them
401
+ value_lines = lines.take_while do |l|
402
+ l = l.rstrip
403
+ indent = l[indent_regexp]
404
+ if indent == l || indent.size >= first_indent_size
405
+ true
406
+ end
407
+ end
408
+ value_lines.map! { |l| (l[first_indent_size..] || '').chomp }
409
+
410
+ if value_lines.size != lines.size && !value_lines.last.empty?
411
+ warn "#{filename}:#{line_no} Multiline directive :#{directive}: should end with a blank line."
412
+ end
413
+ value_lines.pop while value_lines.last&.empty?
414
+ value_lines
415
+ end
416
+ end
417
+ end
236
418
  end
@@ -132,47 +132,56 @@ class RDoc::CrossReference
132
132
  end
133
133
 
134
134
  ##
135
- # Returns a method reference to +name+.
135
+ # Returns a method, attribute or constant reference to +name+
136
+ # if it exists in the containing context object. It returns
137
+ # nil otherwise.
138
+ #
139
+ # For example, this method would decompose name = 'A::CONSTANT' into a
140
+ # container object A and a symbol 'CONSTANT', and it would try to find
141
+ # 'CONSTANT' in A.
136
142
 
137
- def resolve_method(name)
143
+ def resolve_local_symbol(name)
138
144
  ref = nil
145
+ type = nil
146
+ container = nil
139
147
 
140
- if /#{CLASS_REGEXP_STR}([.#]|::)#{METHOD_REGEXP_STR}/o =~ name then
148
+ case name
149
+ when /#{CLASS_REGEXP_STR}::([A-Z]\w*)\z/o then
150
+ symbol = $2
151
+ container = @context.find_symbol_module($1)
152
+ when /#{CLASS_REGEXP_STR}([.#]|::)#{METHOD_REGEXP_STR}/o then
141
153
  type = $2
142
154
  if '.' == type # will find either #method or ::method
143
- method = $3
155
+ symbol = $3
144
156
  else
145
- method = "#{type}#{$3}"
157
+ symbol = "#{type}#{$3}"
146
158
  end
147
159
  container = @context.find_symbol_module($1)
148
- elsif /^([.#]|::)#{METHOD_REGEXP_STR}/o =~ name then
160
+ when /^([.#]|::)#{METHOD_REGEXP_STR}/o then
149
161
  type = $1
150
162
  if '.' == type
151
- method = $2
163
+ symbol = $2
152
164
  else
153
- method = "#{type}#{$2}"
165
+ symbol = "#{type}#{$2}"
154
166
  end
155
167
  container = @context
156
- else
157
- type = nil
158
- container = nil
159
168
  end
160
169
 
161
170
  if container then
162
171
  unless RDoc::TopLevel === container then
163
172
  if '.' == type then
164
- if 'new' == method then # AnyClassName.new will be class method
165
- ref = container.find_local_symbol method
166
- ref = container.find_ancestor_local_symbol method unless ref
173
+ if 'new' == symbol then # AnyClassName.new will be class method
174
+ ref = container.find_local_symbol symbol
175
+ ref = container.find_ancestor_local_symbol symbol unless ref
167
176
  else
168
- ref = container.find_local_symbol "::#{method}"
169
- ref = container.find_ancestor_local_symbol "::#{method}" unless ref
170
- ref = container.find_local_symbol "##{method}" unless ref
171
- ref = container.find_ancestor_local_symbol "##{method}" unless ref
177
+ ref = container.find_local_symbol "::#{symbol}"
178
+ ref = container.find_ancestor_local_symbol "::#{symbol}" unless ref
179
+ ref = container.find_local_symbol "##{symbol}" unless ref
180
+ ref = container.find_ancestor_local_symbol "##{symbol}" unless ref
172
181
  end
173
182
  else
174
- ref = container.find_local_symbol method
175
- ref = container.find_ancestor_local_symbol method unless ref
183
+ ref = container.find_local_symbol symbol
184
+ ref = container.find_ancestor_local_symbol symbol unless ref
176
185
  end
177
186
  end
178
187
  end
@@ -197,10 +206,10 @@ class RDoc::CrossReference
197
206
  @context.find_symbol name
198
207
  end
199
208
 
200
- ref = resolve_method name unless ref
209
+ ref = resolve_local_symbol name unless ref
201
210
 
202
211
  # Try a page name
203
- ref = @store.page name if not ref and name =~ /^[\w.]+$/
212
+ ref = @store.page name if not ref and name =~ /^[\w.\/]+$/
204
213
 
205
214
  ref = nil if RDoc::Alias === ref # external alias, can't link to it
206
215