asciidoctor 0.0.9 → 0.1.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 (42) hide show
  1. data/README.asciidoc +163 -41
  2. data/Rakefile +3 -1
  3. data/asciidoctor.gemspec +13 -5
  4. data/bin/asciidoctor +6 -3
  5. data/bin/asciidoctor-safe +13 -0
  6. data/lib/asciidoctor.rb +237 -26
  7. data/lib/asciidoctor/abstract_node.rb +27 -17
  8. data/lib/asciidoctor/attribute_list.rb +6 -0
  9. data/lib/asciidoctor/backends/base_template.rb +3 -4
  10. data/lib/asciidoctor/backends/docbook45.rb +114 -55
  11. data/lib/asciidoctor/backends/html5.rb +173 -104
  12. data/lib/asciidoctor/cli/invoker.rb +105 -0
  13. data/lib/asciidoctor/cli/options.rb +146 -0
  14. data/lib/asciidoctor/document.rb +135 -35
  15. data/lib/asciidoctor/lexer.rb +86 -33
  16. data/lib/asciidoctor/list_item.rb +2 -2
  17. data/lib/asciidoctor/reader.rb +6 -7
  18. data/lib/asciidoctor/section.rb +17 -5
  19. data/lib/asciidoctor/substituters.rb +216 -97
  20. data/lib/asciidoctor/table.rb +9 -2
  21. data/lib/asciidoctor/version.rb +1 -1
  22. data/man/asciidoctor.1 +212 -0
  23. data/man/asciidoctor.ad +156 -0
  24. data/test/attributes_test.rb +108 -5
  25. data/test/blocks_test.rb +102 -15
  26. data/test/document_test.rb +214 -3
  27. data/test/fixtures/encoding.asciidoc +4 -0
  28. data/test/fixtures/sample.asciidoc +26 -0
  29. data/test/invoker_test.rb +254 -0
  30. data/test/lexer_test.rb +53 -0
  31. data/test/links_test.rb +30 -0
  32. data/test/lists_test.rb +648 -9
  33. data/test/options_test.rb +68 -0
  34. data/test/paragraphs_test.rb +65 -1
  35. data/test/reader_test.rb +18 -4
  36. data/test/{headers_test.rb → sections_test.rb} +237 -0
  37. data/test/substitutions_test.rb +247 -5
  38. data/test/tables_test.rb +22 -4
  39. data/test/test_helper.rb +47 -3
  40. data/test/text_test.rb +20 -4
  41. metadata +34 -6
  42. data/noof.rb +0 -16
@@ -0,0 +1,105 @@
1
+ module Asciidoctor
2
+ module Cli
3
+ # Public Invocation class for starting Asciidoctor via CLI
4
+ class Invoker
5
+ attr_reader :options
6
+ attr_reader :document
7
+ attr_reader :code
8
+ attr_reader :timings
9
+
10
+ def initialize(*options)
11
+ @document = nil
12
+ @out = nil
13
+ @err = nil
14
+ @code = 0
15
+ @timings = {}
16
+ options = options.flatten
17
+ if !options.empty? && options.first.is_a?(Asciidoctor::Cli::Options)
18
+ @options = options.first
19
+ elsif options.first.is_a? Hash
20
+ @options = Asciidoctor::Cli::Options.new(options)
21
+ else
22
+ @options = Asciidoctor::Cli::Options.parse!(options)
23
+ # hmmm
24
+ if @options.is_a?(Integer)
25
+ @code = @options
26
+ @options = nil
27
+ end
28
+ end
29
+ end
30
+
31
+ def invoke!
32
+ return if @options.nil?
33
+
34
+ begin
35
+ @timings = {}
36
+ infile = @options[:input_file]
37
+ outfile = @options[:output_file]
38
+ if infile == '-'
39
+ # allow use of block to supply stdin, particularly useful for tests
40
+ input = block_given? ? yield : STDIN
41
+ else
42
+ input = File.new(infile)
43
+ end
44
+ start = Time.now
45
+ @document = Asciidoctor.load(input, @options)
46
+ timings[:parse] = Time.now - start
47
+ start = Time.now
48
+ output = @document.render
49
+ timings[:render] = Time.now - start
50
+ if @options[:verbose]
51
+ puts "Time to read and parse source: #{timings[:parse]}"
52
+ puts "Time to render document: #{timings[:render]}"
53
+ puts "Total time to read, parse and render: #{timings.reduce(0) {|sum, (_, v)| sum += v}}"
54
+ end
55
+ if outfile == '/dev/null'
56
+ # output nothing
57
+ elsif outfile == '-' || (infile == '-' && (outfile.nil? || outfile.empty?))
58
+ (@out || $stdout).puts output
59
+ else
60
+ if outfile.nil? || outfile.empty?
61
+ if @options[:destination_dir]
62
+ destination_dir = File.expand_path(@options[:destination_dir])
63
+ else
64
+ destination_dir = @document.base_dir
65
+ end
66
+ outfile = File.join(destination_dir, "#{@document.attributes['docname']}#{@document.attributes['outfilesuffix']}")
67
+ else
68
+ outfile = @document.normalize_asset_path outfile
69
+ end
70
+
71
+ # this assignment is primarily for testing or other post analysis
72
+ @document.attributes['outfile'] = outfile
73
+ @document.attributes['outdir'] = File.dirname(outfile)
74
+ File.open(outfile, 'w') {|file| file.write output }
75
+ end
76
+ rescue Exception => e
77
+ raise e if @options[:trace] || SystemExit === e
78
+ err = (@err || $stderr)
79
+ err.print "#{e.class}: " if e.class != RuntimeError
80
+ err.puts e.message
81
+ err.puts ' Use --trace for backtrace'
82
+ @code = 1
83
+ end
84
+ end
85
+
86
+ def redirect_streams(out, err = nil)
87
+ @out = out
88
+ @err = err
89
+ end
90
+
91
+ def read_output
92
+ !@out.nil? ? @out.string : ''
93
+ end
94
+
95
+ def read_error
96
+ !@err.nil? ? @err.string : ''
97
+ end
98
+
99
+ def reset_streams
100
+ @out = nil
101
+ @err = nil
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,146 @@
1
+ require 'optparse'
2
+
3
+ module Asciidoctor
4
+ module Cli
5
+
6
+ # Public: List of options that can be specified on the command line
7
+ class Options < Hash
8
+
9
+ def initialize(options = {})
10
+ self[:attributes] = options[:attributes] || {}
11
+ self[:input_file] = options[:input_file] || nil
12
+ self[:output_file] = options[:output_file] || nil
13
+ self[:safe] = options[:safe] || Asciidoctor::SafeMode::UNSAFE
14
+ self[:header_footer] = options[:header_footer] || true
15
+ self[:template_dir] = options[:template_dir] || nil
16
+ if options[:doctype]
17
+ self[:attributes]['doctype'] = options[:doctype]
18
+ end
19
+ if options[:backend]
20
+ self[:attributes]['backend'] = options[:backend]
21
+ end
22
+ self[:eruby] = options[:eruby] || nil
23
+ self[:compact] = options[:compact] || false
24
+ self[:verbose] = options[:verbose] || false
25
+ self[:base_dir] = options[:base_dir] || nil
26
+ self[:destination_dir] = options[:destination_dir] || nil
27
+ self[:trace] = false
28
+ end
29
+
30
+ def self.parse!(args)
31
+ Options.new.parse! args
32
+ end
33
+
34
+ def parse!(args)
35
+ opts_parser = OptionParser.new do |opts|
36
+ opts.banner = <<-EOS
37
+ Usage: asciidoctor [OPTION]... [FILE]
38
+ Translate the AsciiDoc source FILE into the backend output format (e.g., HTML 5, DocBook 4.5, etc.)
39
+ By default, the output is written to a file with the basename of the source file and the appropriate extension.
40
+ Example: asciidoctor -b html5 source.asciidoc
41
+
42
+ EOS
43
+
44
+ opts.on('-v', '--verbose', 'enable verbose mode (default: false)') do |verbose|
45
+ self[:verbose] = true
46
+ end
47
+ opts.on('-b', '--backend BACKEND', ['html5', 'docbook45'], 'set output format (i.e., backend): [html5, docbook45] (default: html5)') do |backend|
48
+ self[:attributes]['backend'] = backend
49
+ end
50
+ opts.on('-d', '--doctype DOCTYPE', ['article', 'book'],
51
+ 'document type to use when rendering output: [article, book] (default: article)') do |doc_type|
52
+ self[:attributes]['doctype'] = doc_type
53
+ end
54
+ opts.on('-o', '--out-file FILE', 'output file (default: based on input file path); use - to output to STDOUT') do |output_file|
55
+ self[:output_file] = output_file
56
+ end
57
+ opts.on('--safe',
58
+ 'set safe mode to safe (default: secure)',
59
+ 'enables include macros, but restricts access to ancestor paths of source file',
60
+ 'provided for compatibility with the asciidoc command') do
61
+ self[:safe] = Asciidoctor::SafeMode::SAFE
62
+ end
63
+ opts.on('-S', '--safe-mode SAFE_MODE', ['unsafe', 'safe', 'secure'],
64
+ 'set safe mode level explicitly: [unsafe, safe, secure] (default: secure)',
65
+ 'disables potentially dangerous macros in source files, such as include::[]') do |safe_mode|
66
+ self[:safe] = Asciidoctor::SafeMode.const_get(safe_mode.upcase)
67
+ end
68
+ opts.on('-s', '--no-header-footer', 'suppress output of header and footer (default: false)') do
69
+ self[:header_footer] = false
70
+ end
71
+ opts.on('-n', '--section-numbers', 'auto-number section titles in the HTML backend; disabled by default') do
72
+ self[:attributes]['numbered'] = ''
73
+ end
74
+ opts.on('-e', '--eruby ERUBY', ['erb', 'erubis'],
75
+ 'specify eRuby implementation to render built-in templates: [erb, erubis] (default: erb)') do |eruby|
76
+ self[:eruby] = eruby
77
+ end
78
+ opts.on('-C', '--compact', 'compact the output by removing blank lines (default: false)') do
79
+ self[:compact] = true
80
+ end
81
+ opts.on('-a', '--attribute key1=value,key2=value2,...', Array,
82
+ 'a list of attributes, in the form key or key=value pair, to set on the document',
83
+ 'these attributes take precedence over attributes defined in the source file') do |attribs|
84
+ attribs.each do |attrib|
85
+ tokens = attrib.split('=')
86
+ self[:attributes][tokens[0]] = tokens[1] || ''
87
+ end
88
+ end
89
+ opts.on('-T', '--template-dir DIR', 'directory containing custom render templates the override the built-in set') do |template_dir|
90
+ self[:template_dir] = template_dir
91
+ end
92
+ opts.on('-B', '--base-dir DIR', 'base directory containing the document and resources (default: directory of source file)') do |base_dir|
93
+ self[:base_dir] = base_dir
94
+ end
95
+ opts.on('-D', '--destination-dir DIR', 'destination output directory (default: directory of source file)') do |dest_dir|
96
+ self[:destination_dir] = dest_dir
97
+ end
98
+ opts.on('--trace', 'include backtrace information on errors (default: false)') do |trace|
99
+ self[:trace] = true
100
+ end
101
+
102
+ opts.on_tail('-h', '--help', 'show this message') do
103
+ $stdout.puts opts
104
+ return 0
105
+ end
106
+
107
+ opts.on_tail('-V', '--version', 'display the version') do
108
+ $stdout.puts "Asciidoctor #{Asciidoctor::VERSION} [http://asciidoctor.org]"
109
+ return 0
110
+ end
111
+
112
+ end
113
+
114
+ begin
115
+ # shave off the file to process so that options errors appear correctly
116
+ if args.last && (args.last == '-' || !args.last.start_with?('-'))
117
+ self[:input_file] = args.pop
118
+ end
119
+ opts_parser.parse!(args)
120
+ if args.size > 0
121
+ # warn, but don't panic; we may have enough to proceed, so we won't force a failure
122
+ $stderr.puts "asciidoctor: WARNING: extra arguments detected (unparsed arguments: #{args.map{|a| "'#{a}'"} * ', '})"
123
+ end
124
+
125
+ if self[:input_file].nil? || self[:input_file].empty?
126
+ $stderr.puts opts_parser
127
+ return 1
128
+ elsif self[:input_file] != '-' && !File.exist?(self[:input_file])
129
+ $stderr.puts "asciidoctor: FAILED: input file #{self[:input_file]} missing"
130
+ return 1
131
+ end
132
+ rescue OptionParser::MissingArgument
133
+ $stderr.puts "asciidoctor: option #{$!.message}"
134
+ $stdout.puts opts_parser
135
+ return 1
136
+ rescue OptionParser::InvalidOption, OptionParser::InvalidArgument
137
+ $stderr.puts "asciidoctor: #{$!.message}"
138
+ $stdout.puts opts_parser
139
+ return 1
140
+ end
141
+ self
142
+ end # parse()
143
+
144
+ end
145
+ end
146
+ end
@@ -19,6 +19,8 @@ class Asciidoctor::Document < Asciidoctor::AbstractBlock
19
19
 
20
20
  include Asciidoctor
21
21
 
22
+ Footnote = Struct.new(:index, :id, :text)
23
+
22
24
  # Public A read-only integer value indicating the level of security that
23
25
  # should be enforced while processing this document. The value must be
24
26
  # set in the Document constructor using the :safe option.
@@ -30,14 +32,23 @@ class Asciidoctor::Document < Asciidoctor::AbstractBlock
30
32
  # it prevents access to files which reside outside of the parent directory
31
33
  # of the source file and disables any macro other than the include macro.
32
34
  #
33
- # A value of 10 (SECURE) disallows the document from attempting to read
34
- # files from the file system and including the contents of them into the
35
- # document. In particular, it disallows use of the include::[] macro and the
36
- # embedding of binary content (data uri), stylesheets and JavaScripts
37
- # referenced by the document. (Asciidoctor and trusted extensions may still
38
- # be allowed to embed trusted content into the document). Since Asciidoctor
39
- # is aiming for wide adoption, this value is the default and is recommended
40
- # for server-side deployments.
35
+ # A value of 10 (SERVER) disallows the document from setting attributes that
36
+ # would affect the rendering of the document, in addition to all the security
37
+ # features of SafeMode::SAFE. For instance, this value disallows changing the
38
+ # backend or the source-highlighter using an attribute defined in the source
39
+ # document. This is the most fundamental level of security for server-side
40
+ # deployments (hence the name).
41
+ #
42
+ # A value of 20 (SECURE) disallows the document from attempting to read files
43
+ # from the file system and including the contents of them into the document,
44
+ # in addition to all the security features of SafeMode::SECURE. In
45
+ # particular, it disallows use of the include::[] macro and the embedding of
46
+ # binary content (data uri), stylesheets and JavaScripts referenced by the
47
+ # document. (Asciidoctor and trusted extensions may still be allowed to embed
48
+ # trusted content into the document).
49
+ #
50
+ # Since Asciidoctor is aiming for wide adoption, 20 (SECURE) is the default
51
+ # value and is recommended for server-side deployments.
41
52
  #
42
53
  # A value of 100 (PARANOID) is planned to disallow the use of passthrough
43
54
  # macros and prevents the document from setting any known attributes in
@@ -48,13 +59,17 @@ class Asciidoctor::Document < Asciidoctor::AbstractBlock
48
59
  # Public: Get the Hash of document references
49
60
  attr_reader :references
50
61
 
62
+ # Public: Get the Hash of document counters
63
+ attr_reader :counters
64
+
51
65
  # Public: Get the Hash of callouts
52
66
  attr_reader :callouts
53
67
 
54
68
  # Public: The section level 0 block
55
69
  attr_reader :header
56
70
 
57
- # Public: Base directory for rendering this document
71
+ # Public: Base directory for rendering this document. Defaults to directory of the source file.
72
+ # If the source is a string, defaults to the current directory.
58
73
  attr_reader :base_dir
59
74
 
60
75
  # Public: A reference to the parent document of this nested document.
@@ -82,6 +97,8 @@ class Asciidoctor::Document < Asciidoctor::AbstractBlock
82
97
  @parent_document = options.delete(:parent)
83
98
  # should we dup here?
84
99
  options[:attributes] = @parent_document.attributes
100
+ options[:safe] ||= @parent_document.safe
101
+ options[:base_dir] ||= @parent_document.base_dir
85
102
  @renderer = @parent_document.renderer
86
103
  else
87
104
  @parent_document = nil
@@ -90,17 +107,20 @@ class Asciidoctor::Document < Asciidoctor::AbstractBlock
90
107
  @header = nil
91
108
  @references = {
92
109
  :ids => {},
110
+ :footnotes => [],
93
111
  :links => [],
94
- :images => []
112
+ :images => [],
113
+ :indexterms => []
95
114
  }
115
+ @counters = {}
96
116
  @callouts = Callouts.new
97
117
  @options = options
98
118
  @safe = @options.fetch(:safe, SafeMode::SECURE).to_i
99
- @options[:header_footer] = @options.fetch(:header_footer, true)
119
+ @options[:header_footer] = @options.fetch(:header_footer, false)
100
120
 
101
- @attributes['asciidoctor'] = true
121
+ @attributes['asciidoctor'] = ''
102
122
  @attributes['asciidoctor-version'] = VERSION
103
- @attributes['sectids'] = true
123
+ @attributes['sectids'] = ''
104
124
  @attributes['encoding'] = 'UTF-8'
105
125
 
106
126
  attribute_overrides = options[:attributes] || {}
@@ -109,35 +129,58 @@ class Asciidoctor::Document < Asciidoctor::AbstractBlock
109
129
  # 10 is the AsciiDoc default, though currently Asciidoctor only supports 1 level
110
130
  attribute_overrides['include-depth'] ||= 10
111
131
 
112
- # TODO we should go with one or the other, this is confusing
113
- # for now, base_dir takes precedence if set
114
- if options.has_key? :base_dir
115
- @base_dir = attribute_overrides['docdir'] = options[:base_dir]
132
+ # if the base_dir option is specified, it overrides docdir as the root for relative paths
133
+ # otherwise, the base_dir is the directory of the source file (docdir) or the current
134
+ # directory of the input is a string
135
+ if options[:base_dir].nil?
136
+ if attribute_overrides['docdir']
137
+ @base_dir = attribute_overrides['docdir'] = File.expand_path(attribute_overrides['docdir'])
138
+ else
139
+ # perhaps issue a warning here?
140
+ @base_dir = attribute_overrides['docdir'] = Dir.pwd
141
+ end
116
142
  else
117
- attribute_overrides['docdir'] ||= Dir.pwd
118
- @base_dir = attribute_overrides['docdir']
143
+ @base_dir = attribute_overrides['docdir'] = File.expand_path(options[:base_dir])
119
144
  end
120
145
 
121
- # restrict document from setting source-highlighter in SECURE safe mode
122
- # it can only be set via the constructor
123
- if @safe >= SafeMode::SECURE
146
+ if @safe >= SafeMode::SERVER
147
+ # restrict document from setting source-highlighter and backend
124
148
  attribute_overrides['source-highlighter'] ||= nil
149
+ attribute_overrides['backend'] ||= DEFAULT_BACKEND
150
+ # 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]
153
+ end
154
+ attribute_overrides['docdir'] = ''
155
+ # restrict document from enabling icons
156
+ if @safe >= SafeMode::SECURE
157
+ attribute_overrides['icons'] ||= nil
158
+ end
125
159
  end
126
160
 
127
- attribute_overrides.each {|key, val|
161
+ attribute_overrides.delete_if {|key, val|
162
+ verdict = false
128
163
  # a nil or negative key undefines the attribute
129
- if (val.nil? || key[-1..-1] == '!')
164
+ if val.nil? || key[-1..-1] == '!'
130
165
  @attributes.delete(key.chomp '!')
131
166
  # otherwise it's an attribute assignment
132
167
  else
168
+ # a value ending in @ indicates this attribute does not override
169
+ # an attribute with the same key in the document souce
170
+ if val.is_a?(String) && val.end_with?('@')
171
+ val.chop!
172
+ verdict = true
173
+ end
133
174
  @attributes[key] = val
134
175
  end
176
+ verdict
135
177
  }
136
178
 
137
179
  @attributes['backend'] ||= DEFAULT_BACKEND
180
+ @attributes['doctype'] ||= DEFAULT_DOCTYPE
138
181
  update_backend_attributes
139
182
 
140
- if nested?
183
+ if !@parent_document.nil?
141
184
  # don't need to do the extra processing within our own document
142
185
  @reader = Reader.new(data)
143
186
  else
@@ -145,16 +188,16 @@ class Asciidoctor::Document < Asciidoctor::AbstractBlock
145
188
  end
146
189
 
147
190
  # dynamic intrinstic attribute values
148
- @attributes['doctype'] ||= DEFAULT_DOCTYPE
149
-
150
191
  now = Time.new
151
192
  @attributes['localdate'] ||= now.strftime('%Y-%m-%d')
152
- @attributes['localtime'] ||= now.strftime('%H:%m:%S %Z')
153
- @attributes['localdatetime'] ||= [@attributes['localdate'], @attributes['localtime']].join(' ')
193
+ @attributes['localtime'] ||= now.strftime('%H:%M:%S %Z')
194
+ @attributes['localdatetime'] ||= [@attributes['localdate'], @attributes['localtime']] * ' '
154
195
 
155
- # docdate and doctime should default to localdate and localtime if not otherwise set
196
+ # docdate, doctime and docdatetime should default to
197
+ # localdate, localtime and localdatetime if not otherwise set
156
198
  @attributes['docdate'] ||= @attributes['localdate']
157
199
  @attributes['doctime'] ||= @attributes['localtime']
200
+ @attributes['docdatetime'] ||= @attributes['localdatetime']
158
201
 
159
202
  @attributes['iconsdir'] ||= File.join(@attributes.fetch('imagesdir', 'images'), 'icons')
160
203
 
@@ -175,15 +218,61 @@ class Asciidoctor::Document < Asciidoctor::AbstractBlock
175
218
  }
176
219
  end
177
220
 
221
+ # Public: Get the named counter and take the next number in the sequence.
222
+ #
223
+ # name - the String name of the counter
224
+ # seed - the initial value as a String or Integer
225
+ #
226
+ # returns the next number in the sequence for the specified counter
227
+ def counter(name, seed = nil)
228
+ if !@counters.has_key? name
229
+ if seed.nil?
230
+ seed = nextval(@attributes.has_key?(name) ? @attributes[name] : 0)
231
+ elsif seed.to_i.to_s == seed
232
+ seed = seed.to_i
233
+ end
234
+ @counters[name] = seed
235
+ else
236
+ @counters[name] = nextval(@counters[name])
237
+ end
238
+
239
+ (@attributes[name] = @counters[name])
240
+ end
241
+
242
+ # Internal: Get the next value in the sequence.
243
+ #
244
+ # Handles both integer and character sequences.
245
+ #
246
+ # current - the value to increment as a String or Integer
247
+ #
248
+ # returns the next value in the sequence according to the current value's type
249
+ def nextval(current)
250
+ if current.is_a?(Integer)
251
+ current + 1
252
+ else
253
+ intval = current.to_i
254
+ if intval.to_s != current.to_s
255
+ (current[0].ord + 1).chr
256
+ else
257
+ intval + 1
258
+ end
259
+ end
260
+ end
261
+
178
262
  def register(type, value)
179
- if type == :ids
263
+ case type
264
+ when :ids
180
265
  if value.is_a?(Array)
181
266
  @references[:ids][value[0]] = (value[1] || '[' + value[0] + ']')
182
267
  else
183
268
  @references[:ids][value] = '[' + value + ']'
184
269
  end
185
- elsif @options[:catalog_assets]
270
+ when :footnotes, :indexterms
186
271
  @references[type] << value
272
+ else
273
+ if @options[:catalog_assets]
274
+ @references[type] << value
275
+ end
187
276
  end
188
277
  end
189
278
 
@@ -242,6 +331,9 @@ class Asciidoctor::Document < Asciidoctor::AbstractBlock
242
331
  # Public: Update the backend attributes to reflect a change in the selected backend
243
332
  def update_backend_attributes()
244
333
  backend = @attributes['backend']
334
+ if BACKEND_ALIASES.has_key? backend
335
+ backend = @attributes['backend'] = BACKEND_ALIASES[backend]
336
+ end
245
337
  basebackend = backend.sub(/[[:digit:]]+$/, '')
246
338
  page_width = DEFAULT_PAGE_WIDTHS[basebackend]
247
339
  if page_width
@@ -249,9 +341,17 @@ class Asciidoctor::Document < Asciidoctor::AbstractBlock
249
341
  else
250
342
  @attributes.delete('pagewidth')
251
343
  end
252
- @attributes['backend-' + backend] = 1
344
+ @attributes["backend-#{backend}"] = ''
253
345
  @attributes['basebackend'] = basebackend
254
- @attributes['basebackend-' + basebackend] = 1
346
+ @attributes["basebackend-#{basebackend}"] = ''
347
+ # REVIEW cases for the next two assignments
348
+ @attributes["#{backend}-#{@attributes['doctype']}"] = ''
349
+ @attributes["#{basebackend}-#{@attributes['doctype']}"] = ''
350
+ ext = DEFAULT_EXTENSIONS[basebackend] || '.html'
351
+ @attributes['outfilesuffix'] = ext
352
+ file_type = ext[1..-1]
353
+ @attributes['filetype'] = file_type
354
+ @attributes["filetype-#{file_type}"] = ''
255
355
  end
256
356
 
257
357
  def splain
@@ -301,7 +401,7 @@ class Asciidoctor::Document < Asciidoctor::AbstractBlock
301
401
  # using the appropriate built-in template.
302
402
  def render(opts = {})
303
403
  r = renderer(opts)
304
- @options.merge(opts)[:header_footer] ? r.render('document', self) : r.render('embedded', self)
404
+ @options.merge(opts)[:header_footer] ? r.render('document', self).strip : r.render('embedded', self)
305
405
  end
306
406
 
307
407
  def content