asciidoctor 0.1.0 → 0.1.1

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 (40) hide show
  1. data/README.asciidoc +11 -2
  2. data/asciidoctor.gemspec +3 -2
  3. data/lib/asciidoctor.rb +95 -62
  4. data/lib/asciidoctor/abstract_block.rb +7 -5
  5. data/lib/asciidoctor/abstract_node.rb +63 -15
  6. data/lib/asciidoctor/attribute_list.rb +3 -1
  7. data/lib/asciidoctor/backends/base_template.rb +17 -7
  8. data/lib/asciidoctor/backends/docbook45.rb +182 -150
  9. data/lib/asciidoctor/backends/html5.rb +138 -110
  10. data/lib/asciidoctor/block.rb +21 -18
  11. data/lib/asciidoctor/callouts.rb +3 -1
  12. data/lib/asciidoctor/cli/invoker.rb +3 -3
  13. data/lib/asciidoctor/cli/options.rb +6 -6
  14. data/lib/asciidoctor/debug.rb +7 -6
  15. data/lib/asciidoctor/document.rb +197 -25
  16. data/lib/asciidoctor/errors.rb +1 -1
  17. data/lib/asciidoctor/helpers.rb +29 -0
  18. data/lib/asciidoctor/inline.rb +11 -4
  19. data/lib/asciidoctor/lexer.rb +338 -182
  20. data/lib/asciidoctor/list_item.rb +14 -12
  21. data/lib/asciidoctor/reader.rb +423 -206
  22. data/lib/asciidoctor/renderer.rb +59 -15
  23. data/lib/asciidoctor/section.rb +7 -4
  24. data/lib/asciidoctor/substituters.rb +536 -511
  25. data/lib/asciidoctor/table.rb +473 -472
  26. data/lib/asciidoctor/version.rb +1 -1
  27. data/man/asciidoctor.1 +23 -14
  28. data/man/asciidoctor.ad +13 -7
  29. data/test/attributes_test.rb +42 -8
  30. data/test/blocks_test.rb +161 -1
  31. data/test/document_test.rb +134 -16
  32. data/test/invoker_test.rb +14 -6
  33. data/test/lexer_test.rb +45 -18
  34. data/test/lists_test.rb +79 -0
  35. data/test/paragraphs_test.rb +9 -1
  36. data/test/reader_test.rb +456 -19
  37. data/test/sections_test.rb +19 -0
  38. data/test/substitutions_test.rb +14 -12
  39. data/test/tables_test.rb +10 -10
  40. metadata +3 -5
@@ -1,3 +1,4 @@
1
+ module Asciidoctor
1
2
  # Public: Methods for managing blocks of Asciidoc content in a section.
2
3
  #
3
4
  # Examples
@@ -5,7 +6,7 @@
5
6
  # block = Asciidoctor::Block.new(document, :paragraph, ["`This` is a <test>"])
6
7
  # block.content
7
8
  # => ["<em>This</em> is a &lt;test&gt;"]
8
- class Asciidoctor::Block < Asciidoctor::AbstractBlock
9
+ class Block < AbstractBlock
9
10
 
10
11
  # Public: Create alias for context to be consistent w/ AsciiDoc
11
12
  alias :blockname :context
@@ -32,7 +33,8 @@ class Asciidoctor::Block < Asciidoctor::AbstractBlock
32
33
  # rendered and returned as content that can be included in the
33
34
  # parent block's template.
34
35
  def render
35
- Asciidoctor.debug { "Now rendering #{@context} block for #{self}" }
36
+ Debug.debug { "Now rendering #{@context} block for #{self}" }
37
+ @document.playback_attributes @attributes
36
38
  out = renderer.render("block_#{@context}", self)
37
39
  @document.callouts.next_list if @context == :colist
38
40
  out
@@ -40,42 +42,42 @@ class Asciidoctor::Block < Asciidoctor::AbstractBlock
40
42
 
41
43
  def splain(parent_level = 0)
42
44
  parent_level += 1
43
- Asciidoctor.puts_indented(parent_level, "Block id: #{id}") unless self.id.nil?
44
- Asciidoctor.puts_indented(parent_level, "Block title: #{title}") unless self.title.nil?
45
- Asciidoctor.puts_indented(parent_level, "Block caption: #{caption}") unless self.caption.nil?
46
- Asciidoctor.puts_indented(parent_level, "Block level: #{level}") unless self.level.nil?
47
- Asciidoctor.puts_indented(parent_level, "Block context: #{context}") unless self.context.nil?
45
+ Debug.puts_indented(parent_level, "Block id: #{id}") unless self.id.nil?
46
+ Debug.puts_indented(parent_level, "Block title: #{title}") unless self.title.nil?
47
+ Debug.puts_indented(parent_level, "Block caption: #{caption}") unless self.caption.nil?
48
+ Debug.puts_indented(parent_level, "Block level: #{level}") unless self.level.nil?
49
+ Debug.puts_indented(parent_level, "Block context: #{context}") unless self.context.nil?
48
50
 
49
- Asciidoctor.puts_indented(parent_level, "Blocks: #{@blocks.count}")
51
+ Debug.puts_indented(parent_level, "Blocks: #{@blocks.count}")
50
52
 
51
53
  if buffer.is_a? Enumerable
52
54
  buffer.each_with_index do |buf, i|
53
- Asciidoctor.puts_indented(parent_level, "v" * (60 - parent_level*2))
54
- Asciidoctor.puts_indented(parent_level, "Buffer ##{i} is a #{buf.class}")
55
- Asciidoctor.puts_indented(parent_level, "Name is #{buf.title rescue 'n/a'}")
55
+ Debug.puts_indented(parent_level, "v" * (60 - parent_level*2))
56
+ Debug.puts_indented(parent_level, "Buffer ##{i} is a #{buf.class}")
57
+ Debug.puts_indented(parent_level, "Name is #{buf.title rescue 'n/a'}")
56
58
 
57
59
  if buf.respond_to? :splain
58
60
  buf.splain(parent_level)
59
61
  else
60
- Asciidoctor.puts_indented(parent_level, "Buffer: #{buf}")
62
+ Debug.puts_indented(parent_level, "Buffer: #{buf}")
61
63
  end
62
- Asciidoctor.puts_indented(parent_level, "^" * (60 - parent_level*2))
64
+ Debug.puts_indented(parent_level, "^" * (60 - parent_level*2))
63
65
  end
64
66
  else
65
67
  if buffer.respond_to? :splain
66
68
  buffer.splain(parent_level)
67
69
  else
68
- Asciidoctor.puts_indented(parent_level, "Buffer: #{@buffer}")
70
+ Debug.puts_indented(parent_level, "Buffer: #{@buffer}")
69
71
  end
70
72
  end
71
73
 
72
74
  @blocks.each_with_index do |block, i|
73
- Asciidoctor.puts_indented(parent_level, "v" * (60 - parent_level*2))
74
- Asciidoctor.puts_indented(parent_level, "Block ##{i} is a #{block.class}")
75
- Asciidoctor.puts_indented(parent_level, "Name is #{block.title rescue 'n/a'}")
75
+ Debug.puts_indented(parent_level, "v" * (60 - parent_level*2))
76
+ Debug.puts_indented(parent_level, "Block ##{i} is a #{block.class}")
77
+ Debug.puts_indented(parent_level, "Name is #{block.title rescue 'n/a'}")
76
78
 
77
79
  block.splain(parent_level) if block.respond_to? :splain
78
- Asciidoctor.puts_indented(parent_level, "^" * (60 - parent_level*2))
80
+ Debug.puts_indented(parent_level, "^" * (60 - parent_level*2))
79
81
  end
80
82
 
81
83
  nil
@@ -119,3 +121,4 @@ class Asciidoctor::Block < Asciidoctor::AbstractBlock
119
121
  "#{super.to_s} - #@context [blocks:#{(@blocks || []).size}]"
120
122
  end
121
123
  end
124
+ end
@@ -1,5 +1,6 @@
1
+ module Asciidoctor
1
2
  # Public: Maintains a catalog of callouts and their associations.
2
- class Asciidoctor::Callouts
3
+ class Callouts
3
4
  def initialize
4
5
  @lists = []
5
6
  @list_index = 0
@@ -115,3 +116,4 @@ class Asciidoctor::Callouts
115
116
  "CO#{list_index}-#{co_index}"
116
117
  end
117
118
  end
119
+ end
@@ -14,12 +14,12 @@ module Asciidoctor
14
14
  @code = 0
15
15
  @timings = {}
16
16
  options = options.flatten
17
- if !options.empty? && options.first.is_a?(Asciidoctor::Cli::Options)
17
+ if !options.empty? && options.first.is_a?(Cli::Options)
18
18
  @options = options.first
19
19
  elsif options.first.is_a? Hash
20
- @options = Asciidoctor::Cli::Options.new(options)
20
+ @options = Cli::Options.new(options)
21
21
  else
22
- @options = Asciidoctor::Cli::Options.parse!(options)
22
+ @options = Cli::Options.parse!(options)
23
23
  # hmmm
24
24
  if @options.is_a?(Integer)
25
25
  @code = @options
@@ -10,7 +10,7 @@ module Asciidoctor
10
10
  self[:attributes] = options[:attributes] || {}
11
11
  self[:input_file] = options[:input_file] || nil
12
12
  self[:output_file] = options[:output_file] || nil
13
- self[:safe] = options[:safe] || Asciidoctor::SafeMode::UNSAFE
13
+ self[:safe] = options[:safe] || SafeMode::UNSAFE
14
14
  self[:header_footer] = options[:header_footer] || true
15
15
  self[:template_dir] = options[:template_dir] || nil
16
16
  if options[:doctype]
@@ -55,15 +55,15 @@ Example: asciidoctor -b html5 source.asciidoc
55
55
  self[:output_file] = output_file
56
56
  end
57
57
  opts.on('--safe',
58
- 'set safe mode to safe (default: secure)',
58
+ 'set safe mode level to safe (default: unsafe)',
59
59
  'enables include macros, but restricts access to ancestor paths of source file',
60
60
  'provided for compatibility with the asciidoc command') do
61
- self[:safe] = Asciidoctor::SafeMode::SAFE
61
+ self[:safe] = SafeMode::SAFE
62
62
  end
63
- opts.on('-S', '--safe-mode SAFE_MODE', ['unsafe', 'safe', 'secure'],
64
- 'set safe mode level explicitly: [unsafe, safe, secure] (default: secure)',
63
+ opts.on('-S', '--safe-mode SAFE_MODE', ['unsafe', 'safe', 'server', 'secure'],
64
+ 'set safe mode level explicitly: [unsafe, safe, server, secure] (default: unsafe)',
65
65
  'disables potentially dangerous macros in source files, such as include::[]') do |safe_mode|
66
- self[:safe] = Asciidoctor::SafeMode.const_get(safe_mode.upcase)
66
+ self[:safe] = SafeMode.const_get(safe_mode.upcase)
67
67
  end
68
68
  opts.on('-s', '--no-header-footer', 'suppress output of header and footer (default: false)') do
69
69
  self[:header_footer] = false
@@ -1,24 +1,25 @@
1
1
  module Asciidoctor
2
+ module Debug
2
3
  @show_debug = nil
3
-
4
+
4
5
  def self.debug
5
6
  puts yield if self.show_debug_output?
6
7
  end
7
-
8
+
8
9
  def self.set_debug(value)
9
10
  @show_debug = value
10
11
  end
11
-
12
+
12
13
  def self.show_debug_output?
13
14
  @show_debug || (ENV['DEBUG'] == 'true' && ENV['SUPPRESS_DEBUG'] != 'true')
14
15
  end
15
-
16
+
16
17
  def self.puts_indented(level, *args)
17
18
  indentation = " " * level * 2
18
-
19
+
19
20
  args.each do |arg|
20
21
  self.debug { "#{indentation}#{arg}" }
21
22
  end
22
23
  end
23
24
  end
24
-
25
+ end
@@ -1,3 +1,4 @@
1
+ module Asciidoctor
1
2
  # Public: Methods for parsing Asciidoc documents and rendering them
2
3
  # using erb templates.
3
4
  #
@@ -15,11 +16,19 @@
15
16
  #
16
17
  # notitle - The h1 heading should not be shown
17
18
  # noheader - The header block (h1 heading, author, revision info) should not be shown
18
- class Asciidoctor::Document < Asciidoctor::AbstractBlock
19
-
20
- include Asciidoctor
19
+ class Document < AbstractBlock
21
20
 
22
21
  Footnote = Struct.new(:index, :id, :text)
22
+ AttributeEntry = Struct.new(:name, :value, :negate) do
23
+ def initialize(name, value, negate = nil)
24
+ super(name, value, negate.nil? ? value.nil? : false)
25
+ end
26
+
27
+ def save_to(block_attributes)
28
+ block_attributes[:attribute_entries] ||= []
29
+ block_attributes[:attribute_entries] << self
30
+ end
31
+ end
23
32
 
24
33
  # Public A read-only integer value indicating the level of security that
25
34
  # should be enforced while processing this document. The value must be
@@ -122,43 +131,66 @@ class Asciidoctor::Document < Asciidoctor::AbstractBlock
122
131
  @attributes['asciidoctor-version'] = VERSION
123
132
  @attributes['sectids'] = ''
124
133
  @attributes['encoding'] = 'UTF-8'
125
-
126
- attribute_overrides = options[:attributes] || {}
134
+ @attributes['notitle'] = '' if !@options[:header_footer]
135
+
136
+ # language strings
137
+ # TODO load these based on language settings
138
+ @attributes['caution-caption'] = 'Caution'
139
+ @attributes['important-caption'] = 'Important'
140
+ @attributes['note-caption'] = 'Note'
141
+ @attributes['tip-caption'] = 'Tip'
142
+ @attributes['warning-caption'] = 'Warning'
143
+ @attributes['appendix-caption'] = 'Appendix'
144
+ @attributes['example-caption'] = 'Example'
145
+ @attributes['figure-caption'] = 'Figure'
146
+ @attributes['table-caption'] = 'Table'
147
+ @attributes['toc-title'] = 'Table of Contents'
148
+
149
+ @attribute_overrides = options[:attributes] || {}
127
150
 
128
151
  # the only way to set the include-depth attribute is via the document options
129
152
  # 10 is the AsciiDoc default, though currently Asciidoctor only supports 1 level
130
- attribute_overrides['include-depth'] ||= 10
153
+ @attribute_overrides['include-depth'] ||= 10
131
154
 
132
155
  # if the base_dir option is specified, it overrides docdir as the root for relative paths
133
156
  # otherwise, the base_dir is the directory of the source file (docdir) or the current
134
157
  # directory of the input is a string
135
158
  if options[:base_dir].nil?
136
- if attribute_overrides['docdir']
137
- @base_dir = attribute_overrides['docdir'] = File.expand_path(attribute_overrides['docdir'])
159
+ if @attribute_overrides['docdir']
160
+ @base_dir = @attribute_overrides['docdir'] = File.expand_path(@attribute_overrides['docdir'])
138
161
  else
139
162
  # perhaps issue a warning here?
140
- @base_dir = attribute_overrides['docdir'] = Dir.pwd
163
+ @base_dir = @attribute_overrides['docdir'] = Dir.pwd
141
164
  end
142
165
  else
143
- @base_dir = attribute_overrides['docdir'] = File.expand_path(options[:base_dir])
166
+ @base_dir = @attribute_overrides['docdir'] = File.expand_path(options[:base_dir])
167
+ end
168
+
169
+ # allow common attributes backend and doctype to be set using options hash
170
+ unless @options[:backend].nil?
171
+ @attribute_overrides['backend'] = @options[:backend]
172
+ end
173
+
174
+ unless @options[:doctype].nil?
175
+ @attribute_overrides['doctype'] = @options[:doctype]
144
176
  end
145
177
 
146
178
  if @safe >= SafeMode::SERVER
147
179
  # restrict document from setting source-highlighter and backend
148
- attribute_overrides['source-highlighter'] ||= nil
149
- attribute_overrides['backend'] ||= DEFAULT_BACKEND
180
+ @attribute_overrides['source-highlighter'] ||= nil
181
+ @attribute_overrides['backend'] ||= DEFAULT_BACKEND
150
182
  # restrict document from seeing the docdir and trim docfile to relative path
151
- if attribute_overrides.has_key?('docfile') && @parent_document.nil?
152
- attribute_overrides['docfile'] = attribute_overrides['docfile'][(attribute_overrides['docdir'].length + 1)..-1]
183
+ if @attribute_overrides.has_key?('docfile') && @parent_document.nil?
184
+ @attribute_overrides['docfile'] = @attribute_overrides['docfile'][(@attribute_overrides['docdir'].length + 1)..-1]
153
185
  end
154
- attribute_overrides['docdir'] = ''
186
+ @attribute_overrides['docdir'] = ''
155
187
  # restrict document from enabling icons
156
188
  if @safe >= SafeMode::SECURE
157
- attribute_overrides['icons'] ||= nil
189
+ @attribute_overrides['icons'] ||= nil
158
190
  end
159
191
  end
160
192
 
161
- attribute_overrides.delete_if {|key, val|
193
+ @attribute_overrides.delete_if {|key, val|
162
194
  verdict = false
163
195
  # a nil or negative key undefines the attribute
164
196
  if val.nil? || key[-1..-1] == '!'
@@ -184,7 +216,7 @@ class Asciidoctor::Document < Asciidoctor::AbstractBlock
184
216
  # don't need to do the extra processing within our own document
185
217
  @reader = Reader.new(data)
186
218
  else
187
- @reader = Reader.new(data, self, attribute_overrides, &block)
219
+ @reader = Reader.new(data, self, true, &block)
188
220
  end
189
221
 
190
222
  # dynamic intrinstic attribute values
@@ -202,13 +234,11 @@ class Asciidoctor::Document < Asciidoctor::AbstractBlock
202
234
  @attributes['iconsdir'] ||= File.join(@attributes.fetch('imagesdir', 'images'), 'icons')
203
235
 
204
236
  # Now parse the lines in the reader into blocks
205
- Lexer.parse(@reader, self)
206
- # or we could make it...
207
- #self << *Lexer.parse(@reader, self)
237
+ Lexer.parse(@reader, self, :header_only => @options.fetch(:parse_header_only, false))
208
238
 
209
239
  @callouts.rewind
210
240
 
211
- Asciidoctor.debug {
241
+ Debug.debug {
212
242
  msg = []
213
243
  msg << "Found #{@blocks.size} blocks in this document:"
214
244
  @blocks.each {|b|
@@ -276,12 +306,25 @@ class Asciidoctor::Document < Asciidoctor::AbstractBlock
276
306
  end
277
307
  end
278
308
 
309
+ def footnotes?
310
+ not @references[:footnotes].empty?
311
+ end
312
+
313
+ def footnotes
314
+ @references[:footnotes]
315
+ end
316
+
279
317
  def nested?
280
318
  !@parent_document.nil?
281
319
  end
282
320
 
283
321
  # Make the raw source for the Document available.
284
322
  def source
323
+ @reader.source.join if @reader
324
+ end
325
+
326
+ # Make the raw source lines for the Document available.
327
+ def source_lines
285
328
  @reader.source if @reader
286
329
  end
287
330
 
@@ -289,13 +332,17 @@ class Asciidoctor::Document < Asciidoctor::AbstractBlock
289
332
  @attributes['doctype']
290
333
  end
291
334
 
335
+ def backend
336
+ @attributes['backend']
337
+ end
338
+
292
339
  # The title explicitly defined in the document attributes
293
340
  def title
294
341
  @attributes['title']
295
342
  end
296
343
 
297
344
  def title=(title)
298
- @header = Section.new self
345
+ @header ||= Section.new self
299
346
  @header.title = title
300
347
  end
301
348
 
@@ -311,6 +358,20 @@ class Asciidoctor::Document < Asciidoctor::AbstractBlock
311
358
  end
312
359
  alias :name :doctitle
313
360
 
361
+ # Public: Convenience method to retrieve the document attribute 'author'
362
+ #
363
+ # returns the full name of the author as a String
364
+ def author
365
+ @attributes['author']
366
+ end
367
+
368
+ # Public: Convenience method to retrieve the document attribute 'revdate'
369
+ #
370
+ # returns the date of last revision for the document as a String
371
+ def revdate
372
+ @attributes['revdate']
373
+ end
374
+
314
375
  def notitle
315
376
  @attributes.has_key? 'notitle'
316
377
  end
@@ -327,6 +388,114 @@ class Asciidoctor::Document < Asciidoctor::AbstractBlock
327
388
  def has_header?
328
389
  !@header.nil?
329
390
  end
391
+
392
+ # Internal: Branch the attributes so that the original state can be restored
393
+ # at a future time.
394
+ def save_attributes
395
+ # css-signature cannot be updated after header attributes are processed
396
+ if @id.nil? && @attributes.has_key?('css-signature')
397
+ @id = @attributes['css-signature']
398
+ end
399
+ @original_attributes = @attributes.dup
400
+ end
401
+
402
+ # Internal: Restore the attributes to the previously saved state
403
+ def restore_attributes
404
+ @attributes = @original_attributes
405
+ end
406
+
407
+ # Internal: Delete any attributes stored for playback
408
+ def clear_playback_attributes(attributes)
409
+ attributes.delete(:attribute_entries)
410
+ end
411
+
412
+ # Internal: Replay attribute assignments at the block level
413
+ def playback_attributes(block_attributes)
414
+ if block_attributes.has_key? :attribute_entries
415
+ block_attributes[:attribute_entries].each do |entry|
416
+ if entry.negate
417
+ @attributes.delete(entry.name)
418
+ else
419
+ @attributes[entry.name] = entry.value
420
+ end
421
+ end
422
+ end
423
+ end
424
+
425
+ # Public: Set the specified attribute on the document if the name is not locked
426
+ #
427
+ # If the attribute is locked, false is returned. Otherwise, the value is
428
+ # assigned to the attribute name after first performing attribute
429
+ # substitutions on the value. If the attribute name is 'backend', then the
430
+ # value of backend-related attributes are updated.
431
+ #
432
+ # name - the String attribute name
433
+ # value - the String attribute value
434
+ #
435
+ # returns true if the attribute was set, false if it was not set because it's locked
436
+ def set_attribute(name, value)
437
+ if attribute_locked?(name)
438
+ false
439
+ else
440
+ @attributes[name] = apply_attribute_value_subs(value)
441
+ if name == 'backend'
442
+ update_backend_attributes()
443
+ end
444
+ true
445
+ end
446
+ end
447
+
448
+ # Public: Delete the specified attribute from the document if the name is not locked
449
+ #
450
+ # If the attribute is locked, false is returned. Otherwise, the attribute is deleted.
451
+ #
452
+ # name - the String attribute name
453
+ #
454
+ # returns true if the attribute was deleted, false if it was not because it's locked
455
+ def delete_attribute(name)
456
+ if attribute_locked?(name)
457
+ false
458
+ else
459
+ @attributes.delete(name)
460
+ true
461
+ end
462
+ end
463
+
464
+ # Public: Determine if the attribute has been locked by being assigned in document options
465
+ #
466
+ # key - The attribute key to check
467
+ #
468
+ # Returns true if the attribute is locked, false otherwise
469
+ def attribute_locked?(name)
470
+ @attribute_overrides.has_key?(name) || @attribute_overrides.has_key?("#{name}!")
471
+ end
472
+
473
+ # Internal: Apply substitutions to the attribute value
474
+ #
475
+ # If the value is an inline passthrough macro (e.g., pass:[text]), then
476
+ # apply the substitutions defined on the macro to the text. Otherwise,
477
+ # apply the verbatim substitutions to the value.
478
+ #
479
+ # value - The String attribute value on which to perform substitutions
480
+ #
481
+ # Returns The String value with substitutions performed.
482
+ def apply_attribute_value_subs(value)
483
+ if value.match(REGEXP[:pass_macro_basic])
484
+ # copy match for Ruby 1.8.7 compat
485
+ m = $~
486
+ subs = []
487
+ if !m[1].empty?
488
+ subs = resolve_subs(m[1])
489
+ end
490
+ if !subs.empty?
491
+ apply_subs(m[2], subs)
492
+ else
493
+ m[2]
494
+ end
495
+ else
496
+ apply_header_subs(value)
497
+ end
498
+ end
330
499
 
331
500
  # Public: Update the backend attributes to reflect a change in the selected backend
332
501
  def update_backend_attributes()
@@ -355,7 +524,7 @@ class Asciidoctor::Document < Asciidoctor::AbstractBlock
355
524
  end
356
525
 
357
526
  def splain
358
- Asciidoctor.debug {
527
+ Debug.debug {
359
528
  msg = ''
360
529
  if @header
361
530
  msg = "Header is #{@header}"
@@ -381,11 +550,12 @@ class Asciidoctor::Document < Asciidoctor::AbstractBlock
381
550
  render_options = {}
382
551
 
383
552
  # Load up relevant Document @options
384
- if @options[:template_dir]
553
+ if @options.has_key? :template_dir
385
554
  render_options[:template_dir] = @options[:template_dir]
386
555
  end
387
556
 
388
557
  render_options[:backend] = @attributes.fetch('backend', 'html5')
558
+ render_options[:template_engine] = @options[:template_engine]
389
559
  render_options[:eruby] = @options.fetch(:eruby, 'erb')
390
560
  render_options[:compact] = @options.fetch(:compact, false)
391
561
 
@@ -400,6 +570,7 @@ class Asciidoctor::Document < Asciidoctor::AbstractBlock
400
570
  # or a template is missing, the renderer will fall back to
401
571
  # using the appropriate built-in template.
402
572
  def render(opts = {})
573
+ restore_attributes
403
574
  r = renderer(opts)
404
575
  @options.merge(opts)[:header_footer] ? r.render('document', self).strip : r.render('embedded', self)
405
576
  end
@@ -415,3 +586,4 @@ class Asciidoctor::Document < Asciidoctor::AbstractBlock
415
586
  end
416
587
 
417
588
  end
589
+ end