asciidoctor 1.5.1 → 1.5.2
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 +85 -0
- data/README.adoc +79 -6
- data/Rakefile +22 -1
- data/benchmark/benchmark.rb +3 -1
- data/compat/asciidoc.conf +2 -2
- data/data/stylesheets/asciidoctor-default.css +3 -3
- data/features/step_definitions.rb +12 -1
- data/lib/asciidoctor.rb +74 -42
- data/lib/asciidoctor/abstract_block.rb +5 -1
- data/lib/asciidoctor/abstract_node.rb +40 -23
- data/lib/asciidoctor/attribute_list.rb +14 -5
- data/lib/asciidoctor/block.rb +45 -12
- data/lib/asciidoctor/callouts.rb +1 -0
- data/lib/asciidoctor/cli/invoker.rb +6 -2
- data/lib/asciidoctor/cli/options.rb +23 -9
- data/lib/asciidoctor/converter.rb +9 -19
- data/lib/asciidoctor/converter/base.rb +11 -14
- data/lib/asciidoctor/converter/composite.rb +8 -19
- data/lib/asciidoctor/converter/docbook45.rb +1 -0
- data/lib/asciidoctor/converter/docbook5.rb +24 -2
- data/lib/asciidoctor/converter/factory.rb +1 -0
- data/lib/asciidoctor/converter/html5.rb +61 -25
- data/lib/asciidoctor/converter/template.rb +19 -26
- data/lib/asciidoctor/document.rb +73 -45
- data/lib/asciidoctor/extensions.rb +121 -9
- data/lib/asciidoctor/helpers.rb +1 -0
- data/lib/asciidoctor/inline.rb +1 -0
- data/lib/asciidoctor/list.rb +1 -0
- data/lib/asciidoctor/opal_ext.rb +22 -0
- data/lib/asciidoctor/opal_ext/file.rb +26 -13
- data/lib/asciidoctor/parser.rb +15 -18
- data/lib/asciidoctor/path_resolver.rb +18 -0
- data/lib/asciidoctor/reader.rb +8 -9
- data/lib/asciidoctor/section.rb +5 -8
- data/lib/asciidoctor/stylesheets.rb +1 -0
- data/lib/asciidoctor/substitutors.rb +18 -18
- data/lib/asciidoctor/table.rb +2 -1
- data/lib/asciidoctor/timings.rb +1 -0
- data/lib/asciidoctor/version.rb +1 -1
- data/man/asciidoctor.1 +10 -11
- data/man/asciidoctor.adoc +80 -99
- data/test/attributes_test.rb +42 -0
- data/test/blocks_test.rb +19 -7
- data/test/document_test.rb +114 -0
- data/test/extensions_test.rb +100 -0
- data/test/fixtures/custom-docinfodir/basic-docinfo.html +1 -0
- data/test/fixtures/custom-docinfodir/docinfo.html +1 -0
- data/test/fixtures/hello-asciidoctor.pdf +0 -0
- data/test/invoker_test.rb +4 -3
- data/test/lists_test.rb +31 -5
- data/test/options_test.rb +1 -1
- data/test/paths_test.rb +21 -0
- data/test/preamble_test.rb +33 -0
- data/test/reader_test.rb +13 -0
- data/test/sections_test.rb +22 -0
- data/test/substitutions_test.rb +49 -0
- data/test/tables_test.rb +76 -0
- data/test/test_helper.rb +4 -2
- metadata +7 -5
- data/lib/asciidoctor/debug.rb +0 -25
@@ -1,3 +1,4 @@
|
|
1
|
+
# encoding: UTF-8
|
1
2
|
module Asciidoctor
|
2
3
|
# A {Converter} implementation that delegates to the chain of {Converter}
|
3
4
|
# objects passed to the constructor. Selects the first {Converter} that
|
@@ -15,20 +16,6 @@ module Asciidoctor
|
|
15
16
|
@converter_map = {}
|
16
17
|
end
|
17
18
|
|
18
|
-
# Public: Delegates to the first converter that identifies itself as the
|
19
|
-
# handler for the given transform.
|
20
|
-
#
|
21
|
-
# node - the AbstractNode to convert
|
22
|
-
# transform - the optional String transform, or the name of the node if no
|
23
|
-
# transform is specified. (default: nil)
|
24
|
-
#
|
25
|
-
# Returns the String result returned from the delegate's convert method
|
26
|
-
def convert node, transform = nil
|
27
|
-
transform ||= node.node_name
|
28
|
-
# QUESTION is there a way we can control whether to use convert or send?
|
29
|
-
(converter_for transform).convert node, transform
|
30
|
-
end
|
31
|
-
|
32
19
|
# Public: Delegates to the first converter that identifies itself as the
|
33
20
|
# handler for the given transform. The optional Hash is passed as the last
|
34
21
|
# option to the delegate's convert method.
|
@@ -36,20 +23,22 @@ module Asciidoctor
|
|
36
23
|
# node - the AbstractNode to convert
|
37
24
|
# transform - the optional String transform, or the name of the node if no
|
38
25
|
# transform is specified. (default: nil)
|
39
|
-
# opts -
|
26
|
+
# opts - an optional Hash that is passed to the delegate's convert method. (default: {})
|
40
27
|
#
|
41
28
|
# Returns the String result returned from the delegate's convert method
|
42
|
-
def
|
29
|
+
def convert node, transform = nil, opts = {}
|
43
30
|
transform ||= node.node_name
|
44
|
-
|
45
|
-
(converter_for transform).convert_with_options node, transform, opts
|
31
|
+
(converter_for transform).convert node, transform, opts
|
46
32
|
end
|
47
33
|
|
34
|
+
# Alias for backward compatibility.
|
35
|
+
alias :convert_with_options :convert
|
36
|
+
|
48
37
|
# Public: Retrieve the converter for the specified transform.
|
49
38
|
#
|
50
39
|
# Returns the matching [Converter] object
|
51
40
|
def converter_for transform
|
52
|
-
@converter_map[transform] ||= find_converter transform
|
41
|
+
@converter_map[transform] ||= (find_converter transform)
|
53
42
|
end
|
54
43
|
|
55
44
|
# Internal: Find the converter for the specified transform.
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# encoding: UTF-8
|
1
2
|
module Asciidoctor
|
2
3
|
# A built-in {Converter} implementation that generates DocBook 5 output
|
3
4
|
# similar to the docbook45 backend from AsciiDoc Python, but migrated to the
|
@@ -5,7 +6,9 @@ module Asciidoctor
|
|
5
6
|
class Converter::DocBook5Converter < Converter::BuiltIn
|
6
7
|
def document node
|
7
8
|
result = []
|
8
|
-
root_tag_name = node.doctype
|
9
|
+
if (root_tag_name = node.doctype) == 'manpage'
|
10
|
+
root_tag_name = 'refentry'
|
11
|
+
end
|
9
12
|
result << '<?xml version="1.0" encoding="UTF-8"?>'
|
10
13
|
if (doctype_line = doctype_declaration root_tag_name)
|
11
14
|
result << doctype_line
|
@@ -39,10 +42,18 @@ module Asciidoctor
|
|
39
42
|
alias :embedded :content
|
40
43
|
|
41
44
|
def section node
|
45
|
+
doctype = node.document.doctype
|
42
46
|
tag_name = if node.special
|
43
47
|
node.level <= 1 ? node.sectname : 'section'
|
44
48
|
else
|
45
|
-
|
49
|
+
doctype == 'book' && node.level <= 1 ? (node.level == 0 ? 'part' : 'chapter') : 'section'
|
50
|
+
end
|
51
|
+
if doctype == 'manpage'
|
52
|
+
if tag_name == 'section'
|
53
|
+
tag_name = 'refsection'
|
54
|
+
elsif tag_name == 'synopsis'
|
55
|
+
tag_name = 'refsynopsisdiv'
|
56
|
+
end
|
46
57
|
end
|
47
58
|
%(<#{tag_name}#{common_attributes node.id, node.role, node.reftext}>
|
48
59
|
<title>#{node.title}</title>
|
@@ -668,6 +679,17 @@ module Asciidoctor
|
|
668
679
|
end
|
669
680
|
result << %(</#{info_tag_prefix}info>)
|
670
681
|
|
682
|
+
if doc.doctype == 'manpage'
|
683
|
+
result << '<refmeta>'
|
684
|
+
result << %(<refentrytitle>#{doc.attr 'mantitle'}</refentrytitle>) if doc.attr? 'mantitle'
|
685
|
+
result << %(<manvolnum>#{doc.attr 'manvolnum'}</manvolnum>) if doc.attr? 'manvolnum'
|
686
|
+
result << '</refmeta>'
|
687
|
+
result << '<refnamediv>'
|
688
|
+
result << %(<refname>#{doc.attr 'manname'}</refname>) if doc.attr? 'manname'
|
689
|
+
result << %(<refpurpose>#{doc.attr 'manpurpose'}</refpurpose>) if doc.attr? 'manpurpose'
|
690
|
+
result << '</refnamediv>'
|
691
|
+
end
|
692
|
+
|
671
693
|
result * EOL
|
672
694
|
end
|
673
695
|
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# encoding: UTF-8
|
1
2
|
module Asciidoctor
|
2
3
|
# A built-in {Converter} implementation that generates HTML 5 output
|
3
4
|
# consistent with the html5 backend from AsciiDoc Python.
|
@@ -48,13 +49,13 @@ module Asciidoctor
|
|
48
49
|
result << %(<meta name="author" content="#{node.attr 'authors'}"#{slash}>) if node.attr? 'authors'
|
49
50
|
result << %(<meta name="copyright" content="#{node.attr 'copyright'}"#{slash}>) if node.attr? 'copyright'
|
50
51
|
|
51
|
-
result << %(<title>#{node.doctitle :sanitize => true, :use_fallback => true}</title>)
|
52
|
+
result << %(<title>#{node.doctitle :sanitize => true, :use_fallback => true}</title>)
|
52
53
|
if DEFAULT_STYLESHEET_KEYS.include?(node.attr 'stylesheet')
|
53
54
|
if (webfonts = node.attr 'webfonts')
|
54
|
-
result << %(<link rel="stylesheet" href="#{asset_uri_scheme}//fonts.googleapis.com/css?family=#{webfonts.empty? ? 'Open+Sans:300,300italic,400,400italic,600,600italic
|
55
|
+
result << %(<link rel="stylesheet" href="#{asset_uri_scheme}//fonts.googleapis.com/css?family=#{webfonts.empty? ? 'Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400' : webfonts}"#{slash}>)
|
55
56
|
end
|
56
57
|
if linkcss
|
57
|
-
result << %(<link rel="stylesheet" href="#{node.normalize_web_path DEFAULT_STYLESHEET_NAME, (node.attr 'stylesdir', '')}"#{slash}>)
|
58
|
+
result << %(<link rel="stylesheet" href="#{node.normalize_web_path DEFAULT_STYLESHEET_NAME, (node.attr 'stylesdir', ''), false}"#{slash}>)
|
58
59
|
else
|
59
60
|
result << @stylesheets.embed_primary_stylesheet
|
60
61
|
end
|
@@ -63,17 +64,17 @@ module Asciidoctor
|
|
63
64
|
result << %(<link rel="stylesheet" href="#{node.normalize_web_path((node.attr 'stylesheet'), (node.attr 'stylesdir', ''))}"#{slash}>)
|
64
65
|
else
|
65
66
|
result << %(<style>
|
66
|
-
#{node.read_asset node.normalize_system_path((node.attr 'stylesheet'), (node.attr 'stylesdir', '')), true}
|
67
|
+
#{node.read_asset node.normalize_system_path((node.attr 'stylesheet'), (node.attr 'stylesdir', '')), :warn_on_failure => true}
|
67
68
|
</style>)
|
68
69
|
end
|
69
70
|
end
|
70
71
|
|
71
72
|
if node.attr? 'icons', 'font'
|
72
73
|
if node.attr? 'iconfont-remote'
|
73
|
-
result << %(<link rel="stylesheet" href="#{node.attr 'iconfont-cdn', %[#{cdn_base}/font-awesome/4.
|
74
|
+
result << %(<link rel="stylesheet" href="#{node.attr 'iconfont-cdn', %[#{cdn_base}/font-awesome/4.2.0/css/font-awesome.min.css]}"#{slash}>)
|
74
75
|
else
|
75
76
|
iconfont_stylesheet = %(#{node.attr 'iconfont-name', 'font-awesome'}.css)
|
76
|
-
result << %(<link rel="stylesheet" href="#{node.normalize_web_path iconfont_stylesheet, (node.attr 'stylesdir', '')}"#{slash}>)
|
77
|
+
result << %(<link rel="stylesheet" href="#{node.normalize_web_path iconfont_stylesheet, (node.attr 'stylesdir', ''), false}"#{slash}>)
|
77
78
|
end
|
78
79
|
end
|
79
80
|
|
@@ -81,7 +82,7 @@ module Asciidoctor
|
|
81
82
|
when 'coderay'
|
82
83
|
if (node.attr 'coderay-css', 'class') == 'class'
|
83
84
|
if linkcss
|
84
|
-
result << %(<link rel="stylesheet" href="#{node.normalize_web_path @stylesheets.coderay_stylesheet_name, (node.attr 'stylesdir', '')}"#{slash}>)
|
85
|
+
result << %(<link rel="stylesheet" href="#{node.normalize_web_path @stylesheets.coderay_stylesheet_name, (node.attr 'stylesdir', ''), false}"#{slash}>)
|
85
86
|
else
|
86
87
|
result << @stylesheets.embed_coderay_stylesheet
|
87
88
|
end
|
@@ -90,13 +91,13 @@ module Asciidoctor
|
|
90
91
|
if (node.attr 'pygments-css', 'class') == 'class'
|
91
92
|
pygments_style = node.attr 'pygments-style'
|
92
93
|
if linkcss
|
93
|
-
result << %(<link rel="stylesheet" href="#{node.normalize_web_path @stylesheets.pygments_stylesheet_name(pygments_style), (node.attr 'stylesdir', '')}"#{slash}>)
|
94
|
+
result << %(<link rel="stylesheet" href="#{node.normalize_web_path @stylesheets.pygments_stylesheet_name(pygments_style), (node.attr 'stylesdir', ''), false}"#{slash}>)
|
94
95
|
else
|
95
96
|
result << (@stylesheets.embed_pygments_stylesheet pygments_style)
|
96
97
|
end
|
97
98
|
end
|
98
99
|
when 'highlightjs', 'highlight.js'
|
99
|
-
highlightjs_path = node.attr 'highlightjsdir', %(#{cdn_base}/highlight.js/8.
|
100
|
+
highlightjs_path = node.attr 'highlightjsdir', %(#{cdn_base}/highlight.js/8.4)
|
100
101
|
result << %(<link rel="stylesheet" href="#{highlightjs_path}/styles/#{node.attr 'highlightjs-theme', 'github'}.min.css"#{slash}>
|
101
102
|
<script src="#{highlightjs_path}/highlight.min.js"></script>
|
102
103
|
<script>hljs.initHighlightingOnLoad()</script>)
|
@@ -108,17 +109,22 @@ module Asciidoctor
|
|
108
109
|
end
|
109
110
|
|
110
111
|
if node.attr? 'stem'
|
112
|
+
# IMPORTANT to_s calls on delimiter arrays are intentional for JavaScript compat (emulates JSON.stringify)
|
113
|
+
eqnums_val = node.attr 'eqnums', 'none'
|
114
|
+
eqnums_val = 'AMS' if eqnums_val == ''
|
115
|
+
eqnums_opt = %( equationNumbers: { autoNumber: "#{eqnums_val}" } )
|
111
116
|
result << %(<script type="text/x-mathjax-config">
|
112
117
|
MathJax.Hub.Config({
|
113
118
|
tex2jax: {
|
114
|
-
inlineMath: [#{INLINE_MATH_DELIMITERS[:latexmath]}],
|
115
|
-
displayMath: [#{BLOCK_MATH_DELIMITERS[:latexmath]}],
|
119
|
+
inlineMath: [#{INLINE_MATH_DELIMITERS[:latexmath].to_s}],
|
120
|
+
displayMath: [#{BLOCK_MATH_DELIMITERS[:latexmath].to_s}],
|
116
121
|
ignoreClass: "nostem|nolatexmath"
|
117
122
|
},
|
118
123
|
asciimath2jax: {
|
119
|
-
delimiters: [#{BLOCK_MATH_DELIMITERS[:asciimath]}],
|
124
|
+
delimiters: [#{BLOCK_MATH_DELIMITERS[:asciimath].to_s}],
|
120
125
|
ignoreClass: "nostem|noasciimath"
|
121
|
-
}
|
126
|
+
},
|
127
|
+
TeX: {#{eqnums_opt}}
|
122
128
|
});
|
123
129
|
</script>
|
124
130
|
<script src="#{cdn_base}/mathjax/2.4.0/MathJax.js?config=TeX-MML-AM_HTMLorMML"></script>)
|
@@ -505,7 +511,7 @@ Your browser does not support the audio tag.
|
|
505
511
|
|
506
512
|
def image node
|
507
513
|
align = (node.attr? 'align') ? (node.attr 'align') : nil
|
508
|
-
float = (node.attr? 'float') ? (node.attr 'float') : nil
|
514
|
+
float = (node.attr? 'float') ? (node.attr 'float') : nil
|
509
515
|
style_attribute = if align || float
|
510
516
|
styles = [align ? %(text-align: #{align}) : nil, float ? %(float: #{float}) : nil].compact
|
511
517
|
%( style="#{styles * ';'}")
|
@@ -591,7 +597,7 @@ Your browser does not support the audio tag.
|
|
591
597
|
unless ((equation = node.content).start_with? open) && (equation.end_with? close)
|
592
598
|
equation = %(#{open}#{equation}#{close})
|
593
599
|
end
|
594
|
-
|
600
|
+
|
595
601
|
%(<div#{id_attribute} class="#{(role = node.role) ? ['stemblock', role] * ' ' : 'stemblock'}">
|
596
602
|
#{title_element}<div class="content">
|
597
603
|
#{equation}
|
@@ -733,7 +739,7 @@ Your browser does not support the audio tag.
|
|
733
739
|
end
|
734
740
|
|
735
741
|
def table node
|
736
|
-
result = []
|
742
|
+
result = []
|
737
743
|
id_attribute = node.id ? %( id="#{node.id}") : nil
|
738
744
|
classes = ['tableblock', %(frame-#{node.attr 'frame', 'all'}), %(grid-#{node.attr 'grid', 'all'})]
|
739
745
|
styles = []
|
@@ -905,30 +911,60 @@ Your browser does not support the audio tag.
|
|
905
911
|
height_attribute = (node.attr? 'height') ? %( height="#{node.attr 'height'}") : nil
|
906
912
|
case node.attr 'poster'
|
907
913
|
when 'vimeo'
|
908
|
-
start_anchor = (node.attr? 'start') ?
|
914
|
+
start_anchor = (node.attr? 'start', nil, false) ? %(#at=#{node.attr 'start'}) : nil
|
909
915
|
delimiter = '?'
|
910
|
-
autoplay_param = (node.option? 'autoplay') ?
|
916
|
+
autoplay_param = (node.option? 'autoplay') ? %(#{delimiter}autoplay=1) : nil
|
911
917
|
delimiter = '&' if autoplay_param
|
912
|
-
loop_param = (node.option? 'loop') ?
|
918
|
+
loop_param = (node.option? 'loop') ? %(#{delimiter}loop=1) : nil
|
913
919
|
%(<div#{id_attribute}#{class_attribute}>#{title_element}
|
914
920
|
<div class="content">
|
915
|
-
<iframe#{width_attribute}#{height_attribute} src="//player.vimeo.com/video/#{node.attr 'target'}#{start_anchor}#{autoplay_param}#{loop_param}" frameborder="0"#{
|
921
|
+
<iframe#{width_attribute}#{height_attribute} src="//player.vimeo.com/video/#{node.attr 'target'}#{start_anchor}#{autoplay_param}#{loop_param}" frameborder="0"#{(node.option? 'nofullscreen') ? nil : (append_boolean_attribute 'allowfullscreen', xml)}></iframe>
|
916
922
|
</div>
|
917
923
|
</div>)
|
918
924
|
when 'youtube'
|
919
|
-
|
920
|
-
|
925
|
+
rel_param_val = (node.option? 'related') ? 1 : 0
|
926
|
+
start_param = (node.attr? 'start', nil, false) ? %(&start=#{node.attr 'start'}) : nil
|
927
|
+
end_param = (node.attr? 'end', nil, false) ? %(&end=#{node.attr 'end'}) : nil
|
921
928
|
autoplay_param = (node.option? 'autoplay') ? '&autoplay=1' : nil
|
922
929
|
loop_param = (node.option? 'loop') ? '&loop=1' : nil
|
923
930
|
controls_param = (node.option? 'nocontrols') ? '&controls=0' : nil
|
931
|
+
# cover both ways of controlling fullscreen option
|
932
|
+
if node.option? 'nofullscreen'
|
933
|
+
fs_param = '&fs=0'
|
934
|
+
fs_attribute = nil
|
935
|
+
else
|
936
|
+
fs_param = nil
|
937
|
+
fs_attribute = append_boolean_attribute 'allowfullscreen', xml
|
938
|
+
end
|
939
|
+
modest_param = (node.option? 'modest') ? '&modestbranding=1' : nil
|
940
|
+
theme_param = (node.attr? 'theme', nil, false) ? %(&theme=#{node.attr 'theme'}) : nil
|
941
|
+
hl_param = (node.attr? 'lang') ? %(&hl=#{node.attr 'lang'}) : nil
|
942
|
+
|
943
|
+
# parse video_id/list_id syntax where list_id (i.e., playlist) is optional
|
944
|
+
target, list = (node.attr 'target').split '/', 2
|
945
|
+
if (list ||= (node.attr 'list', nil, false))
|
946
|
+
list_param = %(&list=#{list})
|
947
|
+
else
|
948
|
+
# parse dynamic playlist syntax: video_id1,video_id2,...
|
949
|
+
target, playlist = target.split ',', 2
|
950
|
+
if (playlist ||= (node.attr 'playlist', nil, false))
|
951
|
+
# INFO playlist bar doesn't appear in Firefox unless showinfo=1 and modestbranding=1
|
952
|
+
list_param = %(&playlist=#{playlist})
|
953
|
+
else
|
954
|
+
list_param = nil
|
955
|
+
end
|
956
|
+
end
|
957
|
+
|
924
958
|
%(<div#{id_attribute}#{class_attribute}>#{title_element}
|
925
959
|
<div class="content">
|
926
|
-
<iframe#{width_attribute}#{height_attribute} src="//www.youtube.com/embed/#{
|
960
|
+
<iframe#{width_attribute}#{height_attribute} src="//www.youtube.com/embed/#{target}?rel=#{rel_param_val}#{start_param}#{end_param}#{autoplay_param}#{loop_param}#{controls_param}#{list_param}#{fs_param}#{modest_param}#{theme_param}#{hl_param}" frameborder="0"#{fs_attribute}></iframe>
|
927
961
|
</div>
|
928
962
|
</div>)
|
929
|
-
else
|
963
|
+
else
|
930
964
|
poster_attribute = %(#{poster = node.attr 'poster'}).empty? ? nil : %( poster="#{node.media_uri poster}")
|
931
|
-
|
965
|
+
start_t = node.attr 'start', nil, false
|
966
|
+
end_t = node.attr 'end', nil, false
|
967
|
+
time_anchor = (start_t || end_t) ? %(#t=#{start_t}#{end_t ? ',' : nil}#{end_t}) : nil
|
932
968
|
%(<div#{id_attribute}#{class_attribute}>#{title_element}
|
933
969
|
<div class="content">
|
934
970
|
<video src="#{node.media_uri(node.attr 'target')}#{time_anchor}"#{width_attribute}#{height_attribute}#{poster_attribute}#{(node.option? 'autoplay') ? (append_boolean_attribute 'autoplay', xml) : nil}#{(node.option? 'nocontrols') ? nil : (append_boolean_attribute 'controls', xml)}#{(node.option? 'loop') ? (append_boolean_attribute 'loop', xml) : nil}>
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# encoding: UTF-8
|
1
2
|
module Asciidoctor
|
2
3
|
# A {Converter} implementation that uses templates composed in template
|
3
4
|
# languages supported by {https://github.com/rtomayko/tilt Tilt} to convert
|
@@ -166,40 +167,28 @@ module Asciidoctor
|
|
166
167
|
# template_name - the String name of the template to use, or the value of
|
167
168
|
# the node_name property on the node if a template name is
|
168
169
|
# not specified. (optional, default: nil)
|
170
|
+
# opts - an optional Hash that is passed as local variables to the
|
171
|
+
# template. (optional, default: {})
|
169
172
|
#
|
170
173
|
# Returns the [String] result from rendering the template
|
171
|
-
def convert node, template_name = nil
|
174
|
+
def convert node, template_name = nil, opts = {}
|
172
175
|
template_name ||= node.node_name
|
173
176
|
unless (template = @templates[template_name])
|
174
177
|
raise %(Could not find a custom template to handle transform: #{template_name})
|
175
178
|
end
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
179
|
+
|
180
|
+
# Slim doesn't include helpers in the template's execution scope such as
|
181
|
+
# HAML, so we must do it ourselves.
|
182
|
+
if (defined? ::Slim::Helpers) && (template.is_a? ::Slim::Template)
|
183
|
+
node.extend ::Slim::Helpers
|
180
184
|
end
|
181
|
-
end
|
182
185
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
#
|
189
|
-
# node - the AbstractNode to convert
|
190
|
-
# template_name - the String name of the template to use, or the value of
|
191
|
-
# the node_name property on the node if a template name is
|
192
|
-
# not specified. (optional, default: nil)
|
193
|
-
# opts - an optional Hash that is passed as local variables to the
|
194
|
-
# template. (optional, default: {})
|
195
|
-
#
|
196
|
-
# Returns the [String] result from rendering the template
|
197
|
-
def convert_with_options node, template_name = nil, opts = {}
|
198
|
-
template_name ||= node.node_name
|
199
|
-
unless (template = @templates[template_name])
|
200
|
-
raise %(Could not find a custom template to handle transform: #{template_name})
|
186
|
+
# NOTE opts become locals in the template
|
187
|
+
if template_name == 'document'
|
188
|
+
(template.render node, opts).strip
|
189
|
+
else
|
190
|
+
(template.render node, opts).chomp
|
201
191
|
end
|
202
|
-
(template.render node, opts).chomp
|
203
192
|
end
|
204
193
|
|
205
194
|
# Public: Checks whether there is a Tilt template registered with the specified name.
|
@@ -260,8 +249,12 @@ module Asciidoctor
|
|
260
249
|
if ext_name == 'slim'
|
261
250
|
# slim doesn't get loaded by Tilt, so we have to load it explicitly
|
262
251
|
Helpers.require_library 'slim' unless defined? ::Slim
|
252
|
+
# load include plugin when using Slim >= 2.1
|
253
|
+
unless ::Slim::VERSION < '2.1' || (defined? ::Slim::Include)
|
254
|
+
Helpers.require_library 'slim/include', false
|
255
|
+
end
|
263
256
|
elsif ext_name == 'erb'
|
264
|
-
template_class, extra_engine_options = (eruby_loaded ||= load_eruby
|
257
|
+
template_class, extra_engine_options = (eruby_loaded ||= load_eruby(@eruby))
|
265
258
|
end
|
266
259
|
next unless ::Tilt.registered? ext_name
|
267
260
|
unless template_cache && (template = template_cache[file])
|
data/lib/asciidoctor/document.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# encoding: UTF-8
|
1
2
|
module Asciidoctor
|
2
3
|
# Public: Methods for parsing and converting AsciiDoc documents.
|
3
4
|
#
|
@@ -133,6 +134,9 @@ class Document < AbstractBlock
|
|
133
134
|
# If the source is a string, defaults to the current directory.
|
134
135
|
attr_reader :base_dir
|
135
136
|
|
137
|
+
# Public: Get the Hash of resolved options used to initialize this Document
|
138
|
+
attr_reader :options
|
139
|
+
|
136
140
|
# Public: Get a reference to the parent Document of this nested document.
|
137
141
|
attr_reader :parent_document
|
138
142
|
|
@@ -151,6 +155,8 @@ class Document < AbstractBlock
|
|
151
155
|
# options - A Hash of options to control processing (e.g., safe mode value (:safe), backend (:backend),
|
152
156
|
# header/footer toggle (:header_footer), custom attributes (:attributes)). (default: {})
|
153
157
|
#
|
158
|
+
# Duplication of the options Hash is handled in the enclosing API.
|
159
|
+
#
|
154
160
|
# Examples
|
155
161
|
#
|
156
162
|
# data = File.read filename
|
@@ -235,7 +241,9 @@ class Document < AbstractBlock
|
|
235
241
|
@callouts = Callouts.new
|
236
242
|
@attributes_modified = ::Set.new
|
237
243
|
@options = options
|
244
|
+
@docinfo_processor_extensions = {}
|
238
245
|
header_footer = (options[:header_footer] ||= false)
|
246
|
+
options.freeze
|
239
247
|
|
240
248
|
attrs = @attributes
|
241
249
|
attrs['encoding'] = 'UTF-8'
|
@@ -263,6 +271,7 @@ class Document < AbstractBlock
|
|
263
271
|
#attrs['listing-caption'] = 'Listing'
|
264
272
|
attrs['table-caption'] = 'Table'
|
265
273
|
attrs['toc-title'] = 'Table of Contents'
|
274
|
+
#attrs['preface-title'] = 'Preface'
|
266
275
|
attrs['manname-title'] = 'NAME'
|
267
276
|
attrs['untitled-label'] = 'Untitled'
|
268
277
|
attrs['version-label'] = 'Version'
|
@@ -346,7 +355,7 @@ class Document < AbstractBlock
|
|
346
355
|
else
|
347
356
|
# a value ending in @ indicates this attribute does not override
|
348
357
|
# an attribute with the same key in the document souce
|
349
|
-
if
|
358
|
+
if ::String === val && (val.end_with? '@')
|
350
359
|
val = val.chop
|
351
360
|
verdict = true
|
352
361
|
end
|
@@ -403,16 +412,20 @@ class Document < AbstractBlock
|
|
403
412
|
attrs['stylesdir'] ||= '.'
|
404
413
|
attrs['iconsdir'] ||= ::File.join(attrs.fetch('imagesdir', './images'), 'icons')
|
405
414
|
|
406
|
-
|
407
|
-
|
408
|
-
if
|
409
|
-
|
410
|
-
|
415
|
+
if initialize_extensions
|
416
|
+
if (registry = options[:extensions_registry])
|
417
|
+
if Extensions::Registry === registry || (::RUBY_ENGINE_JRUBY &&
|
418
|
+
::AsciidoctorJ::Extensions::ExtensionRegistry === registry)
|
419
|
+
# take it as it is
|
420
|
+
else
|
421
|
+
registry = Extensions::Registry.new
|
411
422
|
end
|
412
|
-
elsif (ext_block = options[:extensions])
|
413
|
-
Extensions.build_registry(&ext_block)
|
423
|
+
elsif ::Proc === (ext_block = options[:extensions])
|
424
|
+
registry = Extensions.build_registry(&ext_block)
|
425
|
+
else
|
426
|
+
registry = Extensions::Registry.new
|
414
427
|
end
|
415
|
-
|
428
|
+
@extensions = registry.activate self
|
416
429
|
end
|
417
430
|
|
418
431
|
@reader = PreprocessorReader.new self, data, Reader::Cursor.new(attrs['docfile'], @base_dir)
|
@@ -504,7 +517,7 @@ class Document < AbstractBlock
|
|
504
517
|
#
|
505
518
|
# returns the next value in the sequence according to the current value's type
|
506
519
|
def nextval(current)
|
507
|
-
if
|
520
|
+
if ::Integer === current
|
508
521
|
current + 1
|
509
522
|
else
|
510
523
|
intval = current.to_i
|
@@ -519,7 +532,7 @@ class Document < AbstractBlock
|
|
519
532
|
def register(type, value)
|
520
533
|
case type
|
521
534
|
when :ids
|
522
|
-
if
|
535
|
+
if ::Array === value
|
523
536
|
@references[:ids][value[0]] = (value[1] || '[' + value[0] + ']')
|
524
537
|
else
|
525
538
|
@references[:ids][value] = '[' + value + ']'
|
@@ -902,7 +915,7 @@ class Document < AbstractBlock
|
|
902
915
|
attrs['backend'] = new_backend
|
903
916
|
attrs[%(backend-#{new_backend})] = ''
|
904
917
|
# (re)initialize converter
|
905
|
-
if (@converter = create_converter)
|
918
|
+
if Converter::BackendInfo === (@converter = create_converter)
|
906
919
|
new_basebackend = @converter.basebackend
|
907
920
|
attrs['outfilesuffix'] = @converter.outfilesuffix unless attribute_locked? 'outfilesuffix'
|
908
921
|
new_filetype = @converter.filetype
|
@@ -974,10 +987,10 @@ class Document < AbstractBlock
|
|
974
987
|
converter_opts[:template_engine_options] = @options[:template_engine_options]
|
975
988
|
converter_opts[:eruby] = @options[:eruby]
|
976
989
|
end
|
977
|
-
|
978
|
-
Converter::Factory.new ::Hash[backend, converter]
|
990
|
+
if (converter = @options[:converter])
|
991
|
+
converter_factory = Converter::Factory.new ::Hash[backend, converter]
|
979
992
|
else
|
980
|
-
Converter::Factory.default false
|
993
|
+
converter_factory = Converter::Factory.default false
|
981
994
|
end
|
982
995
|
# QUESTION should we honor the convert_opts?
|
983
996
|
# QUESTION should we pass through all options and attributes too?
|
@@ -992,6 +1005,11 @@ class Document < AbstractBlock
|
|
992
1005
|
def convert opts = {}
|
993
1006
|
parse unless @parsed
|
994
1007
|
restore_attributes
|
1008
|
+
unless @safe >= SafeMode::SERVER || opts.empty?
|
1009
|
+
# QUESTION should we store these on the Document object?
|
1010
|
+
@attributes.delete 'outfile' unless (@attributes['outfile'] = opts['outfile'])
|
1011
|
+
@attributes.delete 'outdir' unless (@attributes['outdir'] = opts['outdir'])
|
1012
|
+
end
|
995
1013
|
|
996
1014
|
# QUESTION should we add processors that execute before conversion begins?
|
997
1015
|
unless @converter
|
@@ -1029,7 +1047,7 @@ class Document < AbstractBlock
|
|
1029
1047
|
# If the converter responds to :write, delegate the work of writing the file
|
1030
1048
|
# to that method. Otherwise, write the output the specified file.
|
1031
1049
|
def write output, target
|
1032
|
-
if @converter
|
1050
|
+
if Writer === @converter
|
1033
1051
|
@converter.write output, target
|
1034
1052
|
else
|
1035
1053
|
if target.respond_to? :write
|
@@ -1068,59 +1086,69 @@ class Document < AbstractBlock
|
|
1068
1086
|
super
|
1069
1087
|
end
|
1070
1088
|
|
1071
|
-
# Public: Read the docinfo file(s) for inclusion in the
|
1072
|
-
# document template
|
1089
|
+
# Public: Read the docinfo file(s) for inclusion in the document template
|
1073
1090
|
#
|
1074
1091
|
# If the docinfo1 attribute is set, read the docinfo.ext file. If the docinfo
|
1075
1092
|
# attribute is set, read the doc-name.docinfo.ext file. If the docinfo2
|
1076
1093
|
# attribute is set, read both files in that order.
|
1077
1094
|
#
|
1078
|
-
#
|
1079
|
-
# ext
|
1080
|
-
#
|
1095
|
+
# location - The Symbol location of the docinfo, either :header or :footer. (default: :header)
|
1096
|
+
# ext - The extension of the docinfo file(s). If not set, the extension
|
1097
|
+
# will be determined based on the basebackend. (default: nil)
|
1081
1098
|
#
|
1082
1099
|
# returns The contents of the docinfo file(s)
|
1083
|
-
def docinfo(
|
1100
|
+
def docinfo(location = :header, ext = nil)
|
1084
1101
|
if safe >= SafeMode::SECURE
|
1085
1102
|
''
|
1086
1103
|
else
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
else
|
1091
|
-
qualifier = nil
|
1092
|
-
end
|
1093
|
-
ext = @attributes['outfilesuffix'] if ext.nil?
|
1104
|
+
qualifier = (location == :footer ? '-footer' : nil)
|
1105
|
+
ext = @attributes['outfilesuffix'] unless ext
|
1106
|
+
docinfodir = @attributes['docinfodir']
|
1094
1107
|
|
1095
1108
|
content = nil
|
1096
1109
|
|
1097
1110
|
docinfo = @attributes.key?('docinfo')
|
1098
1111
|
docinfo1 = @attributes.key?('docinfo1')
|
1099
1112
|
docinfo2 = @attributes.key?('docinfo2')
|
1100
|
-
docinfo_filename =
|
1113
|
+
docinfo_filename = %(docinfo#{qualifier}#{ext})
|
1101
1114
|
if docinfo1 || docinfo2
|
1102
|
-
docinfo_path = normalize_system_path(docinfo_filename)
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
1106
|
-
content.force_encoding ::Encoding::UTF_8 if FORCE_ENCODING
|
1107
|
-
content = sub_attributes(content.split EOL) * EOL
|
1115
|
+
docinfo_path = normalize_system_path(docinfo_filename, docinfodir)
|
1116
|
+
# NOTE normalizing the lines is essential if we're performing substitutions
|
1117
|
+
if (content = read_asset(docinfo_path, :normalize => true))
|
1118
|
+
content = sub_attributes(content)
|
1108
1119
|
end
|
1109
1120
|
end
|
1110
1121
|
|
1111
1122
|
if (docinfo || docinfo2) && @attributes.key?('docname')
|
1112
|
-
docinfo_path = normalize_system_path(
|
1113
|
-
|
1114
|
-
|
1115
|
-
|
1116
|
-
content2
|
1117
|
-
content2 = sub_attributes(content2.split EOL) * EOL
|
1118
|
-
content = content.nil? ? content2 : "#{content}#{EOL}#{content2}"
|
1123
|
+
docinfo_path = normalize_system_path(%(#{@attributes['docname']}-#{docinfo_filename}), docinfodir)
|
1124
|
+
# NOTE normalizing the lines is essential if we're performing substitutions
|
1125
|
+
if (content2 = read_asset(docinfo_path, :normalize => true))
|
1126
|
+
content2 = sub_attributes(content2)
|
1127
|
+
content = content ? %(#{content}#{EOL}#{content2}) : content2
|
1119
1128
|
end
|
1120
1129
|
end
|
1121
1130
|
|
1122
|
-
#
|
1123
|
-
|
1131
|
+
# TODO allow document to control whether extension docinfo is contributed
|
1132
|
+
if @extensions && docinfo_processors?(location)
|
1133
|
+
contentx = @docinfo_processor_extensions[location].map {|candidate| candidate.process_method[self] }.compact * EOL
|
1134
|
+
content = content ? %(#{content}#{EOL}#{contentx}) : contentx
|
1135
|
+
end
|
1136
|
+
|
1137
|
+
# coerce to string (in case the value is nil)
|
1138
|
+
%(#{content})
|
1139
|
+
end
|
1140
|
+
end
|
1141
|
+
|
1142
|
+
def docinfo_processors?(location = :header)
|
1143
|
+
if @docinfo_processor_extensions.key?(location)
|
1144
|
+
# false means we already performed a lookup and didn't find any
|
1145
|
+
@docinfo_processor_extensions[location] != false
|
1146
|
+
else
|
1147
|
+
if @extensions && @document.extensions.docinfo_processors?(location)
|
1148
|
+
!!(@docinfo_processor_extensions[location] = @document.extensions.docinfo_processors(location))
|
1149
|
+
else
|
1150
|
+
@docinfo_processor_extensions[location] = false
|
1151
|
+
end
|
1124
1152
|
end
|
1125
1153
|
end
|
1126
1154
|
|