1## 2# A comment holds the text comment for a RDoc::CodeObject and provides a 3# unified way of cleaning it up and parsing it into an RDoc::Markup::Document. 4# 5# Each comment may have a different markup format set by #format=. By default 6# 'rdoc' is used. The :markup: directive tells RDoc which format to use. 7# 8# See RDoc::Markup@Other+directives for instructions on adding an alternate 9# format. 10 11class RDoc::Comment 12 13 include RDoc::Text 14 15 ## 16 # The format of this comment. Defaults to RDoc::Markup 17 18 attr_reader :format 19 20 ## 21 # The RDoc::TopLevel this comment was found in 22 23 attr_accessor :location 24 25 ## 26 # For duck-typing when merging classes at load time 27 28 alias file location # :nodoc: 29 30 ## 31 # The text for this comment 32 33 attr_reader :text 34 35 ## 36 # Overrides the content returned by #parse. Use when there is no #text 37 # source for this comment 38 39 attr_writer :document 40 41 ## 42 # Creates a new comment with +text+ that is found in the RDoc::TopLevel 43 # +location+. 44 45 def initialize text = nil, location = nil 46 @location = location 47 @text = text 48 49 @document = nil 50 @format = 'rdoc' 51 @normalized = false 52 end 53 54 ## 55 #-- 56 # TODO deep copy @document 57 58 def initialize_copy copy # :nodoc: 59 @text = copy.text.dup 60 end 61 62 def == other # :nodoc: 63 self.class === other and 64 other.text == @text and other.location == @location 65 end 66 67 ## 68 # Look for a 'call-seq' in the comment to override the normal parameter 69 # handling. The :call-seq: is indented from the baseline. All lines of the 70 # same indentation level and prefix are consumed. 71 # 72 # For example, all of the following will be used as the :call-seq: 73 # 74 # # :call-seq: 75 # # ARGF.readlines(sep=$/) -> array 76 # # ARGF.readlines(limit) -> array 77 # # ARGF.readlines(sep, limit) -> array 78 # # 79 # # ARGF.to_a(sep=$/) -> array 80 # # ARGF.to_a(limit) -> array 81 # # ARGF.to_a(sep, limit) -> array 82 83 def extract_call_seq method 84 # we must handle situations like the above followed by an unindented first 85 # comment. The difficulty is to make sure not to match lines starting 86 # with ARGF at the same indent, but that are after the first description 87 # paragraph. 88 if @text =~ /^\s*:?call-seq:(.*?(?:\S).*?)^\s*$/m then 89 all_start, all_stop = $~.offset(0) 90 seq_start, seq_stop = $~.offset(1) 91 92 # we get the following lines that start with the leading word at the 93 # same indent, even if they have blank lines before 94 if $1 =~ /(^\s*\n)+^(\s*\w+)/m then 95 leading = $2 # ' * ARGF' in the example above 96 re = %r% 97 \A( 98 (^\s*\n)+ 99 (^#{Regexp.escape leading}.*?\n)+ 100 )+ 101 ^\s*$ 102 %xm 103 104 if @text[seq_stop..-1] =~ re then 105 all_stop = seq_stop + $~.offset(0).last 106 seq_stop = seq_stop + $~.offset(1).last 107 end 108 end 109 110 seq = @text[seq_start..seq_stop] 111 seq.gsub!(/^\s*(\S|\n)/m, '\1') 112 @text.slice! all_start...all_stop 113 114 method.call_seq = seq.chomp 115 116 elsif @text.sub!(/^\s*:?call-seq:(.*?)(^\s*$|\z)/m, '') then 117 seq = $1 118 seq.gsub!(/^\s*/, '') 119 method.call_seq = seq 120 end 121 #elsif @text.sub!(/\A\/\*\s*call-seq:(.*?)\*\/\Z/, '') then 122 # method.call_seq = $1.strip 123 #end 124 125 method 126 end 127 128 ## 129 # A comment is empty if its text String is empty. 130 131 def empty? 132 @text.empty? 133 end 134 135 ## 136 # HACK dubious 137 138 def force_encoding encoding 139 @text.force_encoding encoding 140 end 141 142 ## 143 # Sets the format of this comment and resets any parsed document 144 145 def format= format 146 @format = format 147 @document = nil 148 end 149 150 def inspect # :nodoc: 151 location = @location ? @location.relative_name : '(unknown)' 152 153 "#<%s:%x %s %p>" % [self.class, object_id, location, @text] 154 end 155 156 ## 157 # Normalizes the text. See RDoc::Text#normalize_comment for details 158 159 def normalize 160 return self unless @text 161 return self if @normalized # TODO eliminate duplicate normalization 162 163 @text = normalize_comment @text 164 165 @normalized = true 166 167 self 168 end 169 170 ## 171 # Was this text normalized? 172 173 def normalized? # :nodoc: 174 @normalized 175 end 176 177 ## 178 # Parses the comment into an RDoc::Markup::Document. The parsed document is 179 # cached until the text is changed. 180 181 def parse 182 return @document if @document 183 184 @document = super @text, @format 185 @document.file = @location 186 @document 187 end 188 189 ## 190 # Removes private sections from this comment. Private sections are flush to 191 # the comment marker and start with <tt>--</tt> and end with <tt>++</tt>. 192 # For C-style comments, a private marker may not start at the opening of the 193 # comment. 194 # 195 # /* 196 # *-- 197 # * private 198 # *++ 199 # * public 200 # */ 201 202 def remove_private 203 # Workaround for gsub encoding for Ruby 1.9.2 and earlier 204 empty = '' 205 empty.force_encoding @text.encoding if Object.const_defined? :Encoding 206 207 @text = @text.gsub(%r%^\s*([#*]?)--.*?^\s*(\1)\+\+\n?%m, empty) 208 @text = @text.sub(%r%^\s*[#*]?--.*%m, '') 209 end 210 211 ## 212 # Replaces this comment's text with +text+ and resets the parsed document. 213 # 214 # An error is raised if the comment contains a document but no text. 215 216 def text= text 217 raise RDoc::Error, 'replacing document-only comment is not allowed' if 218 @text.nil? and @document 219 220 @document = nil 221 @text = text 222 end 223 224 ## 225 # Returns true if this comment is in TomDoc format. 226 227 def tomdoc? 228 @format == 'tomdoc' 229 end 230 231end 232 233