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
|
require 'rdoc/markup'
require 'rdoc/encoding'
##
# 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
class RDoc::Markup::PreProcess
@registered = {}
##
# 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.
def self.register directive, &block
@registered[directive] = block
end
##
# Registered directives
def self.registered
@registered
end
##
# 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
end
##
# Look for directives in a chunk of +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 param is set as metadata on the
# +code_object+. See RDoc::CodeObject#metadata
def handle text, code_object = nil
# regexp helper (square brackets for optional)
# $1 $2 $3 $4 $5
# [prefix][\]:directive:[spaces][param]newline
text.gsub!(/^([ \t]*#?[ \t]*)(\\?):(\w+):([ \t]*)(.+)?\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?
prefix = $1
directive = $3.downcase
param = $5
case directive
when 'include' then
filename = param.split[0]
encoding = if defined?(Encoding) then text.encoding else nil end
include_file filename, prefix, encoding
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
text
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
|