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.

Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +209 -25
  3. data/{LICENSE → LICENSE.adoc} +4 -3
  4. data/README.adoc +392 -395
  5. data/Rakefile +94 -137
  6. data/benchmark/benchmark.rb +127 -0
  7. data/benchmark/sample-data/mdbasics.adoc +334 -0
  8. data/bin/asciidoctor +5 -8
  9. data/bin/asciidoctor-safe +4 -8
  10. data/compat/asciidoc.conf +78 -11
  11. data/compat/font-awesome-3-compat.css +397 -0
  12. data/data/stylesheets/asciidoctor-default.css +399 -0
  13. data/data/stylesheets/coderay-asciidoctor.css +89 -0
  14. data/features/open_block.feature +92 -0
  15. data/features/pass_block.feature +66 -0
  16. data/features/step_definitions.rb +42 -0
  17. data/features/text_formatting.feature +55 -0
  18. data/features/xref.feature +116 -0
  19. data/lib/asciidoctor.rb +1155 -605
  20. data/lib/asciidoctor/abstract_block.rb +157 -71
  21. data/lib/asciidoctor/abstract_node.rb +150 -93
  22. data/lib/asciidoctor/attribute_list.rb +85 -90
  23. data/lib/asciidoctor/block.rb +51 -24
  24. data/lib/asciidoctor/callouts.rb +4 -7
  25. data/lib/asciidoctor/cli.rb +3 -0
  26. data/lib/asciidoctor/cli/invoker.rb +86 -76
  27. data/lib/asciidoctor/cli/options.rb +111 -61
  28. data/lib/asciidoctor/converter.rb +232 -0
  29. data/lib/asciidoctor/converter/base.rb +58 -0
  30. data/lib/asciidoctor/converter/composite.rb +66 -0
  31. data/lib/asciidoctor/converter/docbook45.rb +94 -0
  32. data/lib/asciidoctor/converter/docbook5.rb +684 -0
  33. data/lib/asciidoctor/converter/factory.rb +225 -0
  34. data/lib/asciidoctor/converter/html5.rb +1081 -0
  35. data/lib/asciidoctor/converter/template.rb +296 -0
  36. data/lib/asciidoctor/core_ext.rb +7 -0
  37. data/lib/asciidoctor/core_ext/object/nil_or_empty.rb +23 -0
  38. data/lib/asciidoctor/core_ext/string/chr.rb +6 -0
  39. data/lib/asciidoctor/core_ext/symbol/length.rb +6 -0
  40. data/lib/asciidoctor/document.rb +590 -304
  41. data/lib/asciidoctor/extensions.rb +1100 -308
  42. data/lib/asciidoctor/helpers.rb +109 -46
  43. data/lib/asciidoctor/inline.rb +16 -9
  44. data/lib/asciidoctor/list.rb +23 -15
  45. data/lib/asciidoctor/opal_ext.rb +4 -0
  46. data/lib/asciidoctor/opal_ext/comparable.rb +38 -0
  47. data/lib/asciidoctor/opal_ext/dir.rb +13 -0
  48. data/lib/asciidoctor/opal_ext/error.rb +2 -0
  49. data/lib/asciidoctor/opal_ext/file.rb +125 -0
  50. data/lib/asciidoctor/{lexer.rb → parser.rb} +646 -455
  51. data/lib/asciidoctor/path_resolver.rb +141 -77
  52. data/lib/asciidoctor/reader.rb +257 -187
  53. data/lib/asciidoctor/section.rb +12 -16
  54. data/lib/asciidoctor/stylesheets.rb +91 -0
  55. data/lib/asciidoctor/substitutors.rb +1548 -0
  56. data/lib/asciidoctor/table.rb +73 -57
  57. data/lib/asciidoctor/timings.rb +39 -0
  58. data/lib/asciidoctor/version.rb +1 -1
  59. data/man/asciidoctor.1 +22 -14
  60. data/man/asciidoctor.adoc +18 -10
  61. data/test/attributes_test.rb +314 -14
  62. data/test/blocks_test.rb +763 -118
  63. data/test/converter_test.rb +352 -0
  64. data/test/document_test.rb +518 -199
  65. data/test/extensions_test.rb +273 -103
  66. data/test/fixtures/asciidoc_index.txt +27 -13
  67. data/test/fixtures/basic-docinfo.xml +1 -1
  68. data/test/fixtures/chapter-a.adoc +3 -0
  69. data/test/fixtures/custom-backends/erb/html5/block_paragraph.html.erb +6 -0
  70. data/test/fixtures/docinfo.xml +1 -1
  71. data/test/fixtures/include-file.asciidoc +2 -0
  72. data/test/fixtures/master.adoc +5 -0
  73. data/test/invoker_test.rb +173 -61
  74. data/test/links_test.rb +97 -21
  75. data/test/lists_test.rb +181 -22
  76. data/test/options_test.rb +86 -2
  77. data/test/paragraphs_test.rb +47 -5
  78. data/test/{lexer_test.rb → parser_test.rb} +128 -57
  79. data/test/paths_test.rb +36 -1
  80. data/test/preamble_test.rb +25 -17
  81. data/test/reader_test.rb +404 -249
  82. data/test/sections_test.rb +623 -58
  83. data/test/substitutions_test.rb +609 -132
  84. data/test/tables_test.rb +198 -24
  85. data/test/test_helper.rb +101 -31
  86. data/test/text_test.rb +88 -31
  87. metadata +160 -64
  88. data/Gemfile +0 -12
  89. data/Guardfile +0 -18
  90. data/asciidoctor.gemspec +0 -143
  91. data/lib/asciidoctor/backends/_stylesheets.rb +0 -466
  92. data/lib/asciidoctor/backends/base_template.rb +0 -114
  93. data/lib/asciidoctor/backends/docbook45.rb +0 -774
  94. data/lib/asciidoctor/backends/docbook5.rb +0 -103
  95. data/lib/asciidoctor/backends/html5.rb +0 -1214
  96. data/lib/asciidoctor/renderer.rb +0 -259
  97. data/lib/asciidoctor/substituters.rb +0 -1083
  98. data/test/fixtures/asciidoc.txt +0 -105
  99. data/test/fixtures/ascshort.txt +0 -32
  100. data/test/fixtures/list_elements.asciidoc +0 -10
  101. 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
- def initialize(parent, context)
28
- super(parent, context)
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
- @template_name = "block_#{context}"
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
- @level = 0
39
- elsif !parent.nil? && !self.is_a?(Section)
40
- @level = parent.level
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: Get the rendered String content for this Block. If the block
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
- # rendered and returned as content that can be included in the
66
+ # converted and returned as content that can be included in the
51
67
  # parent block's template.
52
- def render
68
+ def convert
53
69
  @document.playback_attributes @attributes
54
- renderer.render(@template_name, self)
70
+ converter.convert self
55
71
  end
56
72
 
57
- # Public: Get an rendered version of the block content, rendering the
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.render } * EOL
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.to_s.empty?
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
- # returns an Array of Section objects
193
+ # Returns an [Array] of Section objects
165
194
  def sections
166
- @blocks.inject([]) {|collector, block|
167
- collector << block if block.is_a?(Section)
168
- collector
169
- }
195
+ @blocks.select {|block| block.context == :section }
170
196
  end
171
197
 
172
- # Internal: Lock-in the substitutions for this block
173
- #
174
- # Looks for an attribute named "subs". If present, resolves the
175
- # substitutions and assigns it to the subs property on this block.
176
- # Otherwise, assigns a set of default substitutions based on the
177
- # content model of the block.
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 lock_in_subs
181
- default_subs = []
182
- case @content_model
183
- when :simple
184
- default_subs = SUBS[:normal]
185
- when :verbatim
186
- if @context == :listing || (@context == :literal && !(option? 'listparagraph'))
187
- default_subs = SUBS[:verbatim]
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
- default_subs = SUBS[:basic]
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
- if (custom_subs = @attributes['subs'])
198
- @subs = resolve_block_subs custom_subs, @context
199
- else
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
- # QUESION delegate this logic to method?
204
- if @context == :listing && @style == 'source' && (@document.basebackend? 'html') &&
205
- ((highlighter = @document.attributes['source-highlighter']) == 'coderay' ||
206
- highlighter == 'pygments') && (attr? 'language')
207
- @subs = @subs.map {|sub| sub == :specialcharacters ? :highlight : sub }
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
- # returns nothing
313
+ # Returns nothing
237
314
  def assign_caption(caption = nil, key = nil)
238
- unless title? || @caption.nil?
239
- return nil
240
- end
315
+ return unless title? || !@caption
241
316
 
242
- if caption.nil?
243
- if @document.attributes.has_key? 'caption'
244
- @caption = @document.attributes['caption']
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.has_key? caption_key
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
- # returns nothing
339
+ # Returns nothing
268
340
  def assign_index(section)
269
341
  section.index = @next_section_index
270
342
  @next_section_index += 1
271
- if section.numbered
272
- section.number = @next_section_number
273
- @next_section_number += 1
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
- # returns nothing
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.is_a?(Section)
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 Substituters
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 id of this node
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(parent, context)
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
- @document = (parent.nil? ? nil : parent.document)
33
+ if (@parent = parent)
34
+ @document = parent.document
35
+ else
36
+ @document = nil
37
+ end
32
38
  end
33
39
  @context = context
34
- @attributes = {}
35
- @passthroughs = []
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 - the String or Symbol name of the attribute to lookup
58
- # default - the Object value to return if the attribute is not found (default: nil)
59
- # inherit - a Boolean indicating whether to check for the attribute on the
60
- # AsciiDoctor::Document if not found on this node (default: false)
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, default = nil, inherit = true)
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] || default
90
+ @attributes[name] || @document.attributes[name] || default_value
69
91
  else
70
- @attributes[name] || default
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 specified key in this
104
- # block's attributes hash.
125
+ # Public: Assign the value to the attribute name for the current node.
105
126
  #
106
- # key - The attribute key (or name)
107
- # val - The value to assign to the key
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
- # returns a flag indicating whether the assignment was performed
110
- def set_attr(key, val, overwrite = nil)
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[key] = val
135
+ @attributes[name] = value
113
136
  true
114
137
  else
115
- if overwrite || @attributes.has_key?(key)
116
- @attributes[key] = val
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
- # {name}-option attribute is defined on the current node.
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? "#{name}-option"
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
- # returns nothing
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::Renderer instance being used for the
182
- # Asciidoctor::Document to which this node belongs
183
- def renderer
184
- @document.renderer
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("#{name}.#{@document.attr('icontype', 'png')}", 'iconsdir')
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 target.include?(':') && target.match(Asciidoctor::REGEXP[:uri_sniff])
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(target)
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 target_image.include?(':') && target_image.match(Asciidoctor::REGEXP[:uri_sniff])
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(target_image)
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
- Helpers.require_library 'base64'
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
- if !File.readable? image_path
338
- warn "asciidoctor: WARNING: image to embed not found or not readable: #{image_path}"
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
- # returns the contents of the file at the specified path, or nil
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
- File.read(path).chomp
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::web_path(target, start) for details.
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
- # returns the resolved String path
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::system_path(target, start, jail, opts) for details.
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
- # returns a String path resolved from the start and target paths, with any
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(target, start = nil, jail = nil, opts = {})
409
- if start.nil?
410
- start = @document.base_dir
411
- end
412
- if jail.nil? && @document.safe >= SafeMode::SAFE
413
- jail = @document.base_dir
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(target, start, jail, opts)
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
- # returns the single-character String keyword that represents the marker for the specified list type
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