asciidoctor 2.0.6 → 2.0.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.adoc +159 -6
- data/LICENSE +2 -1
- data/README-de.adoc +5 -5
- data/README-fr.adoc +4 -4
- data/README-jp.adoc +248 -183
- data/README-zh_CN.adoc +6 -6
- data/README.adoc +17 -11
- data/asciidoctor.gemspec +8 -8
- data/data/locale/attributes-ar.adoc +4 -3
- data/data/locale/attributes-bg.adoc +4 -3
- data/data/locale/attributes-ca.adoc +6 -5
- data/data/locale/attributes-cs.adoc +4 -3
- data/data/locale/attributes-da.adoc +6 -5
- data/data/locale/attributes-de.adoc +4 -4
- data/data/locale/attributes-en.adoc +4 -4
- data/data/locale/attributes-es.adoc +6 -5
- data/data/locale/attributes-fa.adoc +4 -3
- data/data/locale/attributes-fi.adoc +4 -3
- data/data/locale/attributes-fr.adoc +6 -5
- data/data/locale/attributes-hu.adoc +4 -3
- data/data/locale/attributes-id.adoc +4 -3
- data/data/locale/attributes-it.adoc +4 -3
- data/data/locale/attributes-ja.adoc +4 -3
- data/data/locale/{attributes-kr.adoc → attributes-ko.adoc} +4 -3
- data/data/locale/attributes-nb.adoc +4 -3
- data/data/locale/attributes-nl.adoc +4 -3
- data/data/locale/attributes-nn.adoc +4 -3
- data/data/locale/attributes-pl.adoc +8 -7
- data/data/locale/attributes-pt.adoc +6 -5
- data/data/locale/attributes-pt_BR.adoc +6 -5
- data/data/locale/attributes-ro.adoc +4 -3
- data/data/locale/attributes-ru.adoc +6 -5
- data/data/locale/attributes-sr.adoc +4 -4
- data/data/locale/attributes-sr_Latn.adoc +4 -4
- data/data/locale/attributes-sv.adoc +4 -4
- data/data/locale/attributes-tr.adoc +4 -3
- data/data/locale/attributes-uk.adoc +6 -5
- data/data/locale/attributes-zh_CN.adoc +4 -3
- data/data/locale/attributes-zh_TW.adoc +4 -3
- data/data/stylesheets/asciidoctor-default.css +29 -26
- data/lib/asciidoctor.rb +94 -1098
- data/lib/asciidoctor/abstract_block.rb +19 -11
- data/lib/asciidoctor/abstract_node.rb +21 -15
- data/lib/asciidoctor/attribute_list.rb +59 -67
- data/lib/asciidoctor/cli/invoker.rb +2 -0
- data/lib/asciidoctor/cli/options.rb +8 -8
- data/lib/asciidoctor/convert.rb +198 -0
- data/lib/asciidoctor/converter.rb +14 -13
- data/lib/asciidoctor/converter/docbook5.rb +9 -25
- data/lib/asciidoctor/converter/html5.rb +65 -42
- data/lib/asciidoctor/converter/manpage.rb +13 -12
- data/lib/asciidoctor/converter/template.rb +6 -3
- data/lib/asciidoctor/document.rb +40 -48
- data/lib/asciidoctor/extensions.rb +3 -3
- data/lib/asciidoctor/helpers.rb +38 -39
- data/lib/asciidoctor/inline.rb +1 -1
- data/lib/asciidoctor/load.rb +117 -0
- data/lib/asciidoctor/parser.rb +29 -25
- data/lib/asciidoctor/path_resolver.rb +35 -25
- data/lib/asciidoctor/reader.rb +14 -7
- data/lib/asciidoctor/rx.rb +722 -0
- data/lib/asciidoctor/substitutors.rb +62 -40
- data/lib/asciidoctor/syntax_highlighter.rb +22 -8
- data/lib/asciidoctor/syntax_highlighter/coderay.rb +1 -1
- data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +12 -4
- data/lib/asciidoctor/syntax_highlighter/prettify.rb +7 -4
- data/lib/asciidoctor/syntax_highlighter/pygments.rb +2 -3
- data/lib/asciidoctor/syntax_highlighter/rouge.rb +18 -11
- data/lib/asciidoctor/table.rb +49 -20
- data/lib/asciidoctor/version.rb +1 -1
- data/man/asciidoctor.1 +17 -17
- data/man/asciidoctor.adoc +15 -14
- metadata +12 -9
@@ -38,12 +38,13 @@ class AbstractBlock < AbstractNode
|
|
38
38
|
@blocks = []
|
39
39
|
@subs = []
|
40
40
|
@id = @title = @caption = @numeral = @style = @default_subs = @source_location = nil
|
41
|
-
|
42
|
-
when :document, :section
|
41
|
+
if context == :document || context == :section
|
43
42
|
@level = @next_section_index = 0
|
44
43
|
@next_section_ordinal = 1
|
44
|
+
elsif AbstractBlock === parent
|
45
|
+
@level = parent.level
|
45
46
|
else
|
46
|
-
@level =
|
47
|
+
@level = nil
|
47
48
|
end
|
48
49
|
end
|
49
50
|
|
@@ -140,6 +141,11 @@ class AbstractBlock < AbstractNode
|
|
140
141
|
(Integer @numeral) rescue @numeral
|
141
142
|
end
|
142
143
|
|
144
|
+
# Deprecated: Legacy property to set the numeral of this section by coercing the value to a String.
|
145
|
+
def number= val
|
146
|
+
@numeral = val.to_s
|
147
|
+
end
|
148
|
+
|
143
149
|
# Public: Walk the document tree and find all block-level nodes that match the specified selector (context, style, id,
|
144
150
|
# role, and/or custom filter).
|
145
151
|
#
|
@@ -222,6 +228,8 @@ class AbstractBlock < AbstractNode
|
|
222
228
|
text = sub_specialchars text
|
223
229
|
(ReplaceableTextRx.match? text) ? (sub_replacements text) : text
|
224
230
|
end
|
231
|
+
else
|
232
|
+
''
|
225
233
|
end
|
226
234
|
end
|
227
235
|
|
@@ -335,17 +343,17 @@ class AbstractBlock < AbstractNode
|
|
335
343
|
if (val = reftext) && !val.empty?
|
336
344
|
val
|
337
345
|
# NOTE xrefstyle only applies to blocks with a title and a caption or number
|
338
|
-
elsif xrefstyle && @title &&
|
346
|
+
elsif xrefstyle && @title && !@caption.nil_or_empty?
|
339
347
|
case xrefstyle
|
340
348
|
when 'full'
|
341
349
|
quoted_title = sub_placeholder (sub_quotes @document.compat_mode ? %q(``%s'') : '"`%s`"'), title
|
342
|
-
if @numeral && (caption_attr_name =
|
350
|
+
if @numeral && (caption_attr_name = CAPTION_ATTRIBUTE_NAMES[@context]) && (prefix = @document.attributes[caption_attr_name])
|
343
351
|
%(#{prefix} #{@numeral}, #{quoted_title})
|
344
352
|
else
|
345
353
|
%(#{@caption.chomp '. '}, #{quoted_title})
|
346
354
|
end
|
347
355
|
when 'short'
|
348
|
-
if @numeral && (caption_attr_name =
|
356
|
+
if @numeral && (caption_attr_name = CAPTION_ATTRIBUTE_NAMES[@context]) && (prefix = @document.attributes[caption_attr_name])
|
349
357
|
%(#{prefix} #{@numeral})
|
350
358
|
else
|
351
359
|
@caption.chomp '. '
|
@@ -369,15 +377,15 @@ class AbstractBlock < AbstractNode
|
|
369
377
|
# The parts of a complete caption are: <prefix> <number>. <title>
|
370
378
|
# This partial caption represents the part the precedes the title.
|
371
379
|
#
|
372
|
-
# value - The
|
373
|
-
# caption_context - The Symbol context to use when resolving caption-related attributes.
|
374
|
-
#
|
380
|
+
# value - The String caption to assign to this block or nil to use document attribute.
|
381
|
+
# caption_context - The Symbol context to use when resolving caption-related attributes. If not provided, the name of
|
382
|
+
# the context for this block is used. Only certain contexts allow the caption to be looked up.
|
375
383
|
# (default: @context)
|
376
384
|
#
|
377
385
|
# Returns nothing.
|
378
|
-
def assign_caption value
|
386
|
+
def assign_caption value, caption_context = @context
|
379
387
|
unless @caption || !@title || (@caption = value || @document.attributes['caption'])
|
380
|
-
if (attr_name =
|
388
|
+
if (attr_name = CAPTION_ATTRIBUTE_NAMES[caption_context]) && (prefix = @document.attributes[attr_name])
|
381
389
|
@caption = %(#{prefix} #{@numeral = @document.increment_and_store_counter %(#{caption_context}-number), self}. )
|
382
390
|
nil
|
383
391
|
end
|
@@ -65,7 +65,7 @@ class AbstractNode
|
|
65
65
|
#
|
66
66
|
# parent - The Block to set as the parent of this Block
|
67
67
|
#
|
68
|
-
# Returns the
|
68
|
+
# Returns the value of the parent argument
|
69
69
|
def parent= parent
|
70
70
|
@parent, @document = parent, parent.document
|
71
71
|
end
|
@@ -161,10 +161,10 @@ class AbstractNode
|
|
161
161
|
nil
|
162
162
|
end
|
163
163
|
|
164
|
-
# Public: Retrieve the Set of option names that are
|
164
|
+
# Public: Retrieve the Set of option names that are enabled on this node
|
165
165
|
#
|
166
166
|
# Returns a [Set] of option names
|
167
|
-
def
|
167
|
+
def enabled_options
|
168
168
|
::Set.new.tap {|accum| @attributes.each_key {|k| accum << (k.slice 0, k.length - 7) if k.to_s.end_with? '-option' } }
|
169
169
|
end
|
170
170
|
|
@@ -216,6 +216,15 @@ class AbstractNode
|
|
216
216
|
(val = @attributes['role']) ? (%( #{val} ).include? %( #{name} )) : false
|
217
217
|
end
|
218
218
|
|
219
|
+
# Public: Sets the value of the role attribute on this ndoe.
|
220
|
+
#
|
221
|
+
# names - A single role name, a space-separated String of role names, or an Array of role names
|
222
|
+
#
|
223
|
+
# Returns the value of the names argument
|
224
|
+
def role= names
|
225
|
+
@attributes['role'] = (::Array === names) ? (names.join ' ') : names
|
226
|
+
end
|
227
|
+
|
219
228
|
# Public: Adds the given role directly to this node.
|
220
229
|
#
|
221
230
|
# Returns a [Boolean] indicating whether the role was added.
|
@@ -310,14 +319,10 @@ class AbstractNode
|
|
310
319
|
# Returns A String reference or data URI for the target image
|
311
320
|
def image_uri(target_image, asset_dir_key = 'imagesdir')
|
312
321
|
if (doc = @document).safe < SafeMode::SECURE && (doc.attr? 'data-uri')
|
313
|
-
if ((Helpers.uriish? target_image) && (target_image = Helpers.
|
322
|
+
if ((Helpers.uriish? target_image) && (target_image = Helpers.encode_spaces_in_uri target_image)) ||
|
314
323
|
(asset_dir_key && (images_base = doc.attr asset_dir_key) && (Helpers.uriish? images_base) &&
|
315
324
|
(target_image = normalize_web_path target_image, images_base, false))
|
316
|
-
|
317
|
-
generate_data_uri_from_uri target_image, (doc.attr? 'cache-uri')
|
318
|
-
else
|
319
|
-
target_image
|
320
|
-
end
|
325
|
+
(doc.attr? 'allow-uri-read') ? (generate_data_uri_from_uri target_image, (doc.attr? 'cache-uri')) : target_image
|
321
326
|
else
|
322
327
|
generate_data_uri target_image, asset_dir_key
|
323
328
|
end
|
@@ -474,7 +479,7 @@ class AbstractNode
|
|
474
479
|
# Returns the resolved [String] path
|
475
480
|
def normalize_web_path(target, start = nil, preserve_uri_target = true)
|
476
481
|
if preserve_uri_target && (Helpers.uriish? target)
|
477
|
-
Helpers.
|
482
|
+
Helpers.encode_spaces_in_uri target
|
478
483
|
else
|
479
484
|
@document.path_resolver.web_path target, start
|
480
485
|
end
|
@@ -518,6 +523,7 @@ class AbstractNode
|
|
518
523
|
# * :normalize a Boolean that indicates whether the data should be normalized (default: false)
|
519
524
|
# * :start the String relative base path to use when resolving the target (default: nil)
|
520
525
|
# * :warn_on_failure a Boolean that indicates whether warnings are issued if the target cannot be read (default: true)
|
526
|
+
# * :warn_if_empty a Boolean that indicates whether a warning is issued if contents of target is empty (default: false)
|
521
527
|
# Returns the contents of the resolved target or nil if the resolved target cannot be read
|
522
528
|
# --
|
523
529
|
# TODO refactor other methods in this class to use this method were possible (repurposing if necessary)
|
@@ -529,22 +535,22 @@ class AbstractNode
|
|
529
535
|
Helpers.require_library 'open-uri/cached', 'open-uri-cached' if doc.attr? 'cache-uri'
|
530
536
|
begin
|
531
537
|
if opts[:normalize]
|
532
|
-
(Helpers.prepare_source_string ::OpenURI.open_uri(target, URI_READ_MODE) {|f| f.read }).join LF
|
538
|
+
contents = (Helpers.prepare_source_string ::OpenURI.open_uri(target, URI_READ_MODE) {|f| f.read }).join LF
|
533
539
|
else
|
534
|
-
::OpenURI.open_uri(target, URI_READ_MODE) {|f| f.read }
|
540
|
+
contents = ::OpenURI.open_uri(target, URI_READ_MODE) {|f| f.read }
|
535
541
|
end
|
536
542
|
rescue
|
537
543
|
logger.warn %(could not retrieve contents of #{opts[:label] || 'asset'} at URI: #{target}) if opts.fetch :warn_on_failure, true
|
538
|
-
return
|
539
544
|
end
|
540
545
|
else
|
541
546
|
logger.warn %(cannot retrieve contents of #{opts[:label] || 'asset'} at URI: #{target} (allow-uri-read attribute not enabled)) if opts.fetch :warn_on_failure, true
|
542
|
-
return
|
543
547
|
end
|
544
548
|
else
|
545
549
|
target = normalize_system_path target, opts[:start], nil, target_name: (opts[:label] || 'asset')
|
546
|
-
read_asset target, normalize: opts[:normalize], warn_on_failure: (opts.fetch :warn_on_failure, true), label: opts[:label]
|
550
|
+
contents = read_asset target, normalize: opts[:normalize], warn_on_failure: (opts.fetch :warn_on_failure, true), label: opts[:label]
|
547
551
|
end
|
552
|
+
logger.warn %(contents of #{opts[:label] || 'asset'} is empty: #{target}) if contents && opts[:warn_if_empty] && contents.empty?
|
553
|
+
contents
|
548
554
|
end
|
549
555
|
|
550
556
|
# Deprecated: Check whether the specified String is a URI by
|
@@ -22,19 +22,20 @@ module Asciidoctor
|
|
22
22
|
# => { 'style' => 'quote', 'attribution' => 'Famous Person', 'citetitle' => 'Famous Book (2001)' }
|
23
23
|
#
|
24
24
|
class AttributeList
|
25
|
-
BACKSLASH = '\\'
|
26
25
|
APOS = '\''
|
26
|
+
BACKSLASH = '\\'
|
27
|
+
QUOT = '"'
|
27
28
|
|
28
29
|
# Public: Regular expressions for detecting the boundary of a value
|
29
30
|
BoundaryRxs = {
|
30
|
-
|
31
|
+
QUOT => /.*?[^\\](?=")/,
|
31
32
|
APOS => /.*?[^\\](?=')/,
|
32
33
|
',' => /.*?(?=[ \t]*(,|$))/
|
33
34
|
}
|
34
35
|
|
35
36
|
# Public: Regular expressions for unescaping quoted characters
|
36
37
|
EscapedQuotes = {
|
37
|
-
|
38
|
+
QUOT => '\\"',
|
38
39
|
APOS => '\\\''
|
39
40
|
}
|
40
41
|
|
@@ -45,7 +46,9 @@ class AttributeList
|
|
45
46
|
BlankRx = /[ \t]+/
|
46
47
|
|
47
48
|
# Public: Regular expressions for skipping delimiters
|
48
|
-
SkipRxs = {
|
49
|
+
SkipRxs = {
|
50
|
+
',' => /[ \t]*(,|$)/
|
51
|
+
}
|
49
52
|
|
50
53
|
def initialize source, block = nil, delimiter = ','
|
51
54
|
@scanner = ::StringScanner.new source
|
@@ -65,8 +68,6 @@ class AttributeList
|
|
65
68
|
return @attributes if @attributes
|
66
69
|
|
67
70
|
@attributes = {}
|
68
|
-
# QUESTION do we want to store the attribute list as the zero-index attribute?
|
69
|
-
#attributes[0] = @scanner.string
|
70
71
|
index = 0
|
71
72
|
|
72
73
|
while parse_attribute index, positional_attrs
|
@@ -83,75 +84,73 @@ class AttributeList
|
|
83
84
|
end
|
84
85
|
|
85
86
|
def self.rekey attributes, positional_attrs
|
86
|
-
|
87
|
-
|
88
|
-
index += 1
|
89
|
-
if (val = attributes[index])
|
87
|
+
positional_attrs.each_with_index do |key, index|
|
88
|
+
if key && (val = attributes[index + 1])
|
90
89
|
# QUESTION should we delete the positional key?
|
91
90
|
attributes[key] = val
|
92
|
-
end
|
91
|
+
end
|
93
92
|
end
|
94
93
|
attributes
|
95
94
|
end
|
96
95
|
|
97
96
|
private
|
98
97
|
|
99
|
-
def parse_attribute index
|
100
|
-
|
98
|
+
def parse_attribute index, positional_attrs
|
99
|
+
continue = true
|
101
100
|
skip_blank
|
102
|
-
|
103
|
-
|
101
|
+
case @scanner.peek 1
|
102
|
+
# example: "quote" || "foo
|
103
|
+
when QUOT
|
104
104
|
name = parse_attribute_value @scanner.get_byte
|
105
|
-
|
106
|
-
|
107
|
-
elsif first == APOS
|
105
|
+
# example: 'quote' || 'foo
|
106
|
+
when APOS
|
108
107
|
name = parse_attribute_value @scanner.get_byte
|
109
|
-
|
110
|
-
single_quoted_value = true unless name.start_with? APOS
|
108
|
+
single_quoted = true unless name.start_with? APOS
|
111
109
|
else
|
112
|
-
name = scan_name
|
113
|
-
|
114
|
-
skipped = 0
|
115
|
-
c = nil
|
110
|
+
skipped = ((name = scan_name) && skip_blank) || 0
|
116
111
|
if @scanner.eos?
|
117
|
-
return
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
else
|
131
|
-
skip_blank
|
132
|
-
if @scanner.peek(1)
|
133
|
-
# example: foo="bar" || foo="ba\"zaar"
|
134
|
-
if (c = @scanner.get_byte) == '"'
|
112
|
+
return unless name || (@scanner.string.rstrip.end_with? @delimiter)
|
113
|
+
# example: quote (at eos)
|
114
|
+
continue = nil
|
115
|
+
# example: quote,
|
116
|
+
elsif (c = @scanner.get_byte) == @delimiter
|
117
|
+
@scanner.unscan
|
118
|
+
elsif name
|
119
|
+
# example: foo=...
|
120
|
+
if c == '='
|
121
|
+
skip_blank
|
122
|
+
case (c = @scanner.get_byte)
|
123
|
+
# example: foo="bar" || foo="ba\"zaar" || foo="bar
|
124
|
+
when QUOT
|
135
125
|
value = parse_attribute_value c
|
136
|
-
# example: foo='bar' || foo='ba\'zaar' || foo='ba"zaar'
|
137
|
-
|
126
|
+
# example: foo='bar' || foo='ba\'zaar' || foo='ba"zaar' || foo='bar
|
127
|
+
when APOS
|
138
128
|
value = parse_attribute_value c
|
139
|
-
|
129
|
+
single_quoted = true unless value.start_with? APOS
|
140
130
|
# example: foo=,
|
141
|
-
|
131
|
+
when @delimiter
|
132
|
+
value = ''
|
133
|
+
@scanner.unscan
|
134
|
+
# example: foo= (at eos)
|
135
|
+
when nil
|
142
136
|
value = ''
|
143
|
-
# example: foo=bar
|
137
|
+
# example: foo=bar || foo=None
|
144
138
|
else
|
145
139
|
value = %(#{c}#{scan_to_delimiter})
|
146
140
|
return true if value == 'None'
|
147
141
|
end
|
142
|
+
# example: foo bar
|
143
|
+
else
|
144
|
+
name = %(#{name}#{' ' * skipped}#{c}#{scan_to_delimiter})
|
148
145
|
end
|
146
|
+
# example: =foo= || !foo
|
147
|
+
else
|
148
|
+
name = %(#{c}#{scan_to_delimiter})
|
149
149
|
end
|
150
150
|
end
|
151
151
|
|
152
152
|
if value
|
153
|
-
# example: options="opt1,opt2,opt3"
|
154
|
-
# opts is an alias for options
|
153
|
+
# example: options="opt1,opt2,opt3" || opts="opts1,opt2,opt3"
|
155
154
|
case name
|
156
155
|
when 'options', 'opts'
|
157
156
|
if value.include? ','
|
@@ -161,7 +160,7 @@ class AttributeList
|
|
161
160
|
@attributes[%(#{value}-option)] = '' unless value.empty?
|
162
161
|
end
|
163
162
|
else
|
164
|
-
if
|
163
|
+
if single_quoted && @block
|
165
164
|
case name
|
166
165
|
when 'title', 'reftext'
|
167
166
|
@attributes[name] = value
|
@@ -173,33 +172,26 @@ class AttributeList
|
|
173
172
|
end
|
174
173
|
end
|
175
174
|
else
|
176
|
-
|
175
|
+
name = @block.apply_subs name if single_quoted && @block
|
177
176
|
if (positional_attr_name = positional_attrs[index])
|
178
|
-
@attributes[positional_attr_name] =
|
177
|
+
@attributes[positional_attr_name] = name
|
179
178
|
end
|
180
|
-
# QUESTION should we
|
181
|
-
@attributes[index + 1] =
|
182
|
-
# QUESTION should we assign the resolved name as an attribute?
|
183
|
-
#@attributes[resolved_name] = nil
|
179
|
+
# QUESTION should we assign the positional key even when it's claimed by a positional attribute?
|
180
|
+
@attributes[index + 1] = name
|
184
181
|
end
|
185
182
|
|
186
|
-
|
183
|
+
continue
|
187
184
|
end
|
188
185
|
|
189
186
|
def parse_attribute_value quote
|
190
187
|
# empty quoted value
|
191
|
-
if @scanner.peek
|
188
|
+
if (@scanner.peek 1) == quote
|
192
189
|
@scanner.get_byte
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
if (value = scan_to_quote quote)
|
190
|
+
''
|
191
|
+
elsif (value = scan_to_quote quote)
|
197
192
|
@scanner.get_byte
|
198
|
-
|
199
|
-
|
200
|
-
else
|
201
|
-
value
|
202
|
-
end
|
193
|
+
(value.include? BACKSLASH) ? (value.gsub EscapedQuotes[quote], quote) : value
|
194
|
+
# leading quote only
|
203
195
|
else
|
204
196
|
%(#{quote}#{scan_to_delimiter})
|
205
197
|
end
|
@@ -42,6 +42,8 @@ module Asciidoctor
|
|
42
42
|
non_posix_env = ::File::ALT_SEPARATOR == RS
|
43
43
|
err = @err || $stderr
|
44
44
|
show_timings = false
|
45
|
+
# NOTE in Ruby 2.7, RubyGems sets SOURCE_DATE_EPOCH if it's not set
|
46
|
+
::ENV.delete 'SOURCE_DATE_EPOCH' if (::ENV.key? 'IGNORE_SOURCE_DATE_EPOCH') && (::Gem.respond_to? :source_date_epoch)
|
45
47
|
|
46
48
|
@options.map do |key, val|
|
47
49
|
case key
|
@@ -76,17 +76,17 @@ module Asciidoctor
|
|
76
76
|
opts.on('-n', '--section-numbers', 'auto-number section titles in the HTML backend; disabled by default') do
|
77
77
|
self[:attributes]['sectnums'] = ''
|
78
78
|
end
|
79
|
-
opts.on('--eruby ERUBY', ['erb', 'erubis'],
|
80
|
-
'specify eRuby implementation to use when rendering custom ERB templates: [erb, erubis] (default: erb)') do |eruby|
|
79
|
+
opts.on('--eruby ERUBY', ['erb', 'erubi', 'erubis'],
|
80
|
+
'specify eRuby implementation to use when rendering custom ERB templates: [erb, erubi, erubis] (default: erb)') do |eruby|
|
81
81
|
self[:eruby] = eruby
|
82
82
|
end
|
83
|
-
opts.on('-a', '--attribute
|
84
|
-
'
|
85
|
-
'
|
83
|
+
opts.on('-a', '--attribute name[=value]', 'a document attribute to set in the form of name, name!, or name=value pair',
|
84
|
+
'this attribute takes precedence over the same attribute defined in the source document',
|
85
|
+
'unless either the name or value ends in @ (i.e., name@=value or name=value@)') do |attr|
|
86
86
|
next if (attr = attr.rstrip).empty? || attr == '='
|
87
87
|
attr = attr.encode UTF_8 unless attr.encoding == UTF_8
|
88
|
-
|
89
|
-
self[:attributes][
|
88
|
+
name, _, val = attr.partition '='
|
89
|
+
self[:attributes][name] = val
|
90
90
|
end
|
91
91
|
opts.on('-T', '--template-dir DIR', 'a directory containing custom converter templates that override the built-in converter (requires tilt gem)',
|
92
92
|
'may be specified multiple times') do |template_dir|
|
@@ -205,7 +205,7 @@ module Asciidoctor
|
|
205
205
|
# shave off the file to process so that options errors appear correctly
|
206
206
|
if args.size == 1 && args[0] == '-'
|
207
207
|
infiles << args.pop
|
208
|
-
|
208
|
+
else
|
209
209
|
args.each do |file|
|
210
210
|
if file.start_with? '-'
|
211
211
|
# warn, but don't panic; we may have enough to proceed, so we won't force a failure
|
@@ -0,0 +1,198 @@
|
|
1
|
+
module Asciidoctor
|
2
|
+
class << self
|
3
|
+
# Public: Parse the AsciiDoc source input into an Asciidoctor::Document and
|
4
|
+
# convert it to the specified backend format.
|
5
|
+
#
|
6
|
+
# Accepts input as an IO (or StringIO), String or String Array object. If the
|
7
|
+
# input is a File, the object is expected to be opened for reading and is not
|
8
|
+
# closed afterwards by this method. Information about the file (filename,
|
9
|
+
# directory name, etc) gets assigned to attributes on the Document object.
|
10
|
+
#
|
11
|
+
# If the :to_file option is true, and the input is a File, the output is
|
12
|
+
# written to a file adjacent to the input file, having an extension that
|
13
|
+
# corresponds to the backend format. Otherwise, if the :to_file option is
|
14
|
+
# specified, the file is written to that file. If :to_file is not an absolute
|
15
|
+
# path, it is resolved relative to :to_dir, if given, otherwise the
|
16
|
+
# Document#base_dir. If the target directory does not exist, it will not be
|
17
|
+
# created unless the :mkdirs option is set to true. If the file cannot be
|
18
|
+
# written because the target directory does not exist, or because it falls
|
19
|
+
# outside of the Document#base_dir in safe mode, an IOError is raised.
|
20
|
+
#
|
21
|
+
# If the output is going to be written to a file, the header and footer are
|
22
|
+
# included unless specified otherwise (writing to a file implies creating a
|
23
|
+
# standalone document). Otherwise, the header and footer are not included by
|
24
|
+
# default and the converted result is returned.
|
25
|
+
#
|
26
|
+
# input - the String AsciiDoc source filename
|
27
|
+
# options - a String, Array or Hash of options to control processing (default: {})
|
28
|
+
# String and Array values are converted into a Hash.
|
29
|
+
# See Asciidoctor::Document#initialize for details about options.
|
30
|
+
#
|
31
|
+
# Returns the Document object if the converted String is written to a
|
32
|
+
# file, otherwise the converted String
|
33
|
+
def convert input, options = {}
|
34
|
+
(options = options.merge).delete :parse
|
35
|
+
to_dir = options.delete :to_dir
|
36
|
+
mkdirs = options.delete :mkdirs
|
37
|
+
|
38
|
+
case (to_file = options.delete :to_file)
|
39
|
+
when true, nil
|
40
|
+
unless (write_to_target = to_dir)
|
41
|
+
sibling_path = ::File.absolute_path input.path if ::File === input
|
42
|
+
end
|
43
|
+
to_file = nil
|
44
|
+
when false
|
45
|
+
to_file = nil
|
46
|
+
when '/dev/null'
|
47
|
+
return load input, options
|
48
|
+
else
|
49
|
+
options[:to_file] = write_to_target = to_file unless (stream_output = to_file.respond_to? :write)
|
50
|
+
end
|
51
|
+
|
52
|
+
unless options.key? :standalone
|
53
|
+
if sibling_path || write_to_target
|
54
|
+
options[:standalone] = options.fetch :header_footer, true
|
55
|
+
elsif options.key? :header_footer
|
56
|
+
options[:standalone] = options[:header_footer]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# NOTE outfile may be controlled by document attributes, so resolve outfile after loading
|
61
|
+
if sibling_path
|
62
|
+
options[:to_dir] = outdir = ::File.dirname sibling_path
|
63
|
+
elsif write_to_target
|
64
|
+
if to_dir
|
65
|
+
if to_file
|
66
|
+
options[:to_dir] = ::File.dirname ::File.expand_path to_file, to_dir
|
67
|
+
else
|
68
|
+
options[:to_dir] = ::File.expand_path to_dir
|
69
|
+
end
|
70
|
+
elsif to_file
|
71
|
+
options[:to_dir] = ::File.dirname ::File.expand_path to_file
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# NOTE :to_dir is always set when outputting to a file
|
76
|
+
# NOTE :to_file option only passed if assigned an explicit path
|
77
|
+
doc = load input, options
|
78
|
+
|
79
|
+
if sibling_path # write to file in same directory
|
80
|
+
outfile = ::File.join outdir, %(#{doc.attributes['docname']}#{doc.outfilesuffix})
|
81
|
+
raise ::IOError, %(input file and output file cannot be the same: #{outfile}) if outfile == sibling_path
|
82
|
+
elsif write_to_target # write to explicit file or directory
|
83
|
+
working_dir = (options.key? :base_dir) ? (::File.expand_path options[:base_dir]) : ::Dir.pwd
|
84
|
+
# QUESTION should the jail be the working_dir or doc.base_dir???
|
85
|
+
jail = doc.safe >= SafeMode::SAFE ? working_dir : nil
|
86
|
+
if to_dir
|
87
|
+
outdir = doc.normalize_system_path(to_dir, working_dir, jail, target_name: 'to_dir', recover: false)
|
88
|
+
if to_file
|
89
|
+
outfile = doc.normalize_system_path(to_file, outdir, nil, target_name: 'to_dir', recover: false)
|
90
|
+
# reestablish outdir as the final target directory (in the case to_file had directory segments)
|
91
|
+
outdir = ::File.dirname outfile
|
92
|
+
else
|
93
|
+
outfile = ::File.join outdir, %(#{doc.attributes['docname']}#{doc.outfilesuffix})
|
94
|
+
end
|
95
|
+
elsif to_file
|
96
|
+
outfile = doc.normalize_system_path(to_file, working_dir, jail, target_name: 'to_dir', recover: false)
|
97
|
+
# establish outdir as the final target directory (in the case to_file had directory segments)
|
98
|
+
outdir = ::File.dirname outfile
|
99
|
+
end
|
100
|
+
|
101
|
+
if ::File === input && outfile == (::File.absolute_path input.path)
|
102
|
+
raise ::IOError, %(input file and output file cannot be the same: #{outfile})
|
103
|
+
end
|
104
|
+
|
105
|
+
if mkdirs
|
106
|
+
Helpers.mkdir_p outdir
|
107
|
+
else
|
108
|
+
# NOTE we intentionally refer to the directory as it was passed to the API
|
109
|
+
raise ::IOError, %(target directory does not exist: #{to_dir} (hint: set :mkdirs option)) unless ::File.directory? outdir
|
110
|
+
end
|
111
|
+
else # write to stream
|
112
|
+
outfile = to_file
|
113
|
+
outdir = nil
|
114
|
+
end
|
115
|
+
|
116
|
+
if outfile && !stream_output
|
117
|
+
output = doc.convert 'outfile' => outfile, 'outdir' => outdir
|
118
|
+
else
|
119
|
+
output = doc.convert
|
120
|
+
end
|
121
|
+
|
122
|
+
if outfile
|
123
|
+
doc.write output, outfile
|
124
|
+
|
125
|
+
# NOTE document cannot control this behavior if safe >= SafeMode::SERVER
|
126
|
+
# NOTE skip if stylesdir is a URI
|
127
|
+
if !stream_output && doc.safe < SafeMode::SECURE && (doc.attr? 'linkcss') && (doc.attr? 'copycss') &&
|
128
|
+
(doc.basebackend? 'html') && !((stylesdir = (doc.attr 'stylesdir')) && (Helpers.uriish? stylesdir))
|
129
|
+
if (stylesheet = doc.attr 'stylesheet')
|
130
|
+
if DEFAULT_STYLESHEET_KEYS.include? stylesheet
|
131
|
+
copy_asciidoctor_stylesheet = true
|
132
|
+
elsif !(Helpers.uriish? stylesheet)
|
133
|
+
copy_user_stylesheet = true
|
134
|
+
end
|
135
|
+
end
|
136
|
+
copy_syntax_hl_stylesheet = (syntax_hl = doc.syntax_highlighter) && (syntax_hl.write_stylesheet? doc)
|
137
|
+
if copy_asciidoctor_stylesheet || copy_user_stylesheet || copy_syntax_hl_stylesheet
|
138
|
+
stylesoutdir = doc.normalize_system_path(stylesdir, outdir, doc.safe >= SafeMode::SAFE ? outdir : nil)
|
139
|
+
if mkdirs
|
140
|
+
Helpers.mkdir_p stylesoutdir
|
141
|
+
else
|
142
|
+
raise ::IOError, %(target stylesheet directory does not exist: #{stylesoutdir} (hint: set :mkdirs option)) unless ::File.directory? stylesoutdir
|
143
|
+
end
|
144
|
+
|
145
|
+
if copy_asciidoctor_stylesheet
|
146
|
+
Stylesheets.instance.write_primary_stylesheet stylesoutdir
|
147
|
+
# FIXME should Stylesheets also handle the user stylesheet?
|
148
|
+
elsif copy_user_stylesheet
|
149
|
+
if (stylesheet_src = doc.attr 'copycss') == '' || stylesheet_src == true
|
150
|
+
stylesheet_src = doc.normalize_system_path stylesheet
|
151
|
+
else
|
152
|
+
# NOTE in this case, copycss is a source location (but cannot be a URI)
|
153
|
+
stylesheet_src = doc.normalize_system_path stylesheet_src.to_s
|
154
|
+
end
|
155
|
+
stylesheet_dest = doc.normalize_system_path stylesheet, stylesoutdir, (doc.safe >= SafeMode::SAFE ? outdir : nil)
|
156
|
+
# NOTE don't warn if src can't be read and dest already exists (see #2323)
|
157
|
+
if stylesheet_src != stylesheet_dest && (stylesheet_data = doc.read_asset stylesheet_src,
|
158
|
+
warn_on_failure: !(::File.file? stylesheet_dest), label: 'stylesheet')
|
159
|
+
if (stylesheet_outdir = ::File.dirname stylesheet_dest) != stylesoutdir && !(::File.directory? stylesheet_outdir)
|
160
|
+
if mkdirs
|
161
|
+
Helpers.mkdir_p stylesheet_outdir
|
162
|
+
else
|
163
|
+
raise ::IOError, %(target stylesheet directory does not exist: #{stylesheet_outdir} (hint: set :mkdirs option))
|
164
|
+
end
|
165
|
+
end
|
166
|
+
::File.write stylesheet_dest, stylesheet_data, mode: FILE_WRITE_MODE
|
167
|
+
end
|
168
|
+
end
|
169
|
+
syntax_hl.write_stylesheet doc, stylesoutdir if copy_syntax_hl_stylesheet
|
170
|
+
end
|
171
|
+
end
|
172
|
+
doc
|
173
|
+
else
|
174
|
+
output
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Public: Parse the contents of the AsciiDoc source file into an
|
179
|
+
# Asciidoctor::Document and convert it to the specified backend format.
|
180
|
+
#
|
181
|
+
# input - the String AsciiDoc source filename
|
182
|
+
# options - a String, Array or Hash of options to control processing (default: {})
|
183
|
+
# String and Array values are converted into a Hash.
|
184
|
+
# See Asciidoctor::Document#initialize for details about options.
|
185
|
+
#
|
186
|
+
# Returns the Document object if the converted String is written to a
|
187
|
+
# file, otherwise the converted String
|
188
|
+
def convert_file filename, options = {}
|
189
|
+
::File.open(filename, FILE_READ_MODE) {|file| convert file, options }
|
190
|
+
end
|
191
|
+
|
192
|
+
# Deprecated: Use {Asciidoctor.convert} instead.
|
193
|
+
alias render convert
|
194
|
+
|
195
|
+
# Deprecated: Use {Asciidoctor.convert_file} instead.
|
196
|
+
alias render_file convert_file
|
197
|
+
end
|
198
|
+
end
|