rdoc 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rdoc might be problematic. Click here for more details.
- data/History.txt +13 -0
- data/Manifest.txt +61 -0
- data/README.txt +34 -0
- data/Rakefile +10 -0
- data/bin/rdoc +22 -0
- data/bin/ri +6 -0
- data/lib/rdoc.rb +277 -0
- data/lib/rdoc/code_objects.rb +776 -0
- data/lib/rdoc/diagram.rb +338 -0
- data/lib/rdoc/dot.rb +249 -0
- data/lib/rdoc/generator.rb +1048 -0
- data/lib/rdoc/generator/chm.rb +113 -0
- data/lib/rdoc/generator/chm/chm.rb +98 -0
- data/lib/rdoc/generator/html.rb +370 -0
- data/lib/rdoc/generator/html/hefss.rb +414 -0
- data/lib/rdoc/generator/html/html.rb +704 -0
- data/lib/rdoc/generator/html/kilmer.rb +418 -0
- data/lib/rdoc/generator/html/one_page_html.rb +121 -0
- data/lib/rdoc/generator/ri.rb +229 -0
- data/lib/rdoc/generator/xml.rb +120 -0
- data/lib/rdoc/generator/xml/rdf.rb +113 -0
- data/lib/rdoc/generator/xml/xml.rb +111 -0
- data/lib/rdoc/markup.rb +473 -0
- data/lib/rdoc/markup/attribute_manager.rb +274 -0
- data/lib/rdoc/markup/formatter.rb +14 -0
- data/lib/rdoc/markup/fragments.rb +337 -0
- data/lib/rdoc/markup/inline.rb +101 -0
- data/lib/rdoc/markup/lines.rb +152 -0
- data/lib/rdoc/markup/preprocess.rb +71 -0
- data/lib/rdoc/markup/to_flow.rb +185 -0
- data/lib/rdoc/markup/to_html.rb +353 -0
- data/lib/rdoc/markup/to_html_crossref.rb +86 -0
- data/lib/rdoc/markup/to_latex.rb +328 -0
- data/lib/rdoc/markup/to_test.rb +50 -0
- data/lib/rdoc/options.rb +616 -0
- data/lib/rdoc/parsers/parse_c.rb +775 -0
- data/lib/rdoc/parsers/parse_f95.rb +1841 -0
- data/lib/rdoc/parsers/parse_rb.rb +2584 -0
- data/lib/rdoc/parsers/parse_simple.rb +40 -0
- data/lib/rdoc/parsers/parserfactory.rb +99 -0
- data/lib/rdoc/rdoc.rb +277 -0
- data/lib/rdoc/ri.rb +4 -0
- data/lib/rdoc/ri/cache.rb +188 -0
- data/lib/rdoc/ri/descriptions.rb +150 -0
- data/lib/rdoc/ri/display.rb +274 -0
- data/lib/rdoc/ri/driver.rb +452 -0
- data/lib/rdoc/ri/formatter.rb +616 -0
- data/lib/rdoc/ri/paths.rb +102 -0
- data/lib/rdoc/ri/reader.rb +106 -0
- data/lib/rdoc/ri/util.rb +81 -0
- data/lib/rdoc/ri/writer.rb +68 -0
- data/lib/rdoc/stats.rb +25 -0
- data/lib/rdoc/template.rb +64 -0
- data/lib/rdoc/tokenstream.rb +33 -0
- data/test/test_rdoc_c_parser.rb +261 -0
- data/test/test_rdoc_markup.rb +613 -0
- data/test/test_rdoc_markup_attribute_manager.rb +224 -0
- data/test/test_rdoc_ri_attribute_formatter.rb +42 -0
- data/test/test_rdoc_ri_default_display.rb +295 -0
- data/test/test_rdoc_ri_formatter.rb +318 -0
- data/test/test_rdoc_ri_overstrike_formatter.rb +69 -0
- metadata +134 -0
@@ -0,0 +1,353 @@
|
|
1
|
+
require 'rdoc/markup/formatter'
|
2
|
+
require 'rdoc/markup/fragments'
|
3
|
+
require 'rdoc/markup/inline'
|
4
|
+
|
5
|
+
require 'cgi'
|
6
|
+
|
7
|
+
class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
|
8
|
+
|
9
|
+
LIST_TYPE_TO_HTML = {
|
10
|
+
:BULLET => %w[<ul> </ul>],
|
11
|
+
:NUMBER => %w[<ol> </ol>],
|
12
|
+
:UPPERALPHA => %w[<ol> </ol>],
|
13
|
+
:LOWERALPHA => %w[<ol> </ol>],
|
14
|
+
:LABELED => %w[<dl> </dl>],
|
15
|
+
:NOTE => %w[<table> </table>],
|
16
|
+
}
|
17
|
+
|
18
|
+
InlineTag = Struct.new(:bit, :on, :off)
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
super
|
22
|
+
|
23
|
+
# external hyperlinks
|
24
|
+
@markup.add_special(/((link:|https?:|mailto:|ftp:|www\.)\S+\w)/, :HYPERLINK)
|
25
|
+
|
26
|
+
# and links of the form <text>[<url>]
|
27
|
+
@markup.add_special(/(((\{.*?\})|\b\S+?)\[\S+?\.\S+?\])/, :TIDYLINK)
|
28
|
+
|
29
|
+
init_tags
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Generate a hyperlink for url, labeled with text. Handle the
|
34
|
+
# special cases for img: and link: described under handle_special_HYPEDLINK
|
35
|
+
|
36
|
+
def gen_url(url, text)
|
37
|
+
if url =~ /([A-Za-z]+):(.*)/ then
|
38
|
+
type = $1
|
39
|
+
path = $2
|
40
|
+
else
|
41
|
+
type = "http"
|
42
|
+
path = url
|
43
|
+
url = "http://#{url}"
|
44
|
+
end
|
45
|
+
|
46
|
+
if type == "link" then
|
47
|
+
url = if path[0, 1] == '#' then # is this meaningful?
|
48
|
+
path
|
49
|
+
else
|
50
|
+
HTML.gen_url @from_path, path
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
if (type == "http" or type == "link") and
|
55
|
+
url =~ /\.(gif|png|jpg|jpeg|bmp)$/ then
|
56
|
+
"<img src=\"#{url}\" />"
|
57
|
+
else
|
58
|
+
"<a href=\"#{url}\">#{text.sub(%r{^#{type}:/*}, '')}</a>"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# And we're invoked with a potential external hyperlink mailto:
|
64
|
+
# just gets inserted. http: links are checked to see if they
|
65
|
+
# reference an image. If so, that image gets inserted using an
|
66
|
+
# <img> tag. Otherwise a conventional <a href> is used. We also
|
67
|
+
# support a special type of hyperlink, link:, which is a reference
|
68
|
+
# to a local file whose path is relative to the --op directory.
|
69
|
+
|
70
|
+
def handle_special_HYPERLINK(special)
|
71
|
+
url = special.text
|
72
|
+
gen_url url, url
|
73
|
+
end
|
74
|
+
|
75
|
+
##
|
76
|
+
# Here's a hypedlink where the label is different to the URL
|
77
|
+
# <label>[url] or {long label}[url]
|
78
|
+
|
79
|
+
def handle_special_TIDYLINK(special)
|
80
|
+
text = special.text
|
81
|
+
|
82
|
+
return text unless text =~ /\{(.*?)\}\[(.*?)\]/ or text =~ /(\S+)\[(.*?)\]/
|
83
|
+
|
84
|
+
label = $1
|
85
|
+
url = $2
|
86
|
+
gen_url url, label
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Set up the standard mapping of attributes to HTML tags
|
91
|
+
|
92
|
+
def init_tags
|
93
|
+
@attr_tags = [
|
94
|
+
InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:BOLD), "<b>", "</b>"),
|
95
|
+
InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:TT), "<tt>", "</tt>"),
|
96
|
+
InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:EM), "<em>", "</em>"),
|
97
|
+
]
|
98
|
+
end
|
99
|
+
|
100
|
+
##
|
101
|
+
# Add a new set of HTML tags for an attribute. We allow separate start and
|
102
|
+
# end tags for flexibility.
|
103
|
+
|
104
|
+
def add_tag(name, start, stop)
|
105
|
+
@attr_tags << InlineTag.new(RDoc::Markup::Attribute.bitmap_for(name), start, stop)
|
106
|
+
end
|
107
|
+
|
108
|
+
##
|
109
|
+
# Given an HTML tag, decorate it with class information and the like if
|
110
|
+
# required. This is a no-op in the base class, but is overridden in HTML
|
111
|
+
# output classes that implement style sheets.
|
112
|
+
|
113
|
+
def annotate(tag)
|
114
|
+
tag
|
115
|
+
end
|
116
|
+
|
117
|
+
##
|
118
|
+
# Here's the client side of the visitor pattern
|
119
|
+
|
120
|
+
def start_accepting
|
121
|
+
@res = ""
|
122
|
+
@in_list_entry = []
|
123
|
+
end
|
124
|
+
|
125
|
+
def end_accepting
|
126
|
+
@res
|
127
|
+
end
|
128
|
+
|
129
|
+
def accept_paragraph(am, fragment)
|
130
|
+
@res << annotate("<p>") + "\n"
|
131
|
+
@res << wrap(convert_flow(am.flow(fragment.txt)))
|
132
|
+
@res << annotate("</p>") + "\n"
|
133
|
+
end
|
134
|
+
|
135
|
+
def accept_verbatim(am, fragment)
|
136
|
+
@res << annotate("<pre>") + "\n"
|
137
|
+
@res << CGI.escapeHTML(fragment.txt)
|
138
|
+
@res << annotate("</pre>") << "\n"
|
139
|
+
end
|
140
|
+
|
141
|
+
def accept_rule(am, fragment)
|
142
|
+
size = fragment.param
|
143
|
+
size = 10 if size > 10
|
144
|
+
@res << "<hr size=\"#{size}\"></hr>"
|
145
|
+
end
|
146
|
+
|
147
|
+
def accept_list_start(am, fragment)
|
148
|
+
@res << html_list_name(fragment.type, true) << "\n"
|
149
|
+
@in_list_entry.push false
|
150
|
+
end
|
151
|
+
|
152
|
+
def accept_list_end(am, fragment)
|
153
|
+
if tag = @in_list_entry.pop
|
154
|
+
@res << annotate(tag) << "\n"
|
155
|
+
end
|
156
|
+
@res << html_list_name(fragment.type, false) << "\n"
|
157
|
+
end
|
158
|
+
|
159
|
+
def accept_list_item(am, fragment)
|
160
|
+
if tag = @in_list_entry.last
|
161
|
+
@res << annotate(tag) << "\n"
|
162
|
+
end
|
163
|
+
|
164
|
+
@res << list_item_start(am, fragment)
|
165
|
+
|
166
|
+
@res << wrap(convert_flow(am.flow(fragment.txt))) << "\n"
|
167
|
+
|
168
|
+
@in_list_entry[-1] = list_end_for(fragment.type)
|
169
|
+
end
|
170
|
+
|
171
|
+
def accept_blank_line(am, fragment)
|
172
|
+
# @res << annotate("<p />") << "\n"
|
173
|
+
end
|
174
|
+
|
175
|
+
def accept_heading(am, fragment)
|
176
|
+
@res << convert_heading(fragment.head_level, am.flow(fragment.txt))
|
177
|
+
end
|
178
|
+
|
179
|
+
##
|
180
|
+
# This is a higher speed (if messier) version of wrap
|
181
|
+
|
182
|
+
def wrap(txt, line_len = 76)
|
183
|
+
res = ""
|
184
|
+
sp = 0
|
185
|
+
ep = txt.length
|
186
|
+
while sp < ep
|
187
|
+
# scan back for a space
|
188
|
+
p = sp + line_len - 1
|
189
|
+
if p >= ep
|
190
|
+
p = ep
|
191
|
+
else
|
192
|
+
while p > sp and txt[p] != ?\s
|
193
|
+
p -= 1
|
194
|
+
end
|
195
|
+
if p <= sp
|
196
|
+
p = sp + line_len
|
197
|
+
while p < ep and txt[p] != ?\s
|
198
|
+
p += 1
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
res << txt[sp...p] << "\n"
|
203
|
+
sp = p
|
204
|
+
sp += 1 while sp < ep and txt[sp] == ?\s
|
205
|
+
end
|
206
|
+
res
|
207
|
+
end
|
208
|
+
|
209
|
+
private
|
210
|
+
|
211
|
+
def on_tags(res, item)
|
212
|
+
attr_mask = item.turn_on
|
213
|
+
return if attr_mask.zero?
|
214
|
+
|
215
|
+
@attr_tags.each do |tag|
|
216
|
+
if attr_mask & tag.bit != 0
|
217
|
+
res << annotate(tag.on)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def off_tags(res, item)
|
223
|
+
attr_mask = item.turn_off
|
224
|
+
return if attr_mask.zero?
|
225
|
+
|
226
|
+
@attr_tags.reverse_each do |tag|
|
227
|
+
if attr_mask & tag.bit != 0
|
228
|
+
res << annotate(tag.off)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def convert_flow(flow)
|
234
|
+
res = ""
|
235
|
+
|
236
|
+
flow.each do |item|
|
237
|
+
case item
|
238
|
+
when String
|
239
|
+
res << convert_string(item)
|
240
|
+
when RDoc::Markup::AttrChanger
|
241
|
+
off_tags(res, item)
|
242
|
+
on_tags(res, item)
|
243
|
+
when RDoc::Markup::Special
|
244
|
+
res << convert_special(item)
|
245
|
+
else
|
246
|
+
raise "Unknown flow element: #{item.inspect}"
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
res
|
251
|
+
end
|
252
|
+
|
253
|
+
##
|
254
|
+
# some of these patterns are taken from SmartyPants...
|
255
|
+
|
256
|
+
def convert_string(item)
|
257
|
+
CGI.escapeHTML(item).
|
258
|
+
|
259
|
+
# convert -- to em-dash, (-- to en-dash)
|
260
|
+
gsub(/---?/, '—'). #gsub(/--/, '–').
|
261
|
+
|
262
|
+
# convert ... to elipsis (and make sure .... becomes .<elipsis>)
|
263
|
+
gsub(/\.\.\.\./, '.…').gsub(/\.\.\./, '…').
|
264
|
+
|
265
|
+
# convert single closing quote
|
266
|
+
gsub(%r{([^ \t\r\n\[\{\(])\'}, '\1’').
|
267
|
+
gsub(%r{\'(?=\W|s\b)}, '’').
|
268
|
+
|
269
|
+
# convert single opening quote
|
270
|
+
gsub(/'/, '‘').
|
271
|
+
|
272
|
+
# convert double closing quote
|
273
|
+
gsub(%r{([^ \t\r\n\[\{\(])\'(?=\W)}, '\1”').
|
274
|
+
|
275
|
+
# convert double opening quote
|
276
|
+
gsub(/'/, '“').
|
277
|
+
|
278
|
+
# convert copyright
|
279
|
+
gsub(/\(c\)/, '©').
|
280
|
+
|
281
|
+
# convert and registered trademark
|
282
|
+
gsub(/\(r\)/, '®')
|
283
|
+
|
284
|
+
end
|
285
|
+
|
286
|
+
def convert_special(special)
|
287
|
+
handled = false
|
288
|
+
RDoc::Markup::Attribute.each_name_of(special.type) do |name|
|
289
|
+
method_name = "handle_special_#{name}"
|
290
|
+
if self.respond_to? method_name
|
291
|
+
special.text = send(method_name, special)
|
292
|
+
handled = true
|
293
|
+
end
|
294
|
+
end
|
295
|
+
raise "Unhandled special: #{special}" unless handled
|
296
|
+
special.text
|
297
|
+
end
|
298
|
+
|
299
|
+
def convert_heading(level, flow)
|
300
|
+
res =
|
301
|
+
annotate("<h#{level}>") +
|
302
|
+
convert_flow(flow) +
|
303
|
+
annotate("</h#{level}>\n")
|
304
|
+
end
|
305
|
+
|
306
|
+
def html_list_name(list_type, is_open_tag)
|
307
|
+
tags = LIST_TYPE_TO_HTML[list_type] || raise("Invalid list type: #{list_type.inspect}")
|
308
|
+
annotate(tags[ is_open_tag ? 0 : 1])
|
309
|
+
end
|
310
|
+
|
311
|
+
def list_item_start(am, fragment)
|
312
|
+
case fragment.type
|
313
|
+
when :BULLET, :NUMBER then
|
314
|
+
annotate("<li>")
|
315
|
+
|
316
|
+
when :UPPERALPHA then
|
317
|
+
annotate("<li type=\"A\">")
|
318
|
+
|
319
|
+
when :LOWERALPHA then
|
320
|
+
annotate("<li type=\"a\">")
|
321
|
+
|
322
|
+
when :LABELED then
|
323
|
+
annotate("<dt>") +
|
324
|
+
convert_flow(am.flow(fragment.param)) +
|
325
|
+
annotate("</dt>") +
|
326
|
+
annotate("<dd>")
|
327
|
+
|
328
|
+
when :NOTE then
|
329
|
+
annotate("<tr>") +
|
330
|
+
annotate("<td valign=\"top\">") +
|
331
|
+
convert_flow(am.flow(fragment.param)) +
|
332
|
+
annotate("</td>") +
|
333
|
+
annotate("<td>")
|
334
|
+
else
|
335
|
+
raise "Invalid list type"
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
def list_end_for(fragment_type)
|
340
|
+
case fragment_type
|
341
|
+
when :BULLET, :NUMBER, :UPPERALPHA, :LOWERALPHA then
|
342
|
+
"</li>"
|
343
|
+
when :LABELED then
|
344
|
+
"</dd>"
|
345
|
+
when :NOTE then
|
346
|
+
"</td></tr>"
|
347
|
+
else
|
348
|
+
raise "Invalid list type"
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
end
|
353
|
+
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'rdoc/markup/to_html'
|
2
|
+
|
3
|
+
##
|
4
|
+
# Subclass of the RDoc::Markup::ToHtml class that supports looking up words in
|
5
|
+
# the AllReferences list. Those that are found (like AllReferences in this
|
6
|
+
# comment) will be hyperlinked
|
7
|
+
|
8
|
+
class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml
|
9
|
+
|
10
|
+
attr_accessor :context
|
11
|
+
|
12
|
+
##
|
13
|
+
# We need to record the html path of our caller so we can generate
|
14
|
+
# correct relative paths for any hyperlinks that we find
|
15
|
+
|
16
|
+
def initialize(from_path, context, show_hash)
|
17
|
+
super()
|
18
|
+
|
19
|
+
# class names, variable names, or instance variables
|
20
|
+
@markup.add_special(/(
|
21
|
+
# A::B.meth(**) (for operator in Fortran95)
|
22
|
+
\w+(::\w+)*[.\#]\w+(\([\.\w+\*\/\+\-\=\<\>]+\))?
|
23
|
+
# meth(**) (for operator in Fortran95)
|
24
|
+
| \#\w+(\([.\w\*\/\+\-\=\<\>]+\))?
|
25
|
+
| \b([A-Z]\w*(::\w+)*[.\#]\w+) # A::B.meth
|
26
|
+
| \b([A-Z]\w+(::\w+)*) # A::B
|
27
|
+
| \#\w+[!?=]? # #meth_name
|
28
|
+
| \\?\b\w+([_\/\.]+\w+)*[!?=]? # meth_name
|
29
|
+
)/x,
|
30
|
+
:CROSSREF)
|
31
|
+
|
32
|
+
@from_path = from_path
|
33
|
+
@context = context
|
34
|
+
@show_hash = show_hash
|
35
|
+
|
36
|
+
@seen = {}
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# We're invoked when any text matches the CROSSREF pattern
|
41
|
+
# (defined in MarkUp). If we fine the corresponding reference,
|
42
|
+
# generate a hyperlink. If the name we're looking for contains
|
43
|
+
# no punctuation, we look for it up the module/class chain. For
|
44
|
+
# example, HyperlinkHtml is found, even without the Generator::
|
45
|
+
# prefix, because we look for it in module Generator first.
|
46
|
+
|
47
|
+
def handle_special_CROSSREF(special)
|
48
|
+
name = special.text
|
49
|
+
|
50
|
+
return @seen[name] if @seen.include? name
|
51
|
+
|
52
|
+
if name[0,1] == '#' then
|
53
|
+
lookup = name[1..-1]
|
54
|
+
name = lookup unless @show_hash
|
55
|
+
else
|
56
|
+
lookup = name
|
57
|
+
end
|
58
|
+
|
59
|
+
# Find class, module, or method in class or module.
|
60
|
+
if /([A-Z]\w*)[.\#](\w+[!?=]?)/ =~ lookup then
|
61
|
+
container = $1
|
62
|
+
method = $2
|
63
|
+
ref = @context.find_symbol container, method
|
64
|
+
elsif /([A-Za-z]\w*)[.\#](\w+(\([\.\w+\*\/\+\-\=\<\>]+\))?)/ =~ lookup then
|
65
|
+
container = $1
|
66
|
+
method = $2
|
67
|
+
ref = @context.find_symbol container, method
|
68
|
+
else
|
69
|
+
ref = @context.find_symbol lookup
|
70
|
+
end
|
71
|
+
|
72
|
+
out = if lookup =~ /^\\/ then
|
73
|
+
$'
|
74
|
+
elsif ref and ref.document_self then
|
75
|
+
"<a href=\"#{ref.as_href(@from_path)}\">#{name}</a>"
|
76
|
+
else
|
77
|
+
name
|
78
|
+
end
|
79
|
+
|
80
|
+
@seen[name] = out
|
81
|
+
|
82
|
+
out
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
@@ -0,0 +1,328 @@
|
|
1
|
+
require 'rdoc/markup/formatter'
|
2
|
+
require 'rdoc/markup/fragments'
|
3
|
+
require 'rdoc/markup/inline'
|
4
|
+
|
5
|
+
require 'cgi'
|
6
|
+
|
7
|
+
##
|
8
|
+
# Convert SimpleMarkup to basic LaTeX report format.
|
9
|
+
|
10
|
+
class RDoc::Markup::ToLaTeX < RDoc::Markup::Formatter
|
11
|
+
|
12
|
+
BS = "\020" # \
|
13
|
+
OB = "\021" # {
|
14
|
+
CB = "\022" # }
|
15
|
+
DL = "\023" # Dollar
|
16
|
+
|
17
|
+
BACKSLASH = "#{BS}symbol#{OB}92#{CB}"
|
18
|
+
HAT = "#{BS}symbol#{OB}94#{CB}"
|
19
|
+
BACKQUOTE = "#{BS}symbol#{OB}0#{CB}"
|
20
|
+
TILDE = "#{DL}#{BS}sim#{DL}"
|
21
|
+
LESSTHAN = "#{DL}<#{DL}"
|
22
|
+
GREATERTHAN = "#{DL}>#{DL}"
|
23
|
+
|
24
|
+
def self.l(str)
|
25
|
+
str.tr('\\', BS).tr('{', OB).tr('}', CB).tr('$', DL)
|
26
|
+
end
|
27
|
+
|
28
|
+
def l(arg)
|
29
|
+
RDoc::Markup::ToLaTeX.l(arg)
|
30
|
+
end
|
31
|
+
|
32
|
+
LIST_TYPE_TO_LATEX = {
|
33
|
+
:BULLET => [ l("\\begin{itemize}"), l("\\end{itemize}") ],
|
34
|
+
:NUMBER => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\arabic" ],
|
35
|
+
:UPPERALPHA => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\Alph" ],
|
36
|
+
:LOWERALPHA => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\alph" ],
|
37
|
+
:LABELED => [ l("\\begin{description}"), l("\\end{description}") ],
|
38
|
+
:NOTE => [
|
39
|
+
l("\\begin{tabularx}{\\linewidth}{@{} l X @{}}"),
|
40
|
+
l("\\end{tabularx}") ],
|
41
|
+
}
|
42
|
+
|
43
|
+
InlineTag = Struct.new(:bit, :on, :off)
|
44
|
+
|
45
|
+
def initialize
|
46
|
+
init_tags
|
47
|
+
@list_depth = 0
|
48
|
+
@prev_list_types = []
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Set up the standard mapping of attributes to LaTeX
|
53
|
+
|
54
|
+
def init_tags
|
55
|
+
@attr_tags = [
|
56
|
+
InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:BOLD), l("\\textbf{"), l("}")),
|
57
|
+
InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:TT), l("\\texttt{"), l("}")),
|
58
|
+
InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:EM), l("\\emph{"), l("}")),
|
59
|
+
]
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# Escape a LaTeX string
|
64
|
+
|
65
|
+
def escape(str)
|
66
|
+
$stderr.print "FE: ", str if $DEBUG_RDOC
|
67
|
+
s = str.
|
68
|
+
sub(/\s+$/, '').
|
69
|
+
gsub(/([_\${}&%#])/, "#{BS}\\1").
|
70
|
+
gsub(/\\/, BACKSLASH).
|
71
|
+
gsub(/\^/, HAT).
|
72
|
+
gsub(/~/, TILDE).
|
73
|
+
gsub(/</, LESSTHAN).
|
74
|
+
gsub(/>/, GREATERTHAN).
|
75
|
+
gsub(/,,/, ",{},").
|
76
|
+
gsub(/\`/, BACKQUOTE)
|
77
|
+
$stderr.print "-> ", s, "\n" if $DEBUG_RDOC
|
78
|
+
s
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# Add a new set of LaTeX tags for an attribute. We allow
|
83
|
+
# separate start and end tags for flexibility
|
84
|
+
|
85
|
+
def add_tag(name, start, stop)
|
86
|
+
@attr_tags << InlineTag.new(RDoc::Markup::Attribute.bitmap_for(name), start, stop)
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Here's the client side of the visitor pattern
|
91
|
+
|
92
|
+
def start_accepting
|
93
|
+
@res = ""
|
94
|
+
@in_list_entry = []
|
95
|
+
end
|
96
|
+
|
97
|
+
def end_accepting
|
98
|
+
@res.tr(BS, '\\').tr(OB, '{').tr(CB, '}').tr(DL, '$')
|
99
|
+
end
|
100
|
+
|
101
|
+
def accept_paragraph(am, fragment)
|
102
|
+
@res << wrap(convert_flow(am.flow(fragment.txt)))
|
103
|
+
@res << "\n"
|
104
|
+
end
|
105
|
+
|
106
|
+
def accept_verbatim(am, fragment)
|
107
|
+
@res << "\n\\begin{code}\n"
|
108
|
+
@res << fragment.txt.sub(/[\n\s]+\Z/, '')
|
109
|
+
@res << "\n\\end{code}\n\n"
|
110
|
+
end
|
111
|
+
|
112
|
+
def accept_rule(am, fragment)
|
113
|
+
size = fragment.param
|
114
|
+
size = 10 if size > 10
|
115
|
+
@res << "\n\n\\rule{\\linewidth}{#{size}pt}\n\n"
|
116
|
+
end
|
117
|
+
|
118
|
+
def accept_list_start(am, fragment)
|
119
|
+
@res << list_name(fragment.type, true) << "\n"
|
120
|
+
@in_list_entry.push false
|
121
|
+
end
|
122
|
+
|
123
|
+
def accept_list_end(am, fragment)
|
124
|
+
if tag = @in_list_entry.pop
|
125
|
+
@res << tag << "\n"
|
126
|
+
end
|
127
|
+
@res << list_name(fragment.type, false) << "\n"
|
128
|
+
end
|
129
|
+
|
130
|
+
def accept_list_item(am, fragment)
|
131
|
+
if tag = @in_list_entry.last
|
132
|
+
@res << tag << "\n"
|
133
|
+
end
|
134
|
+
@res << list_item_start(am, fragment)
|
135
|
+
@res << wrap(convert_flow(am.flow(fragment.txt))) << "\n"
|
136
|
+
@in_list_entry[-1] = list_end_for(fragment.type)
|
137
|
+
end
|
138
|
+
|
139
|
+
def accept_blank_line(am, fragment)
|
140
|
+
# @res << "\n"
|
141
|
+
end
|
142
|
+
|
143
|
+
def accept_heading(am, fragment)
|
144
|
+
@res << convert_heading(fragment.head_level, am.flow(fragment.txt))
|
145
|
+
end
|
146
|
+
|
147
|
+
##
|
148
|
+
# This is a higher speed (if messier) version of wrap
|
149
|
+
|
150
|
+
def wrap(txt, line_len = 76)
|
151
|
+
res = ""
|
152
|
+
sp = 0
|
153
|
+
ep = txt.length
|
154
|
+
while sp < ep
|
155
|
+
# scan back for a space
|
156
|
+
p = sp + line_len - 1
|
157
|
+
if p >= ep
|
158
|
+
p = ep
|
159
|
+
else
|
160
|
+
while p > sp and txt[p] != ?\s
|
161
|
+
p -= 1
|
162
|
+
end
|
163
|
+
if p <= sp
|
164
|
+
p = sp + line_len
|
165
|
+
while p < ep and txt[p] != ?\s
|
166
|
+
p += 1
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
res << txt[sp...p] << "\n"
|
171
|
+
sp = p
|
172
|
+
sp += 1 while sp < ep and txt[sp] == ?\s
|
173
|
+
end
|
174
|
+
res
|
175
|
+
end
|
176
|
+
|
177
|
+
private
|
178
|
+
|
179
|
+
def on_tags(res, item)
|
180
|
+
attr_mask = item.turn_on
|
181
|
+
return if attr_mask.zero?
|
182
|
+
|
183
|
+
@attr_tags.each do |tag|
|
184
|
+
if attr_mask & tag.bit != 0
|
185
|
+
res << tag.on
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def off_tags(res, item)
|
191
|
+
attr_mask = item.turn_off
|
192
|
+
return if attr_mask.zero?
|
193
|
+
|
194
|
+
@attr_tags.reverse_each do |tag|
|
195
|
+
if attr_mask & tag.bit != 0
|
196
|
+
res << tag.off
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def convert_flow(flow)
|
202
|
+
res = ""
|
203
|
+
flow.each do |item|
|
204
|
+
case item
|
205
|
+
when String
|
206
|
+
$stderr.puts "Converting '#{item}'" if $DEBUG_RDOC
|
207
|
+
res << convert_string(item)
|
208
|
+
when AttrChanger
|
209
|
+
off_tags(res, item)
|
210
|
+
on_tags(res, item)
|
211
|
+
when Special
|
212
|
+
res << convert_special(item)
|
213
|
+
else
|
214
|
+
raise "Unknown flow element: #{item.inspect}"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
res
|
218
|
+
end
|
219
|
+
|
220
|
+
##
|
221
|
+
# some of these patterns are taken from SmartyPants...
|
222
|
+
|
223
|
+
def convert_string(item)
|
224
|
+
escape(item).
|
225
|
+
|
226
|
+
# convert ... to elipsis (and make sure .... becomes .<elipsis>)
|
227
|
+
gsub(/\.\.\.\./, '.\ldots{}').gsub(/\.\.\./, '\ldots{}').
|
228
|
+
|
229
|
+
# convert single closing quote
|
230
|
+
gsub(%r{([^ \t\r\n\[\{\(])\'}, '\1\'').
|
231
|
+
gsub(%r{\'(?=\W|s\b)}, "'" ).
|
232
|
+
|
233
|
+
# convert single opening quote
|
234
|
+
gsub(/'/, '`').
|
235
|
+
|
236
|
+
# convert double closing quote
|
237
|
+
gsub(%r{([^ \t\r\n\[\{\(])\"(?=\W)}, "\\1''").
|
238
|
+
|
239
|
+
# convert double opening quote
|
240
|
+
gsub(/"/, "``").
|
241
|
+
|
242
|
+
# convert copyright
|
243
|
+
gsub(/\(c\)/, '\copyright{}')
|
244
|
+
|
245
|
+
end
|
246
|
+
|
247
|
+
def convert_special(special)
|
248
|
+
handled = false
|
249
|
+
Attribute.each_name_of(special.type) do |name|
|
250
|
+
method_name = "handle_special_#{name}"
|
251
|
+
if self.respond_to? method_name
|
252
|
+
special.text = send(method_name, special)
|
253
|
+
handled = true
|
254
|
+
end
|
255
|
+
end
|
256
|
+
raise "Unhandled special: #{special}" unless handled
|
257
|
+
special.text
|
258
|
+
end
|
259
|
+
|
260
|
+
def convert_heading(level, flow)
|
261
|
+
res =
|
262
|
+
case level
|
263
|
+
when 1 then "\\chapter{"
|
264
|
+
when 2 then "\\section{"
|
265
|
+
when 3 then "\\subsection{"
|
266
|
+
when 4 then "\\subsubsection{"
|
267
|
+
else "\\paragraph{"
|
268
|
+
end +
|
269
|
+
convert_flow(flow) +
|
270
|
+
"}\n"
|
271
|
+
end
|
272
|
+
|
273
|
+
def list_name(list_type, is_open_tag)
|
274
|
+
tags = LIST_TYPE_TO_LATEX[list_type] || raise("Invalid list type: #{list_type.inspect}")
|
275
|
+
if tags[2] # enumerate
|
276
|
+
if is_open_tag
|
277
|
+
@list_depth += 1
|
278
|
+
if @prev_list_types[@list_depth] != tags[2]
|
279
|
+
case @list_depth
|
280
|
+
when 1
|
281
|
+
roman = "i"
|
282
|
+
when 2
|
283
|
+
roman = "ii"
|
284
|
+
when 3
|
285
|
+
roman = "iii"
|
286
|
+
when 4
|
287
|
+
roman = "iv"
|
288
|
+
else
|
289
|
+
raise("Too deep list: level #{@list_depth}")
|
290
|
+
end
|
291
|
+
@prev_list_types[@list_depth] = tags[2]
|
292
|
+
return l("\\renewcommand{\\labelenum#{roman}}{#{tags[2]}{enum#{roman}}}") + "\n" + tags[0]
|
293
|
+
end
|
294
|
+
else
|
295
|
+
@list_depth -= 1
|
296
|
+
end
|
297
|
+
end
|
298
|
+
tags[ is_open_tag ? 0 : 1]
|
299
|
+
end
|
300
|
+
|
301
|
+
def list_item_start(am, fragment)
|
302
|
+
case fragment.type
|
303
|
+
when :BULLET, :NUMBER, :UPPERALPHA, :LOWERALPHA then
|
304
|
+
"\\item "
|
305
|
+
|
306
|
+
when :LABELED then
|
307
|
+
"\\item[" + convert_flow(am.flow(fragment.param)) + "] "
|
308
|
+
|
309
|
+
when :NOTE then
|
310
|
+
convert_flow(am.flow(fragment.param)) + " & "
|
311
|
+
else
|
312
|
+
raise "Invalid list type"
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
def list_end_for(fragment_type)
|
317
|
+
case fragment_type
|
318
|
+
when :BULLET, :NUMBER, :UPPERALPHA, :LOWERALPHA, :LABELED then
|
319
|
+
""
|
320
|
+
when :NOTE
|
321
|
+
"\\\\\n"
|
322
|
+
else
|
323
|
+
raise "Invalid list type"
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
end
|
328
|
+
|