asciidoctor 0.1.4 → 1.5.0
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 +209 -25
- data/{LICENSE → LICENSE.adoc} +4 -3
- data/README.adoc +392 -395
- data/Rakefile +94 -137
- data/benchmark/benchmark.rb +127 -0
- data/benchmark/sample-data/mdbasics.adoc +334 -0
- data/bin/asciidoctor +5 -8
- data/bin/asciidoctor-safe +4 -8
- data/compat/asciidoc.conf +78 -11
- data/compat/font-awesome-3-compat.css +397 -0
- data/data/stylesheets/asciidoctor-default.css +399 -0
- data/data/stylesheets/coderay-asciidoctor.css +89 -0
- data/features/open_block.feature +92 -0
- data/features/pass_block.feature +66 -0
- data/features/step_definitions.rb +42 -0
- data/features/text_formatting.feature +55 -0
- data/features/xref.feature +116 -0
- data/lib/asciidoctor.rb +1155 -605
- data/lib/asciidoctor/abstract_block.rb +157 -71
- data/lib/asciidoctor/abstract_node.rb +150 -93
- data/lib/asciidoctor/attribute_list.rb +85 -90
- data/lib/asciidoctor/block.rb +51 -24
- data/lib/asciidoctor/callouts.rb +4 -7
- data/lib/asciidoctor/cli.rb +3 -0
- data/lib/asciidoctor/cli/invoker.rb +86 -76
- data/lib/asciidoctor/cli/options.rb +111 -61
- data/lib/asciidoctor/converter.rb +232 -0
- data/lib/asciidoctor/converter/base.rb +58 -0
- data/lib/asciidoctor/converter/composite.rb +66 -0
- data/lib/asciidoctor/converter/docbook45.rb +94 -0
- data/lib/asciidoctor/converter/docbook5.rb +684 -0
- data/lib/asciidoctor/converter/factory.rb +225 -0
- data/lib/asciidoctor/converter/html5.rb +1081 -0
- data/lib/asciidoctor/converter/template.rb +296 -0
- data/lib/asciidoctor/core_ext.rb +7 -0
- data/lib/asciidoctor/core_ext/object/nil_or_empty.rb +23 -0
- data/lib/asciidoctor/core_ext/string/chr.rb +6 -0
- data/lib/asciidoctor/core_ext/symbol/length.rb +6 -0
- data/lib/asciidoctor/document.rb +590 -304
- data/lib/asciidoctor/extensions.rb +1100 -308
- data/lib/asciidoctor/helpers.rb +109 -46
- data/lib/asciidoctor/inline.rb +16 -9
- data/lib/asciidoctor/list.rb +23 -15
- data/lib/asciidoctor/opal_ext.rb +4 -0
- data/lib/asciidoctor/opal_ext/comparable.rb +38 -0
- data/lib/asciidoctor/opal_ext/dir.rb +13 -0
- data/lib/asciidoctor/opal_ext/error.rb +2 -0
- data/lib/asciidoctor/opal_ext/file.rb +125 -0
- data/lib/asciidoctor/{lexer.rb → parser.rb} +646 -455
- data/lib/asciidoctor/path_resolver.rb +141 -77
- data/lib/asciidoctor/reader.rb +257 -187
- data/lib/asciidoctor/section.rb +12 -16
- data/lib/asciidoctor/stylesheets.rb +91 -0
- data/lib/asciidoctor/substitutors.rb +1548 -0
- data/lib/asciidoctor/table.rb +73 -57
- data/lib/asciidoctor/timings.rb +39 -0
- data/lib/asciidoctor/version.rb +1 -1
- data/man/asciidoctor.1 +22 -14
- data/man/asciidoctor.adoc +18 -10
- data/test/attributes_test.rb +314 -14
- data/test/blocks_test.rb +763 -118
- data/test/converter_test.rb +352 -0
- data/test/document_test.rb +518 -199
- data/test/extensions_test.rb +273 -103
- data/test/fixtures/asciidoc_index.txt +27 -13
- data/test/fixtures/basic-docinfo.xml +1 -1
- data/test/fixtures/chapter-a.adoc +3 -0
- data/test/fixtures/custom-backends/erb/html5/block_paragraph.html.erb +6 -0
- data/test/fixtures/docinfo.xml +1 -1
- data/test/fixtures/include-file.asciidoc +2 -0
- data/test/fixtures/master.adoc +5 -0
- data/test/invoker_test.rb +173 -61
- data/test/links_test.rb +97 -21
- data/test/lists_test.rb +181 -22
- data/test/options_test.rb +86 -2
- data/test/paragraphs_test.rb +47 -5
- data/test/{lexer_test.rb → parser_test.rb} +128 -57
- data/test/paths_test.rb +36 -1
- data/test/preamble_test.rb +25 -17
- data/test/reader_test.rb +404 -249
- data/test/sections_test.rb +623 -58
- data/test/substitutions_test.rb +609 -132
- data/test/tables_test.rb +198 -24
- data/test/test_helper.rb +101 -31
- data/test/text_test.rb +88 -31
- metadata +160 -64
- data/Gemfile +0 -12
- data/Guardfile +0 -18
- data/asciidoctor.gemspec +0 -143
- data/lib/asciidoctor/backends/_stylesheets.rb +0 -466
- data/lib/asciidoctor/backends/base_template.rb +0 -114
- data/lib/asciidoctor/backends/docbook45.rb +0 -774
- data/lib/asciidoctor/backends/docbook5.rb +0 -103
- data/lib/asciidoctor/backends/html5.rb +0 -1214
- data/lib/asciidoctor/renderer.rb +0 -259
- data/lib/asciidoctor/substituters.rb +0 -1083
- data/test/fixtures/asciidoc.txt +0 -105
- data/test/fixtures/ascshort.txt +0 -32
- data/test/fixtures/list_elements.asciidoc +0 -10
- data/test/renderer_test.rb +0 -162
@@ -6,9 +6,6 @@ class AbstractBlock < AbstractNode
|
|
6
6
|
# Public: Substitutions to be applied to content in this block
|
7
7
|
attr_reader :subs
|
8
8
|
|
9
|
-
# Public: Get/Set the String name of the render template
|
10
|
-
attr_accessor :template_name
|
11
|
-
|
12
9
|
# Public: Get the Array of Asciidoctor::AbstractBlock sub-blocks for this block
|
13
10
|
attr_reader :blocks
|
14
11
|
|
@@ -24,40 +21,72 @@ class AbstractBlock < AbstractNode
|
|
24
21
|
# Public: Get/Set the caption for this block
|
25
22
|
attr_accessor :caption
|
26
23
|
|
27
|
-
|
28
|
-
|
24
|
+
# Public: Gets/Sets the location in the AsciiDoc source where this block begins
|
25
|
+
attr_accessor :source_location
|
26
|
+
|
27
|
+
def initialize parent, context, opts = {}
|
28
|
+
super
|
29
29
|
@content_model = :compound
|
30
30
|
@subs = []
|
31
|
-
@
|
31
|
+
@default_subs = nil
|
32
32
|
@blocks = []
|
33
33
|
@id = nil
|
34
34
|
@title = nil
|
35
35
|
@caption = nil
|
36
36
|
@style = nil
|
37
|
-
if context == :document
|
38
|
-
|
39
|
-
elsif
|
40
|
-
|
41
|
-
else
|
42
|
-
@level = nil
|
37
|
+
@level = if context == :document
|
38
|
+
0
|
39
|
+
elsif parent && context != :section
|
40
|
+
parent.level
|
43
41
|
end
|
44
42
|
@next_section_index = 0
|
45
43
|
@next_section_number = 1
|
44
|
+
@source_location = nil
|
45
|
+
end
|
46
|
+
|
47
|
+
def block?
|
48
|
+
true
|
49
|
+
end
|
50
|
+
|
51
|
+
def inline?
|
52
|
+
false
|
46
53
|
end
|
47
54
|
|
48
|
-
# Public:
|
55
|
+
# Public: Update the context of this block.
|
56
|
+
#
|
57
|
+
# This method changes the context of this block. It also
|
58
|
+
# updates the node name accordingly.
|
59
|
+
def context=(context)
|
60
|
+
@context = context
|
61
|
+
@node_name = context.to_s
|
62
|
+
end
|
63
|
+
|
64
|
+
# Public: Get the converted String content for this Block. If the block
|
49
65
|
# has child blocks, the content method should cause them to be
|
50
|
-
#
|
66
|
+
# converted and returned as content that can be included in the
|
51
67
|
# parent block's template.
|
52
|
-
def
|
68
|
+
def convert
|
53
69
|
@document.playback_attributes @attributes
|
54
|
-
|
70
|
+
converter.convert self
|
55
71
|
end
|
56
72
|
|
57
|
-
#
|
73
|
+
# Alias render to convert to maintain backwards compatibility
|
74
|
+
alias :render :convert
|
75
|
+
|
76
|
+
# Public: Get the converted result of the child blocks by converting the
|
58
77
|
# children appropriate to content model that this block supports.
|
59
78
|
def content
|
60
|
-
@blocks.map {|b| b.
|
79
|
+
@blocks.map {|b| b.convert } * EOL
|
80
|
+
end
|
81
|
+
|
82
|
+
# Public: Get the source file where this block started
|
83
|
+
def file
|
84
|
+
@source_location ? @source_location.file : nil
|
85
|
+
end
|
86
|
+
|
87
|
+
# Public: Get the source line number where this block started
|
88
|
+
def lineno
|
89
|
+
@source_location ? @source_location.lineno : nil
|
61
90
|
end
|
62
91
|
|
63
92
|
# Public: A convenience method that checks whether the specified
|
@@ -74,7 +103,7 @@ class AbstractBlock < AbstractNode
|
|
74
103
|
# Public: A convenience method that indicates whether the title instance
|
75
104
|
# variable is blank (nil or empty)
|
76
105
|
def title?
|
77
|
-
!@title.
|
106
|
+
!@title.nil_or_empty?
|
78
107
|
end
|
79
108
|
|
80
109
|
# Public: Get the String title of this Block with title substitions applied
|
@@ -161,52 +190,100 @@ class AbstractBlock < AbstractNode
|
|
161
190
|
# section.sections.size
|
162
191
|
# # => 1
|
163
192
|
#
|
164
|
-
#
|
193
|
+
# Returns an [Array] of Section objects
|
165
194
|
def sections
|
166
|
-
@blocks.
|
167
|
-
collector << block if block.is_a?(Section)
|
168
|
-
collector
|
169
|
-
}
|
195
|
+
@blocks.select {|block| block.context == :section }
|
170
196
|
end
|
171
197
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
#
|
177
|
-
#
|
198
|
+
# stage the Enumerable mixin until we're sure we've got it right
|
199
|
+
=begin
|
200
|
+
include ::Enumerable
|
201
|
+
|
202
|
+
# Public: Yield the block on this block node and all its descendant
|
203
|
+
# block node children to satisfy the Enumerable contract.
|
178
204
|
#
|
179
205
|
# Returns nothing
|
180
|
-
def
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
206
|
+
def each &block
|
207
|
+
# yucky, dlist is a special case
|
208
|
+
if @context == :dlist
|
209
|
+
@blocks.flatten.each &block
|
210
|
+
else
|
211
|
+
#yield self.header if @context == :document && header?
|
212
|
+
@blocks.each &block
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
#--
|
217
|
+
# TODO is there a way to make this lazy?
|
218
|
+
def each_recursive &block
|
219
|
+
block = lambda {|node| node } unless block_given?
|
220
|
+
results = []
|
221
|
+
self.each do |node|
|
222
|
+
results << block.call(node)
|
223
|
+
results.concat(node.each_recursive(&block)) if ::Enumerable === node
|
224
|
+
end
|
225
|
+
block_given? ? results : results.to_enum
|
226
|
+
end
|
227
|
+
=end
|
228
|
+
|
229
|
+
# Public: Query for all descendant block nodes in the document tree that
|
230
|
+
# match the specified Symbol filter_context and, optionally, the style and/or
|
231
|
+
# role specified in the options Hash. If a block is provided, it's used as an
|
232
|
+
# additional filter. If no filters are specified, all block nodes in the tree
|
233
|
+
# are returned.
|
234
|
+
#
|
235
|
+
# Examples
|
236
|
+
#
|
237
|
+
# doc.find_by context: :section
|
238
|
+
# #=> Asciidoctor::Section@14459860 { level: 0, title: "Hello, AsciiDoc!", blocks: 0 }
|
239
|
+
# #=> Asciidoctor::Section@14505460 { level: 1, title: "First Section", blocks: 1 }
|
240
|
+
#
|
241
|
+
# doc.find_by(context: :section) {|section| section.level == 1 }
|
242
|
+
# #=> Asciidoctor::Section@14505460 { level: 1, title: "First Section", blocks: 1 }
|
243
|
+
#
|
244
|
+
# doc.find_by context: :listing, style: 'source'
|
245
|
+
# #=> Asciidoctor::Block@13136720 { context: :listing, content_model: :verbatim, style: "source", lines: 1 }
|
246
|
+
#
|
247
|
+
# Returns An Array of block nodes that match the given selector or nil if no matches are found
|
248
|
+
#--
|
249
|
+
# TODO support jQuery-style selector (e.g., image.thumb)
|
250
|
+
def find_by selector = {}, &block
|
251
|
+
result = []
|
252
|
+
|
253
|
+
if ((any_context = !(context_selector = selector[:context])) || context_selector == @context) &&
|
254
|
+
(!(style_selector = selector[:style]) || style_selector == @style) &&
|
255
|
+
(!(role_selector = selector[:role]) || has_role?(role_selector)) &&
|
256
|
+
(!(id_selector = selector[:id]) || id_selector == @id)
|
257
|
+
if id_selector
|
258
|
+
return [(block_given? && yield(self) ? self : self)]
|
188
259
|
else
|
189
|
-
|
260
|
+
result << (block_given? && yield(self) ? self : self)
|
190
261
|
end
|
191
|
-
when :raw
|
192
|
-
default_subs = SUBS[:pass]
|
193
|
-
else
|
194
|
-
return
|
195
262
|
end
|
196
263
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
@subs = default_subs.dup
|
264
|
+
# process document header as a section if present
|
265
|
+
if @context == :document && (any_context || context_selector == :section) && header?
|
266
|
+
result.concat(@header.find_by(selector, &block) || [])
|
201
267
|
end
|
202
268
|
|
203
|
-
#
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
269
|
+
# yuck, dlist is a special case
|
270
|
+
unless context_selector == :document # optimization
|
271
|
+
if @context == :dlist
|
272
|
+
if any_context || context_selector != :section # optimization
|
273
|
+
@blocks.flatten.each do |li|
|
274
|
+
result.concat(li.find_by(selector, &block) || [])
|
275
|
+
end
|
276
|
+
end
|
277
|
+
elsif
|
278
|
+
@blocks.each do |b|
|
279
|
+
next if (context_selector == :section && b.context != :section) # optimization
|
280
|
+
result.concat(b.find_by(selector, &block) || [])
|
281
|
+
end
|
282
|
+
end
|
208
283
|
end
|
284
|
+
result.empty? ? nil : result
|
209
285
|
end
|
286
|
+
alias :query :find_by
|
210
287
|
|
211
288
|
# Public: Remove a substitution from this block
|
212
289
|
#
|
@@ -233,28 +310,23 @@ class AbstractBlock < AbstractNode
|
|
233
310
|
# If not provided, the name of the context for this block
|
234
311
|
# is used. (default: nil).
|
235
312
|
#
|
236
|
-
#
|
313
|
+
# Returns nothing
|
237
314
|
def assign_caption(caption = nil, key = nil)
|
238
|
-
unless title? ||
|
239
|
-
return nil
|
240
|
-
end
|
315
|
+
return unless title? || !@caption
|
241
316
|
|
242
|
-
if caption
|
243
|
-
|
244
|
-
|
317
|
+
if caption
|
318
|
+
@caption = caption
|
319
|
+
else
|
320
|
+
if (value = @document.attributes['caption'])
|
321
|
+
@caption = value
|
245
322
|
elsif title?
|
246
323
|
key ||= @context.to_s
|
247
324
|
caption_key = "#{key}-caption"
|
248
|
-
if @document.attributes
|
249
|
-
caption_title = @document.attributes["#{key}-caption"]
|
325
|
+
if (caption_title = @document.attributes[caption_key])
|
250
326
|
caption_num = @document.counter_increment("#{key}-number", self)
|
251
327
|
@caption = "#{caption_title} #{caption_num}. "
|
252
328
|
end
|
253
|
-
else
|
254
|
-
@caption = caption
|
255
329
|
end
|
256
|
-
else
|
257
|
-
@caption = caption
|
258
330
|
end
|
259
331
|
nil
|
260
332
|
end
|
@@ -264,13 +336,27 @@ class AbstractBlock < AbstractNode
|
|
264
336
|
# Assign the next index of this section within the parent
|
265
337
|
# Block (in document order)
|
266
338
|
#
|
267
|
-
#
|
339
|
+
# Returns nothing
|
268
340
|
def assign_index(section)
|
269
341
|
section.index = @next_section_index
|
270
342
|
@next_section_index += 1
|
271
|
-
|
272
|
-
|
273
|
-
@
|
343
|
+
|
344
|
+
if section.sectname == 'appendix'
|
345
|
+
appendix_number = @document.counter 'appendix-number', 'A'
|
346
|
+
section.number = appendix_number if section.numbered
|
347
|
+
if (caption = @document.attr 'appendix-caption', '') != ''
|
348
|
+
section.caption = %(#{caption} #{appendix_number}: )
|
349
|
+
else
|
350
|
+
section.caption = %(#{appendix_number}. )
|
351
|
+
end
|
352
|
+
elsif section.numbered
|
353
|
+
# chapters in a book doctype should be sequential even when divided into parts
|
354
|
+
if (section.level == 1 || (section.level == 0 && section.special)) && @document.doctype == 'book'
|
355
|
+
section.number = @document.counter('chapter-number', 1)
|
356
|
+
else
|
357
|
+
section.number = @next_section_number
|
358
|
+
@next_section_number += 1
|
359
|
+
end
|
274
360
|
end
|
275
361
|
end
|
276
362
|
|
@@ -280,12 +366,12 @@ class AbstractBlock < AbstractNode
|
|
280
366
|
# and reassign the section 0-based index value to each Section
|
281
367
|
# as it appears in document order.
|
282
368
|
#
|
283
|
-
#
|
369
|
+
# Returns nothing
|
284
370
|
def reindex_sections
|
285
371
|
@next_section_index = 0
|
286
372
|
@next_section_number = 0
|
287
373
|
@blocks.each {|block|
|
288
|
-
if block.
|
374
|
+
if block.context == :section
|
289
375
|
assign_index(block)
|
290
376
|
block.reindex_sections
|
291
377
|
end
|
@@ -4,7 +4,7 @@ module Asciidoctor
|
|
4
4
|
# all content segments in an AsciiDoc document.
|
5
5
|
class AbstractNode
|
6
6
|
|
7
|
-
include
|
7
|
+
include Substitutors
|
8
8
|
|
9
9
|
# Public: Get the element which is the parent of this node
|
10
10
|
attr_reader :parent
|
@@ -15,24 +15,32 @@ class AbstractNode
|
|
15
15
|
# Public: Get the Symbol context for this node
|
16
16
|
attr_reader :context
|
17
17
|
|
18
|
-
# Public: Get the
|
18
|
+
# Public: Get the String name of this node
|
19
|
+
attr_reader :node_name
|
20
|
+
|
21
|
+
# Public: Get/Set the id of this node
|
19
22
|
attr_accessor :id
|
20
23
|
|
21
24
|
# Public: Get the Hash of attributes for this node
|
22
25
|
attr_reader :attributes
|
23
26
|
|
24
|
-
def initialize
|
27
|
+
def initialize parent, context, opts = {}
|
25
28
|
# document is a special case, should refer to itself
|
26
29
|
if context == :document
|
27
30
|
@parent = nil
|
28
31
|
@document = parent
|
29
32
|
else
|
30
|
-
@parent = parent
|
31
|
-
|
33
|
+
if (@parent = parent)
|
34
|
+
@document = parent.document
|
35
|
+
else
|
36
|
+
@document = nil
|
37
|
+
end
|
32
38
|
end
|
33
39
|
@context = context
|
34
|
-
@
|
35
|
-
|
40
|
+
@node_name = context.to_s
|
41
|
+
# QUESTION are we correct in duplicating the attributes (seems to be just as fast)
|
42
|
+
@attributes = opts.key?(:attributes) ? (opts[:attributes] || {}).dup : {}
|
43
|
+
@passthroughs = {}
|
36
44
|
end
|
37
45
|
|
38
46
|
# Public: Associate this Block with a new parent Block
|
@@ -46,6 +54,20 @@ class AbstractNode
|
|
46
54
|
nil
|
47
55
|
end
|
48
56
|
|
57
|
+
# Public: Returns whether this {AbstractNode} is an instance of {Inline}
|
58
|
+
#
|
59
|
+
# Returns [Boolean]
|
60
|
+
def inline?
|
61
|
+
raise ::NotImplementedError
|
62
|
+
end
|
63
|
+
|
64
|
+
# Public: Returns whether this {AbstractNode} is an instance of {Block}
|
65
|
+
#
|
66
|
+
# Returns [Boolean]
|
67
|
+
def block?
|
68
|
+
raise ::NotImplementedError
|
69
|
+
end
|
70
|
+
|
49
71
|
# Public: Get the value of the specified attribute
|
50
72
|
#
|
51
73
|
# Get the value for the specified attribute. First look in the attributes on
|
@@ -54,20 +76,20 @@ class AbstractNode
|
|
54
76
|
# Document node and return the value of the attribute if found. Otherwise,
|
55
77
|
# return the default value, which defaults to nil.
|
56
78
|
#
|
57
|
-
# name
|
58
|
-
#
|
59
|
-
# inherit
|
60
|
-
#
|
79
|
+
# name - the String or Symbol name of the attribute to lookup
|
80
|
+
# default_value - the Object value to return if the attribute is not found (default: nil)
|
81
|
+
# inherit - a Boolean indicating whether to check for the attribute on the
|
82
|
+
# AsciiDoctor::Document if not found on this node (default: false)
|
61
83
|
#
|
62
84
|
# return the value of the attribute or the default value if the attribute
|
63
85
|
# is not found in the attributes of this node or the document node
|
64
|
-
def attr(name,
|
65
|
-
name = name.to_s if name.is_a?(Symbol)
|
86
|
+
def attr(name, default_value = nil, inherit = true)
|
87
|
+
name = name.to_s if name.is_a?(::Symbol)
|
66
88
|
inherit = false if self == @document
|
67
89
|
if inherit
|
68
|
-
@attributes[name] || @document.attributes[name] ||
|
90
|
+
@attributes[name] || @document.attributes[name] || default_value
|
69
91
|
else
|
70
|
-
@attributes[name] ||
|
92
|
+
@attributes[name] || default_value
|
71
93
|
end
|
72
94
|
end
|
73
95
|
|
@@ -89,7 +111,7 @@ class AbstractNode
|
|
89
111
|
# comparison value is specified, whether the value of the attribute matches
|
90
112
|
# the comparison value
|
91
113
|
def attr?(name, expect = nil, inherit = true)
|
92
|
-
name = name.to_s if name.is_a?(Symbol)
|
114
|
+
name = name.to_s if name.is_a?(::Symbol)
|
93
115
|
inherit = false if self == @document
|
94
116
|
if expect.nil?
|
95
117
|
@attributes.has_key?(name) || (inherit && @document.attributes.has_key?(name))
|
@@ -100,20 +122,21 @@ class AbstractNode
|
|
100
122
|
end
|
101
123
|
end
|
102
124
|
|
103
|
-
# Public: Assign the value to the
|
104
|
-
# block's attributes hash.
|
125
|
+
# Public: Assign the value to the attribute name for the current node.
|
105
126
|
#
|
106
|
-
#
|
107
|
-
#
|
127
|
+
# name - The String attribute name
|
128
|
+
# value - The Object value to assign to the attribute name
|
129
|
+
# overwrite - A Boolean indicating whether to assign the attribute
|
130
|
+
# if currently present in the attributes Hash
|
108
131
|
#
|
109
|
-
#
|
110
|
-
def set_attr
|
132
|
+
# Returns a [Boolean] indicating whether the assignment was performed
|
133
|
+
def set_attr name, value, overwrite = nil
|
111
134
|
if overwrite.nil?
|
112
|
-
@attributes[
|
135
|
+
@attributes[name] = value
|
113
136
|
true
|
114
137
|
else
|
115
|
-
if overwrite || @attributes.
|
116
|
-
@attributes[
|
138
|
+
if overwrite || !(@attributes.key? name)
|
139
|
+
@attributes[name] = value
|
117
140
|
true
|
118
141
|
else
|
119
142
|
false
|
@@ -135,33 +158,13 @@ class AbstractNode
|
|
135
158
|
# enabled on the current node.
|
136
159
|
#
|
137
160
|
# Check if the option is enabled. This method simply checks to see if the
|
138
|
-
#
|
161
|
+
# %name%-option attribute is defined on the current node.
|
139
162
|
#
|
140
163
|
# name - the String or Symbol name of the option
|
141
164
|
#
|
142
165
|
# return a Boolean indicating whether the option has been specified
|
143
166
|
def option?(name)
|
144
|
-
@attributes.has_key?
|
145
|
-
end
|
146
|
-
|
147
|
-
# Public: Get the execution context of this object (via Kernel#binding).
|
148
|
-
#
|
149
|
-
# This method is used to set the 'self' reference as well as local variables
|
150
|
-
# that map to this method's arguments during the evaluation of a backend
|
151
|
-
# template.
|
152
|
-
#
|
153
|
-
# Each object in Ruby has a binding context that can be used to set the 'self'
|
154
|
-
# reference in an evaluation context. Any arguments passed to this
|
155
|
-
# method are also available in the execution environment.
|
156
|
-
#
|
157
|
-
# template - The BaseTemplate instance in which this binding will be active.
|
158
|
-
# Bound to the local variable of the same name, template.
|
159
|
-
#
|
160
|
-
# returns the execution context for this object so it can be be transferred to
|
161
|
-
# the backend template and binds the method arguments as local variables in
|
162
|
-
# that same environment.
|
163
|
-
def get_binding template
|
164
|
-
binding
|
167
|
+
@attributes.has_key? %(#{name}-option)
|
165
168
|
end
|
166
169
|
|
167
170
|
# Public: Update the attributes of this node with the new values in
|
@@ -172,16 +175,16 @@ class AbstractNode
|
|
172
175
|
#
|
173
176
|
# attributes - A Hash of attributes to assign to this node.
|
174
177
|
#
|
175
|
-
#
|
178
|
+
# Returns nothing
|
176
179
|
def update_attributes(attributes)
|
177
180
|
@attributes.update(attributes)
|
178
181
|
nil
|
179
182
|
end
|
180
183
|
|
181
|
-
# Public: Get the Asciidoctor::
|
182
|
-
# Asciidoctor::Document
|
183
|
-
def
|
184
|
-
@document.
|
184
|
+
# Public: Get the Asciidoctor::Converter instance being used to convert the
|
185
|
+
# current Asciidoctor::Document.
|
186
|
+
def converter
|
187
|
+
@document.converter
|
185
188
|
end
|
186
189
|
|
187
190
|
# Public: A convenience method that checks if the role attribute is specified
|
@@ -249,7 +252,7 @@ class AbstractNode
|
|
249
252
|
if attr? 'icon'
|
250
253
|
image_uri(attr('icon'), nil)
|
251
254
|
else
|
252
|
-
image_uri(
|
255
|
+
image_uri(%(#{name}.#{@document.attr('icontype', 'png')}), 'iconsdir')
|
253
256
|
end
|
254
257
|
end
|
255
258
|
|
@@ -268,12 +271,10 @@ class AbstractNode
|
|
268
271
|
#
|
269
272
|
# Returns A String reference for the target media
|
270
273
|
def media_uri(target, asset_dir_key = 'imagesdir')
|
271
|
-
if
|
274
|
+
if is_uri? target
|
272
275
|
target
|
273
|
-
elsif asset_dir_key && attr?(asset_dir_key)
|
274
|
-
normalize_web_path(target, @document.attr(asset_dir_key))
|
275
276
|
else
|
276
|
-
normalize_web_path(
|
277
|
+
normalize_web_path target, (asset_dir_key ? @document.attr(asset_dir_key) : nil)
|
277
278
|
end
|
278
279
|
end
|
279
280
|
|
@@ -297,14 +298,22 @@ class AbstractNode
|
|
297
298
|
#
|
298
299
|
# Returns A String reference or data URI for the target image
|
299
300
|
def image_uri(target_image, asset_dir_key = 'imagesdir')
|
300
|
-
if
|
301
|
+
if (doc = @document).safe < SafeMode::SECURE && doc.attr?('data-uri')
|
302
|
+
if is_uri?(target_image) ||
|
303
|
+
(asset_dir_key && (images_base = doc.attr(asset_dir_key)) &&
|
304
|
+
is_uri?(images_base) && (target_image = normalize_web_path target_image, images_base))
|
305
|
+
if doc.attr? 'allow-uri-read'
|
306
|
+
generate_data_uri_from_uri target_image, doc.attr?('cache-uri')
|
307
|
+
else
|
308
|
+
target_image
|
309
|
+
end
|
310
|
+
else
|
311
|
+
generate_data_uri target_image, asset_dir_key
|
312
|
+
end
|
313
|
+
elsif is_uri? target_image
|
301
314
|
target_image
|
302
|
-
elsif @document.safe < Asciidoctor::SafeMode::SECURE && @document.attr?('data-uri')
|
303
|
-
generate_data_uri(target_image, asset_dir_key)
|
304
|
-
elsif asset_dir_key && attr?(asset_dir_key)
|
305
|
-
normalize_web_path(target_image, @document.attr(asset_dir_key))
|
306
315
|
else
|
307
|
-
normalize_web_path(
|
316
|
+
normalize_web_path target_image, (asset_dir_key ? doc.attr(asset_dir_key) : nil)
|
308
317
|
end
|
309
318
|
end
|
310
319
|
|
@@ -321,32 +330,69 @@ class AbstractNode
|
|
321
330
|
#
|
322
331
|
# Returns A String data URI containing the content of the target image
|
323
332
|
def generate_data_uri(target_image, asset_dir_key = nil)
|
324
|
-
|
325
|
-
|
326
|
-
ext = File.extname(target_image)[1..-1]
|
327
|
-
mimetype = 'image/' + ext
|
328
|
-
mimetype = "#{mimetype}+xml" if ext == 'svg'
|
333
|
+
ext = ::File.extname(target_image)[1..-1]
|
334
|
+
mimetype = (ext == 'svg' ? 'image/svg+xml' : %(image/#{ext}))
|
329
335
|
if asset_dir_key
|
330
|
-
#asset_dir_path = normalize_system_path(@document.attr(asset_dir_key), nil, nil, :target_name => asset_dir_key)
|
331
|
-
#image_path = normalize_system_path(target_image, asset_dir_path, nil, :target_name => 'image')
|
332
336
|
image_path = normalize_system_path(target_image, @document.attr(asset_dir_key), nil, :target_name => 'image')
|
333
337
|
else
|
334
338
|
image_path = normalize_system_path(target_image)
|
335
339
|
end
|
336
340
|
|
337
|
-
|
338
|
-
warn
|
341
|
+
unless ::File.readable? image_path
|
342
|
+
warn %(asciidoctor: WARNING: image to embed not found or not readable: #{image_path})
|
343
|
+
# must enclose string following return in " for Opal
|
339
344
|
return "data:#{mimetype}:base64,"
|
345
|
+
# uncomment to return 1 pixel white dot instead
|
340
346
|
#return 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='
|
341
347
|
end
|
342
348
|
|
343
349
|
bindata = nil
|
344
|
-
if IO.respond_to? :binread
|
345
|
-
bindata = IO.binread(image_path)
|
350
|
+
if ::IO.respond_to? :binread
|
351
|
+
bindata = ::IO.binread(image_path)
|
346
352
|
else
|
347
|
-
bindata = File.open(image_path, 'rb') {|file| file.read }
|
353
|
+
bindata = ::File.open(image_path, 'rb') {|file| file.read }
|
354
|
+
end
|
355
|
+
%(data:#{mimetype};base64,#{::Base64.encode64(bindata).delete EOL})
|
356
|
+
end
|
357
|
+
|
358
|
+
# Public: Read the image data from the specified URI and generate a data URI
|
359
|
+
#
|
360
|
+
# The image data is read from the URI and converted to Base64. A data URI is
|
361
|
+
# constructed from the content_type header and Base64 data and returned,
|
362
|
+
# which can then be used in an image tag.
|
363
|
+
#
|
364
|
+
# image_uri - The URI from which to read the image data. Can be http://, https:// or ftp://
|
365
|
+
# cache_uri - A Boolean to control caching. When true, the open-uri-cached library
|
366
|
+
# is used to cache the image for subsequent reads. (default: false)
|
367
|
+
#
|
368
|
+
# Returns A data URI string built from Base64 encoded data read from the URI
|
369
|
+
# and the mime type specified in the Content Type header.
|
370
|
+
def generate_data_uri_from_uri image_uri, cache_uri = false
|
371
|
+
Helpers.require_library 'base64'
|
372
|
+
if cache_uri
|
373
|
+
# caching requires the open-uri-cached gem to be installed
|
374
|
+
# processing will be automatically aborted if these libraries can't be opened
|
375
|
+
Helpers.require_library 'open-uri/cached', 'open-uri-cached'
|
376
|
+
elsif !::RUBY_ENGINE_OPAL
|
377
|
+
# autoload open-uri
|
378
|
+
::OpenURI
|
379
|
+
end
|
380
|
+
|
381
|
+
begin
|
382
|
+
mimetype = nil
|
383
|
+
bindata = open(image_uri, 'rb') {|file|
|
384
|
+
mimetype = file.content_type
|
385
|
+
file.read
|
386
|
+
}
|
387
|
+
%(data:#{mimetype};base64,#{Base64.encode64(bindata).delete EOL})
|
388
|
+
rescue
|
389
|
+
warn %(asciidoctor: WARNING: could not retrieve image data from URI: #{image_uri})
|
390
|
+
image_uri
|
391
|
+
# uncomment to return empty data (however, mimetype needs to be resolved)
|
392
|
+
#%(data:#{mimetype}:base64,)
|
393
|
+
# uncomment to return 1 pixel white dot instead
|
394
|
+
#'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='
|
348
395
|
end
|
349
|
-
"data:#{mimetype};base64,#{Base64.encode64(bindata).delete("\n")}"
|
350
396
|
end
|
351
397
|
|
352
398
|
# Public: Read the contents of the file at the specified path.
|
@@ -357,11 +403,12 @@ class AbstractNode
|
|
357
403
|
# warn_on_failure - a Boolean that controls whether a warning is issued if
|
358
404
|
# the file cannot be read
|
359
405
|
#
|
360
|
-
#
|
406
|
+
# Returns the [String] content of the file at the specified path, or nil
|
361
407
|
# if the file does not exist.
|
362
408
|
def read_asset(path, warn_on_failure = false)
|
363
|
-
if File.readable? path
|
364
|
-
|
409
|
+
if ::File.readable? path
|
410
|
+
# QUESTION should we use strip or rstrip instead of chomp here?
|
411
|
+
::File.read(path).chomp
|
365
412
|
else
|
366
413
|
warn "asciidoctor: WARNING: file does not exist or cannot be read: #{path}" if warn_on_failure
|
367
414
|
nil
|
@@ -370,20 +417,20 @@ class AbstractNode
|
|
370
417
|
|
371
418
|
# Public: Normalize the web page using the PathResolver.
|
372
419
|
#
|
373
|
-
# See PathResolver
|
420
|
+
# See {PathResolver#web_path} for details.
|
374
421
|
#
|
375
422
|
# target - the String target path
|
376
423
|
# start - the String start (i.e, parent) path (optional, default: nil)
|
377
424
|
#
|
378
|
-
#
|
425
|
+
# Returns the resolved [String] path
|
379
426
|
def normalize_web_path(target, start = nil)
|
380
|
-
PathResolver.new.web_path(target, start)
|
427
|
+
(@path_resolver ||= PathResolver.new).web_path(target, start)
|
381
428
|
end
|
382
429
|
|
383
430
|
# Public: Resolve and normalize a secure path from the target and start paths
|
384
431
|
# using the PathResolver.
|
385
432
|
#
|
386
|
-
# See PathResolver
|
433
|
+
# See {PathResolver#system_path} for details.
|
387
434
|
#
|
388
435
|
# The most important functionality in this method is to prevent resolving a
|
389
436
|
# path outside of the jail (which defaults to the directory of the source
|
@@ -402,17 +449,21 @@ class AbstractNode
|
|
402
449
|
# raises a SecurityError if a jail is specified and the resolved path is
|
403
450
|
# outside the jail.
|
404
451
|
#
|
405
|
-
#
|
452
|
+
# Returns the [String] path resolved from the start and target paths, with any
|
406
453
|
# parent references resolved and self references removed. If a jail is provided,
|
407
454
|
# this path will be guaranteed to be contained within the jail.
|
408
|
-
def normalize_system_path
|
409
|
-
if
|
410
|
-
start
|
411
|
-
|
412
|
-
|
413
|
-
|
455
|
+
def normalize_system_path target, start = nil, jail = nil, opts = {}
|
456
|
+
if (doc = @document).safe < SafeMode::SAFE
|
457
|
+
if start
|
458
|
+
start = ::File.join doc.base_dir, start unless (@path_resolver ||= PathResolver.new).is_root? start
|
459
|
+
else
|
460
|
+
start = doc.base_dir
|
461
|
+
end
|
462
|
+
else
|
463
|
+
start = doc.base_dir unless start
|
464
|
+
jail = doc.base_dir unless jail
|
414
465
|
end
|
415
|
-
PathResolver.new.system_path
|
466
|
+
(@path_resolver ||= PathResolver.new).system_path target, start, jail, opts
|
416
467
|
end
|
417
468
|
|
418
469
|
# Public: Normalize the asset file or directory to a concrete and rinsed path
|
@@ -426,7 +477,13 @@ class AbstractNode
|
|
426
477
|
|
427
478
|
# Public: Calculate the relative path to this absolute filename from the Document#base_dir
|
428
479
|
def relative_path(filename)
|
429
|
-
PathResolver.new.relative_path filename, @document.base_dir
|
480
|
+
(@path_resolver ||= PathResolver.new).relative_path filename, @document.base_dir
|
481
|
+
end
|
482
|
+
|
483
|
+
# Public: Check whether the specified String is a URI by
|
484
|
+
# matching it against the Asciidoctor::UriSniffRx regex.
|
485
|
+
def is_uri? str
|
486
|
+
str.include?(':') && UriSniffRx =~ str
|
430
487
|
end
|
431
488
|
|
432
489
|
# Public: Retrieve the list marker keyword for the specified list type.
|
@@ -435,7 +492,7 @@ class AbstractNode
|
|
435
492
|
#
|
436
493
|
# list_type - the type of list; default to the @style if not specified
|
437
494
|
#
|
438
|
-
#
|
495
|
+
# Returns the single-character [String] keyword that represents the marker for the specified list type
|
439
496
|
def list_marker_keyword(list_type = nil)
|
440
497
|
ORDERED_LIST_KEYWORDS[list_type || @style]
|
441
498
|
end
|