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
@@ -0,0 +1,296 @@
|
|
1
|
+
module Asciidoctor
|
2
|
+
# A {Converter} implementation that uses templates composed in template
|
3
|
+
# languages supported by {https://github.com/rtomayko/tilt Tilt} to convert
|
4
|
+
# {AbstractNode} objects from a parsed AsciiDoc document tree to the backend
|
5
|
+
# format.
|
6
|
+
#
|
7
|
+
# The converter scans the provided directories for template files that are
|
8
|
+
# supported by Tilt. If an engine name (e.g., "slim") is specified in the
|
9
|
+
# options Hash passed to the constructor, the scan is limited to template
|
10
|
+
# files that have a matching extension (e.g., ".slim"). The scanner trims any
|
11
|
+
# extensions from the basename of the file and uses the resulting name as the
|
12
|
+
# key under which to store the template. When the {Converter#convert} method
|
13
|
+
# is invoked, the transform argument is used to select the template from this
|
14
|
+
# table and use it to convert the node.
|
15
|
+
#
|
16
|
+
# For example, the template file "path/to/templates/paragraph.html.slim" will
|
17
|
+
# be registered as the "paragraph" transform. The template would then be used
|
18
|
+
# to convert a paragraph {Block} object from the parsed AsciiDoc tree to an
|
19
|
+
# HTML backend format (e.g., "html5").
|
20
|
+
#
|
21
|
+
# As an optimization, scan results and templates are cached for the lifetime
|
22
|
+
# of the Ruby process. If the {https://rubygems.org/gems/thread_safe
|
23
|
+
# thread_safe} gem is installed, these caches are guaranteed to be thread
|
24
|
+
# safe. If this gem is not present, a warning is issued.
|
25
|
+
class Converter::TemplateConverter < Converter::Base
|
26
|
+
DEFAULT_ENGINE_OPTIONS = {
|
27
|
+
:erb => { :trim => '<' },
|
28
|
+
# TODO line 466 of haml/compiler.rb sorts the attributes; file an issue to make this configurable
|
29
|
+
# NOTE AsciiDoc syntax expects HTML/XML output to use double quotes around attribute values
|
30
|
+
:haml => { :format => :xhtml, :attr_wrapper => '"', :ugly => true, :escape_attrs => false },
|
31
|
+
:slim => { :disable_escape => true, :sort_attrs => false, :pretty => false }
|
32
|
+
}
|
33
|
+
|
34
|
+
# QUESTION are we handling how we load the thread_safe support correctly?
|
35
|
+
begin
|
36
|
+
require 'thread_safe' unless defined? ::ThreadSafe
|
37
|
+
@caches = { :scans => ::ThreadSafe::Cache.new, :templates => ::ThreadSafe::Cache.new }
|
38
|
+
rescue ::LoadError
|
39
|
+
@caches = {}
|
40
|
+
# FIXME perhaps only warn if the cache option is enabled?
|
41
|
+
warn 'asciidoctor: WARNING: gem \'thread_safe\' is not installed. This gem recommended when using custom backend templates.'
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.caches
|
45
|
+
@caches
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.clear_caches
|
49
|
+
@caches[:scans].clear if @caches[:scans]
|
50
|
+
@caches[:templates].clear if @caches[:templates]
|
51
|
+
end
|
52
|
+
|
53
|
+
def initialize backend, template_dirs, opts = {}
|
54
|
+
@backend = backend
|
55
|
+
@templates = {}
|
56
|
+
@template_dirs = template_dirs
|
57
|
+
@eruby = opts[:eruby]
|
58
|
+
@engine = opts[:template_engine]
|
59
|
+
@engine_options = DEFAULT_ENGINE_OPTIONS.inject({}) do |accum, (engine, default_opts)|
|
60
|
+
accum[engine] = default_opts.dup
|
61
|
+
accum
|
62
|
+
end
|
63
|
+
if (overrides = opts[:template_engine_options])
|
64
|
+
overrides.each do |engine, override_opts|
|
65
|
+
(@engine_options[engine] ||= {}).update override_opts
|
66
|
+
end
|
67
|
+
end
|
68
|
+
@engine_options[:haml][:format] = @engine_options[:slim][:format] = :html5 if opts[:htmlsyntax] == 'html'
|
69
|
+
case opts[:template_cache]
|
70
|
+
when true
|
71
|
+
@caches = self.class.caches
|
72
|
+
when ::Hash
|
73
|
+
@caches = opts[:template_cache]
|
74
|
+
else
|
75
|
+
@caches = {}
|
76
|
+
end
|
77
|
+
scan
|
78
|
+
#create_handlers
|
79
|
+
end
|
80
|
+
|
81
|
+
=begin
|
82
|
+
# Public: Called when this converter is added to a composite converter.
|
83
|
+
def composed parent
|
84
|
+
# TODO set the backend info determined during the scan
|
85
|
+
end
|
86
|
+
=end
|
87
|
+
|
88
|
+
# Internal: Scans the template directories specified in the constructor for Tilt-supported
|
89
|
+
# templates, loads the templates and stores the in a Hash that is accessible via the
|
90
|
+
# {TemplateConverter#templates} method.
|
91
|
+
#
|
92
|
+
# Returns nothing
|
93
|
+
def scan
|
94
|
+
path_resolver = PathResolver.new
|
95
|
+
backend = @backend
|
96
|
+
engine = @engine
|
97
|
+
@template_dirs.each do |template_dir|
|
98
|
+
# FIXME need to think about safe mode restrictions here
|
99
|
+
template_dir = path_resolver.system_path template_dir, nil
|
100
|
+
# NOTE last matching template wins for template name if no engine is given
|
101
|
+
file_pattern = '*'
|
102
|
+
if engine
|
103
|
+
file_pattern = %(*.#{engine})
|
104
|
+
# example: templates/haml
|
105
|
+
if ::File.directory?(engine_dir = (::File.join template_dir, engine))
|
106
|
+
template_dir = engine_dir
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# example: templates/html5 or templates/haml/html5
|
111
|
+
if ::File.directory?(backend_dir = (::File.join template_dir, backend))
|
112
|
+
template_dir = backend_dir
|
113
|
+
end
|
114
|
+
|
115
|
+
pattern = ::File.join template_dir, file_pattern
|
116
|
+
|
117
|
+
if (scan_cache = @caches[:scans])
|
118
|
+
template_cache = @caches[:templates]
|
119
|
+
unless (templates = scan_cache[pattern])
|
120
|
+
templates = (scan_cache[pattern] = (scan_dir template_dir, pattern, template_cache))
|
121
|
+
end
|
122
|
+
templates.each do |name, template|
|
123
|
+
@templates[name] = template_cache[template.file] = template
|
124
|
+
end
|
125
|
+
else
|
126
|
+
@templates.update scan_dir(template_dir, pattern, @caches[:templates])
|
127
|
+
end
|
128
|
+
nil
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
=begin
|
133
|
+
# Internal: Creates convert methods (e.g., inline_anchor) that delegate to the discovered templates.
|
134
|
+
#
|
135
|
+
# Returns nothing
|
136
|
+
def create_handlers
|
137
|
+
@templates.each do |name, template|
|
138
|
+
create_handler name, template
|
139
|
+
end
|
140
|
+
nil
|
141
|
+
end
|
142
|
+
|
143
|
+
# Internal: Creates a convert method for the specified name that delegates to the specified template.
|
144
|
+
#
|
145
|
+
# Returns nothing
|
146
|
+
def create_handler name, template
|
147
|
+
metaclass = class << self; self; end
|
148
|
+
if name == 'document'
|
149
|
+
metaclass.send :define_method, name do |node|
|
150
|
+
(template.render node).strip
|
151
|
+
end
|
152
|
+
else
|
153
|
+
metaclass.send :define_method, name do |node|
|
154
|
+
(template.render node).chomp
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
=end
|
159
|
+
|
160
|
+
# Public: Convert an {AbstractNode} to the backend format using the named template.
|
161
|
+
#
|
162
|
+
# Looks for a template that matches the value of the
|
163
|
+
# {AbstractNode#node_name} property if a template name is not specified.
|
164
|
+
#
|
165
|
+
# node - the AbstractNode to convert
|
166
|
+
# template_name - the String name of the template to use, or the value of
|
167
|
+
# the node_name property on the node if a template name is
|
168
|
+
# not specified. (optional, default: nil)
|
169
|
+
#
|
170
|
+
# Returns the [String] result from rendering the template
|
171
|
+
def convert node, template_name = nil
|
172
|
+
template_name ||= node.node_name
|
173
|
+
unless (template = @templates[template_name])
|
174
|
+
raise %(Could not find a custom template to handle transform: #{template_name})
|
175
|
+
end
|
176
|
+
if template_name == 'document'
|
177
|
+
(template.render node).strip
|
178
|
+
else
|
179
|
+
(template.render node).chomp
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Public: Convert an {AbstractNode} using the named template with the
|
184
|
+
# additional options provided.
|
185
|
+
#
|
186
|
+
# Looks for a template that matches the value of the
|
187
|
+
# {AbstractNode#node_name} property if a template name is not specified.
|
188
|
+
#
|
189
|
+
# node - the AbstractNode to convert
|
190
|
+
# template_name - the String name of the template to use, or the value of
|
191
|
+
# the node_name property on the node if a template name is
|
192
|
+
# not specified. (optional, default: nil)
|
193
|
+
# opts - an optional Hash that is passed as local variables to the
|
194
|
+
# template. (optional, default: {})
|
195
|
+
#
|
196
|
+
# Returns the [String] result from rendering the template
|
197
|
+
def convert_with_options node, template_name = nil, opts = {}
|
198
|
+
template_name ||= node.node_name
|
199
|
+
unless (template = @templates[template_name])
|
200
|
+
raise %(Could not find a custom template to handle transform: #{template_name})
|
201
|
+
end
|
202
|
+
(template.render node, opts).chomp
|
203
|
+
end
|
204
|
+
|
205
|
+
# Public: Checks whether there is a Tilt template registered with the specified name.
|
206
|
+
#
|
207
|
+
# name - the String template name
|
208
|
+
#
|
209
|
+
# Returns a [Boolean] that indicates whether a Tilt template is registered for the
|
210
|
+
# specified template name.
|
211
|
+
def handles? name
|
212
|
+
@templates.key? name
|
213
|
+
end
|
214
|
+
|
215
|
+
# Public: Retrieves the templates that this converter manages.
|
216
|
+
#
|
217
|
+
# Returns a [Hash] of Tilt template objects keyed by template name.
|
218
|
+
def templates
|
219
|
+
@templates.dup.freeze
|
220
|
+
end
|
221
|
+
|
222
|
+
# Public: Registers a Tilt template with this converter.
|
223
|
+
#
|
224
|
+
# name - the String template name
|
225
|
+
# template - the Tilt template object to register
|
226
|
+
#
|
227
|
+
# Returns the Tilt template object
|
228
|
+
def register name, template
|
229
|
+
@templates[name] = if (template_cache = @caches[:templates])
|
230
|
+
template_cache[template.file] = template
|
231
|
+
else
|
232
|
+
template
|
233
|
+
end
|
234
|
+
#create_handler name, template
|
235
|
+
end
|
236
|
+
|
237
|
+
# Internal: Scan the specified directory for template files matching pattern and instantiate
|
238
|
+
# a Tilt template for each matched file.
|
239
|
+
#
|
240
|
+
# Returns the scan result as a [Hash]
|
241
|
+
def scan_dir template_dir, pattern, template_cache = nil
|
242
|
+
result = {}
|
243
|
+
eruby_loaded = nil
|
244
|
+
# Grab the files in the top level of the directory (do not recurse)
|
245
|
+
::Dir.glob(pattern).select {|match| ::File.file? match }.each do |file|
|
246
|
+
if (basename = ::File.basename file) == 'helpers.rb' || (path_segments = basename.split '.').size < 2
|
247
|
+
next
|
248
|
+
end
|
249
|
+
# TODO we could derive the basebackend from the minor extension of the template file
|
250
|
+
#name, *rest, ext_name = *path_segments # this form only works in Ruby >= 1.9
|
251
|
+
name = path_segments[0]
|
252
|
+
if name == 'block_ruler'
|
253
|
+
name = 'thematic_break'
|
254
|
+
elsif name.start_with? 'block_'
|
255
|
+
name = name[6..-1]
|
256
|
+
end
|
257
|
+
ext_name = path_segments[-1]
|
258
|
+
template_class = ::Tilt
|
259
|
+
extra_engine_options = {}
|
260
|
+
if ext_name == 'slim'
|
261
|
+
# slim doesn't get loaded by Tilt, so we have to load it explicitly
|
262
|
+
Helpers.require_library 'slim' unless defined? ::Slim
|
263
|
+
elsif ext_name == 'erb'
|
264
|
+
template_class, extra_engine_options = (eruby_loaded ||= load_eruby @eruby)
|
265
|
+
end
|
266
|
+
next unless ::Tilt.registered? ext_name
|
267
|
+
unless template_cache && (template = template_cache[file])
|
268
|
+
template = template_class.new file, 1, (@engine_options[ext_name.to_sym] || {}).merge(extra_engine_options)
|
269
|
+
end
|
270
|
+
result[name] = template
|
271
|
+
end
|
272
|
+
if ::File.file?(helpers = (::File.join template_dir, 'helpers.rb'))
|
273
|
+
require helpers
|
274
|
+
end
|
275
|
+
result
|
276
|
+
end
|
277
|
+
|
278
|
+
# Internal: Load the eRuby implementation
|
279
|
+
#
|
280
|
+
# name - the String name of the eRuby implementation
|
281
|
+
#
|
282
|
+
# Returns an [Array] containing the Tilt template Class for the eRuby implementation
|
283
|
+
# and a Hash of additional options to pass to the initializer
|
284
|
+
def load_eruby name
|
285
|
+
if !name || name == 'erb'
|
286
|
+
require 'erb' unless defined? ::ERB
|
287
|
+
[::Tilt::ERBTemplate, {}]
|
288
|
+
elsif name == 'erubis'
|
289
|
+
Helpers.require_library 'erubis' unless defined? ::Erubis::FastEruby
|
290
|
+
[::Tilt::ErubisTemplate, { :engine_class => ::Erubis::FastEruby }]
|
291
|
+
else
|
292
|
+
raise ::ArgumentError, %(Unknown ERB implementation: #{name})
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# A core library extension that defines the method nil_or_empty? as an alias to
|
2
|
+
# optimize checks for nil? or empty? on common object types such as NilClass,
|
3
|
+
# String, Array and Hash.
|
4
|
+
|
5
|
+
class NilClass
|
6
|
+
alias :nil_or_empty? :nil? unless respond_to? :nil_or_empty?
|
7
|
+
end
|
8
|
+
|
9
|
+
class String
|
10
|
+
alias :nil_or_empty? :empty? unless respond_to? :nil_or_empty?
|
11
|
+
end
|
12
|
+
|
13
|
+
class Array
|
14
|
+
alias :nil_or_empty? :empty? unless respond_to? :nil_or_empty?
|
15
|
+
end
|
16
|
+
|
17
|
+
class Hash
|
18
|
+
alias :nil_or_empty? :empty? unless respond_to? :nil_or_empty?
|
19
|
+
end
|
20
|
+
|
21
|
+
class Numeric
|
22
|
+
alias :nil_or_empty? :nil? unless respond_to? :nil_or_empty?
|
23
|
+
end
|
data/lib/asciidoctor/document.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
module Asciidoctor
|
2
|
-
# Public: Methods for parsing
|
3
|
-
# using erb templates.
|
2
|
+
# Public: Methods for parsing and converting AsciiDoc documents.
|
4
3
|
#
|
5
4
|
# There are several strategies for getting the title of the document:
|
6
5
|
#
|
@@ -16,21 +15,55 @@ module Asciidoctor
|
|
16
15
|
#
|
17
16
|
# notitle - The h1 heading should not be shown
|
18
17
|
# noheader - The header block (h1 heading, author, revision info) should not be shown
|
18
|
+
# nofooter - the footer block should not be shown
|
19
19
|
class Document < AbstractBlock
|
20
20
|
|
21
|
-
Footnote = Struct.new
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
Footnote = ::Struct.new :index, :id, :text
|
22
|
+
|
23
|
+
class AttributeEntry
|
24
|
+
attr_reader :name, :value, :negate
|
25
|
+
|
26
|
+
def initialize name, value, negate = nil
|
27
|
+
@name = name
|
28
|
+
@value = value
|
29
|
+
@negate = negate.nil? ? value.nil? : negate
|
25
30
|
end
|
26
31
|
|
27
|
-
def save_to
|
32
|
+
def save_to block_attributes
|
28
33
|
(block_attributes[:attribute_entries] ||= []) << self
|
29
34
|
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Public Parsed and stores a partitioned title (i.e., title & subtitle).
|
38
|
+
class Title
|
39
|
+
attr_reader :main
|
40
|
+
attr_reader :subtitle
|
41
|
+
attr_reader :combined
|
42
|
+
|
43
|
+
def initialize val, opts = {}
|
44
|
+
# TODO separate sanitization by type (:cdata for HTML/XML, :plain for non-SGML, false for none)
|
45
|
+
if (@sanitized = opts[:sanitize]) && val.include?('<')
|
46
|
+
val = val.gsub(XmlSanitizeRx, '').tr_s(' ', ' ').strip
|
47
|
+
end
|
48
|
+
if (@combined = val).include? ': '
|
49
|
+
@main, _, @subtitle = val.rpartition ': '
|
50
|
+
else
|
51
|
+
@main = val
|
52
|
+
@subtitle = nil
|
53
|
+
end
|
54
|
+
end
|
30
55
|
|
31
|
-
|
32
|
-
|
33
|
-
|
56
|
+
def sanitized?
|
57
|
+
@sanitized
|
58
|
+
end
|
59
|
+
|
60
|
+
def subtitle?
|
61
|
+
!!@subtitle
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_s
|
65
|
+
@combined
|
66
|
+
end
|
34
67
|
end
|
35
68
|
|
36
69
|
# Public A read-only integer value indicating the level of security that
|
@@ -45,7 +78,7 @@ class Document < AbstractBlock
|
|
45
78
|
# of the source file and disables any macro other than the include macro.
|
46
79
|
#
|
47
80
|
# A value of 10 (SERVER) disallows the document from setting attributes that
|
48
|
-
# would affect the
|
81
|
+
# would affect the conversion of the document, in addition to all the security
|
49
82
|
# features of SafeMode::SAFE. For instance, this value disallows changing the
|
50
83
|
# backend or the source-highlighter using an attribute defined in the source
|
51
84
|
# document. This is the most fundamental level of security for server-side
|
@@ -68,6 +101,20 @@ class Document < AbstractBlock
|
|
68
101
|
# this level is not currently implemented (and therefore not enforced)!
|
69
102
|
attr_reader :safe
|
70
103
|
|
104
|
+
# Public: Get the Boolean AsciiDoc compatibility mode
|
105
|
+
#
|
106
|
+
# enabling this attribute activates the following syntax changes:
|
107
|
+
#
|
108
|
+
# * single quotes as constrained emphasis formatting marks
|
109
|
+
# * single backticks parsed as inline literal, formatted as monospace
|
110
|
+
# * single plus parsed as constrained, monospaced inline formatting
|
111
|
+
# * double plus parsed as constrained, monospaced inline formatting
|
112
|
+
#
|
113
|
+
attr_reader :compat_mode
|
114
|
+
|
115
|
+
# Public: Get the Boolean flag that indicates whether source map information is tracked by the parser
|
116
|
+
attr_reader :sourcemap
|
117
|
+
|
71
118
|
# Public: Get the Hash of document references
|
72
119
|
attr_reader :references
|
73
120
|
|
@@ -77,263 +124,342 @@ class Document < AbstractBlock
|
|
77
124
|
# Public: Get the Hash of callouts
|
78
125
|
attr_reader :callouts
|
79
126
|
|
80
|
-
# Public:
|
127
|
+
# Public: Get the level-0 Section
|
81
128
|
attr_reader :header
|
82
129
|
|
83
|
-
# Public:
|
130
|
+
# Public: Get the String base directory for converting this document.
|
131
|
+
#
|
132
|
+
# Defaults to directory of the source file.
|
84
133
|
# If the source is a string, defaults to the current directory.
|
85
134
|
attr_reader :base_dir
|
86
135
|
|
87
|
-
# Public:
|
136
|
+
# Public: Get a reference to the parent Document of this nested document.
|
88
137
|
attr_reader :parent_document
|
89
138
|
|
90
|
-
# Public:
|
139
|
+
# Public: Get the Reader associated with this document
|
140
|
+
attr_reader :reader
|
141
|
+
|
142
|
+
# Public: Get the Converter associated with this document
|
143
|
+
attr_reader :converter
|
144
|
+
|
145
|
+
# Public: Get the extensions registry
|
91
146
|
attr_reader :extensions
|
92
147
|
|
93
|
-
# Public: Initialize
|
148
|
+
# Public: Initialize a {Document} object.
|
94
149
|
#
|
95
|
-
# data - The
|
96
|
-
# options - A Hash of options to control processing
|
97
|
-
#
|
98
|
-
# (default: {})
|
150
|
+
# data - The AsciiDoc source data as a String or String Array. (default: nil)
|
151
|
+
# options - A Hash of options to control processing (e.g., safe mode value (:safe), backend (:backend),
|
152
|
+
# header/footer toggle (:header_footer), custom attributes (:attributes)). (default: {})
|
99
153
|
#
|
100
154
|
# Examples
|
101
155
|
#
|
102
|
-
# data = File.
|
103
|
-
# doc
|
104
|
-
# puts doc.
|
105
|
-
def initialize
|
106
|
-
super
|
107
|
-
|
108
|
-
if options
|
109
|
-
@parent_document =
|
110
|
-
options[:base_dir] ||=
|
156
|
+
# data = File.read filename
|
157
|
+
# doc = Asciidoctor::Document.new data
|
158
|
+
# puts doc.convert
|
159
|
+
def initialize data = nil, options = {}
|
160
|
+
super self, :document
|
161
|
+
|
162
|
+
if (parent_doc = options.delete :parent)
|
163
|
+
@parent_document = parent_doc
|
164
|
+
options[:base_dir] ||= parent_doc.base_dir
|
165
|
+
@references = parent_doc.references.inject({}) do |accum, (key,ref)|
|
166
|
+
if key == :footnotes
|
167
|
+
accum[:footnotes] = []
|
168
|
+
else
|
169
|
+
accum[key] = ref
|
170
|
+
end
|
171
|
+
accum
|
172
|
+
end
|
111
173
|
# QUESTION should we support setting attribute in parent document from nested document?
|
112
174
|
# NOTE we must dup or else all the assignments to the overrides clobbers the real attributes
|
113
|
-
|
114
|
-
|
115
|
-
|
175
|
+
attr_overrides = parent_doc.attributes.dup
|
176
|
+
attr_overrides.delete 'doctype'
|
177
|
+
attr_overrides.delete 'compat-mode'
|
178
|
+
@attribute_overrides = attr_overrides
|
179
|
+
@safe = parent_doc.safe
|
180
|
+
@compat_mode = parent_doc.compat_mode
|
181
|
+
@sourcemap = parent_doc.sourcemap
|
182
|
+
@converter = parent_doc.converter
|
116
183
|
initialize_extensions = false
|
117
|
-
@extensions =
|
184
|
+
@extensions = parent_doc.extensions
|
118
185
|
else
|
119
186
|
@parent_document = nil
|
187
|
+
@references = {
|
188
|
+
:ids => {},
|
189
|
+
:footnotes => [],
|
190
|
+
:links => [],
|
191
|
+
:images => [],
|
192
|
+
:indexterms => [],
|
193
|
+
:includes => ::Set.new,
|
194
|
+
}
|
120
195
|
# copy attributes map and normalize keys
|
121
196
|
# attribute overrides are attributes that can only be set from the commandline
|
122
197
|
# a direct assignment effectively makes the attribute a constant
|
123
198
|
# a nil value or name with leading or trailing ! will result in the attribute being unassigned
|
124
|
-
|
125
|
-
|
199
|
+
attr_overrides = {}
|
200
|
+
(options[:attributes] || {}).each do |key, value|
|
201
|
+
if key.start_with? '!'
|
126
202
|
key = key[1..-1]
|
127
203
|
value = nil
|
128
|
-
elsif key.end_with?
|
129
|
-
key = key
|
204
|
+
elsif key.end_with? '!'
|
205
|
+
key = key.chop
|
130
206
|
value = nil
|
131
207
|
end
|
132
|
-
|
133
|
-
collector
|
208
|
+
attr_overrides[key.downcase] = value
|
134
209
|
end
|
135
|
-
@
|
136
|
-
@renderer = nil
|
137
|
-
initialize_extensions = Asciidoctor.const_defined?('Extensions')
|
138
|
-
@extensions = nil # initialize furthur down
|
139
|
-
end
|
140
|
-
|
141
|
-
@header = nil
|
142
|
-
@references = {
|
143
|
-
:ids => {},
|
144
|
-
:footnotes => [],
|
145
|
-
:links => [],
|
146
|
-
:images => [],
|
147
|
-
:indexterms => [],
|
148
|
-
:includes => Set.new,
|
149
|
-
}
|
150
|
-
@counters = {}
|
151
|
-
@callouts = Callouts.new
|
152
|
-
@attributes_modified = Set.new
|
153
|
-
@options = options
|
154
|
-
unless @parent_document
|
210
|
+
@attribute_overrides = attr_overrides
|
155
211
|
# safely resolve the safe mode from const, int or string
|
156
|
-
if
|
212
|
+
if !(safe_mode = options[:safe])
|
157
213
|
@safe = SafeMode::SECURE
|
158
|
-
elsif safe_mode
|
214
|
+
elsif ::Fixnum === safe_mode
|
159
215
|
# be permissive in case API user wants to define new levels
|
160
216
|
@safe = safe_mode
|
161
217
|
else
|
218
|
+
# NOTE: not using infix rescue for performance reasons, see https://github.com/jruby/jruby/issues/1816
|
162
219
|
begin
|
163
|
-
@safe = SafeMode.const_get(safe_mode.to_s.upcase)
|
220
|
+
@safe = SafeMode.const_get(safe_mode.to_s.upcase)
|
164
221
|
rescue
|
165
|
-
@safe = SafeMode::SECURE
|
222
|
+
@safe = SafeMode::SECURE
|
166
223
|
end
|
167
224
|
end
|
225
|
+
@sourcemap = options[:sourcemap]
|
226
|
+
@compat_mode = false
|
227
|
+
@converter = nil
|
228
|
+
initialize_extensions = defined? ::Asciidoctor::Extensions
|
229
|
+
@extensions = nil # initialize furthur down
|
168
230
|
end
|
169
|
-
@options[:header_footer] = @options.fetch(:header_footer, false)
|
170
231
|
|
171
|
-
@
|
172
|
-
@
|
173
|
-
@
|
174
|
-
@
|
175
|
-
@
|
176
|
-
@
|
177
|
-
|
178
|
-
|
179
|
-
|
232
|
+
@parsed = false
|
233
|
+
@header = nil
|
234
|
+
@counters = {}
|
235
|
+
@callouts = Callouts.new
|
236
|
+
@attributes_modified = ::Set.new
|
237
|
+
@options = options
|
238
|
+
header_footer = (options[:header_footer] ||= false)
|
239
|
+
|
240
|
+
attrs = @attributes
|
241
|
+
attrs['encoding'] = 'UTF-8'
|
242
|
+
attrs['sectids'] = ''
|
243
|
+
attrs['notitle'] = '' unless header_footer
|
244
|
+
attrs['toc-placement'] = 'auto'
|
245
|
+
attrs['stylesheet'] = ''
|
246
|
+
attrs['webfonts'] = ''
|
247
|
+
attrs['copycss'] = '' if header_footer
|
248
|
+
attrs['prewrap'] = ''
|
249
|
+
attrs['attribute-undefined'] = Compliance.attribute_undefined
|
250
|
+
attrs['attribute-missing'] = Compliance.attribute_missing
|
251
|
+
attrs['iconfont-remote'] = ''
|
180
252
|
|
181
253
|
# language strings
|
182
254
|
# TODO load these based on language settings
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
safe_mode_name = SafeMode.constants.detect {|l| SafeMode.const_get(l) == @safe}.to_s.downcase
|
203
|
-
|
204
|
-
|
205
|
-
|
255
|
+
attrs['caution-caption'] = 'Caution'
|
256
|
+
attrs['important-caption'] = 'Important'
|
257
|
+
attrs['note-caption'] = 'Note'
|
258
|
+
attrs['tip-caption'] = 'Tip'
|
259
|
+
attrs['warning-caption'] = 'Warning'
|
260
|
+
attrs['appendix-caption'] = 'Appendix'
|
261
|
+
attrs['example-caption'] = 'Example'
|
262
|
+
attrs['figure-caption'] = 'Figure'
|
263
|
+
#attrs['listing-caption'] = 'Listing'
|
264
|
+
attrs['table-caption'] = 'Table'
|
265
|
+
attrs['toc-title'] = 'Table of Contents'
|
266
|
+
attrs['manname-title'] = 'NAME'
|
267
|
+
attrs['untitled-label'] = 'Untitled'
|
268
|
+
attrs['version-label'] = 'Version'
|
269
|
+
attrs['last-update-label'] = 'Last updated'
|
270
|
+
|
271
|
+
attr_overrides['asciidoctor'] = ''
|
272
|
+
attr_overrides['asciidoctor-version'] = VERSION
|
273
|
+
|
274
|
+
safe_mode_name = SafeMode.constants.detect {|l| SafeMode.const_get(l) == @safe }.to_s.downcase
|
275
|
+
attr_overrides['safe-mode-name'] = safe_mode_name
|
276
|
+
attr_overrides["safe-mode-#{safe_mode_name}"] = ''
|
277
|
+
attr_overrides['safe-mode-level'] = @safe
|
206
278
|
|
207
279
|
# sync the embedded attribute w/ the value of options...do not allow override
|
208
|
-
|
280
|
+
attr_overrides['embedded'] = header_footer ? nil : ''
|
209
281
|
|
210
282
|
# the only way to set the max-include-depth attribute is via the document options
|
211
283
|
# 64 is the AsciiDoc default
|
212
|
-
|
284
|
+
attr_overrides['max-include-depth'] ||= 64
|
213
285
|
|
214
286
|
# the only way to enable uri reads is via the document options, disabled by default
|
215
|
-
unless
|
216
|
-
|
287
|
+
unless !attr_overrides['allow-uri-read'].nil?
|
288
|
+
attr_overrides['allow-uri-read'] = nil
|
217
289
|
end
|
218
290
|
|
291
|
+
attr_overrides['user-home'] = USER_HOME
|
292
|
+
|
293
|
+
# legacy support for numbered attribute
|
294
|
+
attr_overrides['sectnums'] = attr_overrides.delete 'numbered' if attr_overrides.key? 'numbered'
|
295
|
+
|
219
296
|
# if the base_dir option is specified, it overrides docdir as the root for relative paths
|
220
297
|
# otherwise, the base_dir is the directory of the source file (docdir) or the current
|
221
298
|
# directory of the input is a string
|
222
|
-
if
|
223
|
-
|
224
|
-
|
299
|
+
if options[:base_dir]
|
300
|
+
@base_dir = attr_overrides['docdir'] = ::File.expand_path(options[:base_dir])
|
301
|
+
else
|
302
|
+
if attr_overrides['docdir']
|
303
|
+
@base_dir = attr_overrides['docdir'] = ::File.expand_path(attr_overrides['docdir'])
|
225
304
|
else
|
226
305
|
#warn 'asciidoctor: WARNING: setting base_dir is recommended when working with string documents' unless nested?
|
227
|
-
@base_dir =
|
306
|
+
@base_dir = attr_overrides['docdir'] = ::File.expand_path(::Dir.pwd)
|
228
307
|
end
|
229
|
-
else
|
230
|
-
@base_dir = @attribute_overrides['docdir'] = File.expand_path(@options[:base_dir])
|
231
308
|
end
|
232
309
|
|
233
|
-
# allow common attributes backend and doctype to be set using options hash
|
234
|
-
|
235
|
-
|
310
|
+
# allow common attributes backend and doctype to be set using options hash, coerce values to string
|
311
|
+
if (backend_val = options[:backend])
|
312
|
+
attr_overrides['backend'] = %(#{backend_val})
|
236
313
|
end
|
237
314
|
|
238
|
-
|
239
|
-
|
315
|
+
if (doctype_val = options[:doctype])
|
316
|
+
attr_overrides['doctype'] = %(#{doctype_val})
|
240
317
|
end
|
241
318
|
|
242
319
|
if @safe >= SafeMode::SERVER
|
243
320
|
# restrict document from setting copycss, source-highlighter and backend
|
244
|
-
|
245
|
-
|
246
|
-
|
321
|
+
attr_overrides['copycss'] ||= nil
|
322
|
+
attr_overrides['source-highlighter'] ||= nil
|
323
|
+
attr_overrides['backend'] ||= DEFAULT_BACKEND
|
247
324
|
# restrict document from seeing the docdir and trim docfile to relative path
|
248
|
-
if
|
249
|
-
|
325
|
+
if !parent_doc && attr_overrides.key?('docfile')
|
326
|
+
attr_overrides['docfile'] = attr_overrides['docfile'][(attr_overrides['docdir'].length + 1)..-1]
|
250
327
|
end
|
251
|
-
|
328
|
+
attr_overrides['docdir'] = ''
|
329
|
+
attr_overrides['user-home'] = '.'
|
252
330
|
if @safe >= SafeMode::SECURE
|
253
331
|
# assign linkcss (preventing css embedding) unless explicitly disabled from the commandline or API
|
254
332
|
# effectively the same has "has key 'linkcss' and value == nil"
|
255
|
-
unless
|
256
|
-
|
333
|
+
unless attr_overrides.fetch('linkcss', '').nil?
|
334
|
+
attr_overrides['linkcss'] = ''
|
257
335
|
end
|
258
336
|
# restrict document from enabling icons
|
259
|
-
|
337
|
+
attr_overrides['icons'] ||= nil
|
260
338
|
end
|
261
339
|
end
|
262
340
|
|
263
|
-
|
341
|
+
attr_overrides.delete_if do |key, val|
|
264
342
|
verdict = false
|
265
343
|
# a nil value undefines the attribute
|
266
344
|
if val.nil?
|
267
|
-
|
268
|
-
# a negative key (trailing !) undefines the attribute
|
269
|
-
# NOTE already normalize above as key with nil value
|
270
|
-
#elsif key.end_with? '!'
|
271
|
-
# @attributes.delete(key[0..-2])
|
272
|
-
# a negative key (leading !) undefines the attribute
|
273
|
-
# NOTE already normalize above as key with nil value
|
274
|
-
#elsif key.start_with? '!'
|
275
|
-
# @attributes.delete(key[1..-1])
|
276
|
-
# otherwise it's an attribute assignment
|
345
|
+
attrs.delete(key)
|
277
346
|
else
|
278
347
|
# a value ending in @ indicates this attribute does not override
|
279
348
|
# an attribute with the same key in the document souce
|
280
|
-
if val.is_a?
|
349
|
+
if (val.is_a? ::String) && (val.end_with? '@')
|
281
350
|
val = val.chop
|
282
351
|
verdict = true
|
283
352
|
end
|
284
|
-
|
353
|
+
attrs[key] = val
|
285
354
|
end
|
286
355
|
verdict
|
287
|
-
|
356
|
+
end
|
357
|
+
|
358
|
+
@compat_mode = true if attrs.key? 'compat-mode'
|
288
359
|
|
289
|
-
if
|
360
|
+
if parent_doc
|
361
|
+
# setup default doctype (backend is fixed)
|
362
|
+
attrs['doctype'] ||= DEFAULT_DOCTYPE
|
363
|
+
|
364
|
+
# don't need to do the extra processing within our own document
|
365
|
+
# FIXME line info isn't reported correctly within include files in nested document
|
366
|
+
@reader = Reader.new data, options[:cursor]
|
367
|
+
|
368
|
+
# Now parse the lines in the reader into blocks
|
369
|
+
# Eagerly parse (for now) since a subdocument is not a publicly accessible object
|
370
|
+
Parser.parse @reader, self
|
371
|
+
|
372
|
+
# should we call rewind in some sort of post-parse function?
|
373
|
+
@callouts.rewind
|
374
|
+
@parsed = true
|
375
|
+
else
|
290
376
|
# setup default backend and doctype
|
291
|
-
|
292
|
-
|
293
|
-
update_backend_attributes
|
377
|
+
attrs['backend'] ||= DEFAULT_BACKEND
|
378
|
+
attrs['doctype'] ||= DEFAULT_DOCTYPE
|
379
|
+
update_backend_attributes attrs['backend'], true
|
294
380
|
|
295
|
-
|
296
|
-
|
381
|
+
#attrs['indir'] = attrs['docdir']
|
382
|
+
#attrs['infile'] = attrs['docfile']
|
297
383
|
|
298
384
|
# dynamic intrinstic attribute values
|
299
|
-
now = Time.
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
385
|
+
now = ::Time.now
|
386
|
+
localdate = (attrs['localdate'] ||= now.strftime('%Y-%m-%d'))
|
387
|
+
unless (localtime = attrs['localtime'])
|
388
|
+
begin
|
389
|
+
localtime = attrs['localtime'] = now.strftime('%H:%M:%S %Z')
|
390
|
+
rescue
|
391
|
+
localtime = attrs['localtime'] = now.strftime('%H:%M:%S')
|
392
|
+
end
|
393
|
+
end
|
394
|
+
attrs['localdatetime'] ||= %(#{localdate} #{localtime})
|
395
|
+
|
304
396
|
# docdate, doctime and docdatetime should default to
|
305
397
|
# localdate, localtime and localdatetime if not otherwise set
|
306
|
-
|
307
|
-
|
308
|
-
|
398
|
+
attrs['docdate'] ||= localdate
|
399
|
+
attrs['doctime'] ||= localtime
|
400
|
+
attrs['docdatetime'] ||= %(#{localdate} #{localtime})
|
309
401
|
|
310
402
|
# fallback directories
|
311
|
-
|
312
|
-
|
403
|
+
attrs['stylesdir'] ||= '.'
|
404
|
+
attrs['iconsdir'] ||= ::File.join(attrs.fetch('imagesdir', './images'), 'icons')
|
405
|
+
|
406
|
+
@extensions = if initialize_extensions
|
407
|
+
registry = if (ext_registry = options[:extensions_registry])
|
408
|
+
if (ext_registry.is_a? Extensions::Registry) ||
|
409
|
+
(::RUBY_ENGINE_JRUBY && (ext_registry.is_a? ::AsciidoctorJ::Extensions::ExtensionRegistry))
|
410
|
+
ext_registry
|
411
|
+
end
|
412
|
+
elsif (ext_block = options[:extensions]).is_a? ::Proc
|
413
|
+
Extensions.build_registry(&ext_block)
|
414
|
+
end
|
415
|
+
(registry ||= Extensions::Registry.new).activate self
|
416
|
+
end
|
313
417
|
|
314
|
-
@
|
315
|
-
|
418
|
+
@reader = PreprocessorReader.new self, data, Reader::Cursor.new(attrs['docfile'], @base_dir)
|
419
|
+
end
|
420
|
+
end
|
316
421
|
|
317
|
-
|
318
|
-
|
319
|
-
|
422
|
+
# Public: Parse the AsciiDoc source stored in the {Reader} into an abstract syntax tree.
|
423
|
+
#
|
424
|
+
# If the data parameter is not nil, create a new {PreprocessorReader} and assigned it to the reader
|
425
|
+
# property of this object. Otherwise, continue with the reader that was created in {#initialize}.
|
426
|
+
# Pass the reader to {Parser.parse} to parse the source data into an abstract syntax tree.
|
427
|
+
#
|
428
|
+
# If parsing has already been performed, this method returns without performing any processing.
|
429
|
+
#
|
430
|
+
# data - The optional replacement AsciiDoc source data as a String or String Array. (default: nil)
|
431
|
+
#
|
432
|
+
# Returns this [Document]
|
433
|
+
def parse data = nil
|
434
|
+
if @parsed
|
435
|
+
self
|
436
|
+
else
|
437
|
+
doc = self
|
438
|
+
# create reader if data is provided (used when data is not known at the time the Document object is created)
|
439
|
+
@reader = PreprocessorReader.new doc, data, Reader::Cursor.new(@attributes['docfile'], @base_dir) if data
|
440
|
+
|
441
|
+
if (exts = @parent_document ? nil : @extensions) && exts.preprocessors?
|
442
|
+
exts.preprocessors.each do |ext|
|
443
|
+
@reader = ext.process_method[doc, @reader] || @reader
|
320
444
|
end
|
321
445
|
end
|
322
|
-
else
|
323
|
-
# don't need to do the extra processing within our own document
|
324
|
-
# FIXME line info isn't reported correctly within include files in nested document
|
325
|
-
@reader = Reader.new data, options[:cursor]
|
326
|
-
end
|
327
446
|
|
328
|
-
|
329
|
-
|
447
|
+
# Now parse the lines in the reader into blocks
|
448
|
+
Parser.parse @reader, doc, :header_only => !!@options[:parse_header_only]
|
330
449
|
|
331
|
-
|
450
|
+
# should we call rewind in some sort of post-parse function?
|
451
|
+
@callouts.rewind
|
332
452
|
|
333
|
-
|
334
|
-
|
335
|
-
|
453
|
+
if exts && exts.treeprocessors?
|
454
|
+
exts.treeprocessors.each do |ext|
|
455
|
+
if (result = ext.process_method[doc]) && Document === result && result != doc
|
456
|
+
doc = result
|
457
|
+
end
|
458
|
+
end
|
336
459
|
end
|
460
|
+
|
461
|
+
@parsed = true
|
462
|
+
doc
|
337
463
|
end
|
338
464
|
end
|
339
465
|
|
@@ -344,15 +470,15 @@ class Document < AbstractBlock
|
|
344
470
|
#
|
345
471
|
# returns the next number in the sequence for the specified counter
|
346
472
|
def counter(name, seed = nil)
|
347
|
-
if
|
473
|
+
if (attr_is_seed = !(attr_val = @attributes[name]).nil_or_empty?) && @counters.key?(name)
|
474
|
+
@counters[name] = nextval(attr_val)
|
475
|
+
else
|
348
476
|
if seed.nil?
|
349
|
-
seed = nextval(
|
477
|
+
seed = nextval(attr_is_seed ? attr_val : 0)
|
350
478
|
elsif seed.to_i.to_s == seed
|
351
479
|
seed = seed.to_i
|
352
480
|
end
|
353
481
|
@counters[name] = seed
|
354
|
-
else
|
355
|
-
@counters[name] = nextval(@counters[name])
|
356
482
|
end
|
357
483
|
|
358
484
|
(@attributes[name] = @counters[name])
|
@@ -378,7 +504,7 @@ class Document < AbstractBlock
|
|
378
504
|
#
|
379
505
|
# returns the next value in the sequence according to the current value's type
|
380
506
|
def nextval(current)
|
381
|
-
if current.is_a?(Integer)
|
507
|
+
if current.is_a?(::Integer)
|
382
508
|
current + 1
|
383
509
|
else
|
384
510
|
intval = current.to_i
|
@@ -393,7 +519,7 @@ class Document < AbstractBlock
|
|
393
519
|
def register(type, value)
|
394
520
|
case type
|
395
521
|
when :ids
|
396
|
-
if value.is_a?(Array)
|
522
|
+
if value.is_a?(::Array)
|
397
523
|
@references[:ids][value[0]] = (value[1] || '[' + value[0] + ']')
|
398
524
|
else
|
399
525
|
@references[:ids][value] = '[' + value + ']'
|
@@ -408,7 +534,7 @@ class Document < AbstractBlock
|
|
408
534
|
end
|
409
535
|
|
410
536
|
def footnotes?
|
411
|
-
|
537
|
+
!@references[:footnotes].empty?
|
412
538
|
end
|
413
539
|
|
414
540
|
def footnotes
|
@@ -416,16 +542,16 @@ class Document < AbstractBlock
|
|
416
542
|
end
|
417
543
|
|
418
544
|
def nested?
|
419
|
-
|
545
|
+
!!@parent_document
|
420
546
|
end
|
421
547
|
|
422
548
|
def embedded?
|
423
549
|
# QUESTION should this be !@options[:header_footer] ?
|
424
|
-
@attributes.
|
550
|
+
@attributes.key? 'embedded'
|
425
551
|
end
|
426
552
|
|
427
553
|
def extensions?
|
428
|
-
|
554
|
+
!!@extensions
|
429
555
|
end
|
430
556
|
|
431
557
|
# Make the raw source for the Document available.
|
@@ -439,11 +565,11 @@ class Document < AbstractBlock
|
|
439
565
|
end
|
440
566
|
|
441
567
|
def doctype
|
442
|
-
@attributes['doctype']
|
568
|
+
@doctype ||= @attributes['doctype']
|
443
569
|
end
|
444
570
|
|
445
571
|
def backend
|
446
|
-
@attributes['backend']
|
572
|
+
@backend ||= @attributes['backend']
|
447
573
|
end
|
448
574
|
|
449
575
|
def basebackend? base
|
@@ -460,18 +586,38 @@ class Document < AbstractBlock
|
|
460
586
|
@header.title = title
|
461
587
|
end
|
462
588
|
|
463
|
-
#
|
464
|
-
|
465
|
-
|
589
|
+
# Public: Resolves the primary title for the document
|
590
|
+
#
|
591
|
+
# Searches the locations to find the first non-empty
|
592
|
+
# value:
|
593
|
+
#
|
594
|
+
# * document-level attribute named title
|
595
|
+
# * header title (known as the document title)
|
596
|
+
# * title of the first section
|
597
|
+
# * document-level attribute named untitled-label (if :use_fallback option is set)
|
598
|
+
#
|
599
|
+
# If no value can be resolved, nil is returned.
|
600
|
+
#
|
601
|
+
# If the :partition attribute is specified, the value is parsed into an Document::Title object.
|
602
|
+
# If the :sanitize attribute is specified, XML elements are removed from the value.
|
603
|
+
#
|
604
|
+
# Returns the resolved title as a [Title] if the :partition option is passed or a [String] if not
|
605
|
+
# or nil if no value can be resolved.
|
606
|
+
def doctitle opts = {}
|
607
|
+
if !(val = @attributes['title'].nil_or_empty?)
|
466
608
|
val = title
|
467
|
-
elsif
|
609
|
+
elsif (sect = first_section) && sect.title?
|
468
610
|
val = sect.title
|
611
|
+
elsif opts[:use_fallback] && (val = @attributes['untitled-label'])
|
612
|
+
# use val set in condition
|
469
613
|
else
|
470
|
-
return
|
614
|
+
return
|
471
615
|
end
|
472
616
|
|
473
|
-
if opts[:
|
474
|
-
|
617
|
+
if opts[:partition]
|
618
|
+
Title.new val, opts
|
619
|
+
elsif opts[:sanitize] && val.include?('<')
|
620
|
+
val.gsub(XmlSanitizeRx, '').tr_s(' ', ' ').strip
|
475
621
|
else
|
476
622
|
val
|
477
623
|
end
|
@@ -493,21 +639,26 @@ class Document < AbstractBlock
|
|
493
639
|
end
|
494
640
|
|
495
641
|
def notitle
|
496
|
-
!@attributes.
|
642
|
+
!@attributes.key?('showtitle') && @attributes.key?('notitle')
|
497
643
|
end
|
498
644
|
|
499
645
|
def noheader
|
500
|
-
@attributes.
|
646
|
+
@attributes.key? 'noheader'
|
647
|
+
end
|
648
|
+
|
649
|
+
def nofooter
|
650
|
+
@attributes.key? 'nofooter'
|
501
651
|
end
|
502
652
|
|
503
653
|
# QUESTION move to AbstractBlock?
|
504
654
|
def first_section
|
505
|
-
has_header? ? @header : (@blocks || []).detect{|e| e.
|
655
|
+
has_header? ? @header : (@blocks || []).detect{|e| e.context == :section }
|
506
656
|
end
|
507
657
|
|
508
658
|
def has_header?
|
509
659
|
@header ? true : false
|
510
660
|
end
|
661
|
+
alias :header? :has_header?
|
511
662
|
|
512
663
|
# Public: Append a content Block to this Document.
|
513
664
|
#
|
@@ -538,58 +689,75 @@ class Document < AbstractBlock
|
|
538
689
|
# Internal: Branch the attributes so that the original state can be restored
|
539
690
|
# at a future time.
|
540
691
|
def save_attributes
|
541
|
-
# enable toc and numbered by default in DocBook backend
|
692
|
+
# enable toc and sectnums (i.e., numbered) by default in DocBook backend
|
542
693
|
# NOTE the attributes_modified should go away once we have a proper attribute storage & tracking facility
|
543
|
-
if @attributes['basebackend'] == 'docbook'
|
544
|
-
|
545
|
-
|
694
|
+
if (attrs = @attributes)['basebackend'] == 'docbook'
|
695
|
+
attrs['toc'] = '' unless attribute_locked?('toc') || @attributes_modified.include?('toc')
|
696
|
+
attrs['sectnums'] = '' unless attribute_locked?('sectnums') || @attributes_modified.include?('sectnums')
|
546
697
|
end
|
547
698
|
|
548
|
-
unless
|
549
|
-
|
699
|
+
unless attrs.key?('doctitle') || !(val = doctitle)
|
700
|
+
attrs['doctitle'] = val
|
550
701
|
end
|
551
702
|
|
552
703
|
# css-signature cannot be updated after header attributes are processed
|
553
|
-
|
554
|
-
@id = @attributes['css-signature']
|
555
|
-
end
|
704
|
+
@id = attrs['css-signature'] unless @id
|
556
705
|
|
557
|
-
toc_val =
|
558
|
-
|
559
|
-
|
706
|
+
toc_position_val = if (toc_val = (attrs.delete('toc2') ? 'left' : attrs['toc']))
|
707
|
+
# toc-placement allows us to separate position from using fitted slot vs macro
|
708
|
+
(toc_placement = attrs.fetch('toc-placement', 'macro')) && toc_placement != 'auto' ? toc_placement : attrs['toc-position']
|
709
|
+
else
|
710
|
+
nil
|
711
|
+
end
|
560
712
|
|
561
|
-
if
|
713
|
+
if toc_val && (!toc_val.empty? || !toc_position_val.nil_or_empty?)
|
562
714
|
default_toc_position = 'left'
|
715
|
+
# TODO rename toc2 to aside-toc
|
563
716
|
default_toc_class = 'toc2'
|
564
|
-
|
565
|
-
|
566
|
-
|
717
|
+
if !toc_position_val.nil_or_empty?
|
718
|
+
position = toc_position_val
|
719
|
+
elsif !toc_val.empty?
|
720
|
+
position = toc_val
|
721
|
+
else
|
722
|
+
position = default_toc_position
|
723
|
+
end
|
724
|
+
attrs['toc'] = ''
|
725
|
+
attrs['toc-placement'] = 'auto'
|
567
726
|
case position
|
568
727
|
when 'left', '<', '<'
|
569
|
-
|
728
|
+
attrs['toc-position'] = 'left'
|
570
729
|
when 'right', '>', '>'
|
571
|
-
|
730
|
+
attrs['toc-position'] = 'right'
|
572
731
|
when 'top', '^'
|
573
|
-
|
732
|
+
attrs['toc-position'] = 'top'
|
574
733
|
when 'bottom', 'v'
|
575
|
-
|
576
|
-
when '
|
577
|
-
|
734
|
+
attrs['toc-position'] = 'bottom'
|
735
|
+
when 'preamble', 'macro'
|
736
|
+
attrs['toc-position'] = 'content'
|
737
|
+
attrs['toc-placement'] = position
|
738
|
+
default_toc_class = nil
|
739
|
+
else
|
740
|
+
attrs.delete 'toc-position'
|
578
741
|
default_toc_class = nil
|
579
|
-
default_toc_position = 'center'
|
580
742
|
end
|
581
|
-
|
582
|
-
@attributes['toc-position'] ||= default_toc_position if default_toc_position
|
743
|
+
attrs['toc-class'] ||= default_toc_class if default_toc_class
|
583
744
|
end
|
584
745
|
|
585
|
-
|
746
|
+
if attrs.key? 'compat-mode'
|
747
|
+
attrs['source-language'] = attrs['language'] if attrs.has_key? 'language'
|
748
|
+
@compat_mode = true
|
749
|
+
else
|
750
|
+
@compat_mode = false
|
751
|
+
end
|
752
|
+
|
753
|
+
@original_attributes = attrs.dup
|
586
754
|
|
587
755
|
# unfreeze "flexible" attributes
|
588
756
|
unless nested?
|
589
757
|
FLEXIBLE_ATTRIBUTES.each do |name|
|
590
758
|
# turning a flexible attribute off should be permanent
|
591
759
|
# (we may need more config if that's not always the case)
|
592
|
-
if @attribute_overrides.
|
760
|
+
if @attribute_overrides.key?(name) && @attribute_overrides[name]
|
593
761
|
@attribute_overrides.delete(name)
|
594
762
|
end
|
595
763
|
end
|
@@ -597,7 +765,10 @@ class Document < AbstractBlock
|
|
597
765
|
end
|
598
766
|
|
599
767
|
# Internal: Restore the attributes to the previously saved state
|
768
|
+
#--
|
769
|
+
# QUESTION should we restore attributes after parse?
|
600
770
|
def restore_attributes
|
771
|
+
# QUESTION shouldn't this be a dup in case we convert again?
|
601
772
|
@attributes = @original_attributes
|
602
773
|
end
|
603
774
|
|
@@ -608,12 +779,15 @@ class Document < AbstractBlock
|
|
608
779
|
|
609
780
|
# Internal: Replay attribute assignments at the block level
|
610
781
|
def playback_attributes(block_attributes)
|
611
|
-
if block_attributes.
|
782
|
+
if block_attributes.key? :attribute_entries
|
612
783
|
block_attributes[:attribute_entries].each do |entry|
|
784
|
+
name = entry.name
|
613
785
|
if entry.negate
|
614
|
-
@attributes.delete
|
786
|
+
@attributes.delete name
|
787
|
+
@compat_mode = false if name == 'compat-mode'
|
615
788
|
else
|
616
|
-
@attributes[
|
789
|
+
@attributes[name] = entry.value
|
790
|
+
@compat_mode = true if name == 'compat-mode'
|
617
791
|
end
|
618
792
|
end
|
619
793
|
end
|
@@ -634,11 +808,15 @@ class Document < AbstractBlock
|
|
634
808
|
if attribute_locked?(name)
|
635
809
|
false
|
636
810
|
else
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
811
|
+
case name
|
812
|
+
when 'backend'
|
813
|
+
update_backend_attributes apply_attribute_value_subs(value)
|
814
|
+
when 'doctype'
|
815
|
+
update_doctype_attributes apply_attribute_value_subs(value)
|
816
|
+
else
|
817
|
+
@attributes[name] = apply_attribute_value_subs(value)
|
641
818
|
end
|
819
|
+
@attributes_modified << name
|
642
820
|
true
|
643
821
|
end
|
644
822
|
end
|
@@ -666,124 +844,226 @@ class Document < AbstractBlock
|
|
666
844
|
#
|
667
845
|
# Returns true if the attribute is locked, false otherwise
|
668
846
|
def attribute_locked?(name)
|
669
|
-
@attribute_overrides.
|
847
|
+
@attribute_overrides.key?(name)
|
670
848
|
end
|
671
849
|
|
672
850
|
# Internal: Apply substitutions to the attribute value
|
673
851
|
#
|
674
|
-
# If the value is an inline passthrough macro (e.g., pass
|
675
|
-
# apply the substitutions defined
|
676
|
-
#
|
852
|
+
# If the value is an inline passthrough macro (e.g., pass:<subs>[value]),
|
853
|
+
# apply the substitutions defined in <subs> to the value, or leave the value
|
854
|
+
# unmodified if no substitutions are specified. If the value is not an
|
855
|
+
# inline passthrough macro, apply header substitutions to the value.
|
677
856
|
#
|
678
857
|
# value - The String attribute value on which to perform substitutions
|
679
858
|
#
|
680
|
-
# Returns The String value with substitutions performed
|
859
|
+
# Returns The String value with substitutions performed
|
681
860
|
def apply_attribute_value_subs(value)
|
682
|
-
if
|
683
|
-
# copy match for Ruby 1.8.7 compat
|
684
|
-
m = $~
|
861
|
+
if (m = AttributeEntryPassMacroRx.match(value))
|
685
862
|
if !m[1].empty?
|
686
863
|
subs = resolve_pass_subs m[1]
|
687
|
-
subs.empty? ? m[2] : apply_subs
|
864
|
+
subs.empty? ? m[2] : (apply_subs m[2], subs)
|
688
865
|
else
|
689
866
|
m[2]
|
690
867
|
end
|
691
868
|
else
|
692
|
-
apply_header_subs
|
869
|
+
apply_header_subs value
|
693
870
|
end
|
694
871
|
end
|
695
872
|
|
696
873
|
# Public: Update the backend attributes to reflect a change in the selected backend
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
874
|
+
#
|
875
|
+
# This method also handles updating the related doctype attributes if the
|
876
|
+
# doctype attribute is assigned at the time this method is called.
|
877
|
+
def update_backend_attributes new_backend, force = false
|
878
|
+
if force || (new_backend && new_backend != @attributes['backend'])
|
879
|
+
attrs = @attributes
|
880
|
+
current_backend = attrs['backend']
|
881
|
+
current_basebackend = attrs['basebackend']
|
882
|
+
current_doctype = attrs['doctype']
|
883
|
+
if new_backend.start_with? 'xhtml'
|
884
|
+
attrs['htmlsyntax'] = 'xml'
|
885
|
+
new_backend = new_backend[1..-1]
|
886
|
+
elsif new_backend.start_with? 'html'
|
887
|
+
attrs['htmlsyntax'] = 'html'
|
888
|
+
end
|
889
|
+
if (resolved_name = BACKEND_ALIASES[new_backend])
|
890
|
+
new_backend = resolved_name
|
891
|
+
end
|
892
|
+
if current_backend
|
893
|
+
attrs.delete %(backend-#{current_backend})
|
894
|
+
if current_doctype
|
895
|
+
attrs.delete %(backend-#{current_backend}-doctype-#{current_doctype})
|
896
|
+
end
|
897
|
+
end
|
898
|
+
if current_doctype
|
899
|
+
attrs[%(doctype-#{current_doctype})] = ''
|
900
|
+
attrs[%(backend-#{new_backend}-doctype-#{current_doctype})] = ''
|
901
|
+
end
|
902
|
+
attrs['backend'] = new_backend
|
903
|
+
attrs[%(backend-#{new_backend})] = ''
|
904
|
+
# (re)initialize converter
|
905
|
+
if (@converter = create_converter).is_a? Converter::BackendInfo
|
906
|
+
new_basebackend = @converter.basebackend
|
907
|
+
attrs['outfilesuffix'] = @converter.outfilesuffix unless attribute_locked? 'outfilesuffix'
|
908
|
+
new_filetype = @converter.filetype
|
909
|
+
else
|
910
|
+
new_basebackend = new_backend.sub TrailingDigitsRx, ''
|
911
|
+
# QUESTION should we be forcing the basebackend to html if unknown?
|
912
|
+
new_outfilesuffix = DEFAULT_EXTENSIONS[new_basebackend] || '.html'
|
913
|
+
new_filetype = new_outfilesuffix[1..-1]
|
914
|
+
attrs['outfilesuffix'] = new_outfilesuffix unless attribute_locked? 'outfilesuffix'
|
915
|
+
end
|
916
|
+
if (current_filetype = attrs['filetype'])
|
917
|
+
attrs.delete %(filetype-#{current_filetype})
|
918
|
+
end
|
919
|
+
attrs['filetype'] = new_filetype
|
920
|
+
attrs[%(filetype-#{new_filetype})] = ''
|
921
|
+
if (page_width = DEFAULT_PAGE_WIDTHS[new_basebackend])
|
922
|
+
attrs['pagewidth'] = page_width
|
923
|
+
else
|
924
|
+
attrs.delete 'pagewidth'
|
925
|
+
end
|
926
|
+
if new_basebackend != current_basebackend
|
927
|
+
if current_basebackend
|
928
|
+
attrs.delete %(basebackend-#{current_basebackend})
|
929
|
+
if current_doctype
|
930
|
+
attrs.delete %(basebackend-#{current_basebackend}-doctype-#{current_doctype})
|
931
|
+
end
|
932
|
+
end
|
933
|
+
attrs['basebackend'] = new_basebackend
|
934
|
+
attrs[%(basebackend-#{new_basebackend})] = ''
|
935
|
+
attrs[%(basebackend-#{new_basebackend}-doctype-#{current_doctype})] = '' if current_doctype
|
936
|
+
end
|
937
|
+
# clear cached backend value
|
938
|
+
@backend = nil
|
939
|
+
end
|
940
|
+
end
|
726
941
|
|
727
|
-
|
728
|
-
if @
|
729
|
-
|
730
|
-
|
731
|
-
|
942
|
+
def update_doctype_attributes new_doctype
|
943
|
+
if new_doctype && new_doctype != @attributes['doctype']
|
944
|
+
attrs = @attributes
|
945
|
+
current_doctype = attrs['doctype']
|
946
|
+
current_backend = attrs['backend']
|
947
|
+
current_basebackend = attrs['basebackend']
|
948
|
+
if current_doctype
|
949
|
+
attrs.delete %(doctype-#{current_doctype})
|
950
|
+
attrs.delete %(backend-#{current_backend}-doctype-#{current_doctype}) if current_backend
|
951
|
+
attrs.delete %(basebackend-#{current_basebackend}-doctype-#{current_doctype}) if current_basebackend
|
952
|
+
end
|
953
|
+
attrs['doctype'] = new_doctype
|
954
|
+
attrs[%(doctype-#{new_doctype})] = ''
|
955
|
+
attrs[%(backend-#{current_backend}-doctype-#{new_doctype})] = '' if current_backend
|
956
|
+
attrs[%(basebackend-#{current_basebackend}-doctype-#{new_doctype})] = '' if current_basebackend
|
957
|
+
# clear cached doctype value
|
958
|
+
@doctype = nil
|
732
959
|
end
|
733
|
-
|
734
|
-
render_options[:template_cache] = @options.fetch(:template_cache, true)
|
735
|
-
render_options[:backend] = @attributes.fetch('backend', 'html5')
|
736
|
-
render_options[:template_engine] = @options[:template_engine]
|
737
|
-
render_options[:eruby] = @options.fetch(:eruby, 'erb')
|
738
|
-
render_options[:compact] = @options.fetch(:compact, false)
|
739
|
-
|
740
|
-
# Override Document @option settings with options passed in
|
741
|
-
render_options.merge! opts
|
960
|
+
end
|
742
961
|
|
743
|
-
|
962
|
+
# TODO document me
|
963
|
+
def create_converter
|
964
|
+
converter_opts = {}
|
965
|
+
converter_opts[:htmlsyntax] = @attributes['htmlsyntax']
|
966
|
+
template_dirs = if (template_dir = @options[:template_dir])
|
967
|
+
converter_opts[:template_dirs] = [template_dir]
|
968
|
+
elsif (template_dirs = @options[:template_dirs])
|
969
|
+
converter_opts[:template_dirs] = template_dirs
|
970
|
+
end
|
971
|
+
if template_dirs
|
972
|
+
converter_opts[:template_cache] = @options.fetch :template_cache, true
|
973
|
+
converter_opts[:template_engine] = @options[:template_engine]
|
974
|
+
converter_opts[:template_engine_options] = @options[:template_engine_options]
|
975
|
+
converter_opts[:eruby] = @options[:eruby]
|
976
|
+
end
|
977
|
+
converter_factory = if (converter = @options[:converter])
|
978
|
+
Converter::Factory.new ::Hash[backend, converter]
|
979
|
+
else
|
980
|
+
Converter::Factory.default false
|
981
|
+
end
|
982
|
+
# QUESTION should we honor the convert_opts?
|
983
|
+
# QUESTION should we pass through all options and attributes too?
|
984
|
+
#converter_opts.update opts
|
985
|
+
converter_factory.create backend, converter_opts
|
744
986
|
end
|
745
987
|
|
746
|
-
# Public:
|
747
|
-
# loaded by
|
748
|
-
# or a template is missing, the
|
988
|
+
# Public: Convert the AsciiDoc document using the templates
|
989
|
+
# loaded by the Converter. If a :template_dir is not specified,
|
990
|
+
# or a template is missing, the converter will fall back to
|
749
991
|
# using the appropriate built-in template.
|
750
|
-
def
|
992
|
+
def convert opts = {}
|
993
|
+
parse unless @parsed
|
751
994
|
restore_attributes
|
752
|
-
r = renderer(opts)
|
753
995
|
|
754
|
-
# QUESTION should we add
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
# end
|
759
|
-
#end
|
996
|
+
# QUESTION should we add processors that execute before conversion begins?
|
997
|
+
unless @converter
|
998
|
+
fail %(asciidoctor: FAILED: missing converter for backend '#{backend}'. Processing aborted.)
|
999
|
+
end
|
760
1000
|
|
761
1001
|
if doctype == 'inline'
|
762
1002
|
# QUESTION should we warn if @blocks.size > 0 and the first block is not a paragraph?
|
763
|
-
if
|
1003
|
+
if (block = @blocks[0]) && block.content_model != :compound
|
764
1004
|
output = block.content
|
765
1005
|
else
|
766
1006
|
output = ''
|
767
1007
|
end
|
768
1008
|
else
|
769
|
-
|
1009
|
+
transform = ((opts.key? :header_footer) ? opts[:header_footer] : @options[:header_footer]) ? 'document' : 'embedded'
|
1010
|
+
output = @converter.convert self, transform
|
770
1011
|
end
|
771
1012
|
|
772
|
-
|
773
|
-
if @extensions.postprocessors?
|
774
|
-
|
775
|
-
output =
|
1013
|
+
unless @parent_document
|
1014
|
+
if (exts = @extensions) && exts.postprocessors?
|
1015
|
+
exts.postprocessors.each do |ext|
|
1016
|
+
output = ext.process_method[self, output]
|
776
1017
|
end
|
777
1018
|
end
|
778
|
-
@extensions.reset
|
779
1019
|
end
|
780
1020
|
|
781
1021
|
output
|
782
1022
|
end
|
783
1023
|
|
1024
|
+
# Alias render to convert to maintain backwards compatibility
|
1025
|
+
alias :render :convert
|
1026
|
+
|
1027
|
+
# Public: Write the output to the specified file
|
1028
|
+
#
|
1029
|
+
# If the converter responds to :write, delegate the work of writing the file
|
1030
|
+
# to that method. Otherwise, write the output the specified file.
|
1031
|
+
def write output, target
|
1032
|
+
if @converter.is_a? Writer
|
1033
|
+
@converter.write output, target
|
1034
|
+
else
|
1035
|
+
if target.respond_to? :write
|
1036
|
+
target.write output.chomp
|
1037
|
+
# ensure there's a trailing endline
|
1038
|
+
target.write EOL
|
1039
|
+
else
|
1040
|
+
::File.open(target, 'w') {|f| f.write output }
|
1041
|
+
end
|
1042
|
+
nil
|
1043
|
+
end
|
1044
|
+
end
|
1045
|
+
|
1046
|
+
=begin
|
1047
|
+
def convert_to target, opts = {}
|
1048
|
+
start = ::Time.now.to_f if (monitor = opts[:monitor])
|
1049
|
+
output = (r = converter opts).convert
|
1050
|
+
monitor[:convert] = ::Time.now.to_f - start if monitor
|
1051
|
+
|
1052
|
+
unless target.respond_to? :write
|
1053
|
+
@attributes['outfile'] = target = ::File.expand_path target
|
1054
|
+
@attributes['outdir'] = ::File.dirname target
|
1055
|
+
end
|
1056
|
+
|
1057
|
+
start = ::Time.now.to_f if monitor
|
1058
|
+
r.write output, target
|
1059
|
+
monitor[:write] = ::Time.now.to_f - start if monitor
|
1060
|
+
|
1061
|
+
output
|
1062
|
+
end
|
1063
|
+
=end
|
1064
|
+
|
784
1065
|
def content
|
785
|
-
# per AsciiDoc-spec, remove the title before
|
786
|
-
# regardless of whether the header is rendered)
|
1066
|
+
# NOTE per AsciiDoc-spec, remove the title before converting the body
|
787
1067
|
@attributes.delete('title')
|
788
1068
|
super
|
789
1069
|
end
|
@@ -814,22 +1094,28 @@ class Document < AbstractBlock
|
|
814
1094
|
|
815
1095
|
content = nil
|
816
1096
|
|
817
|
-
docinfo = @attributes.
|
818
|
-
docinfo1 = @attributes.
|
819
|
-
docinfo2 = @attributes.
|
1097
|
+
docinfo = @attributes.key?('docinfo')
|
1098
|
+
docinfo1 = @attributes.key?('docinfo1')
|
1099
|
+
docinfo2 = @attributes.key?('docinfo2')
|
820
1100
|
docinfo_filename = "docinfo#{qualifier}#{ext}"
|
821
1101
|
if docinfo1 || docinfo2
|
822
1102
|
docinfo_path = normalize_system_path(docinfo_filename)
|
823
1103
|
content = read_asset(docinfo_path)
|
824
|
-
|
1104
|
+
unless content.nil?
|
1105
|
+
# FIXME normalize these lines!
|
1106
|
+
content.force_encoding ::Encoding::UTF_8 if FORCE_ENCODING
|
1107
|
+
content = sub_attributes(content.split EOL) * EOL
|
1108
|
+
end
|
825
1109
|
end
|
826
1110
|
|
827
|
-
if (docinfo || docinfo2) && @attributes.
|
1111
|
+
if (docinfo || docinfo2) && @attributes.key?('docname')
|
828
1112
|
docinfo_path = normalize_system_path("#{@attributes['docname']}-#{docinfo_filename}")
|
829
1113
|
content2 = read_asset(docinfo_path)
|
830
1114
|
unless content2.nil?
|
831
|
-
|
832
|
-
|
1115
|
+
# FIXME normalize these lines!
|
1116
|
+
content2.force_encoding ::Encoding::UTF_8 if FORCE_ENCODING
|
1117
|
+
content2 = sub_attributes(content2.split EOL) * EOL
|
1118
|
+
content = content.nil? ? content2 : "#{content}#{EOL}#{content2}"
|
833
1119
|
end
|
834
1120
|
end
|
835
1121
|
|
@@ -839,7 +1125,7 @@ class Document < AbstractBlock
|
|
839
1125
|
end
|
840
1126
|
|
841
1127
|
def to_s
|
842
|
-
%
|
1128
|
+
%(#<#{self.class}@#{object_id} {doctype: #{doctype.inspect}, doctitle: #{(@header != nil ? @header.title : nil).inspect}, blocks: #{@blocks.size}}>)
|
843
1129
|
end
|
844
1130
|
|
845
1131
|
end
|