asciidoctor 1.5.2 → 1.5.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of asciidoctor might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.adoc +107 -1
- data/LICENSE.adoc +1 -1
- data/README.adoc +155 -230
- data/Rakefile +2 -1
- data/bin/asciidoctor +5 -1
- data/data/stylesheets/asciidoctor-default.css +37 -29
- data/data/stylesheets/coderay-asciidoctor.css +3 -3
- data/features/text_formatting.feature +2 -0
- data/lib/asciidoctor.rb +46 -21
- data/lib/asciidoctor/abstract_block.rb +14 -8
- data/lib/asciidoctor/abstract_node.rb +77 -24
- data/lib/asciidoctor/attribute_list.rb +1 -1
- data/lib/asciidoctor/block.rb +2 -3
- data/lib/asciidoctor/cli/options.rb +14 -15
- data/lib/asciidoctor/converter/docbook45.rb +8 -8
- data/lib/asciidoctor/converter/docbook5.rb +25 -17
- data/lib/asciidoctor/converter/factory.rb +6 -1
- data/lib/asciidoctor/converter/html5.rb +159 -117
- data/lib/asciidoctor/converter/manpage.rb +671 -0
- data/lib/asciidoctor/converter/template.rb +24 -17
- data/lib/asciidoctor/document.rb +89 -47
- data/lib/asciidoctor/extensions.rb +22 -21
- data/lib/asciidoctor/helpers.rb +73 -16
- data/lib/asciidoctor/list.rb +26 -5
- data/lib/asciidoctor/parser.rb +179 -122
- data/lib/asciidoctor/path_resolver.rb +6 -10
- data/lib/asciidoctor/reader.rb +37 -34
- data/lib/asciidoctor/stylesheets.rb +16 -10
- data/lib/asciidoctor/substitutors.rb +98 -21
- data/lib/asciidoctor/table.rb +21 -17
- data/lib/asciidoctor/timings.rb +3 -3
- data/lib/asciidoctor/version.rb +1 -1
- data/man/asciidoctor.1 +155 -89
- data/man/asciidoctor.adoc +19 -11
- data/test/attributes_test.rb +86 -0
- data/test/blocks_test.rb +203 -15
- data/test/converter_test.rb +15 -2
- data/test/document_test.rb +290 -36
- data/test/extensions_test.rb +22 -3
- data/test/fixtures/circle.svg +8 -0
- data/test/fixtures/subs-docinfo.html +2 -0
- data/test/fixtures/subs.adoc +7 -0
- data/test/invoker_test.rb +25 -0
- data/test/links_test.rb +17 -0
- data/test/lists_test.rb +173 -0
- data/test/options_test.rb +2 -2
- data/test/paragraphs_test.rb +2 -2
- data/test/parser_test.rb +56 -13
- data/test/reader_test.rb +35 -3
- data/test/sections_test.rb +59 -0
- data/test/substitutions_test.rb +53 -14
- data/test/tables_test.rb +158 -2
- data/test/test_helper.rb +7 -2
- metadata +22 -11
- data/benchmark/benchmark.rb +0 -129
- data/benchmark/sample-data/mdbasics.adoc +0 -334
- data/lib/asciidoctor/opal_ext.rb +0 -26
- data/lib/asciidoctor/opal_ext/comparable.rb +0 -38
- data/lib/asciidoctor/opal_ext/dir.rb +0 -13
- data/lib/asciidoctor/opal_ext/error.rb +0 -2
- data/lib/asciidoctor/opal_ext/file.rb +0 -145
@@ -0,0 +1,671 @@
|
|
1
|
+
module Asciidoctor
|
2
|
+
# A built-in {Converter} implementation that generates the man page (troff) format.
|
3
|
+
#
|
4
|
+
# The output follows the groff man page definition while also trying to be
|
5
|
+
# consistent with the output produced by the a2x tool from AsciiDoc Python.
|
6
|
+
#
|
7
|
+
# See http://www.gnu.org/software/groff/manual/html_node/Man-usage.html#Man-usage
|
8
|
+
class Converter::ManPageConverter < Converter::BuiltIn
|
9
|
+
LF = "\n"
|
10
|
+
TAB = "\t"
|
11
|
+
ETAB = ' ' * 8
|
12
|
+
|
13
|
+
# Converts HTML entity references back to their original form, escapes
|
14
|
+
# special man characters and strips trailing whitespace.
|
15
|
+
#
|
16
|
+
# Optional features:
|
17
|
+
# * fold each endline into a single space
|
18
|
+
# * append a newline
|
19
|
+
def manify str, opts = {}
|
20
|
+
append_newline = opts[:append_newline]
|
21
|
+
preserve_space = opts.fetch :preserve_space, true
|
22
|
+
str = preserve_space ? str.gsub(TAB, ETAB) : str.tr_s(%(#{LF}#{TAB} ), ' ')
|
23
|
+
str = str.
|
24
|
+
gsub(/^\.$/, '\\\&.'). # a lone . is also used in troff to indicate paragraph continuation with visual separator
|
25
|
+
gsub(/\\$/, '\\(rs'). # a literal backslash at the end of a line
|
26
|
+
gsub(/^\.((?:URL|MTO) ".*?" ".*?" )( |[^\s]*)(.*?)( *)$/, ".\\1\"\\2\"#{LF}\\3"). # quote last URL argument
|
27
|
+
gsub(/(?:\A\n|\n *(\n))^\.(URL|MTO) /, "\\1\.\\2 "). # strip blank lines in source that precede a URL
|
28
|
+
gsub('-', '\\-').
|
29
|
+
gsub('<', '<').
|
30
|
+
gsub('>', '>').
|
31
|
+
gsub('©', '\\(co'). # copyright sign
|
32
|
+
gsub('®', '\\(rg'). # registered sign
|
33
|
+
gsub('™', '\\(tm'). # trademark sign
|
34
|
+
gsub(' ', ' '). # thin space
|
35
|
+
gsub('–', '\\(en'). # en-dash
|
36
|
+
gsub(/—(?:;​)?/, '\\(em'). # em-dash
|
37
|
+
gsub('‘', '\\(oq'). # left single quotation mark
|
38
|
+
gsub('’', '\\(cq'). # right single quotation mark
|
39
|
+
gsub('“', '\\(lq'). # left double quotation mark
|
40
|
+
gsub('”', '\\(rq'). # right double quotation mark
|
41
|
+
gsub(/…(?:​)?/, '...'). # horizontal ellipsis
|
42
|
+
gsub('←', '\\(<-'). # leftwards arrow
|
43
|
+
gsub('→', '\\(->'). # rightwards arrow
|
44
|
+
gsub('⇐', '\\(lA'). # leftwards double arrow
|
45
|
+
gsub('⇒', '\\(rA'). # rightwards double arrow
|
46
|
+
gsub('​', '\:'). # zero width space
|
47
|
+
gsub('\'', '\\(aq'). # apostrophe-quote
|
48
|
+
gsub(/<\/?BOUNDARY>/, '').# artificial boundary
|
49
|
+
rstrip # strip trailing space
|
50
|
+
append_newline ? %(#{str}#{LF}) : str
|
51
|
+
end
|
52
|
+
|
53
|
+
def skip_with_warning node, name = nil
|
54
|
+
warn %(asciidoctor: WARNING: converter missing for #{name || node.node_name} node in manpage backend)
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
|
58
|
+
def document node
|
59
|
+
unless node.attr? 'mantitle'
|
60
|
+
raise 'asciidoctor: ERROR: doctype must be set to manpage when using manpage backend'
|
61
|
+
end
|
62
|
+
mantitle = node.attr 'mantitle'
|
63
|
+
manvolnum = node.attr 'manvolnum', '1'
|
64
|
+
manname = node.attr 'manname', mantitle
|
65
|
+
result = [%('\\" t
|
66
|
+
.\\" Title: #{mantitle}
|
67
|
+
.\\" Author: #{(node.attr? 'authors') ? (node.attr 'authors') : '[see the "AUTHORS" section]'}
|
68
|
+
.\\" Generator: Asciidoctor #{node.attr 'asciidoctor-version'}
|
69
|
+
.\\" Date: #{docdate = node.attr 'docdate'}
|
70
|
+
.\\" Manual: #{manual = (node.attr? 'manmanual') ? (node.attr 'manmanual') : '\ \&'}
|
71
|
+
.\\" Source: #{source = (node.attr? 'mansource') ? (node.attr 'mansource') : '\ \&'}
|
72
|
+
.\\" Language: English
|
73
|
+
.\\")]
|
74
|
+
# TODO add document-level setting to disable capitalization of manname
|
75
|
+
result << %(.TH "#{manify manname.upcase}" "#{manvolnum}" "#{docdate}" "#{manify source}" "#{manify manual}")
|
76
|
+
# define portability settings
|
77
|
+
# see http://bugs.debian.org/507673
|
78
|
+
# see http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
|
79
|
+
result << '.ie \n(.g .ds Aq \(aq'
|
80
|
+
result << '.el .ds Aq \''
|
81
|
+
# set sentence_space_size to 0 to prevent extra space between sentences separated by a newline
|
82
|
+
# the alternative is to add \& at the end of the line
|
83
|
+
result << '.ss \n[.ss] 0'
|
84
|
+
# disable hyphenation
|
85
|
+
result << '.nh'
|
86
|
+
# disable justification (adjust text to left margin only)
|
87
|
+
result << '.ad l'
|
88
|
+
# define URL macro for portability
|
89
|
+
# see http://web.archive.org/web/20060102165607/http://people.debian.org/~branden/talks/wtfm/wtfm.pdf
|
90
|
+
#
|
91
|
+
# Use: .URL "http://www.debian.org" "Debian" "."
|
92
|
+
#
|
93
|
+
# * First argument: the URL
|
94
|
+
# * Second argument: text to be hyperlinked
|
95
|
+
# * Third (optional) argument: text that needs to immediately trail
|
96
|
+
# the hyperlink without intervening whitespace
|
97
|
+
result << '.de URL
|
98
|
+
\\\\$2 \\(laURL: \\\\$1 \\(ra\\\\$3
|
99
|
+
..
|
100
|
+
.if \n[.g] .mso www.tmac
|
101
|
+
.LINKSTYLE blue R < >'
|
102
|
+
|
103
|
+
unless node.noheader
|
104
|
+
if node.attr? 'manpurpose'
|
105
|
+
result << %(.SH "#{node.attr 'manname-title'}"
|
106
|
+
#{manify mantitle} \\- #{manify node.attr 'manpurpose'})
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
result << node.content
|
111
|
+
|
112
|
+
# QUESTION should NOTES come after AUTHOR(S)?
|
113
|
+
if node.footnotes? && !(node.attr? 'nofootnotes')
|
114
|
+
result << '.SH "NOTES"'
|
115
|
+
result.concat(node.footnotes.map {|fn| %(#{fn.index}. #{fn.text}) })
|
116
|
+
end
|
117
|
+
|
118
|
+
# FIXME detect single author and use appropriate heading; itemize the authors if multiple
|
119
|
+
if node.attr? 'authors'
|
120
|
+
result << %(.SH "AUTHOR(S)"
|
121
|
+
.sp
|
122
|
+
\\fB#{node.attr 'authors'}\\fP
|
123
|
+
.RS 4
|
124
|
+
Author(s).
|
125
|
+
.RE)
|
126
|
+
end
|
127
|
+
|
128
|
+
result * LF
|
129
|
+
end
|
130
|
+
|
131
|
+
# NOTE embedded doesn't really make sense in the manpage backend
|
132
|
+
def embedded node
|
133
|
+
result = [node.content]
|
134
|
+
|
135
|
+
if node.footnotes? && !(node.attr? 'nofootnotes')
|
136
|
+
result << '.SH "NOTES"'
|
137
|
+
result.concat(node.footnotes.map {|fn| %(#{fn.index}. #{fn.text}) })
|
138
|
+
end
|
139
|
+
|
140
|
+
# QUESTION should we add an AUTHOR(S) section?
|
141
|
+
|
142
|
+
result * LF
|
143
|
+
end
|
144
|
+
|
145
|
+
def section node
|
146
|
+
slevel = node.level
|
147
|
+
# QUESTION should the check for slevel be done in section?
|
148
|
+
slevel = 1 if slevel == 0 && node.special
|
149
|
+
result = []
|
150
|
+
if slevel > 1
|
151
|
+
macro = 'SS'
|
152
|
+
# QUESTION why captioned title? why not for slevel == 1?
|
153
|
+
stitle = node.captioned_title
|
154
|
+
else
|
155
|
+
macro = 'SH'
|
156
|
+
stitle = node.title.upcase
|
157
|
+
end
|
158
|
+
result << %(.#{macro} "#{manify stitle}"
|
159
|
+
#{node.content})
|
160
|
+
result * LF
|
161
|
+
end
|
162
|
+
|
163
|
+
def admonition node
|
164
|
+
result = []
|
165
|
+
result << %(.if n \\{\\
|
166
|
+
.sp
|
167
|
+
.\\}
|
168
|
+
.RS 4
|
169
|
+
.it 1 an-trap
|
170
|
+
.nr an-no-space-flag 1
|
171
|
+
.nr an-break-flag 1
|
172
|
+
.br
|
173
|
+
.ps +1
|
174
|
+
.B #{node.caption}#{node.title? ? "\\fP #{manify node.title}" : nil}
|
175
|
+
.ps -1
|
176
|
+
.br
|
177
|
+
#{resolve_content node}
|
178
|
+
.sp .5v
|
179
|
+
.RE)
|
180
|
+
result * LF
|
181
|
+
end
|
182
|
+
|
183
|
+
alias :audio :skip_with_warning
|
184
|
+
|
185
|
+
def colist node
|
186
|
+
result = []
|
187
|
+
result << %(.sp
|
188
|
+
.B #{manify node.title}
|
189
|
+
.br) if node.title?
|
190
|
+
result << %(.TS
|
191
|
+
tab\(:\);
|
192
|
+
r lw\(\\n\(.lu*75u/100u\).)
|
193
|
+
|
194
|
+
node.items.each_with_index do |item, index|
|
195
|
+
result << %(\\fB(#{index + 1})\\fP\\h'-2n':T{
|
196
|
+
#{manify item.text}
|
197
|
+
T})
|
198
|
+
end
|
199
|
+
result << '.TE'
|
200
|
+
result * LF
|
201
|
+
end
|
202
|
+
|
203
|
+
# TODO implement title for dlist
|
204
|
+
# TODO implement horizontal (if it makes sense)
|
205
|
+
def dlist node
|
206
|
+
result = []
|
207
|
+
counter = 0
|
208
|
+
node.items.each do |terms, dd|
|
209
|
+
counter += 1
|
210
|
+
case node.style
|
211
|
+
when 'qanda'
|
212
|
+
result << %(.sp
|
213
|
+
#{counter}. #{manify([*terms].map {|dt| dt.text }.join ' ')}
|
214
|
+
.RS 4)
|
215
|
+
else
|
216
|
+
result << %(.sp
|
217
|
+
#{manify([*terms].map {|dt| dt.text }.join ', ')}
|
218
|
+
.RS 4)
|
219
|
+
end
|
220
|
+
if dd
|
221
|
+
result << (manify dd.text) if dd.text?
|
222
|
+
result << dd.content if dd.blocks?
|
223
|
+
end
|
224
|
+
result << '.RE'
|
225
|
+
end
|
226
|
+
result * LF
|
227
|
+
end
|
228
|
+
|
229
|
+
def example node
|
230
|
+
result = []
|
231
|
+
result << %(.sp
|
232
|
+
.B #{manify node.captioned_title}
|
233
|
+
.br) if node.title?
|
234
|
+
result << %(.RS 4
|
235
|
+
#{resolve_content node}
|
236
|
+
.RE)
|
237
|
+
result * LF
|
238
|
+
end
|
239
|
+
|
240
|
+
def floating_title node
|
241
|
+
%(.SS "#{manify node.title}")
|
242
|
+
end
|
243
|
+
|
244
|
+
alias :image :skip_with_warning
|
245
|
+
|
246
|
+
def listing node
|
247
|
+
result = []
|
248
|
+
result << %(.sp
|
249
|
+
.B #{manify node.captioned_title}
|
250
|
+
.br) if node.title?
|
251
|
+
result << %(.sp
|
252
|
+
.if n \\{\\
|
253
|
+
.RS 4
|
254
|
+
.\\}
|
255
|
+
.nf
|
256
|
+
#{manify node.content}
|
257
|
+
.fi
|
258
|
+
.if n \\{\\
|
259
|
+
.RE
|
260
|
+
.\\})
|
261
|
+
result * LF
|
262
|
+
end
|
263
|
+
|
264
|
+
def literal node
|
265
|
+
result = []
|
266
|
+
result << %(.sp
|
267
|
+
.B #{manify node.title}
|
268
|
+
.br) if node.title?
|
269
|
+
result << %(.sp
|
270
|
+
.if n \\{\\
|
271
|
+
.RS 4
|
272
|
+
.\\}
|
273
|
+
.nf
|
274
|
+
#{manify node.content}
|
275
|
+
.fi
|
276
|
+
.if n \\{\\
|
277
|
+
.RE
|
278
|
+
.\\})
|
279
|
+
result * LF
|
280
|
+
end
|
281
|
+
|
282
|
+
def olist node
|
283
|
+
result = []
|
284
|
+
result << %(.sp
|
285
|
+
.B #{manify node.title}
|
286
|
+
.br) if node.title?
|
287
|
+
|
288
|
+
node.items.each_with_index do |item, idx|
|
289
|
+
result << %(.sp
|
290
|
+
.RS 4
|
291
|
+
.ie n \\{\\
|
292
|
+
\\h'-04' #{idx + 1}.\\h'+01'\\c
|
293
|
+
.\\}
|
294
|
+
.el \\{\\
|
295
|
+
.sp -1
|
296
|
+
.IP " #{idx + 1}." 4.2
|
297
|
+
.\\}
|
298
|
+
#{manify item.text})
|
299
|
+
result << item.content if item.blocks?
|
300
|
+
result << '.RE'
|
301
|
+
end
|
302
|
+
result * LF
|
303
|
+
end
|
304
|
+
|
305
|
+
def open node
|
306
|
+
case node.style
|
307
|
+
when 'abstract', 'partintro'
|
308
|
+
resolve_content node
|
309
|
+
else
|
310
|
+
node.content
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
# TODO use Page Control https://www.gnu.org/software/groff/manual/html_node/Page-Control.html#Page-Control
|
315
|
+
alias :page_break :skip
|
316
|
+
|
317
|
+
def paragraph node
|
318
|
+
if node.title?
|
319
|
+
%(.sp
|
320
|
+
.B #{manify node.title}
|
321
|
+
.br
|
322
|
+
#{manify node.content})
|
323
|
+
else
|
324
|
+
%(.sp
|
325
|
+
#{manify node.content})
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
alias :preamble :content
|
330
|
+
|
331
|
+
def quote node
|
332
|
+
result = []
|
333
|
+
if node.title?
|
334
|
+
result << %(.sp
|
335
|
+
.in +.3i
|
336
|
+
.B #{manify node.title}
|
337
|
+
.br
|
338
|
+
.in)
|
339
|
+
end
|
340
|
+
attribution_line = (node.attr? 'citetitle') ? %(#{node.attr 'citetitle'} ) : nil
|
341
|
+
attribution_line = (node.attr? 'attribution') ? %(#{attribution_line}\\\(em #{node.attr 'attribution'}) : nil
|
342
|
+
result << %(.in +.3i
|
343
|
+
.ll -.3i
|
344
|
+
.nf
|
345
|
+
#{resolve_content node}
|
346
|
+
.fi
|
347
|
+
.br
|
348
|
+
.in
|
349
|
+
.ll)
|
350
|
+
if attribution_line
|
351
|
+
result << %(.in +.5i
|
352
|
+
.ll -.5i
|
353
|
+
#{attribution_line}
|
354
|
+
.in
|
355
|
+
.ll)
|
356
|
+
end
|
357
|
+
result * LF
|
358
|
+
end
|
359
|
+
|
360
|
+
alias :sidebar :skip_with_warning
|
361
|
+
|
362
|
+
def stem node
|
363
|
+
title_element = node.title? ? %(.sp
|
364
|
+
.B #{manify node.title}
|
365
|
+
.br) : nil
|
366
|
+
open, close = BLOCK_MATH_DELIMITERS[node.style.to_sym]
|
367
|
+
|
368
|
+
unless ((equation = node.content).start_with? open) && (equation.end_with? close)
|
369
|
+
equation = %(#{open}#{equation}#{close})
|
370
|
+
end
|
371
|
+
|
372
|
+
%(#{title_element}#{equation})
|
373
|
+
end
|
374
|
+
|
375
|
+
# FIXME: The reason this method is so complicated is because we are not
|
376
|
+
# receiving empty(marked) cells when there are colspans or rowspans. This
|
377
|
+
# method has to create a map of all cells and in the case of rowspans
|
378
|
+
# create empty cells as placeholders of the span.
|
379
|
+
# To fix this, asciidoctor needs to provide an API to tell the user if a
|
380
|
+
# given cell is being used as a colspan or rowspan.
|
381
|
+
def table node
|
382
|
+
result = []
|
383
|
+
if node.title?
|
384
|
+
result << %(.sp
|
385
|
+
.it 1 an-trap
|
386
|
+
.nr an-no-space-flag 1
|
387
|
+
.nr an-break-flag 1
|
388
|
+
.br
|
389
|
+
.B #{manify node.captioned_title})
|
390
|
+
end
|
391
|
+
result << '.TS
|
392
|
+
allbox tab(:);'
|
393
|
+
row_header = []
|
394
|
+
row_text = []
|
395
|
+
row_index = 0
|
396
|
+
[:head, :body, :foot].each do |tsec|
|
397
|
+
node.rows[tsec].each do |row|
|
398
|
+
row_header[row_index] ||= []
|
399
|
+
row_text[row_index] ||= []
|
400
|
+
# result << LF
|
401
|
+
# l left-adjusted
|
402
|
+
# r right-adjusted
|
403
|
+
# c centered-adjusted
|
404
|
+
# n numerical align
|
405
|
+
# a alphabetic align
|
406
|
+
# s spanned
|
407
|
+
# ^ vertically spanned
|
408
|
+
remaining_cells = row.size
|
409
|
+
row.each_with_index do |cell, cell_index|
|
410
|
+
remaining_cells -= 1
|
411
|
+
row_header[row_index][cell_index] ||= []
|
412
|
+
# Add an empty cell if this is a rowspan cell
|
413
|
+
if row_header[row_index][cell_index] == ['^t']
|
414
|
+
row_text[row_index] << %(T{#{LF}.sp#{LF}T}:)
|
415
|
+
end
|
416
|
+
row_text[row_index] << %(T{#{LF}.sp#{LF})
|
417
|
+
cell_halign = (cell.attr 'halign', 'left')[0..0]
|
418
|
+
if tsec == :head
|
419
|
+
if row_header[row_index].empty? ||
|
420
|
+
row_header[row_index][cell_index].empty?
|
421
|
+
row_header[row_index][cell_index] << %(#{cell_halign}tB)
|
422
|
+
else
|
423
|
+
row_header[row_index][cell_index + 1] ||= []
|
424
|
+
row_header[row_index][cell_index + 1] << %(#{cell_halign}tB)
|
425
|
+
end
|
426
|
+
row_text[row_index] << %(#{cell.text}#{LF})
|
427
|
+
elsif tsec == :body
|
428
|
+
if row_header[row_index].empty? ||
|
429
|
+
row_header[row_index][cell_index].empty?
|
430
|
+
row_header[row_index][cell_index] << %(#{cell_halign}t)
|
431
|
+
else
|
432
|
+
row_header[row_index][cell_index + 1] ||= []
|
433
|
+
row_header[row_index][cell_index + 1] << %(#{cell_halign}t)
|
434
|
+
end
|
435
|
+
case cell.style
|
436
|
+
when :asciidoc
|
437
|
+
cell_content = cell.content
|
438
|
+
when :verse, :literal
|
439
|
+
cell_content = cell.text
|
440
|
+
else
|
441
|
+
cell_content = cell.content.join
|
442
|
+
end
|
443
|
+
row_text[row_index] << %(#{cell_content}#{LF})
|
444
|
+
elsif tsec == :foot
|
445
|
+
if row_header[row_index].empty? ||
|
446
|
+
row_header[row_index][cell_index].empty?
|
447
|
+
row_header[row_index][cell_index] << %(#{cell_halign}tB)
|
448
|
+
else
|
449
|
+
row_header[row_index][cell_index + 1] ||= []
|
450
|
+
row_header[row_index][cell_index + 1] << %(#{cell_halign}tB)
|
451
|
+
end
|
452
|
+
row_text[row_index] << %(#{cell.text}#{LF})
|
453
|
+
end
|
454
|
+
if cell.colspan && cell.colspan > 1
|
455
|
+
(cell.colspan - 1).times do |i|
|
456
|
+
if row_header[row_index].empty? ||
|
457
|
+
row_header[row_index][cell_index].empty?
|
458
|
+
row_header[row_index][cell_index + i] << 'st'
|
459
|
+
else
|
460
|
+
row_header[row_index][cell_index + 1 + i] ||= []
|
461
|
+
row_header[row_index][cell_index + 1 + i] << 'st'
|
462
|
+
end
|
463
|
+
end
|
464
|
+
end
|
465
|
+
if cell.rowspan && cell.rowspan > 1
|
466
|
+
(cell.rowspan - 1).times do |i|
|
467
|
+
row_header[row_index + 1 + i] ||= []
|
468
|
+
if row_header[row_index + 1 + i].empty? ||
|
469
|
+
row_header[row_index + 1 + i][cell_index].empty?
|
470
|
+
row_header[row_index + 1 + i][cell_index] ||= []
|
471
|
+
row_header[row_index + 1 + i][cell_index] << '^t'
|
472
|
+
else
|
473
|
+
row_header[row_index + 1 + i][cell_index + 1] ||= []
|
474
|
+
row_header[row_index + 1 + i][cell_index + 1] << '^t'
|
475
|
+
end
|
476
|
+
end
|
477
|
+
end
|
478
|
+
if remaining_cells >= 1
|
479
|
+
row_text[row_index] << 'T}:'
|
480
|
+
else
|
481
|
+
row_text[row_index] << %(T}#{LF})
|
482
|
+
end
|
483
|
+
end
|
484
|
+
row_index += 1
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
#row_header.each do |row|
|
489
|
+
# result << LF
|
490
|
+
# row.each_with_index do |cell, i|
|
491
|
+
# result << (cell.join ' ')
|
492
|
+
# result << ' ' if row.size > i + 1
|
493
|
+
# end
|
494
|
+
#end
|
495
|
+
# FIXME temporary fix to get basic table to display
|
496
|
+
result << LF
|
497
|
+
result << row_header.first.map {|r| 'lt'}.join(' ')
|
498
|
+
|
499
|
+
result << %(.#{LF})
|
500
|
+
row_text.each do |row|
|
501
|
+
result << row.join
|
502
|
+
end
|
503
|
+
result << %(.TE#{LF}.sp)
|
504
|
+
result.join
|
505
|
+
end
|
506
|
+
|
507
|
+
def thematic_break node
|
508
|
+
'.sp
|
509
|
+
.ce
|
510
|
+
\l\'\n(.lu*25u/100u\(ap\''
|
511
|
+
end
|
512
|
+
|
513
|
+
alias :toc :skip
|
514
|
+
|
515
|
+
def ulist node
|
516
|
+
result = []
|
517
|
+
result << %(.sp
|
518
|
+
.B #{manify node.title}
|
519
|
+
.br) if node.title?
|
520
|
+
node.items.map {|item|
|
521
|
+
result << %[.sp
|
522
|
+
.RS 4
|
523
|
+
.ie n \\{\\
|
524
|
+
\\h'-04'\\(bu\\h'+03'\\c
|
525
|
+
.\\}
|
526
|
+
.el \\{\\
|
527
|
+
.sp -1
|
528
|
+
.IP \\(bu 2.3
|
529
|
+
.\\}
|
530
|
+
#{manify item.text}]
|
531
|
+
result << item.content if item.blocks?
|
532
|
+
result << '.RE'
|
533
|
+
}
|
534
|
+
result * LF
|
535
|
+
end
|
536
|
+
|
537
|
+
# FIXME git uses [verse] for the synopsis; detect this special case
|
538
|
+
def verse node
|
539
|
+
result = []
|
540
|
+
if node.title?
|
541
|
+
result << %(.sp
|
542
|
+
.B #{manify node.title}
|
543
|
+
.br)
|
544
|
+
end
|
545
|
+
attribution_line = (node.attr? 'citetitle') ? %(#{node.attr 'citetitle'} ) : nil
|
546
|
+
attribution_line = (node.attr? 'attribution') ? %(#{attribution_line}\\\(em #{node.attr 'attribution'}) : nil
|
547
|
+
result << %(.sp
|
548
|
+
.nf
|
549
|
+
#{manify node.content}
|
550
|
+
.fi
|
551
|
+
.br)
|
552
|
+
if attribution_line
|
553
|
+
result << %(.in +.5i
|
554
|
+
.ll -.5i
|
555
|
+
#{attribution_line}
|
556
|
+
.in
|
557
|
+
.ll)
|
558
|
+
end
|
559
|
+
result * LF
|
560
|
+
end
|
561
|
+
|
562
|
+
def video node
|
563
|
+
start_param = (node.attr? 'start', nil, false) ? %(&start=#{node.attr 'start'}) : nil
|
564
|
+
end_param = (node.attr? 'end', nil, false) ? %(&end=#{node.attr 'end'}) : nil
|
565
|
+
%(.sp
|
566
|
+
#{manify node.captioned_title} (video) <#{node.media_uri(node.attr 'target')}#{start_param}#{end_param}>)
|
567
|
+
end
|
568
|
+
|
569
|
+
def inline_anchor node
|
570
|
+
target = node.target
|
571
|
+
case node.type
|
572
|
+
when :link
|
573
|
+
if (text = node.text) == target
|
574
|
+
text = nil
|
575
|
+
else
|
576
|
+
text = text.gsub '"', '\\(dq'
|
577
|
+
end
|
578
|
+
if target.start_with? 'mailto:'
|
579
|
+
macro = 'MTO'
|
580
|
+
target = target[7..-1].sub('@', '\\(at')
|
581
|
+
else
|
582
|
+
macro = 'URL'
|
583
|
+
end
|
584
|
+
%(#{LF}.#{macro} "#{target}" "#{text}" )
|
585
|
+
when :xref
|
586
|
+
refid = (node.attr 'refid') || target
|
587
|
+
node.text || (node.document.references[:ids][refid] || %([#{refid}]))
|
588
|
+
when :ref, :bibref
|
589
|
+
# These are anchor points, which shouldn't be visual
|
590
|
+
''
|
591
|
+
else
|
592
|
+
warn %(asciidoctor: WARNING: unknown anchor type: #{node.type.inspect})
|
593
|
+
end
|
594
|
+
end
|
595
|
+
|
596
|
+
def inline_break node
|
597
|
+
%(#{node.text}
|
598
|
+
.br)
|
599
|
+
end
|
600
|
+
|
601
|
+
def inline_button node
|
602
|
+
%(\\fB[\\ #{node.text}\\ ]\\fP)
|
603
|
+
end
|
604
|
+
|
605
|
+
def inline_callout node
|
606
|
+
%[\\fB(#{node.text})\\fP]
|
607
|
+
end
|
608
|
+
|
609
|
+
# TODO supposedly groff has footnotes, but we're in search of an example
|
610
|
+
def inline_footnote node
|
611
|
+
if (index = node.attr 'index')
|
612
|
+
%([#{index}])
|
613
|
+
elsif node.type == :xref
|
614
|
+
%([#{node.text}])
|
615
|
+
end
|
616
|
+
end
|
617
|
+
|
618
|
+
def inline_image node
|
619
|
+
# NOTE alt should always be set
|
620
|
+
alt_text = (node.attr? 'alt') ? (node.attr 'alt') : node.target
|
621
|
+
(node.attr? 'link') ? %([#{alt_text}] <#{node.attr 'link'}>) : %([#{alt_text}])
|
622
|
+
end
|
623
|
+
|
624
|
+
def inline_indexterm node
|
625
|
+
node.type == :visible ? node.text : ''
|
626
|
+
end
|
627
|
+
|
628
|
+
def inline_kbd node
|
629
|
+
if (keys = node.attr 'keys').size == 1
|
630
|
+
keys[0]
|
631
|
+
else
|
632
|
+
keys.join '\ +\ '
|
633
|
+
end
|
634
|
+
end
|
635
|
+
|
636
|
+
def inline_menu node
|
637
|
+
caret = '\ \(fc\ '
|
638
|
+
menu = node.attr 'menu'
|
639
|
+
if !(submenus = node.attr 'submenus').empty?
|
640
|
+
submenu_path = submenus.map {|item| %(\\fI#{item}\\fP) }.join caret
|
641
|
+
%(\\fI#{menu}\\fP#{caret}#{submenu_path}#{caret}\\fI#{node.attr 'menuitem'}\\fP)
|
642
|
+
elsif (menuitem = node.attr 'menuitem')
|
643
|
+
%(\\fI#{menu}#{caret}#{menuitem}\\fP)
|
644
|
+
else
|
645
|
+
%(\\fI#{menu}\\fP)
|
646
|
+
end
|
647
|
+
end
|
648
|
+
|
649
|
+
# NOTE use fake <BOUNDARY> element to prevent creating artificial word boundaries
|
650
|
+
def inline_quoted node
|
651
|
+
case node.type
|
652
|
+
when :emphasis
|
653
|
+
%[\\fI<BOUNDARY>#{node.text}</BOUNDARY>\\fP]
|
654
|
+
when :strong
|
655
|
+
%[\\fB<BOUNDARY>#{node.text}</BOUNDARY>\\fP]
|
656
|
+
when :monospaced
|
657
|
+
%[\\f[CR]<BOUNDARY>#{node.text}</BOUNDARY>\\fP]
|
658
|
+
when :single
|
659
|
+
%[\\(oq<BOUNDARY>#{node.text}</BOUNDARY>\\(cq]
|
660
|
+
when :double
|
661
|
+
%[\\(lq<BOUNDARY>#{node.text}</BOUNDARY>\\(rq]
|
662
|
+
else
|
663
|
+
node.text
|
664
|
+
end
|
665
|
+
end
|
666
|
+
|
667
|
+
def resolve_content node
|
668
|
+
node.content_model == :compound ? node.content : %(.sp#{LF}#{manify node.content})
|
669
|
+
end
|
670
|
+
end
|
671
|
+
end
|