asciidoctor 0.0.7 → 0.0.9
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.
- data/Gemfile +2 -0
- data/README.asciidoc +35 -26
- data/Rakefile +9 -6
- data/asciidoctor.gemspec +27 -8
- data/bin/asciidoctor +1 -1
- data/lib/asciidoctor.rb +351 -63
- data/lib/asciidoctor/abstract_block.rb +218 -0
- data/lib/asciidoctor/abstract_node.rb +249 -0
- data/lib/asciidoctor/attribute_list.rb +211 -0
- data/lib/asciidoctor/backends/base_template.rb +99 -0
- data/lib/asciidoctor/backends/docbook45.rb +510 -0
- data/lib/asciidoctor/backends/html5.rb +585 -0
- data/lib/asciidoctor/block.rb +27 -254
- data/lib/asciidoctor/callouts.rb +117 -0
- data/lib/asciidoctor/debug.rb +7 -4
- data/lib/asciidoctor/document.rb +229 -77
- data/lib/asciidoctor/inline.rb +29 -0
- data/lib/asciidoctor/lexer.rb +1330 -502
- data/lib/asciidoctor/list_item.rb +33 -34
- data/lib/asciidoctor/reader.rb +305 -142
- data/lib/asciidoctor/renderer.rb +115 -19
- data/lib/asciidoctor/section.rb +100 -189
- data/lib/asciidoctor/substituters.rb +468 -0
- data/lib/asciidoctor/table.rb +499 -0
- data/lib/asciidoctor/version.rb +1 -1
- data/test/attributes_test.rb +301 -87
- data/test/blocks_test.rb +568 -0
- data/test/document_test.rb +221 -24
- data/test/fixtures/dot.gif +0 -0
- data/test/fixtures/encoding.asciidoc +1 -0
- data/test/fixtures/include-file.asciidoc +1 -0
- data/test/fixtures/tip.gif +0 -0
- data/test/headers_test.rb +411 -43
- data/test/lexer_test.rb +265 -45
- data/test/links_test.rb +144 -3
- data/test/lists_test.rb +2252 -74
- data/test/paragraphs_test.rb +21 -30
- data/test/preamble_test.rb +24 -0
- data/test/reader_test.rb +248 -12
- data/test/renderer_test.rb +22 -0
- data/test/substitutions_test.rb +414 -0
- data/test/tables_test.rb +484 -0
- data/test/test_helper.rb +70 -6
- data/test/text_test.rb +30 -6
- metadata +64 -10
- data/lib/asciidoctor/render_templates.rb +0 -317
- data/lib/asciidoctor/string.rb +0 -12
@@ -0,0 +1,218 @@
|
|
1
|
+
class Asciidoctor::AbstractBlock < Asciidoctor::AbstractNode
|
2
|
+
# Public: Get the Array of Asciidoctor::AbstractBlock sub-blocks for this block
|
3
|
+
attr_reader :blocks
|
4
|
+
|
5
|
+
# Public: Set the Integer level of this Section or the Section level in which this Block resides
|
6
|
+
attr_accessor :level
|
7
|
+
|
8
|
+
# Public: Set the String block title.
|
9
|
+
attr_writer :title
|
10
|
+
|
11
|
+
def initialize(parent, context)
|
12
|
+
super(parent, context)
|
13
|
+
@blocks = []
|
14
|
+
@id = nil
|
15
|
+
@title = nil
|
16
|
+
if context == :document
|
17
|
+
@level = 0
|
18
|
+
elsif !parent.nil? && !self.is_a?(Asciidoctor::Section)
|
19
|
+
@level = parent.level
|
20
|
+
else
|
21
|
+
@level = nil
|
22
|
+
end
|
23
|
+
@next_section_index = 0
|
24
|
+
end
|
25
|
+
|
26
|
+
# Public: A convenience method that indicates whether the title instance
|
27
|
+
# variable is blank (nil or empty)
|
28
|
+
def title?
|
29
|
+
!@title.to_s.empty?
|
30
|
+
end
|
31
|
+
|
32
|
+
# Public: Get the String title of this Block with title substitions applied
|
33
|
+
#
|
34
|
+
# The following substitutions are applied to block and section titles:
|
35
|
+
#
|
36
|
+
# :specialcharacters, :quotes, :replacements, :macros, :attributes and :post_replacements
|
37
|
+
#
|
38
|
+
# Examples
|
39
|
+
#
|
40
|
+
# block.title = "Foo 3^ # {two-colons} Bar(1)"
|
41
|
+
# block.title
|
42
|
+
# => "Foo 3^ # :: Bar(1)"
|
43
|
+
#
|
44
|
+
# Returns the String title of this Block
|
45
|
+
def title
|
46
|
+
# prevent substitutions from being applied multiple times
|
47
|
+
if defined?(@subbed_title)
|
48
|
+
@subbed_title
|
49
|
+
elsif @title
|
50
|
+
@subbed_title = apply_title_subs(@title)
|
51
|
+
else
|
52
|
+
@title
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Public: Determine whether this Block contains block content
|
57
|
+
#
|
58
|
+
# returns Whether this Block has block content
|
59
|
+
#
|
60
|
+
#--
|
61
|
+
# TODO we still need another method that answers
|
62
|
+
# whether this Block *can* have block content
|
63
|
+
# that should be the option 'sectionbody'
|
64
|
+
def blocks?
|
65
|
+
!blocks.empty?
|
66
|
+
end
|
67
|
+
|
68
|
+
# Public: Get the element at i in the array of blocks.
|
69
|
+
#
|
70
|
+
# i - The Integer array index number.
|
71
|
+
#
|
72
|
+
# section = Section.new
|
73
|
+
#
|
74
|
+
# section << 'foo'
|
75
|
+
# section << 'bar'
|
76
|
+
# section[1]
|
77
|
+
# => "bar"
|
78
|
+
def [](i)
|
79
|
+
@blocks[i]
|
80
|
+
end
|
81
|
+
|
82
|
+
# Public: Append a content block to this block's list of blocks.
|
83
|
+
#
|
84
|
+
# block - The new child block.
|
85
|
+
#
|
86
|
+
# Examples
|
87
|
+
#
|
88
|
+
# block = Block.new(parent, :preamble)
|
89
|
+
#
|
90
|
+
# block << Block.new(block, :paragraph, 'p1')
|
91
|
+
# block << Block.new(block, :paragraph, 'p2')
|
92
|
+
# block.blocks
|
93
|
+
# # => ["p1", "p2"]
|
94
|
+
#
|
95
|
+
# Returns nothing.
|
96
|
+
def <<(block)
|
97
|
+
if block.is_a?(Asciidoctor::Section)
|
98
|
+
assign_index(block)
|
99
|
+
end
|
100
|
+
@blocks << block
|
101
|
+
end
|
102
|
+
|
103
|
+
# Public: Insert a content block at the specified index in this block's
|
104
|
+
# list of blocks.
|
105
|
+
#
|
106
|
+
# i - The Integer array index number.
|
107
|
+
# val = The content block to insert.
|
108
|
+
#
|
109
|
+
# section = Section.new
|
110
|
+
#
|
111
|
+
# section << 'foo'
|
112
|
+
# section << 'baz'
|
113
|
+
# section.insert(1, 'bar')
|
114
|
+
# section.blocks
|
115
|
+
# ["foo", "bar", "baz"]
|
116
|
+
def insert(i, block)
|
117
|
+
@blocks.insert(i, block)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Public: Delete the element at i in the array of section blocks,
|
121
|
+
# returning that element or nil if i is out of range.
|
122
|
+
#
|
123
|
+
# i - The Integer array index number.
|
124
|
+
#
|
125
|
+
# section = Section.new
|
126
|
+
#
|
127
|
+
# section << 'foo'
|
128
|
+
# section << 'bar'
|
129
|
+
# section.delete_at(1)
|
130
|
+
# => "bar"
|
131
|
+
#
|
132
|
+
# section.blocks
|
133
|
+
# => ["foo"]
|
134
|
+
def delete_at(i)
|
135
|
+
@blocks.delete_at(i)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Public: Clear this Block's list of blocks.
|
139
|
+
#
|
140
|
+
# section = Section.new
|
141
|
+
#
|
142
|
+
# section << 'foo'
|
143
|
+
# section << 'bar'
|
144
|
+
# section.blocks
|
145
|
+
# => ["foo", "bar"]
|
146
|
+
# section.clear_blocks
|
147
|
+
# section.blocks
|
148
|
+
# => []
|
149
|
+
def clear_blocks
|
150
|
+
@blocks = []
|
151
|
+
end
|
152
|
+
|
153
|
+
# Public: Get the Integer number of blocks in this block
|
154
|
+
#
|
155
|
+
# Examples
|
156
|
+
#
|
157
|
+
# section = Section.new
|
158
|
+
#
|
159
|
+
# section.size
|
160
|
+
# => 0
|
161
|
+
#
|
162
|
+
# section << 'foo'
|
163
|
+
# section << 'bar'
|
164
|
+
# section.size
|
165
|
+
# => 2
|
166
|
+
def size
|
167
|
+
@blocks.size
|
168
|
+
end
|
169
|
+
|
170
|
+
# Public: Get the Array of child Section objects
|
171
|
+
#
|
172
|
+
# Only applies to Document and Section instances
|
173
|
+
#
|
174
|
+
# Examples
|
175
|
+
#
|
176
|
+
# section = Section.new(parent)
|
177
|
+
# section << Block.new(section, :paragraph, 'paragraph 1')
|
178
|
+
# section << Section.new(parent)
|
179
|
+
# section << Block.new(section, :paragraph, 'paragraph 2')
|
180
|
+
# section.sections.size
|
181
|
+
# # => 1
|
182
|
+
#
|
183
|
+
# returns an Array of Section objects
|
184
|
+
def sections
|
185
|
+
@blocks.inject([]) {|collector, block|
|
186
|
+
collector << block if block.is_a?(Asciidoctor::Section)
|
187
|
+
collector
|
188
|
+
}
|
189
|
+
end
|
190
|
+
|
191
|
+
# Internal: Assign the next index (0-based) to this section
|
192
|
+
#
|
193
|
+
# Assign the next index of this section within the parent
|
194
|
+
# Block (in document order)
|
195
|
+
#
|
196
|
+
# returns nothing
|
197
|
+
def assign_index(section)
|
198
|
+
section.index = @next_section_index
|
199
|
+
@next_section_index += 1
|
200
|
+
end
|
201
|
+
|
202
|
+
# Internal: Reassign the section indexes
|
203
|
+
#
|
204
|
+
# Walk the descendents of the current Document or Section
|
205
|
+
# and reassign the section 0-based index value to each Section
|
206
|
+
# as it appears in document order.
|
207
|
+
#
|
208
|
+
# returns nothing
|
209
|
+
def reindex_sections
|
210
|
+
@next_section_index = 0
|
211
|
+
@blocks.each {|block|
|
212
|
+
if block.is_a?(Asciidoctor::Section)
|
213
|
+
assign_index(block)
|
214
|
+
block.reindex_sections
|
215
|
+
end
|
216
|
+
}
|
217
|
+
end
|
218
|
+
end
|
@@ -0,0 +1,249 @@
|
|
1
|
+
# Public: An abstract base class that provides state and methods for managing a
|
2
|
+
# node of AsciiDoc content. The state and methods on this class are comment to
|
3
|
+
# all content segments in an AsciiDoc document.
|
4
|
+
class Asciidoctor::AbstractNode
|
5
|
+
|
6
|
+
include Asciidoctor::Substituters
|
7
|
+
|
8
|
+
# Public: Get the element which is the parent of this node
|
9
|
+
attr_reader :parent
|
10
|
+
|
11
|
+
# Public: Get the Asciidoctor::Document to which this node belongs
|
12
|
+
attr_reader :document
|
13
|
+
|
14
|
+
# Public: Get the Symbol context for this node
|
15
|
+
attr_reader :context
|
16
|
+
|
17
|
+
# Public: Get the id of this node
|
18
|
+
attr_accessor :id
|
19
|
+
|
20
|
+
# Public: Get the Hash of attributes for this node
|
21
|
+
attr_reader :attributes
|
22
|
+
|
23
|
+
def initialize(parent, context)
|
24
|
+
@parent = (context != :document ? parent : nil)
|
25
|
+
|
26
|
+
if !parent.nil?
|
27
|
+
@document = parent.is_a?(Asciidoctor::Document) ? parent : parent.document
|
28
|
+
else
|
29
|
+
@document = nil
|
30
|
+
end
|
31
|
+
|
32
|
+
@context = context
|
33
|
+
@attributes = {}
|
34
|
+
@passthroughs = []
|
35
|
+
end
|
36
|
+
|
37
|
+
def attr(name, default = nil)
|
38
|
+
if self == @document
|
39
|
+
default.nil? ? @attributes[name.to_s] : @attributes.fetch(name.to_s, default)
|
40
|
+
else
|
41
|
+
default.nil? ? @attributes.fetch(name.to_s, @document.attr(name)) :
|
42
|
+
@attributes.fetch(name.to_s, @document.attr(name, default))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def attr?(name)
|
47
|
+
if self == @document
|
48
|
+
@attributes.has_key? name.to_s
|
49
|
+
else
|
50
|
+
@attributes.has_key?(name.to_s) || @document.attr?(name)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Public: Get the execution context of this object (via Kernel#binding).
|
55
|
+
#
|
56
|
+
# This method is used to set the 'self' reference as well as local variables
|
57
|
+
# that map to this method's arguments during the evaluation of a backend
|
58
|
+
# template.
|
59
|
+
#
|
60
|
+
# Each object in Ruby has a binding context that can be used to set the 'self'
|
61
|
+
# reference in an evaluation context. Any arguments passed to this
|
62
|
+
# method are also available in the execution environment.
|
63
|
+
#
|
64
|
+
# template - The BaseTemplate instance in which this binding will be active.
|
65
|
+
# Bound to the local variable of the same name, template.
|
66
|
+
#
|
67
|
+
# returns the execution context for this object so it can be be transferred to
|
68
|
+
# the backend template and binds the method arguments as local variables in
|
69
|
+
# that same environment.
|
70
|
+
def get_binding template
|
71
|
+
binding
|
72
|
+
end
|
73
|
+
|
74
|
+
# Public: Update the attributes of this node with the new values in
|
75
|
+
# the attributes argument.
|
76
|
+
#
|
77
|
+
# If an attribute already exists with the same key, it's value will
|
78
|
+
# be overridden.
|
79
|
+
#
|
80
|
+
# attributes - A Hash of attributes to assign to this node.
|
81
|
+
#
|
82
|
+
# returns nothing
|
83
|
+
def update_attributes(attributes)
|
84
|
+
@attributes.update(attributes)
|
85
|
+
nil
|
86
|
+
end
|
87
|
+
|
88
|
+
# Public: Get the Asciidoctor::Renderer instance being used for the
|
89
|
+
# Asciidoctor::Document to which this node belongs
|
90
|
+
def renderer
|
91
|
+
@document.renderer
|
92
|
+
end
|
93
|
+
|
94
|
+
# Public: Construct a reference or data URI to an icon image for the
|
95
|
+
# specified icon name.
|
96
|
+
#
|
97
|
+
# If the 'icon' attribute is set on this block, the name is ignored and the
|
98
|
+
# value of this attribute is used as the target image path. Otherwise,
|
99
|
+
# construct a target image path by concatenating the value of the 'iconsdir'
|
100
|
+
# attribute, the icon name and the value of the 'iconstype' attribute
|
101
|
+
# (defaulting to 'png').
|
102
|
+
#
|
103
|
+
# The target image path is then passed through the #image_uri() method. If
|
104
|
+
# the 'data-uri' attribute is set on the document, the image will be
|
105
|
+
# safely converted to a data URI.
|
106
|
+
#
|
107
|
+
# The return value of this method can be safely used in an image tag.
|
108
|
+
#
|
109
|
+
# name - The String name of the icon
|
110
|
+
#
|
111
|
+
# Returns A String reference or data URI for an icon image
|
112
|
+
def icon_uri(name)
|
113
|
+
if attr? 'icon'
|
114
|
+
image_uri(attr('icon'), nil)
|
115
|
+
else
|
116
|
+
image_uri(name + '.' + @document.attr('iconstype', 'png'), 'iconsdir')
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Public: Construct a reference or data URI to the target image.
|
121
|
+
#
|
122
|
+
# The target image is resolved relative to the directory retrieved from the
|
123
|
+
# specified attribute key, if provided.
|
124
|
+
#
|
125
|
+
# If the 'data-uri' attribute is set on the document, and the safe mode level
|
126
|
+
# is less than SafeMode::SECURE, the image will be safely converted to a data URI
|
127
|
+
# by reading it from the same directory. If neither of these conditions
|
128
|
+
# are satisfied, a relative path (i.e., URL) will be returned.
|
129
|
+
#
|
130
|
+
# The return value of this method can be safely used in an image tag.
|
131
|
+
#
|
132
|
+
# target_image - A String path to the target image
|
133
|
+
# asset_dir_key - The String attribute key used to lookup the directory where
|
134
|
+
# the image is located (default: 'imagesdir')
|
135
|
+
#
|
136
|
+
# Returns A String reference or data URI for the target image
|
137
|
+
def image_uri(target_image, asset_dir_key = 'imagesdir')
|
138
|
+
if @document.safe < Asciidoctor::SafeMode::SECURE && @document.attr?('data-uri')
|
139
|
+
generate_data_uri(target_image, asset_dir_key)
|
140
|
+
elsif asset_dir_key && attr?(asset_dir_key)
|
141
|
+
File.join(@document.attr(asset_dir_key), target_image)
|
142
|
+
else
|
143
|
+
target_image
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Public: Generate a data URI that can be used to embed an image in the output document
|
148
|
+
#
|
149
|
+
# First, and foremost, the target image path is cleaned if the document safe mode level
|
150
|
+
# is set to at least SafeMode::SAFE (a condition which is true by default) to prevent access
|
151
|
+
# to ancestor paths in the filesystem. The image data is then read and converted to
|
152
|
+
# Base64. Finally, a data URI is built which can be used in an image tag.
|
153
|
+
#
|
154
|
+
# target_image - A String path to the target image
|
155
|
+
# asset_dir_key - The String attribute key used to lookup the directory where
|
156
|
+
# the image is located (default: nil)
|
157
|
+
#
|
158
|
+
# Returns A String data URI containing the content of the target image
|
159
|
+
def generate_data_uri(target_image, asset_dir_key = nil)
|
160
|
+
Asciidoctor.require_library 'base64'
|
161
|
+
|
162
|
+
mimetype = 'image/' + File.extname(target_image)[1..-1]
|
163
|
+
if asset_dir_key
|
164
|
+
image_path = File.join(normalize_asset_path(@document.attr(asset_dir_key, '.'), asset_dir_key), target_image)
|
165
|
+
else
|
166
|
+
image_path = normalize_asset_path(target_image)
|
167
|
+
end
|
168
|
+
|
169
|
+
'data:' + mimetype + ';base64,' + Base64.encode64(IO.read(image_path)).delete("\n")
|
170
|
+
end
|
171
|
+
|
172
|
+
# Public: Normalize the asset file or directory to a concrete and rinsed path
|
173
|
+
#
|
174
|
+
# The most important functionality in this method is to prevent the asset
|
175
|
+
# reference from resolving to a directory outside of the chroot directory
|
176
|
+
# (which defaults to the directory of the source file, stored in the 'docdir'
|
177
|
+
# attribute) if the document safe level is set to SafeMode::SAFE or greater
|
178
|
+
# (a condition which is true by default).
|
179
|
+
#
|
180
|
+
# asset_ref - the String asset file or directory referenced in the document
|
181
|
+
# or configuration attribute
|
182
|
+
# asset_name - the String name of the file or directory being resolved (for use in
|
183
|
+
# the warning message) (default: 'path')
|
184
|
+
#
|
185
|
+
# Examples
|
186
|
+
#
|
187
|
+
# # given these fixtures
|
188
|
+
# document.attr('docdir')
|
189
|
+
# # => "/path/to/docdir"
|
190
|
+
# document.safe >= Asciidoctor::SafeMode::SAFE
|
191
|
+
# # => true
|
192
|
+
#
|
193
|
+
# # then
|
194
|
+
# normalize_asset_path('images')
|
195
|
+
# # => "/path/to/docdir/images"
|
196
|
+
# normalize_asset_path('/etc/images')
|
197
|
+
# # => "/path/to/docdir/images"
|
198
|
+
# normalize_asset_path('../images')
|
199
|
+
# # => "/path/to/docdir/images"
|
200
|
+
#
|
201
|
+
# # given these fixtures
|
202
|
+
# document.attr('docdir')
|
203
|
+
# # => "/path/to/docdir"
|
204
|
+
# document.safe >= Asciidoctor::SafeMode::SAFE
|
205
|
+
# # => false
|
206
|
+
#
|
207
|
+
# # then
|
208
|
+
# normalize_asset_path('images')
|
209
|
+
# # => "/path/to/docdir/images"
|
210
|
+
# normalize_asset_path('/etc/images')
|
211
|
+
# # => "/etc/images"
|
212
|
+
# normalize_asset_path('../images')
|
213
|
+
# # => "/path/to/images"
|
214
|
+
#
|
215
|
+
# Returns The normalized asset file or directory as a String path
|
216
|
+
#--
|
217
|
+
# TODO this method is missing a coordinate; it should be able to resolve
|
218
|
+
# both the directory reference and the path to an asset in it; callers
|
219
|
+
# of this method are still doing a File.join to finish the task
|
220
|
+
def normalize_asset_path(asset_ref, asset_name = 'path')
|
221
|
+
# TODO we may use pathname enough to make it a top-level require
|
222
|
+
Asciidoctor.require_library 'pathname'
|
223
|
+
|
224
|
+
input_path = File.expand_path(@document.attr('docdir'))
|
225
|
+
asset_path = Pathname.new(asset_ref)
|
226
|
+
|
227
|
+
if asset_path.relative?
|
228
|
+
asset_path = File.expand_path(File.join(input_path, asset_ref))
|
229
|
+
else
|
230
|
+
asset_path = asset_path.cleanpath.to_s
|
231
|
+
end
|
232
|
+
|
233
|
+
if @document.safe >= Asciidoctor::SafeMode::SAFE
|
234
|
+
relative_asset_path = Pathname.new(asset_path).relative_path_from(Pathname.new(input_path)).to_s
|
235
|
+
if relative_asset_path.start_with?('..')
|
236
|
+
puts 'asciidoctor: WARNING: ' + asset_name + ' has illegal reference to ancestor of base directory'
|
237
|
+
relative_asset_path.sub!(/^(?:\.\.\/)*/, '')
|
238
|
+
# just to be absolutely sure ;)
|
239
|
+
if relative_asset_path[0..0] == '.'
|
240
|
+
raise 'Substitution of parent path references failed for ' + relative_asset_path
|
241
|
+
end
|
242
|
+
asset_path = File.expand_path(File.join(input_path, relative_asset_path))
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
asset_path
|
247
|
+
end
|
248
|
+
|
249
|
+
end
|