asciidoctor 2.0.10 → 2.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.adoc +211 -22
- data/LICENSE +1 -1
- data/README-de.adoc +12 -13
- data/README-fr.adoc +11 -15
- data/README-jp.adoc +9 -17
- data/README-zh_CN.adoc +17 -18
- data/README.adoc +140 -132
- data/asciidoctor.gemspec +6 -6
- data/data/locale/attributes-ar.adoc +4 -3
- data/data/locale/attributes-be.adoc +23 -0
- data/data/locale/attributes-bg.adoc +4 -3
- data/data/locale/attributes-ca.adoc +6 -5
- data/data/locale/attributes-cs.adoc +4 -3
- data/data/locale/attributes-da.adoc +6 -5
- data/data/locale/attributes-de.adoc +4 -4
- data/data/locale/attributes-en.adoc +4 -4
- data/data/locale/attributes-es.adoc +6 -5
- data/data/locale/attributes-fa.adoc +4 -3
- data/data/locale/attributes-fi.adoc +4 -3
- data/data/locale/attributes-fr.adoc +6 -5
- data/data/locale/attributes-hu.adoc +4 -3
- data/data/locale/attributes-id.adoc +4 -3
- data/data/locale/attributes-it.adoc +6 -5
- data/data/locale/attributes-ja.adoc +4 -3
- data/data/locale/{attributes-kr.adoc → attributes-ko.adoc} +4 -3
- data/data/locale/attributes-nb.adoc +4 -3
- data/data/locale/attributes-nl.adoc +6 -5
- data/data/locale/attributes-nn.adoc +4 -3
- data/data/locale/attributes-pl.adoc +8 -7
- data/data/locale/attributes-pt.adoc +6 -5
- data/data/locale/attributes-pt_BR.adoc +6 -5
- data/data/locale/attributes-ro.adoc +4 -3
- data/data/locale/attributes-ru.adoc +6 -5
- data/data/locale/attributes-sr.adoc +4 -4
- data/data/locale/attributes-sr_Latn.adoc +4 -4
- data/data/locale/attributes-sv.adoc +4 -4
- data/data/locale/attributes-tr.adoc +4 -3
- data/data/locale/attributes-uk.adoc +6 -5
- data/data/locale/attributes-zh_CN.adoc +4 -3
- data/data/locale/attributes-zh_TW.adoc +4 -3
- data/data/reference/syntax.adoc +14 -7
- data/data/stylesheets/asciidoctor-default.css +27 -28
- data/lib/asciidoctor.rb +38 -12
- data/lib/asciidoctor/abstract_block.rb +9 -4
- data/lib/asciidoctor/abstract_node.rb +16 -6
- data/lib/asciidoctor/attribute_list.rb +64 -72
- data/lib/asciidoctor/cli/invoker.rb +2 -0
- data/lib/asciidoctor/cli/options.rb +10 -9
- data/lib/asciidoctor/convert.rb +167 -162
- data/lib/asciidoctor/converter.rb +13 -12
- data/lib/asciidoctor/converter/docbook5.rb +29 -12
- data/lib/asciidoctor/converter/html5.rb +69 -46
- data/lib/asciidoctor/converter/manpage.rb +68 -49
- data/lib/asciidoctor/converter/template.rb +3 -0
- data/lib/asciidoctor/document.rb +43 -50
- data/lib/asciidoctor/extensions.rb +2 -4
- data/lib/asciidoctor/helpers.rb +20 -15
- data/lib/asciidoctor/load.rb +102 -101
- data/lib/asciidoctor/parser.rb +40 -32
- data/lib/asciidoctor/path_resolver.rb +14 -12
- data/lib/asciidoctor/reader.rb +22 -13
- data/lib/asciidoctor/rx.rb +7 -6
- data/lib/asciidoctor/substitutors.rb +78 -57
- data/lib/asciidoctor/syntax_highlighter.rb +8 -5
- data/lib/asciidoctor/syntax_highlighter/coderay.rb +1 -1
- data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +12 -4
- data/lib/asciidoctor/syntax_highlighter/prettify.rb +7 -4
- data/lib/asciidoctor/syntax_highlighter/pygments.rb +6 -7
- data/lib/asciidoctor/syntax_highlighter/rouge.rb +33 -19
- data/lib/asciidoctor/table.rb +52 -23
- data/lib/asciidoctor/version.rb +1 -1
- data/man/asciidoctor.1 +8 -8
- data/man/asciidoctor.adoc +4 -4
- metadata +16 -15
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module Asciidoctor
|
3
3
|
# A built-in {Converter} implementation that generates HTML 5 output
|
4
|
-
# consistent with the html5 backend from AsciiDoc
|
4
|
+
# consistent with the html5 backend from AsciiDoc.py.
|
5
5
|
class Converter::Html5Converter < Converter::Base
|
6
6
|
register_for 'html5'
|
7
7
|
|
@@ -21,15 +21,15 @@ class Converter::Html5Converter < Converter::Base
|
|
21
21
|
#latexmath: INLINE_MATH_DELIMITERS[:latexmath] + [false],
|
22
22
|
}).default = ['', '']
|
23
23
|
|
24
|
-
DropAnchorRx = /<(?:a[
|
24
|
+
DropAnchorRx = /<(?:a\b[^>]*|\/a)>/
|
25
25
|
StemBreakRx = / *\\\n(?:\\?\n)*|\n\n+/
|
26
26
|
if RUBY_ENGINE == 'opal'
|
27
27
|
# NOTE In JavaScript, ^ matches the start of the string when the m flag is not set
|
28
|
-
SvgPreambleRx = /^#{CC_ALL}*?(?=<svg\
|
29
|
-
SvgStartTagRx = /^<svg[^>]
|
28
|
+
SvgPreambleRx = /^#{CC_ALL}*?(?=<svg[\s>])/
|
29
|
+
SvgStartTagRx = /^<svg(?:\s[^>]*)?>/
|
30
30
|
else
|
31
|
-
SvgPreambleRx = /\A.*?(?=<svg\
|
32
|
-
SvgStartTagRx = /\A<svg[^>]
|
31
|
+
SvgPreambleRx = /\A.*?(?=<svg[\s>])/m
|
32
|
+
SvgStartTagRx = /\A<svg(?:\s[^>]*)?>/
|
33
33
|
end
|
34
34
|
DimensionAttributeRx = /\s(?:width|height|style)=(["'])#{CC_ANY}*?\1/
|
35
35
|
|
@@ -96,6 +96,7 @@ class Converter::Html5Converter < Converter::Base
|
|
96
96
|
end
|
97
97
|
cdn_base_url = %(#{asset_uri_scheme}//cdnjs.cloudflare.com/ajax/libs)
|
98
98
|
linkcss = node.attr? 'linkcss'
|
99
|
+
max_width_attr = (node.attr? 'max-width') ? %( style="max-width: #{node.attr 'max-width'};") : ''
|
99
100
|
result = ['<!DOCTYPE html>']
|
100
101
|
lang_attribute = (node.attr? 'nolang') ? '' : %( lang="#{node.attr 'lang', 'en'}")
|
101
102
|
result << %(<html#{@xml_mode ? ' xmlns="http://www.w3.org/1999/xhtml"' : ''}#{lang_attribute}>)
|
@@ -138,7 +139,7 @@ class Converter::Html5Converter < Converter::Base
|
|
138
139
|
result << %(<link rel="stylesheet" href="#{node.normalize_web_path((node.attr 'stylesheet'), (node.attr 'stylesdir', ''))}"#{slash}>)
|
139
140
|
else
|
140
141
|
result << %(<style>
|
141
|
-
#{node.
|
142
|
+
#{node.read_contents (node.attr 'stylesheet'), start: (node.attr 'stylesdir'), warn_on_failure: true, label: 'stylesheet'}
|
142
143
|
</style>)
|
143
144
|
end
|
144
145
|
end
|
@@ -152,8 +153,8 @@ class Converter::Html5Converter < Converter::Base
|
|
152
153
|
end
|
153
154
|
end
|
154
155
|
|
155
|
-
if (syntax_hl = node.syntax_highlighter)
|
156
|
-
result << (
|
156
|
+
if (syntax_hl = node.syntax_highlighter)
|
157
|
+
result << (syntax_hl_docinfo_head_idx = result.size)
|
157
158
|
end
|
158
159
|
|
159
160
|
unless (docinfo_content = node.docinfo).empty?
|
@@ -161,29 +162,27 @@ class Converter::Html5Converter < Converter::Base
|
|
161
162
|
end
|
162
163
|
|
163
164
|
result << '</head>'
|
164
|
-
|
165
|
+
id_attr = node.id ? %( id="#{node.id}") : ''
|
165
166
|
if (sectioned = node.sections?) && (node.attr? 'toc-class') && (node.attr? 'toc') && (node.attr? 'toc-placement', 'auto')
|
166
167
|
classes = [node.doctype, (node.attr 'toc-class'), %(toc-#{node.attr 'toc-position', 'header'})]
|
167
168
|
else
|
168
169
|
classes = [node.doctype]
|
169
170
|
end
|
170
171
|
classes << node.role if node.role?
|
171
|
-
|
172
|
-
body_attrs << %(style="max-width: #{node.attr 'max-width'};") if node.attr? 'max-width'
|
173
|
-
result << %(<body #{body_attrs.join ' '}>)
|
172
|
+
result << %(<body#{id_attr} class="#{classes.join ' '}">)
|
174
173
|
|
175
174
|
unless (docinfo_content = node.docinfo :header).empty?
|
176
175
|
result << docinfo_content
|
177
176
|
end
|
178
177
|
|
179
178
|
unless node.noheader
|
180
|
-
result <<
|
179
|
+
result << %(<div id="header"#{max_width_attr}>)
|
181
180
|
if node.doctype == 'manpage'
|
182
181
|
result << %(<h1>#{node.doctitle} Manual Page</h1>)
|
183
182
|
if sectioned && (node.attr? 'toc') && (node.attr? 'toc-placement', 'auto')
|
184
183
|
result << %(<div id="toc" class="#{node.attr 'toc-class', 'toc'}">
|
185
184
|
<div id="toctitle">#{node.attr 'toc-title'}</div>
|
186
|
-
#{
|
185
|
+
#{node.converter.convert node, 'outline'}
|
187
186
|
</div>)
|
188
187
|
end
|
189
188
|
result << (generate_manname_section node) if node.attr? 'manpurpose'
|
@@ -216,19 +215,19 @@ class Converter::Html5Converter < Converter::Base
|
|
216
215
|
if sectioned && (node.attr? 'toc') && (node.attr? 'toc-placement', 'auto')
|
217
216
|
result << %(<div id="toc" class="#{node.attr 'toc-class', 'toc'}">
|
218
217
|
<div id="toctitle">#{node.attr 'toc-title'}</div>
|
219
|
-
#{
|
218
|
+
#{node.converter.convert node, 'outline'}
|
220
219
|
</div>)
|
221
220
|
end
|
222
221
|
end
|
223
222
|
result << '</div>'
|
224
223
|
end
|
225
224
|
|
226
|
-
result << %(<div id="content">
|
225
|
+
result << %(<div id="content"#{max_width_attr}>
|
227
226
|
#{node.content}
|
228
227
|
</div>)
|
229
228
|
|
230
229
|
if node.footnotes? && !(node.attr? 'nofootnotes')
|
231
|
-
result << %(<div id="footnotes">
|
230
|
+
result << %(<div id="footnotes"#{max_width_attr}>
|
232
231
|
<hr#{slash}>)
|
233
232
|
node.footnotes.each do |footnote|
|
234
233
|
result << %(<div class="footnote" id="_footnotedef_#{footnote.index}">
|
@@ -239,7 +238,7 @@ class Converter::Html5Converter < Converter::Base
|
|
239
238
|
end
|
240
239
|
|
241
240
|
unless node.nofooter
|
242
|
-
result <<
|
241
|
+
result << %(<div id="footer"#{max_width_attr}>)
|
243
242
|
result << '<div id="footer-text">'
|
244
243
|
result << %(#{node.attr 'version-label'} #{node.attr 'revnumber'}#{br}) if node.attr? 'revnumber'
|
245
244
|
result << %(#{node.attr 'last-update-label'} #{node.attr 'docdatetime'}) if (node.attr? 'last-update-label') && !(node.attr? 'reproducible')
|
@@ -250,8 +249,15 @@ class Converter::Html5Converter < Converter::Base
|
|
250
249
|
# JavaScript (and auxiliary stylesheets) loaded at the end of body for performance reasons
|
251
250
|
# See http://www.html5rocks.com/en/tutorials/speed/script-loading/
|
252
251
|
|
253
|
-
if syntax_hl
|
254
|
-
|
252
|
+
if syntax_hl
|
253
|
+
if syntax_hl.docinfo? :head
|
254
|
+
result[syntax_hl_docinfo_head_idx] = syntax_hl.docinfo :head, node, cdn_base_url: cdn_base_url, linkcss: linkcss, self_closing_tag_slash: slash
|
255
|
+
else
|
256
|
+
result.delete_at syntax_hl_docinfo_head_idx
|
257
|
+
end
|
258
|
+
if syntax_hl.docinfo? :footer
|
259
|
+
result << (syntax_hl.docinfo :footer, node, cdn_base_url: cdn_base_url, linkcss: linkcss, self_closing_tag_slash: slash)
|
260
|
+
end
|
255
261
|
end
|
256
262
|
|
257
263
|
if node.attr? 'stem'
|
@@ -275,7 +281,7 @@ MathJax.Hub.Config({
|
|
275
281
|
})
|
276
282
|
MathJax.Hub.Register.StartupHook("AsciiMath Jax Ready", function () {
|
277
283
|
MathJax.InputJax.AsciiMath.postfilterHooks.Add(function (data, node) {
|
278
|
-
if ((node = data.script.parentNode) && (node = node.parentNode) && node.classList.contains(
|
284
|
+
if ((node = data.script.parentNode) && (node = node.parentNode) && node.classList.contains("stemblock")) {
|
279
285
|
data.math.root.display = "block"
|
280
286
|
}
|
281
287
|
return data
|
@@ -311,7 +317,7 @@ MathJax.Hub.Register.StartupHook("AsciiMath Jax Ready", function () {
|
|
311
317
|
if node.sections? && (node.attr? 'toc') && (toc_p = node.attr 'toc-placement') != 'macro' && toc_p != 'preamble'
|
312
318
|
result << %(<div id="toc" class="toc">
|
313
319
|
<div id="toctitle">#{node.attr 'toc-title'}</div>
|
314
|
-
#{
|
320
|
+
#{node.converter.convert node, 'outline'}
|
315
321
|
</div>)
|
316
322
|
end
|
317
323
|
|
@@ -628,9 +634,7 @@ Your browser does not support the audio tag.
|
|
628
634
|
end
|
629
635
|
end
|
630
636
|
img ||= %(<img src="#{node.image_uri target}" alt="#{encode_attribute_value node.alt}"#{width_attr}#{height_attr}#{@void_element_slash}>)
|
631
|
-
if node.attr? 'link'
|
632
|
-
img = %(<a class="image" href="#{node.attr 'link'}"#{(append_link_constraint_attrs node).join}>#{img}</a>)
|
633
|
-
end
|
637
|
+
img = %(<a class="image" href="#{node.attr 'link'}"#{(append_link_constraint_attrs node).join}>#{img}</a>) if node.attr? 'link'
|
634
638
|
id_attr = node.id ? %( id="#{node.id}") : ''
|
635
639
|
classes = ['imageblock']
|
636
640
|
classes << (node.attr 'float') if node.attr? 'float'
|
@@ -689,8 +693,8 @@ Your browser does not support the audio tag.
|
|
689
693
|
open, close = BLOCK_MATH_DELIMITERS[style = node.style.to_sym]
|
690
694
|
if (equation = node.content)
|
691
695
|
if style == :asciimath && (equation.include? LF)
|
692
|
-
br = %(<br#{@void_element_slash}
|
693
|
-
equation = equation.gsub(StemBreakRx) { %(#{close}#{br * ($&.count LF)}#{open}) }
|
696
|
+
br = %(#{LF}<br#{@void_element_slash}>)
|
697
|
+
equation = equation.gsub(StemBreakRx) { %(#{close}#{br * (($&.count LF) - 1)}#{LF}#{open}) }
|
694
698
|
end
|
695
699
|
unless (equation.start_with? open) && (equation.end_with? close)
|
696
700
|
equation = %(#{open}#{equation}#{close})
|
@@ -796,7 +800,7 @@ Your browser does not support the audio tag.
|
|
796
800
|
toc = %(
|
797
801
|
<div id="toc" class="#{doc.attr 'toc-class', 'toc'}">
|
798
802
|
<div id="toctitle">#{doc.attr 'toc-title'}</div>
|
799
|
-
#{
|
803
|
+
#{doc.converter.convert doc, 'outline'}
|
800
804
|
</div>)
|
801
805
|
else
|
802
806
|
toc = ''
|
@@ -848,7 +852,8 @@ Your browser does not support the audio tag.
|
|
848
852
|
def convert_table node
|
849
853
|
result = []
|
850
854
|
id_attribute = node.id ? %( id="#{node.id}") : ''
|
851
|
-
|
855
|
+
frame = 'ends' if (frame = node.attr 'frame', 'all', 'table-frame') == 'topbot'
|
856
|
+
classes = ['tableblock', %(frame-#{frame}), %(grid-#{node.attr 'grid', 'all', 'table-grid'})]
|
852
857
|
if (stripes = node.attr 'stripes', nil, 'table-stripes')
|
853
858
|
classes << %(stripes-#{stripes})
|
854
859
|
end
|
@@ -934,7 +939,7 @@ Your browser does not support the audio tag.
|
|
934
939
|
|
935
940
|
%(<div#{id_attr} class="#{role}">
|
936
941
|
<div#{title_id_attr} class="title">#{title}</div>
|
937
|
-
#{
|
942
|
+
#{doc.converter.convert doc, 'outline', toclevels: levels}
|
938
943
|
</div>)
|
939
944
|
end
|
940
945
|
|
@@ -1089,7 +1094,7 @@ Your browser does not support the audio tag.
|
|
1089
1094
|
time_anchor = (start_t || end_t) ? %(#t=#{start_t || ''}#{end_t ? ",#{end_t}" : ''}) : ''
|
1090
1095
|
%(<div#{id_attribute}#{class_attribute}>#{title_element}
|
1091
1096
|
<div class="content">
|
1092
|
-
<video src="#{node.media_uri(node.attr 'target')}#{time_anchor}"#{width_attribute}#{height_attribute}#{poster_attribute}#{(node.option? 'autoplay') ? (append_boolean_attribute 'autoplay', xml) : ''}#{(node.option? 'nocontrols') ? '' : (append_boolean_attribute 'controls', xml)}#{(node.option? 'loop') ? (append_boolean_attribute 'loop', xml) : ''}#{preload_attribute}>
|
1097
|
+
<video src="#{node.media_uri(node.attr 'target')}#{time_anchor}"#{width_attribute}#{height_attribute}#{poster_attribute}#{(node.option? 'autoplay') ? (append_boolean_attribute 'autoplay', xml) : ''}#{(node.option? 'muted') ? (append_boolean_attribute 'muted', xml) : ''}#{(node.option? 'nocontrols') ? '' : (append_boolean_attribute 'controls', xml)}#{(node.option? 'loop') ? (append_boolean_attribute 'loop', xml) : ''}#{preload_attribute}>
|
1093
1098
|
Your browser does not support the video tag.
|
1094
1099
|
</video>
|
1095
1100
|
</div>
|
@@ -1106,9 +1111,17 @@ Your browser does not support the video tag.
|
|
1106
1111
|
else
|
1107
1112
|
attrs = node.role ? %( class="#{node.role}") : ''
|
1108
1113
|
unless (text = node.text)
|
1109
|
-
refid = node.attributes['refid']
|
1110
|
-
|
1111
|
-
|
1114
|
+
if AbstractNode === (ref = (@refs ||= node.document.catalog[:refs])[refid = node.attributes['refid']] || (refid.nil_or_empty? ? (top = get_root_document node) : nil))
|
1115
|
+
if (@resolving_xref ||= (outer = true)) && outer
|
1116
|
+
if (text = ref.xreftext node.attr 'xrefstyle', nil, true)
|
1117
|
+
text = text.gsub DropAnchorRx, '' if text.include? '<a'
|
1118
|
+
else
|
1119
|
+
text = top ? '[^top]' : %([#{refid}])
|
1120
|
+
end
|
1121
|
+
@resolving_xref = nil
|
1122
|
+
else
|
1123
|
+
text = top ? '[^top]' : %([#{refid}])
|
1124
|
+
end
|
1112
1125
|
else
|
1113
1126
|
text = %([#{refid}])
|
1114
1127
|
end
|
@@ -1144,8 +1157,10 @@ Your browser does not support the video tag.
|
|
1144
1157
|
elsif node.document.attr? 'icons'
|
1145
1158
|
src = node.icon_uri("callouts/#{node.text}")
|
1146
1159
|
%(<img src="#{src}" alt="#{node.text}"#{@void_element_slash}>)
|
1160
|
+
elsif ::Array === (guard = node.attributes['guard'])
|
1161
|
+
%(<!--<b class="conum">(#{node.text})</b>-->)
|
1147
1162
|
else
|
1148
|
-
%(#{
|
1163
|
+
%(#{guard}<b class="conum">(#{node.text})</b>)
|
1149
1164
|
end
|
1150
1165
|
end
|
1151
1166
|
|
@@ -1186,9 +1201,7 @@ Your browser does not support the video tag.
|
|
1186
1201
|
end
|
1187
1202
|
img ||= %(<img src="#{type == 'icon' ? (node.icon_uri target) : (node.image_uri target)}" alt="#{encode_attribute_value node.alt}"#{attrs}#{@void_element_slash}>)
|
1188
1203
|
end
|
1189
|
-
if node.attr? 'link'
|
1190
|
-
img = %(<a class="image" href="#{node.attr 'link'}"#{(append_link_constraint_attrs node).join}>#{img}</a>)
|
1191
|
-
end
|
1204
|
+
img = %(<a class="image" href="#{node.attr 'link'}"#{(append_link_constraint_attrs node).join}>#{img}</a>) if node.attr? 'link'
|
1192
1205
|
if (role = node.role)
|
1193
1206
|
if node.attr? 'float'
|
1194
1207
|
class_attr_val = %(#{type} #{node.attr 'float'} #{role})
|
@@ -1252,16 +1265,19 @@ Your browser does not support the video tag.
|
|
1252
1265
|
|
1253
1266
|
# NOTE expose read_svg_contents for Bespoke converter
|
1254
1267
|
def read_svg_contents node, target
|
1255
|
-
if (svg = node.read_contents target, start: (node.document.attr 'imagesdir'), normalize: true, label: 'SVG')
|
1268
|
+
if (svg = node.read_contents target, start: (node.document.attr 'imagesdir'), normalize: true, label: 'SVG', warn_if_empty: true)
|
1269
|
+
return if svg.empty?
|
1256
1270
|
svg = svg.sub SvgPreambleRx, '' unless svg.start_with? '<svg'
|
1257
|
-
old_start_tag = new_start_tag = nil
|
1271
|
+
old_start_tag = new_start_tag = start_tag_match = nil
|
1258
1272
|
# NOTE width, height and style attributes are removed if either width or height is specified
|
1259
1273
|
['width', 'height'].each do |dim|
|
1260
|
-
|
1261
|
-
|
1262
|
-
|
1263
|
-
new_start_tag =
|
1274
|
+
next unless node.attr? dim
|
1275
|
+
unless new_start_tag
|
1276
|
+
next if (start_tag_match ||= (svg.match SvgStartTagRx) || :no_match) == :no_match
|
1277
|
+
new_start_tag = (old_start_tag = start_tag_match[0]).gsub DimensionAttributeRx, ''
|
1264
1278
|
end
|
1279
|
+
# NOTE a unitless value in HTML is assumed to be px, so we can pass the value straight through
|
1280
|
+
new_start_tag = %(#{new_start_tag.chop} #{dim}="#{node.attr dim}">)
|
1265
1281
|
end
|
1266
1282
|
svg = %(#{new_start_tag}#{svg[old_start_tag.length..-1]}) if new_start_tag
|
1267
1283
|
end
|
@@ -1297,10 +1313,17 @@ Your browser does not support the video tag.
|
|
1297
1313
|
manname_id_attr = (manname_id = node.attr 'manname-id') ? %( id="#{manname_id}") : ''
|
1298
1314
|
%(<h2#{manname_id_attr}>#{manname_title}</h2>
|
1299
1315
|
<div class="sectionbody">
|
1300
|
-
<p>#{node.attr '
|
1316
|
+
<p>#{(node.attr 'mannames').join ', '} - #{node.attr 'manpurpose'}</p>
|
1301
1317
|
</div>)
|
1302
1318
|
end
|
1303
1319
|
|
1320
|
+
def get_root_document node
|
1321
|
+
while (node = node.document).nested?
|
1322
|
+
node = node.parent_document
|
1323
|
+
end
|
1324
|
+
node
|
1325
|
+
end
|
1326
|
+
|
1304
1327
|
# NOTE adapt to older converters that relied on unprefixed method names
|
1305
1328
|
def method_missing id, *params
|
1306
1329
|
!((name = id.to_s).start_with? 'convert_') && (handles? name) ? (send %(convert_#{name}), *params) : super
|
@@ -2,8 +2,12 @@
|
|
2
2
|
module Asciidoctor
|
3
3
|
# A built-in {Converter} implementation that generates the man page (troff) format.
|
4
4
|
#
|
5
|
-
# The output
|
6
|
-
#
|
5
|
+
# The output of this converter adheres to the man definition as defined by
|
6
|
+
# groff and uses the manpage output of the DocBook toolchain as a foundation.
|
7
|
+
# That means if you've previously been generating man pages using the a2x tool
|
8
|
+
# from AsciiDoc.py, you should be able to achieve a very similar result
|
9
|
+
# using this converter. Though you'll also get to enjoy some notable
|
10
|
+
# enhancements that have been added since, such as the customizable linkstyle.
|
7
11
|
#
|
8
12
|
# See http://www.gnu.org/software/groff/manual/html_node/Man-usage.html#Man-usage
|
9
13
|
class Converter::ManPageConverter < Converter::Base
|
@@ -15,13 +19,16 @@ class Converter::ManPageConverter < Converter::Base
|
|
15
19
|
ESC_BS = %(#{ESC}\\) # escaped backslash (indicates troff formatting sequence)
|
16
20
|
ESC_FS = %(#{ESC}.) # escaped full stop (indicates troff macro)
|
17
21
|
|
18
|
-
LiteralBackslashRx =
|
22
|
+
LiteralBackslashRx = /\A\\|(#{ESC})?\\/
|
19
23
|
LeadingPeriodRx = /^\./
|
20
24
|
EscapedMacroRx = /^(?:#{ESC}\\c\n)?#{ESC}\.((?:URL|MTO) "#{CC_ANY}*?" "#{CC_ANY}*?" )( |[^\s]*)(#{CC_ANY}*?)(?: *#{ESC}\\c)?$/
|
21
|
-
|
25
|
+
MalformedEscapedMacroRx = /(#{ESC}\\c) (#{ESC}\.(?:URL|MTO) )/
|
26
|
+
MockMacroRx = /<\/?(#{ESC}\\[^>]+)>/
|
22
27
|
EmDashCharRefRx = /—(?:​)?/
|
23
28
|
EllipsisCharRefRx = /…(?:​)?/
|
24
29
|
WrappedIndentRx = /#{CG_BLANK}*#{LF}#{CG_BLANK}*/
|
30
|
+
XMLMarkupRx = /&#?[a-z\d]+;|</
|
31
|
+
PCDATAFilterRx = /(&#?[a-z\d]+;|<[^>]+>)|([^&<]+)/
|
25
32
|
|
26
33
|
def initialize backend, opts = {}
|
27
34
|
@backend = backend
|
@@ -91,17 +98,14 @@ class Converter::ManPageConverter < Converter::Base
|
|
91
98
|
if node.attr? 'manpurpose'
|
92
99
|
mannames = node.attr 'mannames', [manname]
|
93
100
|
result << %(.SH "#{(node.attr 'manname-title', 'NAME').upcase}"
|
94
|
-
#{mannames.map {|n| manify n }.join ', '} \\- #{manify node.attr('manpurpose'), whitespace: :normalize})
|
101
|
+
#{mannames.map {|n| (manify n).gsub '\-', '-' }.join ', '} \\- #{manify node.attr('manpurpose'), whitespace: :normalize})
|
95
102
|
end
|
96
103
|
end
|
97
104
|
|
98
105
|
result << node.content
|
99
106
|
|
100
107
|
# QUESTION should NOTES come after AUTHOR(S)?
|
101
|
-
|
102
|
-
result << '.SH "NOTES"'
|
103
|
-
result.concat(node.footnotes.map {|fn| %(#{fn.index}. #{fn.text}) })
|
104
|
-
end
|
108
|
+
append_footnotes result, node
|
105
109
|
|
106
110
|
unless (authors = node.authors).empty?
|
107
111
|
if authors.size > 1
|
@@ -124,10 +128,7 @@ class Converter::ManPageConverter < Converter::Base
|
|
124
128
|
def convert_embedded node
|
125
129
|
result = [node.content]
|
126
130
|
|
127
|
-
|
128
|
-
result << '.SH "NOTES"'
|
129
|
-
result.concat(node.footnotes.map {|fn| %(#{fn.index}. #{fn.text}) })
|
130
|
-
end
|
131
|
+
append_footnotes result, node
|
131
132
|
|
132
133
|
# QUESTION should we add an AUTHOR(S) section?
|
133
134
|
|
@@ -142,7 +143,7 @@ class Converter::ManPageConverter < Converter::Base
|
|
142
143
|
stitle = node.captioned_title
|
143
144
|
else
|
144
145
|
macro = 'SH'
|
145
|
-
stitle = node.title
|
146
|
+
stitle = uppercase_pcdata node.title
|
146
147
|
end
|
147
148
|
result << %(.#{macro} "#{manify stitle}"
|
148
149
|
#{node.content})
|
@@ -247,7 +248,9 @@ r lw(\n(.lu*75u/100u).'
|
|
247
248
|
result << %(.sp
|
248
249
|
.if n .RS 4
|
249
250
|
.nf
|
251
|
+
.fam C
|
250
252
|
#{manify node.content, whitespace: :preserve}
|
253
|
+
.fam
|
251
254
|
.fi
|
252
255
|
.if n .RE)
|
253
256
|
result.join LF
|
@@ -261,7 +264,9 @@ r lw(\n(.lu*75u/100u).'
|
|
261
264
|
result << %(.sp
|
262
265
|
.if n .RS 4
|
263
266
|
.nf
|
267
|
+
.fam C
|
264
268
|
#{manify node.content, whitespace: :preserve}
|
269
|
+
.fam
|
265
270
|
.fi
|
266
271
|
.if n .RE)
|
267
272
|
result.join LF
|
@@ -284,15 +289,16 @@ r lw(\n(.lu*75u/100u).'
|
|
284
289
|
.B #{manify node.title}
|
285
290
|
.br) if node.title?
|
286
291
|
|
292
|
+
start = (node.attr 'start', 1).to_i
|
287
293
|
node.items.each_with_index do |item, idx|
|
288
294
|
result << %(.sp
|
289
295
|
.RS 4
|
290
296
|
.ie n \\{\\
|
291
|
-
\\h'-04' #{idx +
|
297
|
+
\\h'-04' #{numeral = idx + start}.\\h'+01'\\c
|
292
298
|
.\\}
|
293
299
|
.el \\{\\
|
294
300
|
. sp -1
|
295
|
-
. IP " #{
|
301
|
+
. IP " #{numeral}." 4.2
|
296
302
|
.\\}
|
297
303
|
#{manify item.text, whitespace: :normalize})
|
298
304
|
result << item.content if item.blocks?
|
@@ -310,8 +316,9 @@ r lw(\n(.lu*75u/100u).'
|
|
310
316
|
end
|
311
317
|
end
|
312
318
|
|
313
|
-
|
314
|
-
|
319
|
+
def convert_page_break node
|
320
|
+
'.bp'
|
321
|
+
end
|
315
322
|
|
316
323
|
def convert_paragraph node
|
317
324
|
if node.title?
|
@@ -526,12 +533,13 @@ allbox tab(:);'
|
|
526
533
|
result.join LF
|
527
534
|
end
|
528
535
|
|
529
|
-
# FIXME git uses [verse] for the synopsis; detect this special case
|
530
536
|
def convert_verse node
|
531
537
|
result = []
|
532
|
-
|
538
|
+
if node.title?
|
539
|
+
result << %(.sp
|
533
540
|
.B #{manify node.title}
|
534
|
-
.br)
|
541
|
+
.br)
|
542
|
+
end
|
535
543
|
attribution_line = (node.attr? 'citetitle') ? %(#{node.attr 'citetitle'} ) : nil
|
536
544
|
attribution_line = (node.attr? 'attribution') ? %[#{attribution_line}\\(em #{node.attr 'attribution'}] : nil
|
537
545
|
result << %(.sp
|
@@ -580,11 +588,7 @@ allbox tab(:);'
|
|
580
588
|
when :xref
|
581
589
|
unless (text = node.text)
|
582
590
|
refid = node.attributes['refid']
|
583
|
-
|
584
|
-
text = (ref.xreftext node.attr('xrefstyle', nil, true)) || %([#{refid}])
|
585
|
-
else
|
586
|
-
text = %([#{refid}])
|
587
|
-
end
|
591
|
+
text = %([#{refid}]) unless AbstractNode === (ref = (@refs ||= node.document.catalog[:refs])[refid]) && (@resolving_xref ||= outer = true) && outer && (text = ref.xreftext node.attr 'xrefstyle', nil, true)
|
588
592
|
end
|
589
593
|
text
|
590
594
|
when :ref, :bibref
|
@@ -601,14 +605,13 @@ allbox tab(:);'
|
|
601
605
|
end
|
602
606
|
|
603
607
|
def convert_inline_button node
|
604
|
-
%(
|
608
|
+
%(<#{ESC_BS}fB>[#{ESC_BS}0#{node.text}#{ESC_BS}0]</#{ESC_BS}fP>)
|
605
609
|
end
|
606
610
|
|
607
611
|
def convert_inline_callout node
|
608
|
-
%(
|
612
|
+
%(<#{ESC_BS}fB>(#{node.text})<#{ESC_BS}fP>)
|
609
613
|
end
|
610
614
|
|
611
|
-
# TODO supposedly groff has footnotes, but we're in search of an example
|
612
615
|
def convert_inline_footnote node
|
613
616
|
if (index = node.attr 'index')
|
614
617
|
%([#{index}])
|
@@ -626,39 +629,35 @@ allbox tab(:);'
|
|
626
629
|
end
|
627
630
|
|
628
631
|
def convert_inline_kbd node
|
629
|
-
|
630
|
-
keys[0]
|
631
|
-
else
|
632
|
-
keys.join %(#{ESC_BS}0+#{ESC_BS}0)
|
633
|
-
end
|
632
|
+
%[<#{ESC_BS}f(CR>#{(keys = node.attr 'keys').size == 1 ? keys[0] : (keys.join "#{ESC_BS}0+#{ESC_BS}0")}</#{ESC_BS}fP>]
|
634
633
|
end
|
635
634
|
|
636
635
|
def convert_inline_menu node
|
637
636
|
caret = %[#{ESC_BS}0#{ESC_BS}(fc#{ESC_BS}0]
|
638
637
|
menu = node.attr 'menu'
|
639
638
|
if !(submenus = node.attr 'submenus').empty?
|
640
|
-
submenu_path = submenus.map {|item| %(
|
641
|
-
%(
|
639
|
+
submenu_path = submenus.map {|item| %(<#{ESC_BS}fI>#{item}</#{ESC_BS}fP>) }.join caret
|
640
|
+
%(<#{ESC_BS}fI>#{menu}</#{ESC_BS}fP>#{caret}#{submenu_path}#{caret}<#{ESC_BS}fI>#{node.attr 'menuitem'}</#{ESC_BS}fP>)
|
642
641
|
elsif (menuitem = node.attr 'menuitem')
|
643
|
-
%(
|
642
|
+
%(<#{ESC_BS}fI>#{menu}#{caret}#{menuitem}</#{ESC_BS}fP>)
|
644
643
|
else
|
645
|
-
%(
|
644
|
+
%(<#{ESC_BS}fI>#{menu}</#{ESC_BS}fP>)
|
646
645
|
end
|
647
646
|
end
|
648
647
|
|
649
|
-
# NOTE use fake
|
648
|
+
# NOTE use fake XML elements to prevent creating artificial word boundaries
|
650
649
|
def convert_inline_quoted node
|
651
650
|
case node.type
|
652
651
|
when :emphasis
|
653
|
-
%(
|
652
|
+
%(<#{ESC_BS}fI>#{node.text}</#{ESC_BS}fP>)
|
654
653
|
when :strong
|
655
|
-
%(
|
654
|
+
%(<#{ESC_BS}fB>#{node.text}</#{ESC_BS}fP>)
|
656
655
|
when :monospaced
|
657
|
-
%[
|
656
|
+
%[<#{ESC_BS}f(CR>#{node.text}</#{ESC_BS}fP>]
|
658
657
|
when :single
|
659
|
-
%[
|
658
|
+
%[<#{ESC_BS}(oq>#{node.text}</#{ESC_BS}(cq>]
|
660
659
|
when :double
|
661
|
-
%[
|
660
|
+
%[<#{ESC_BS}(lq>#{node.text}</#{ESC_BS}(rq>]
|
662
661
|
else
|
663
662
|
node.text
|
664
663
|
end
|
@@ -677,6 +676,22 @@ allbox tab(:);'
|
|
677
676
|
|
678
677
|
private
|
679
678
|
|
679
|
+
def append_footnotes result, node
|
680
|
+
if node.footnotes? && !(node.attr? 'nofootnotes')
|
681
|
+
result << '.SH "NOTES"'
|
682
|
+
node.footnotes.each_with_index do |fn, idx|
|
683
|
+
result << %(.IP [#{fn.index}])
|
684
|
+
# NOTE restore newline in escaped macro that gets removed by normalize_text in substitutor
|
685
|
+
if (text = fn.text).include? %(#{ESC}\\c #{ESC}.)
|
686
|
+
text = (manify %(#{text.gsub MalformedEscapedMacroRx, %(\\1#{LF}\\2)} ), whitespace: :normalize).chomp ' '
|
687
|
+
else
|
688
|
+
text = manify text, whitespace: :normalize
|
689
|
+
end
|
690
|
+
result << text
|
691
|
+
end
|
692
|
+
end
|
693
|
+
end
|
694
|
+
|
680
695
|
# Converts HTML entity references back to their original form, escapes
|
681
696
|
# special man characters and strips trailing whitespace.
|
682
697
|
#
|
@@ -699,10 +714,11 @@ allbox tab(:);'
|
|
699
714
|
str = str.tr_s WHITESPACE, ' '
|
700
715
|
end
|
701
716
|
str = str.
|
702
|
-
gsub(LiteralBackslashRx
|
717
|
+
gsub(LiteralBackslashRx) { $1 ? $& : '\\(rs' }. # literal backslash (not a troff escape sequence)
|
718
|
+
gsub(EllipsisCharRefRx, '...'). # horizontal ellipsis
|
703
719
|
gsub(LeadingPeriodRx, '\\\&.'). # leading . is used in troff for macro call or other formatting; replace with \&.
|
704
720
|
# drop orphaned \c escape lines, unescape troff macro, quote adjacent character, isolate macro line
|
705
|
-
gsub(EscapedMacroRx) { (rest = $3.lstrip).empty? ? %(.#$1"#$2") : %(.#$1"
|
721
|
+
gsub(EscapedMacroRx) { (rest = $3.lstrip).empty? ? %(.#$1"#$2") : %(.#$1"#{$2.rstrip}"#{LF}#{rest}) }.
|
706
722
|
gsub('-', '\-').
|
707
723
|
gsub('<', '<').
|
708
724
|
gsub('>', '>').
|
@@ -717,21 +733,24 @@ allbox tab(:);'
|
|
717
733
|
gsub('’', '\(cq'). # right single quotation mark
|
718
734
|
gsub('“', '\(lq'). # left double quotation mark
|
719
735
|
gsub('”', '\(rq'). # right double quotation mark
|
720
|
-
gsub(EllipsisCharRefRx, '...'). # horizontal ellipsis
|
721
736
|
gsub('←', '\(<-'). # leftwards arrow
|
722
737
|
gsub('→', '\(->'). # rightwards arrow
|
723
738
|
gsub('⇐', '\(lA'). # leftwards double arrow
|
724
739
|
gsub('⇒', '\(rA'). # rightwards double arrow
|
725
740
|
gsub('​', '\:'). # zero width space
|
726
|
-
gsub('&','&').
|
741
|
+
gsub('&', '&'). # literal ampersand (NOTE must take place after any other replacement that includes &)
|
727
742
|
gsub('\'', '\(aq'). # apostrophe-quote
|
728
|
-
gsub(
|
743
|
+
gsub(MockMacroRx, '\1'). # mock boundary
|
729
744
|
gsub(ESC_BS, '\\'). # unescape troff backslash (NOTE update if more escapes are added)
|
730
745
|
gsub(ESC_FS, '.'). # unescape full stop in troff commands (NOTE must take place after gsub(LeadingPeriodRx))
|
731
746
|
rstrip # strip trailing space
|
732
747
|
opts[:append_newline] ? %(#{str}#{LF}) : str
|
733
748
|
end
|
734
749
|
|
750
|
+
def uppercase_pcdata string
|
751
|
+
(XMLMarkupRx.match? string) ? string.gsub(PCDATAFilterRx) { $2 ? $2.upcase : $1 } : string.upcase
|
752
|
+
end
|
753
|
+
|
735
754
|
def enclose_content node
|
736
755
|
node.content_model == :compound ? node.content : %(.sp#{LF}#{manify node.content, whitespace: :normalize})
|
737
756
|
end
|