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