1## 2# Handle common directives that can occur in a block of text: 3# 4# \:include: filename 5# 6# Directives can be escaped by preceding them with a backslash. 7# 8# RDoc plugin authors can register additional directives to be handled by 9# using RDoc::Markup::PreProcess::register. 10# 11# Any directive that is not built-in to RDoc (including those registered via 12# plugins) will be stored in the metadata hash on the CodeObject the comment 13# is attached to. See RDoc::Markup@Directives for the list of built-in 14# directives. 15 16class RDoc::Markup::PreProcess 17 18 ## 19 # An RDoc::Options instance that will be filled in with overrides from 20 # directives 21 22 attr_accessor :options 23 24 ## 25 # Adds a post-process handler for directives. The handler will be called 26 # with the result RDoc::Comment (or text String) and the code object for the 27 # comment (if any). 28 29 def self.post_process &block 30 @post_processors << block 31 end 32 33 ## 34 # Registered post-processors 35 36 def self.post_processors 37 @post_processors 38 end 39 40 ## 41 # Registers +directive+ as one handled by RDoc. If a block is given the 42 # directive will be replaced by the result of the block, otherwise the 43 # directive will be removed from the processed text. 44 # 45 # The block will be called with the directive name and the directive 46 # parameter: 47 # 48 # RDoc::Markup::PreProcess.register 'my-directive' do |directive, param| 49 # # replace text, etc. 50 # end 51 52 def self.register directive, &block 53 @registered[directive] = block 54 end 55 56 ## 57 # Registered directives 58 59 def self.registered 60 @registered 61 end 62 63 ## 64 # Clears all registered directives and post-processors 65 66 def self.reset 67 @post_processors = [] 68 @registered = {} 69 end 70 71 reset 72 73 ## 74 # Creates a new pre-processor for +input_file_name+ that will look for 75 # included files in +include_path+ 76 77 def initialize(input_file_name, include_path) 78 @input_file_name = input_file_name 79 @include_path = include_path 80 @options = nil 81 end 82 83 ## 84 # Look for directives in the given +text+. 85 # 86 # Options that we don't handle are yielded. If the block returns false the 87 # directive is restored to the text. If the block returns nil or no block 88 # was given the directive is handled according to the registered directives. 89 # If a String was returned the directive is replaced with the string. 90 # 91 # If no matching directive was registered the directive is restored to the 92 # text. 93 # 94 # If +code_object+ is given and the directive is unknown then the 95 # directive's parameter is set as metadata on the +code_object+. See 96 # RDoc::CodeObject#metadata for details. 97 98 def handle text, code_object = nil, &block 99 if RDoc::Comment === text then 100 comment = text 101 text = text.text 102 end 103 104 encoding = text.encoding if defined?(Encoding) 105 106 # regexp helper (square brackets for optional) 107 # $1 $2 $3 $4 $5 108 # [prefix][\]:directive:[spaces][param]newline 109 text.gsub!(/^([ \t]*(?:#|\/?\*)?[ \t]*)(\\?):(\w+):([ \t]*)(.+)?(\r?\n|$)/) do 110 # skip something like ':toto::' 111 next $& if $4.empty? and $5 and $5[0, 1] == ':' 112 113 # skip if escaped 114 next "#$1:#$3:#$4#$5\n" unless $2.empty? 115 116 # This is not in handle_directive because I didn't want to pass another 117 # argument into it 118 if comment and $3 == 'markup' then 119 next "#{$1.strip}\n" unless $5 120 comment.format = $5.downcase 121 next "#{$1.strip}\n" 122 end 123 124 handle_directive $1, $3, $5, code_object, encoding, &block 125 end 126 127 comment = text unless comment 128 129 self.class.post_processors.each do |handler| 130 handler.call comment, code_object 131 end 132 133 text 134 end 135 136 ## 137 # Performs the actions described by +directive+ and its parameter +param+. 138 # 139 # +code_object+ is used for directives that operate on a class or module. 140 # +prefix+ is used to ensure the replacement for handled directives is 141 # correct. +encoding+ is used for the <tt>include</tt> directive. 142 # 143 # For a list of directives in RDoc see RDoc::Markup. 144 #-- 145 # When 1.8.7 support is ditched prefix can be defaulted to '' 146 147 def handle_directive prefix, directive, param, code_object = nil, 148 encoding = nil 149 blankline = "#{prefix.strip}\n" 150 directive = directive.downcase 151 152 case directive 153 when 'arg', 'args' then 154 return blankline unless code_object 155 156 code_object.params = param 157 158 blankline 159 when 'category' then 160 if RDoc::Context === code_object then 161 section = code_object.add_section param 162 code_object.temporary_section = section 163 end 164 165 blankline # ignore category if we're not on an RDoc::Context 166 when 'doc' then 167 return blankline unless code_object 168 code_object.document_self = true 169 code_object.force_documentation = true 170 171 blankline 172 when 'enddoc' then 173 return blankline unless code_object 174 code_object.done_documenting = true 175 176 blankline 177 when 'include' then 178 filename = param.split.first 179 include_file filename, prefix, encoding 180 when 'main' then 181 @options.main_page = param if @options.respond_to? :main_page 182 183 blankline 184 when 'nodoc' then 185 return blankline unless code_object 186 code_object.document_self = nil # notify nodoc 187 code_object.document_children = param !~ /all/i 188 189 blankline 190 when 'notnew', 'not_new', 'not-new' then 191 return blankline unless RDoc::AnyMethod === code_object 192 193 code_object.dont_rename_initialize = true 194 195 blankline 196 when 'startdoc' then 197 return blankline unless code_object 198 199 code_object.start_doc 200 code_object.force_documentation = true 201 202 blankline 203 when 'stopdoc' then 204 return blankline unless code_object 205 206 code_object.stop_doc 207 208 blankline 209 when 'title' then 210 @options.default_title = param if @options.respond_to? :default_title= 211 212 blankline 213 when 'yield', 'yields' then 214 return blankline unless code_object 215 # remove parameter &block 216 code_object.params.sub!(/,?\s*&\w+/, '') if code_object.params 217 218 code_object.block_params = param 219 220 blankline 221 else 222 result = yield directive, param if block_given? 223 224 case result 225 when nil then 226 code_object.metadata[directive] = param if code_object 227 228 if RDoc::Markup::PreProcess.registered.include? directive then 229 handler = RDoc::Markup::PreProcess.registered[directive] 230 result = handler.call directive, param if handler 231 else 232 result = "#{prefix}:#{directive}: #{param}\n" 233 end 234 when false then 235 result = "#{prefix}:#{directive}: #{param}\n" 236 end 237 238 result 239 end 240 end 241 242 ## 243 # Handles the <tt>:include: _filename_</tt> directive. 244 # 245 # If the first line of the included file starts with '#', and contains 246 # an encoding information in the form 'coding:' or 'coding=', it is 247 # removed. 248 # 249 # If all lines in the included file start with a '#', this leading '#' 250 # is removed before inclusion. The included content is indented like 251 # the <tt>:include:</tt> directive. 252 #-- 253 # so all content will be verbatim because of the likely space after '#'? 254 # TODO shift left the whole file content in that case 255 # TODO comment stop/start #-- and #++ in included file must be processed here 256 257 def include_file name, indent, encoding 258 full_name = find_include_file name 259 260 unless full_name then 261 warn "Couldn't find file to include '#{name}' from #{@input_file_name}" 262 return '' 263 end 264 265 content = RDoc::Encoding.read_file full_name, encoding, true 266 267 # strip magic comment 268 content = content.sub(/\A# .*coding[=:].*$/, '').lstrip 269 270 # strip leading '#'s, but only if all lines start with them 271 if content =~ /^[^#]/ then 272 content.gsub(/^/, indent) 273 else 274 content.gsub(/^#?/, indent) 275 end 276 end 277 278 ## 279 # Look for the given file in the directory containing the current file, 280 # and then in each of the directories specified in the RDOC_INCLUDE path 281 282 def find_include_file(name) 283 to_search = [File.dirname(@input_file_name)].concat @include_path 284 to_search.each do |dir| 285 full_name = File.join(dir, name) 286 stat = File.stat(full_name) rescue next 287 return full_name if stat.readable? 288 end 289 nil 290 end 291 292end 293 294