@@ -162,6 +162,12 @@ def normalize
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,169 @@ def tomdoc?
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
+ private_constant :MULTILINE_DIRECTIVES , :COLON_LESS_DIRECTIVES
238
+
239
+ class << self
235
240
241
+ ##
242
+ # Create a new parsed comment from a document
243
+
244
+ def from_document ( document ) # :nodoc:
245
+ comment = RDoc ::Comment . new ( '' )
246
+ comment . document = document
247
+ comment . location = RDoc ::TopLevel . new ( document . file ) if document . file
248
+ comment
249
+ end
250
+
251
+ # Parse comment, collect directives as an attribute and return [normalized_comment_text, directives_hash]
252
+ # This method expands include and removes everything not needed in the document text, such as
253
+ # private section, directive line, comment characters `# /* * */` and indent spaces.
254
+ #
255
+ # RDoc comment consists of include, directive, multiline directive, private section and comment text.
256
+ #
257
+ # Include
258
+ # # :include: filename
259
+ #
260
+ # Directive
261
+ # # :directive-without-value:
262
+ # # :directive-with-value: value
263
+ #
264
+ # Multiline directive (only :call-seq:)
265
+ # # :multiline-directive:
266
+ # # value1
267
+ # # value2
268
+ #
269
+ # Private section
270
+ # #--
271
+ # # private comment
272
+ # #++
273
+
274
+ def parse ( text , filename , line_no , type )
275
+ case type
276
+ when :ruby
277
+ text = text . gsub ( /^#+/ , '' ) if text . start_with? ( '#' )
278
+ private_start_regexp = /^-{2,}$/
279
+ private_end_regexp = /^\+ {2}$/
280
+ indent_regexp = /^\s */
281
+ when :c
282
+ private_start_regexp = /^(\s *\* )?-{2,}$/
283
+ private_end_regexp = /^(\s *\* )?\+ {2}$/
284
+ indent_regexp = /^\s *(\/ \* +|\* )?\s */
285
+ text = text . gsub ( /\s *\* +\/ \s *\z / , '' )
286
+ # TODO: should not be here. Looks like another type of directive
287
+ # text = text.gsub %r%Document-method:\s+[\w:.#=!?|^&<>~+\-/*\%@`\[\]]+%, ''
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
+ in_private = false if line . match? ( private_end_regexp )
306
+ line_no += read_lines
307
+ next
308
+ elsif line . match? ( private_start_regexp )
309
+ in_private = true
310
+ line_no += read_lines
311
+ next
312
+ end
313
+
314
+ prefix = line [ indent_regexp ]
315
+ prefix_indent = ' ' * prefix . size
316
+ line = line . byteslice ( prefix . bytesize ..)
317
+ /\A (?<colon>\\ ?:|:?)(?<directive>[\w -]+):(?<param>.*)/ =~ line
318
+
319
+ if colon == '\\:'
320
+ # unescape if escaped
321
+ comment_lines << prefix_indent + line . sub ( '\\:' , ':' )
322
+ elsif !directive || param . start_with? ( ':' ) || ( colon . empty? && !COLON_LESS_DIRECTIVES . include? ( directive ) )
323
+ # Something like `:toto::` is not a directive
324
+ # Only few directives allows to start without a colon
325
+ comment_lines << prefix_indent + line
326
+ elsif directive == 'include'
327
+ filename_to_include = param . strip
328
+ yield ( filename_to_include , prefix_indent ) . lines . each { |l | comment_lines << l . chomp }
329
+ elsif MULTILINE_DIRECTIVES . include? ( directive )
330
+ param = param . strip
331
+ value_lines = take_multiline_directive_value_lines ( directive , filename , line_no , lines , prefix_indent . size , indent_regexp , !param . empty? )
332
+ read_lines += value_lines . size
333
+ lines . shift ( value_lines . size )
334
+ unless param . empty?
335
+ # Accept `:call-seq: first-line\n second-line` for now
336
+ value_lines . unshift ( param )
337
+ end
338
+ value = value_lines . join ( "\n " )
339
+ directives [ directive ] = [ value . empty? ? nil : value , line_no ]
340
+ else
341
+ value = param . strip
342
+ directives [ directive ] = [ value . empty? ? nil : value , line_no ]
343
+ end
344
+ line_no += read_lines
345
+ end
346
+ # normalize comment
347
+ min_spaces = nil
348
+ comment_lines . each do |l |
349
+ next if l . match? ( /\A \s *\z / )
350
+ n = l [ /\A */ ] . size
351
+ min_spaces = n if !min_spaces || n < min_spaces
352
+ end
353
+ comment_lines . map! { |l | l [ min_spaces ..] || '' } if min_spaces
354
+ comment_lines . shift while comment_lines . first &.match? ( /\A \s *\z / )
355
+ [ String . new ( encoding : text . encoding ) << comment_lines . join ( "\n " ) , directives ]
356
+ end
357
+
358
+ # Take value lines of multiline directive
359
+
360
+ private def take_multiline_directive_value_lines ( directive , filename , line_no , lines , base_indent_size , indent_regexp , has_param )
361
+ return [ ] if lines . empty?
362
+
363
+ first_indent_size = lines . first [ indent_regexp ] . size
364
+
365
+ # Blank line or unindented line is not part of multiline-directive value
366
+ return [ ] if first_indent_size <= base_indent_size
367
+
368
+ if has_param
369
+ # :multiline-directive: line1
370
+ # line2
371
+ # line3
372
+ #
373
+ value_lines = lines . take_while do |l |
374
+ l . rstrip [ indent_regexp ] . size > base_indent_size
375
+ end
376
+ min_indent = value_lines . map { |l | l [ indent_regexp ] . size } . min
377
+ value_lines . map { |l | l [ min_indent ..] }
378
+ else
379
+ # Take indented lines accepting blank lines between them
380
+ value_lines = lines . take_while do |l |
381
+ l = l . rstrip
382
+ indent = l [ indent_regexp ]
383
+ if indent == l || indent . size >= first_indent_size
384
+ true
385
+ end
386
+ end
387
+ value_lines . map! { |l | ( l [ first_indent_size ..] || '' ) . chomp }
388
+
389
+ if value_lines . size != lines . size && !value_lines . last . empty?
390
+ warn "#{ filename } :#{ line_no } Multiline directive :#{ directive } : should end with a blank line."
391
+ end
392
+ value_lines . pop while value_lines . last &.empty?
393
+ value_lines
394
+ end
395
+ end
396
+ end
236
397
end
0 commit comments