PageRenderTime 47ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/rdoc/markup/pre_process.rb

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