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