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
@@ -102,9 +102,11 @@ module Asciidoctor
|
|
102
102
|
class PathResolver
|
103
103
|
DOT = '.'
|
104
104
|
DOT_DOT = '..'
|
105
|
+
DOT_SLASH = './'
|
105
106
|
SLASH = '/'
|
106
107
|
BACKSLASH = '\\'
|
107
|
-
|
108
|
+
DOUBLE_SLASH = '//'
|
109
|
+
WindowsRootRx = /^[a-zA-Z]:(?:\\|\/)/
|
108
110
|
|
109
111
|
attr_accessor :file_separator
|
110
112
|
attr_accessor :working_dir
|
@@ -115,16 +117,18 @@ class PathResolver
|
|
115
117
|
# expanded to an absolute path inside the constructor.
|
116
118
|
#
|
117
119
|
# file_separator - the String file separator to use for path operations
|
118
|
-
# (optional, default: File::
|
120
|
+
# (optional, default: File::SEPARATOR)
|
119
121
|
# working_dir - the String working directory (optional, default: Dir.pwd)
|
120
122
|
#
|
121
|
-
def initialize
|
122
|
-
@file_separator = file_separator
|
123
|
-
if working_dir
|
124
|
-
@working_dir = File.expand_path
|
123
|
+
def initialize file_separator = nil, working_dir = nil
|
124
|
+
@file_separator = file_separator ? file_separator : (::File::ALT_SEPARATOR || ::File::SEPARATOR)
|
125
|
+
if working_dir
|
126
|
+
@working_dir = (is_root? working_dir) ? working_dir : (::File.expand_path working_dir)
|
125
127
|
else
|
126
|
-
@working_dir =
|
128
|
+
@working_dir = ::File.expand_path ::Dir.pwd
|
127
129
|
end
|
130
|
+
@_partition_path_sys = {}
|
131
|
+
@_partition_path_web = {}
|
128
132
|
end
|
129
133
|
|
130
134
|
# Public: Check if the specified path is an absolute root path
|
@@ -133,22 +137,33 @@ class PathResolver
|
|
133
137
|
# path - the String path to check
|
134
138
|
#
|
135
139
|
# returns a Boolean indicating whether the path is an absolute root path
|
136
|
-
def is_root?
|
137
|
-
|
140
|
+
def is_root? path
|
141
|
+
# Unix absolute paths and UNC paths start with slash
|
142
|
+
if path.start_with? SLASH
|
138
143
|
true
|
139
|
-
|
144
|
+
# Windows roots can begin with drive letter
|
145
|
+
elsif @file_separator == BACKSLASH && WindowsRootRx =~ path
|
140
146
|
true
|
141
147
|
else
|
142
148
|
false
|
143
149
|
end
|
144
150
|
end
|
145
151
|
|
152
|
+
# Public: Determine if the path is a UNC (root) path
|
153
|
+
#
|
154
|
+
# path - the String path to check
|
155
|
+
#
|
156
|
+
# returns a Boolean indicating whether the path is a UNC path
|
157
|
+
def is_unc? path
|
158
|
+
path.start_with? DOUBLE_SLASH
|
159
|
+
end
|
160
|
+
|
146
161
|
# Public: Determine if the path is an absolute (root) web path
|
147
162
|
#
|
148
163
|
# path - the String path to check
|
149
164
|
#
|
150
165
|
# returns a Boolean indicating whether the path is an absolute (root) web path
|
151
|
-
def is_web_root?
|
166
|
+
def is_web_root? path
|
152
167
|
path.start_with? SLASH
|
153
168
|
end
|
154
169
|
|
@@ -157,9 +172,14 @@ class PathResolver
|
|
157
172
|
# path - the String path to normalize
|
158
173
|
#
|
159
174
|
# returns a String path with any backslashes replaced with forward slashes
|
160
|
-
def posixfy
|
161
|
-
|
162
|
-
|
175
|
+
def posixfy path
|
176
|
+
if path.nil_or_empty?
|
177
|
+
''
|
178
|
+
elsif path.include? BACKSLASH
|
179
|
+
path.tr BACKSLASH, SLASH
|
180
|
+
else
|
181
|
+
path
|
182
|
+
end
|
163
183
|
end
|
164
184
|
|
165
185
|
# Public: Expand the path by resolving any parent references (..)
|
@@ -173,32 +193,76 @@ class PathResolver
|
|
173
193
|
# path - the String path to expand
|
174
194
|
#
|
175
195
|
# returns a String path with any parent or self references resolved.
|
176
|
-
def expand_path
|
177
|
-
path_segments, path_root, _ = partition_path
|
196
|
+
def expand_path path
|
197
|
+
path_segments, path_root, _ = partition_path path
|
178
198
|
join_path path_segments, path_root
|
179
199
|
end
|
180
200
|
|
181
201
|
# Public: Partition the path into path segments and remove any empty segments
|
182
|
-
# or segments that are self references (.). The path is
|
183
|
-
#
|
202
|
+
# or segments that are self references (.). The path is converted to a posix
|
203
|
+
# path before being partitioned.
|
184
204
|
#
|
185
205
|
# path - the String path to partition
|
186
206
|
# web_path - a Boolean indicating whether the path should be handled
|
187
207
|
# as a web path (optional, default: false)
|
188
208
|
#
|
189
|
-
#
|
190
|
-
# path root, if the path is absolute
|
191
|
-
|
209
|
+
# Returns a 3-item Array containing the Array of String path segments, the
|
210
|
+
# path root (e.g., '/', './', 'c:/') if the path is absolute and the posix
|
211
|
+
# version of the path.
|
212
|
+
#--
|
213
|
+
# QUESTION is it worth it to normalize slashes? it doubles the time elapsed
|
214
|
+
def partition_path path, web_path = false
|
215
|
+
if (result = web_path ? @_partition_path_web[path] : @_partition_path_sys[path])
|
216
|
+
return result
|
217
|
+
end
|
218
|
+
|
192
219
|
posix_path = posixfy path
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
220
|
+
|
221
|
+
root = if web_path
|
222
|
+
# ex. /sample/path
|
223
|
+
if is_web_root? posix_path
|
224
|
+
SLASH
|
225
|
+
# ex. ./sample/path
|
226
|
+
elsif posix_path.start_with? DOT_SLASH
|
227
|
+
DOT_SLASH
|
228
|
+
# ex. sample/path
|
229
|
+
else
|
230
|
+
nil
|
231
|
+
end
|
232
|
+
else
|
233
|
+
if is_root? posix_path
|
234
|
+
# ex. //sample/path
|
235
|
+
if is_unc? posix_path
|
236
|
+
DOUBLE_SLASH
|
237
|
+
# ex. /sample/path
|
238
|
+
elsif posix_path.start_with? SLASH
|
239
|
+
SLASH
|
240
|
+
# ex. c:/sample/path
|
241
|
+
else
|
242
|
+
posix_path[0..(posix_path.index SLASH)]
|
243
|
+
end
|
244
|
+
# ex. ./sample/path
|
245
|
+
elsif posix_path.start_with? DOT_SLASH
|
246
|
+
DOT_SLASH
|
247
|
+
# ex. sample/path
|
248
|
+
else
|
249
|
+
nil
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
path_segments = posix_path.split SLASH
|
254
|
+
# shift twice for a UNC path
|
255
|
+
if root == DOUBLE_SLASH
|
256
|
+
path_segments = path_segments[2..-1]
|
257
|
+
# shift once for any other root
|
258
|
+
elsif root
|
259
|
+
path_segments.shift
|
260
|
+
end
|
261
|
+
# strip out all dot entries
|
262
|
+
path_segments.delete DOT
|
263
|
+
# QUESTION should we chomp trailing /? (we pay a small fraction)
|
264
|
+
#posix_path = posix_path.chomp '/'
|
265
|
+
(web_path ? @_partition_path_web : @_partition_path_sys)[path] = [path_segments, root, posix_path]
|
202
266
|
end
|
203
267
|
|
204
268
|
# Public: Join the segments using the posix file separator (since Ruby knows
|
@@ -211,9 +275,9 @@ class PathResolver
|
|
211
275
|
#
|
212
276
|
# returns a String path formed by joining the segments using the posix file
|
213
277
|
# separator and prepending the root, if specified
|
214
|
-
def join_path
|
278
|
+
def join_path segments, root = nil
|
215
279
|
if root
|
216
|
-
|
280
|
+
%(#{root}#{segments * SLASH})
|
217
281
|
else
|
218
282
|
segments * SLASH
|
219
283
|
end
|
@@ -237,68 +301,68 @@ class PathResolver
|
|
237
301
|
# returns a String path that joins the target path with the start path with
|
238
302
|
# any parent references resolved and self references removed and enforces
|
239
303
|
# that the resolved path be contained within the jail, if provided
|
240
|
-
def system_path
|
241
|
-
recover = opts.fetch
|
242
|
-
|
304
|
+
def system_path target, start, jail = nil, opts = {}
|
305
|
+
recover = opts.fetch :recover, true
|
306
|
+
if jail
|
243
307
|
unless is_root? jail
|
244
|
-
raise SecurityError,
|
308
|
+
raise ::SecurityError, %(Jail is not an absolute path: #{jail})
|
245
309
|
end
|
246
310
|
jail = posixfy jail
|
247
311
|
end
|
248
312
|
|
249
|
-
if target.
|
313
|
+
if target.nil_or_empty?
|
250
314
|
target_segments = []
|
251
315
|
else
|
252
|
-
target_segments, target_root, _ = partition_path
|
316
|
+
target_segments, target_root, _ = partition_path target
|
253
317
|
end
|
254
318
|
|
255
319
|
if target_segments.empty?
|
256
|
-
if start.
|
257
|
-
return jail
|
320
|
+
if start.nil_or_empty?
|
321
|
+
return jail ? jail : @working_dir
|
258
322
|
elsif is_root? start
|
259
|
-
|
323
|
+
unless jail
|
260
324
|
return expand_path start
|
261
325
|
end
|
262
326
|
else
|
263
|
-
return system_path
|
327
|
+
return system_path start, jail, jail
|
264
328
|
end
|
265
329
|
end
|
266
330
|
|
267
|
-
if target_root && target_root !=
|
331
|
+
if target_root && target_root != DOT_SLASH
|
268
332
|
resolved_target = join_path target_segments, target_root
|
269
333
|
# if target is absolute and a sub-directory of jail, or
|
270
334
|
# a jail is not in place, let it slide
|
271
|
-
if jail
|
335
|
+
if !jail || (resolved_target.start_with? jail)
|
272
336
|
return resolved_target
|
273
337
|
end
|
274
338
|
end
|
275
339
|
|
276
|
-
if start.
|
277
|
-
start = jail
|
340
|
+
if start.nil_or_empty?
|
341
|
+
start = jail ? jail : @working_dir
|
278
342
|
elsif is_root? start
|
279
343
|
start = posixfy start
|
280
344
|
else
|
281
|
-
start = system_path
|
345
|
+
start = system_path start, jail, jail
|
282
346
|
end
|
283
347
|
|
284
348
|
# both jail and start have been posixfied at this point
|
285
349
|
if jail == start
|
286
|
-
jail_segments, jail_root, _ = partition_path
|
350
|
+
jail_segments, jail_root, _ = partition_path jail
|
287
351
|
start_segments = jail_segments.dup
|
288
|
-
elsif
|
289
|
-
|
290
|
-
raise SecurityError,
|
352
|
+
elsif jail
|
353
|
+
unless start.start_with? jail
|
354
|
+
raise ::SecurityError, %(#{opts[:target_name] || 'Start path'} #{start} is outside of jail: #{jail} (disallowed in safe mode))
|
291
355
|
end
|
292
356
|
|
293
|
-
start_segments, start_root, _ = partition_path
|
294
|
-
jail_segments, jail_root, _ = partition_path
|
357
|
+
start_segments, start_root, _ = partition_path start
|
358
|
+
jail_segments, jail_root, _ = partition_path jail
|
295
359
|
|
296
360
|
# Already checked for this condition
|
297
361
|
#if start_root != jail_root
|
298
|
-
# raise SecurityError,
|
362
|
+
# raise ::SecurityError, %(Jail root #{jail_root} does not match root of #{opts[:target_name] || 'start path'}: #{start_root})
|
299
363
|
#end
|
300
364
|
else
|
301
|
-
start_segments, start_root, _ = partition_path
|
365
|
+
start_segments, start_root, _ = partition_path start
|
302
366
|
jail_root = start_root
|
303
367
|
end
|
304
368
|
|
@@ -306,13 +370,13 @@ class PathResolver
|
|
306
370
|
warned = false
|
307
371
|
target_segments.each do |segment|
|
308
372
|
if segment == DOT_DOT
|
309
|
-
if
|
373
|
+
if jail
|
310
374
|
if resolved_segments.length > jail_segments.length
|
311
375
|
resolved_segments.pop
|
312
376
|
elsif !recover
|
313
|
-
raise SecurityError,
|
377
|
+
raise ::SecurityError, %(#{opts[:target_name] || 'path'} #{target} refers to location outside jail: #{jail} (disallowed in safe mode))
|
314
378
|
elsif !warned
|
315
|
-
warn
|
379
|
+
warn %(asciidoctor: WARNING: #{opts[:target_name] || 'path'} has illegal reference to ancestor of jail, auto-recovering)
|
316
380
|
warned = true
|
317
381
|
end
|
318
382
|
else
|
@@ -336,39 +400,39 @@ class PathResolver
|
|
336
400
|
# returns a String path that joins the target path with the
|
337
401
|
# start path with any parent references resolved and self
|
338
402
|
# references removed
|
339
|
-
def web_path
|
340
|
-
target = posixfy
|
341
|
-
start = posixfy
|
403
|
+
def web_path target, start = nil
|
404
|
+
target = posixfy target
|
405
|
+
start = posixfy start
|
342
406
|
uri_prefix = nil
|
343
407
|
|
344
|
-
unless
|
345
|
-
target =
|
346
|
-
if target.include?
|
408
|
+
unless start.nil_or_empty? || (is_web_root? target)
|
409
|
+
target = %(#{start}#{SLASH}#{target})
|
410
|
+
if (target.include? ':') && UriSniffRx =~ target
|
347
411
|
uri_prefix = $~[0]
|
348
412
|
target = target[uri_prefix.length..-1]
|
349
413
|
end
|
350
414
|
end
|
351
415
|
|
352
|
-
target_segments, target_root, _ = partition_path
|
353
|
-
resolved_segments =
|
416
|
+
target_segments, target_root, _ = partition_path target, true
|
417
|
+
resolved_segments = []
|
418
|
+
target_segments.each do |segment|
|
354
419
|
if segment == DOT_DOT
|
355
|
-
if
|
356
|
-
|
357
|
-
elsif
|
358
|
-
|
420
|
+
if resolved_segments.empty?
|
421
|
+
resolved_segments << segment unless target_root && target_root != DOT_SLASH
|
422
|
+
elsif resolved_segments[-1] == DOT_DOT
|
423
|
+
resolved_segments << segment
|
359
424
|
else
|
360
|
-
|
425
|
+
resolved_segments.pop
|
361
426
|
end
|
362
427
|
else
|
363
|
-
|
428
|
+
resolved_segments << segment
|
364
429
|
end
|
365
|
-
accum
|
366
430
|
end
|
367
431
|
|
368
|
-
if uri_prefix
|
369
|
-
join_path resolved_segments, target_root
|
432
|
+
if uri_prefix
|
433
|
+
%(#{uri_prefix}#{join_path resolved_segments, target_root})
|
370
434
|
else
|
371
|
-
|
435
|
+
join_path resolved_segments, target_root
|
372
436
|
end
|
373
437
|
end
|
374
438
|
|
@@ -380,7 +444,7 @@ class PathResolver
|
|
380
444
|
# base_directory - An absolute base directory as a String
|
381
445
|
#
|
382
446
|
# Return the relative path String of the filename calculated from the base directory
|
383
|
-
def relative_path
|
447
|
+
def relative_path filename, base_directory
|
384
448
|
if (is_root? filename) && (is_root? base_directory)
|
385
449
|
offset = base_directory.chomp(@file_separator).length + 1
|
386
450
|
filename[offset..-1]
|
data/lib/asciidoctor/reader.rb
CHANGED
@@ -17,6 +17,8 @@ class Reader
|
|
17
17
|
def line_info
|
18
18
|
%(#{path}: line #{lineno})
|
19
19
|
end
|
20
|
+
|
21
|
+
alias :to_s :line_info
|
20
22
|
end
|
21
23
|
|
22
24
|
attr_reader :file
|
@@ -33,34 +35,33 @@ class Reader
|
|
33
35
|
attr_accessor :process_lines
|
34
36
|
|
35
37
|
# Public: Initialize the Reader object
|
36
|
-
def initialize data = nil, cursor = nil
|
37
|
-
if cursor
|
38
|
+
def initialize data = nil, cursor = nil, opts = {:normalize => false}
|
39
|
+
if !cursor
|
38
40
|
@file = @dir = nil
|
39
41
|
@path = '<stdin>'
|
40
42
|
@lineno = 1 # IMPORTANT lineno assignment must proceed prepare_lines call!
|
41
|
-
elsif cursor.is_a? String
|
43
|
+
elsif cursor.is_a? ::String
|
42
44
|
@file = cursor
|
43
|
-
@dir = File.
|
44
|
-
@path = File.basename @file
|
45
|
+
@dir, @path = ::File.split @file
|
45
46
|
@lineno = 1 # IMPORTANT lineno assignment must proceed prepare_lines call!
|
46
47
|
else
|
47
48
|
@file = cursor.file
|
48
49
|
@dir = cursor.dir
|
49
50
|
@path = cursor.path || '<stdin>'
|
50
|
-
|
51
|
-
|
51
|
+
if @file
|
52
|
+
unless @dir
|
52
53
|
# REVIEW might to look at this assignment closer
|
53
|
-
@dir = File.dirname @file
|
54
|
+
@dir = ::File.dirname @file
|
54
55
|
@dir = nil if @dir == '.' # right?
|
55
56
|
end
|
56
57
|
|
57
|
-
|
58
|
-
@path = File.basename @file
|
58
|
+
unless cursor.path
|
59
|
+
@path = ::File.basename @file
|
59
60
|
end
|
60
61
|
end
|
61
62
|
@lineno = cursor.lineno || 1 # IMPORTANT lineno assignment must proceed prepare_lines call!
|
62
63
|
end
|
63
|
-
@lines = data
|
64
|
+
@lines = data ? (prepare_lines data, opts) : []
|
64
65
|
@source_lines = @lines.dup
|
65
66
|
@eof = @lines.empty?
|
66
67
|
@look_ahead = 0
|
@@ -77,14 +78,24 @@ class Reader
|
|
77
78
|
#
|
78
79
|
# Any leading or trailing blank lines are also removed.
|
79
80
|
#
|
80
|
-
# The normalized lines are assigned to the @lines instance variable.
|
81
|
-
#
|
82
81
|
# data - A String Array of input data to be normalized
|
83
82
|
# opts - A Hash of options to control what cleansing is done
|
84
83
|
#
|
85
84
|
# Returns The String lines extracted from the data
|
86
85
|
def prepare_lines data, opts = {}
|
87
|
-
data.is_a?
|
86
|
+
if data.is_a? ::String
|
87
|
+
if opts[:normalize]
|
88
|
+
Helpers.normalize_lines_from_string data
|
89
|
+
else
|
90
|
+
data.split EOL
|
91
|
+
end
|
92
|
+
else
|
93
|
+
if opts[:normalize]
|
94
|
+
Helpers.normalize_lines_array data
|
95
|
+
else
|
96
|
+
data.dup
|
97
|
+
end
|
98
|
+
end
|
88
99
|
end
|
89
100
|
|
90
101
|
# Internal: Processes a previously unvisited line
|
@@ -118,7 +129,7 @@ class Reader
|
|
118
129
|
#
|
119
130
|
# Returns True if the there are no more lines or if the next line is empty
|
120
131
|
def next_line_empty?
|
121
|
-
|
132
|
+
peek_line.nil_or_empty?
|
122
133
|
end
|
123
134
|
|
124
135
|
# Public: Peek at the next line of source data. Processes the line, if not
|
@@ -126,7 +137,7 @@ class Reader
|
|
126
137
|
#
|
127
138
|
# This method will probe the reader for more lines. If there is a next line
|
128
139
|
# that has not previously been visited, the line is passed to the
|
129
|
-
# Reader#
|
140
|
+
# Reader#process_line method to be initialized. This call gives
|
130
141
|
# sub-classess the opportunity to do preprocessing. If the return value of
|
131
142
|
# the Reader#process_line is nil, the data is assumed to be changed and
|
132
143
|
# Reader#peek_line is invoked again to perform further processing.
|
@@ -138,7 +149,7 @@ class Reader
|
|
138
149
|
# Returns nil if there is no more data.
|
139
150
|
def peek_line direct = false
|
140
151
|
if direct || @look_ahead > 0
|
141
|
-
@unescape_next_line ? @lines
|
152
|
+
@unescape_next_line ? @lines[0][1..-1] : @lines[0]
|
142
153
|
elsif @eof || @lines.empty?
|
143
154
|
@eof = true
|
144
155
|
@look_ahead = 0
|
@@ -147,7 +158,7 @@ class Reader
|
|
147
158
|
# FIXME the problem with this approach is that we aren't
|
148
159
|
# retaining the modified line (hence the @unescape_next_line tweak)
|
149
160
|
# perhaps we need a stack of proxy lines
|
150
|
-
if (line = process_line @lines
|
161
|
+
if !(line = process_line @lines[0])
|
151
162
|
peek_line
|
152
163
|
else
|
153
164
|
line
|
@@ -171,7 +182,7 @@ class Reader
|
|
171
182
|
def peek_lines num = 1, direct = true
|
172
183
|
old_look_ahead = @look_ahead
|
173
184
|
result = []
|
174
|
-
|
185
|
+
num.times do
|
175
186
|
if (line = read_line direct)
|
176
187
|
result << line
|
177
188
|
else
|
@@ -213,7 +224,7 @@ class Reader
|
|
213
224
|
def read_lines
|
214
225
|
lines = []
|
215
226
|
while has_more_lines?
|
216
|
-
lines <<
|
227
|
+
lines << shift
|
217
228
|
end
|
218
229
|
lines
|
219
230
|
end
|
@@ -225,7 +236,7 @@ class Reader
|
|
225
236
|
#
|
226
237
|
# Returns the lines read joined as a String
|
227
238
|
def read
|
228
|
-
read_lines
|
239
|
+
read_lines * EOL
|
229
240
|
end
|
230
241
|
|
231
242
|
# Public: Advance to the next line by discarding the line at the front of the stack
|
@@ -235,7 +246,7 @@ class Reader
|
|
235
246
|
#
|
236
247
|
# returns a Boolean indicating whether there was a line to discard.
|
237
248
|
def advance direct = true
|
238
|
-
|
249
|
+
!!read_line(direct)
|
239
250
|
end
|
240
251
|
|
241
252
|
# Public: Push the String line onto the beginning of the Array of source data.
|
@@ -283,13 +294,13 @@ class Reader
|
|
283
294
|
# Examples
|
284
295
|
#
|
285
296
|
# @lines
|
286
|
-
# => ["
|
297
|
+
# => ["", "", "Foo", "Bar", ""]
|
287
298
|
#
|
288
299
|
# skip_blank_lines
|
289
300
|
# => 2
|
290
301
|
#
|
291
302
|
# @lines
|
292
|
-
# => ["Foo
|
303
|
+
# => ["Foo", "Bar", ""]
|
293
304
|
#
|
294
305
|
# Returns an Integer of the number of lines skipped
|
295
306
|
def skip_blank_lines
|
@@ -298,7 +309,7 @@ class Reader
|
|
298
309
|
num_skipped = 0
|
299
310
|
# optimized code for shortest execution path
|
300
311
|
while (next_line = peek_line)
|
301
|
-
if next_line.
|
312
|
+
if next_line.empty?
|
302
313
|
advance
|
303
314
|
num_skipped += 1
|
304
315
|
else
|
@@ -313,13 +324,13 @@ class Reader
|
|
313
324
|
#
|
314
325
|
# Examples
|
315
326
|
# @lines
|
316
|
-
# => ["// foo
|
327
|
+
# => ["// foo", "bar"]
|
317
328
|
#
|
318
329
|
# comment_lines = skip_comment_lines
|
319
|
-
# => ["// foo
|
330
|
+
# => ["// foo"]
|
320
331
|
#
|
321
332
|
# @lines
|
322
|
-
# => ["bar
|
333
|
+
# => ["bar"]
|
323
334
|
#
|
324
335
|
# Returns the Array of lines that were skipped
|
325
336
|
def skip_comment_lines opts = {}
|
@@ -328,13 +339,13 @@ class Reader
|
|
328
339
|
comment_lines = []
|
329
340
|
include_blank_lines = opts[:include_blank_lines]
|
330
341
|
while (next_line = peek_line)
|
331
|
-
if include_blank_lines && next_line.
|
332
|
-
comment_lines <<
|
333
|
-
elsif (commentish = next_line.start_with?('//')) && (match =
|
334
|
-
comment_lines <<
|
342
|
+
if include_blank_lines && next_line.empty?
|
343
|
+
comment_lines << shift
|
344
|
+
elsif (commentish = next_line.start_with?('//')) && (match = CommentBlockRx.match(next_line))
|
345
|
+
comment_lines << shift
|
335
346
|
comment_lines.push(*(read_lines_until(:terminator => match[0], :read_last_line => true, :skip_processing => true)))
|
336
|
-
elsif commentish && next_line
|
337
|
-
comment_lines <<
|
347
|
+
elsif commentish && CommentLineRx =~ next_line
|
348
|
+
comment_lines << shift
|
338
349
|
else
|
339
350
|
break
|
340
351
|
end
|
@@ -350,8 +361,8 @@ class Reader
|
|
350
361
|
comment_lines = []
|
351
362
|
# optimized code for shortest execution path
|
352
363
|
while (next_line = peek_line)
|
353
|
-
if next_line
|
354
|
-
comment_lines <<
|
364
|
+
if CommentLineRx =~ next_line
|
365
|
+
comment_lines << shift
|
355
366
|
else
|
356
367
|
break
|
357
368
|
end
|
@@ -399,12 +410,16 @@ class Reader
|
|
399
410
|
#
|
400
411
|
# Examples
|
401
412
|
#
|
402
|
-
#
|
403
|
-
#
|
404
|
-
#
|
413
|
+
# data = [
|
414
|
+
# "First line\n",
|
415
|
+
# "Second line\n",
|
416
|
+
# "\n",
|
417
|
+
# "Third line\n",
|
418
|
+
# ]
|
419
|
+
# reader = Reader.new data, nil, :normalize => true
|
405
420
|
#
|
406
421
|
# reader.read_lines_until
|
407
|
-
# => ["First
|
422
|
+
# => ["First line", "Second line"]
|
408
423
|
def read_lines_until options = {}
|
409
424
|
result = []
|
410
425
|
advance if options[:skip_first_line]
|
@@ -415,34 +430,32 @@ class Reader
|
|
415
430
|
restore_process_lines = false
|
416
431
|
end
|
417
432
|
|
418
|
-
has_block = block_given?
|
419
433
|
if (terminator = options[:terminator])
|
420
434
|
break_on_blank_lines = false
|
421
435
|
break_on_list_continuation = false
|
422
|
-
chomp_last_line = options.fetch :chomp_last_line, false
|
423
436
|
else
|
424
437
|
break_on_blank_lines = options[:break_on_blank_lines]
|
425
438
|
break_on_list_continuation = options[:break_on_list_continuation]
|
426
|
-
chomp_last_line = break_on_blank_lines
|
427
439
|
end
|
428
440
|
skip_line_comments = options[:skip_line_comments]
|
429
441
|
line_read = false
|
430
442
|
line_restored = false
|
431
443
|
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
444
|
+
complete = false
|
445
|
+
while !complete && (line = read_line)
|
446
|
+
complete = while true
|
447
|
+
break true if terminator && line == terminator
|
448
|
+
# QUESTION: can we get away with line.empty? here?
|
449
|
+
break true if break_on_blank_lines && line.empty?
|
450
|
+
if break_on_list_continuation && line_read && line == LIST_CONTINUATION
|
438
451
|
options[:preserve_last_line] = true
|
439
452
|
break true
|
440
453
|
end
|
441
|
-
break true if
|
454
|
+
break true if block_given? && (yield line)
|
442
455
|
break false
|
443
456
|
end
|
444
457
|
|
445
|
-
if
|
458
|
+
if complete
|
446
459
|
if options[:read_last_line]
|
447
460
|
result << line
|
448
461
|
line_read = true
|
@@ -451,27 +464,27 @@ class Reader
|
|
451
464
|
restore_line line
|
452
465
|
line_restored = true
|
453
466
|
end
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
line_read = true
|
467
|
+
else
|
468
|
+
unless skip_line_comments && line.start_with?('//') && CommentLineRx =~ line
|
469
|
+
result << line
|
470
|
+
line_read = true
|
471
|
+
end
|
460
472
|
end
|
461
473
|
end
|
462
474
|
|
463
|
-
if chomp_last_line && line_read
|
464
|
-
result << result.pop.chomp
|
465
|
-
end
|
466
|
-
|
467
475
|
if restore_process_lines
|
468
476
|
@process_lines = true
|
469
|
-
@look_ahead -= 1 if line_restored && terminator
|
477
|
+
@look_ahead -= 1 if line_restored && !terminator
|
470
478
|
end
|
471
479
|
result
|
472
480
|
end
|
473
481
|
|
474
482
|
# Internal: Shift the line off the stack and increment the lineno
|
483
|
+
#
|
484
|
+
# This method can be used directly when you've already called peek_line
|
485
|
+
# and determined that you do, in fact, want to pluck that line off the stack.
|
486
|
+
#
|
487
|
+
# Returns The String line at the top of the stack
|
475
488
|
def shift
|
476
489
|
@lineno += 1
|
477
490
|
@look_ahead -= 1 unless @look_ahead == 0
|
@@ -511,12 +524,12 @@ class Reader
|
|
511
524
|
|
512
525
|
# Public: Get a copy of the remaining lines managed by this Reader joined as a String
|
513
526
|
def string
|
514
|
-
@lines
|
527
|
+
@lines * EOL
|
515
528
|
end
|
516
529
|
|
517
530
|
# Public: Get the source lines for this Reader joined as a String
|
518
531
|
def source
|
519
|
-
@source_lines
|
532
|
+
@source_lines * EOL
|
520
533
|
end
|
521
534
|
|
522
535
|
# Public: Get a summary of this Reader.
|
@@ -537,7 +550,7 @@ class PreprocessorReader < Reader
|
|
537
550
|
# Public: Initialize the PreprocessorReader object
|
538
551
|
def initialize document, data = nil, cursor = nil
|
539
552
|
@document = document
|
540
|
-
super data, cursor
|
553
|
+
super data, cursor, :normalize => true
|
541
554
|
include_depth_default = document.attributes.fetch('max-include-depth', 64).to_i
|
542
555
|
include_depth_default = 0 if include_depth_default < 0
|
543
556
|
# track both absolute depth for comparing to size of include stack and relative depth for reporting
|
@@ -546,39 +559,26 @@ class PreprocessorReader < Reader
|
|
546
559
|
@includes = (document.references[:includes] ||= [])
|
547
560
|
@skipping = false
|
548
561
|
@conditional_stack = []
|
549
|
-
@
|
562
|
+
@include_processor_extensions = nil
|
550
563
|
end
|
551
564
|
|
552
565
|
def prepare_lines data, opts = {}
|
553
|
-
|
554
|
-
if ::Asciidoctor::FORCE_ENCODING
|
555
|
-
result = data.each_line.map {|line| "#{line.rstrip.force_encoding ::Encoding::UTF_8}#{::Asciidoctor::EOL}" }
|
556
|
-
else
|
557
|
-
result = data.each_line.map {|line| "#{line.rstrip}#{::Asciidoctor::EOL}" }
|
558
|
-
end
|
559
|
-
else
|
560
|
-
if ::Asciidoctor::FORCE_ENCODING
|
561
|
-
result = data.map {|line| "#{line.rstrip.force_encoding ::Encoding::UTF_8}#{::Asciidoctor::EOL}" }
|
562
|
-
else
|
563
|
-
result = data.map {|line| "#{line.rstrip}#{::Asciidoctor::EOL}" }
|
564
|
-
end
|
565
|
-
end
|
566
|
+
result = super
|
566
567
|
|
567
568
|
# QUESTION should this work for AsciiDoc table cell content? Currently it does not.
|
568
|
-
|
569
|
+
if @document && (@document.attributes.has_key? 'skip-front-matter')
|
569
570
|
if (front_matter = skip_front_matter! result)
|
570
|
-
@document.attributes['front-matter'] = front_matter
|
571
|
+
@document.attributes['front-matter'] = front_matter * EOL
|
571
572
|
end
|
572
573
|
end
|
573
574
|
|
574
|
-
|
575
|
-
|
576
|
-
result.
|
577
|
-
result.pop while !(last = result.last).nil? && last == ::Asciidoctor::EOL
|
575
|
+
if opts.fetch :condense, true
|
576
|
+
result.shift && @lineno += 1 while (first = result[0]) && first.empty?
|
577
|
+
result.pop while (last = result[-1]) && last.empty?
|
578
578
|
end
|
579
579
|
|
580
580
|
if (indent = opts.fetch(:indent, nil))
|
581
|
-
|
581
|
+
Parser.reset_block_indent! result, indent.to_i
|
582
582
|
end
|
583
583
|
|
584
584
|
result
|
@@ -587,55 +587,63 @@ class PreprocessorReader < Reader
|
|
587
587
|
def process_line line
|
588
588
|
return line unless @process_lines
|
589
589
|
|
590
|
-
if line.
|
590
|
+
if line.empty?
|
591
591
|
@look_ahead += 1
|
592
592
|
return ''
|
593
593
|
end
|
594
594
|
|
595
|
-
|
596
|
-
if
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
if preprocess_conditional_inclusion(*match.captures)
|
604
|
-
# move the pointer past the conditional line
|
605
|
-
advance
|
606
|
-
# treat next line as uncharted territory
|
607
|
-
nil
|
595
|
+
# NOTE highly optimized
|
596
|
+
if line.end_with?(']') && !line.start_with?('[') && line.include?('::')
|
597
|
+
if line.include?('if') && (match = ConditionalDirectiveRx.match(line))
|
598
|
+
# if escaped, mark as processed and return line unescaped
|
599
|
+
if line.start_with?('\\')
|
600
|
+
@unescape_next_line = true
|
601
|
+
@look_ahead += 1
|
602
|
+
line[1..-1]
|
608
603
|
else
|
609
|
-
|
610
|
-
|
604
|
+
if preprocess_conditional_inclusion(*match.captures)
|
605
|
+
# move the pointer past the conditional line
|
606
|
+
advance
|
607
|
+
# treat next line as uncharted territory
|
608
|
+
nil
|
609
|
+
else
|
610
|
+
# the line was not a valid conditional line
|
611
|
+
# mark it as visited and return it
|
612
|
+
@look_ahead += 1
|
613
|
+
line
|
614
|
+
end
|
615
|
+
end
|
616
|
+
elsif @skipping
|
617
|
+
advance
|
618
|
+
nil
|
619
|
+
elsif ((escaped = line.start_with?('\\include::')) || line.start_with?('include::')) && (match = IncludeDirectiveRx.match(line))
|
620
|
+
# if escaped, mark as processed and return line unescaped
|
621
|
+
if escaped
|
622
|
+
@unescape_next_line = true
|
611
623
|
@look_ahead += 1
|
612
|
-
line
|
624
|
+
line[1..-1]
|
625
|
+
else
|
626
|
+
# QUESTION should we strip whitespace from raw attributes in Substitutors#parse_attributes? (check perf)
|
627
|
+
if preprocess_include match[1], match[2].strip
|
628
|
+
# peek again since the content has changed
|
629
|
+
nil
|
630
|
+
else
|
631
|
+
# the line was not a valid include line and is unchanged
|
632
|
+
# mark it as visited and return it
|
633
|
+
@look_ahead += 1
|
634
|
+
line
|
635
|
+
end
|
613
636
|
end
|
637
|
+
else
|
638
|
+
# NOTE optimization to inline super
|
639
|
+
@look_ahead += 1
|
640
|
+
line
|
614
641
|
end
|
615
642
|
elsif @skipping
|
616
643
|
advance
|
617
|
-
nil
|
618
|
-
elsif macroish && line.include?('include::') && (match = line.match(REGEXP[:include_macro]))
|
619
|
-
# if escaped, mark as processed and return line unescaped
|
620
|
-
if line.start_with? '\\'
|
621
|
-
@unescape_next_line = true
|
622
|
-
@look_ahead += 1
|
623
|
-
line[1..-1]
|
624
|
-
else
|
625
|
-
# QUESTION should we strip whitespace from raw attributes in Substituters#parse_attributes? (check perf)
|
626
|
-
if preprocess_include match[1], match[2].strip
|
627
|
-
# peek again since the content has changed
|
628
|
-
nil
|
629
|
-
else
|
630
|
-
# the line was not a valid include line and is unchanged
|
631
|
-
# mark it as visited and return it
|
632
|
-
@look_ahead += 1
|
633
|
-
line
|
634
|
-
end
|
635
|
-
end
|
644
|
+
nil
|
636
645
|
else
|
637
|
-
# optimization to inline super
|
638
|
-
#super
|
646
|
+
# NOTE optimization to inline super
|
639
647
|
@look_ahead += 1
|
640
648
|
line
|
641
649
|
end
|
@@ -685,17 +693,20 @@ class PreprocessorReader < Reader
|
|
685
693
|
# don't honor match if it doesn't meet this criteria
|
686
694
|
# QUESTION should we warn for these bogus declarations?
|
687
695
|
if ((directive == 'ifdef' || directive == 'ifndef') && target.empty?) ||
|
688
|
-
(directive == 'endif' &&
|
696
|
+
(directive == 'endif' && text)
|
689
697
|
return false
|
690
698
|
end
|
691
699
|
|
700
|
+
# attributes are case insensitive
|
701
|
+
target = target.downcase
|
702
|
+
|
692
703
|
if directive == 'endif'
|
693
704
|
stack_size = @conditional_stack.size
|
694
705
|
if stack_size > 0
|
695
|
-
pair = @conditional_stack
|
706
|
+
pair = @conditional_stack[-1]
|
696
707
|
if target.empty? || target == pair[:target]
|
697
708
|
@conditional_stack.pop
|
698
|
-
@skipping = @conditional_stack.empty? ? false : @conditional_stack
|
709
|
+
@skipping = @conditional_stack.empty? ? false : @conditional_stack[-1][:skipping]
|
699
710
|
else
|
700
711
|
warn "asciidoctor: ERROR: #{line_info}: mismatched macro: endif::#{target}[], expected endif::#{pair[:target]}[]"
|
701
712
|
end
|
@@ -736,7 +747,7 @@ class PreprocessorReader < Reader
|
|
736
747
|
when 'ifeval'
|
737
748
|
# the text in brackets must match an expression
|
738
749
|
# don't honor match if it doesn't meet this criteria
|
739
|
-
if !target.empty? || !(expr_match = text.strip
|
750
|
+
if !target.empty? || !(expr_match = EvalExpressionRx.match(text.strip))
|
740
751
|
return false
|
741
752
|
end
|
742
753
|
|
@@ -750,7 +761,7 @@ class PreprocessorReader < Reader
|
|
750
761
|
end
|
751
762
|
|
752
763
|
# conditional inclusion block
|
753
|
-
if directive == 'ifeval' || text
|
764
|
+
if directive == 'ifeval' || !text
|
754
765
|
@skipping = true if skip
|
755
766
|
@conditional_stack << {:target => target, :skip => skip, :skipping => @skipping}
|
756
767
|
# single line conditional inclusion
|
@@ -759,7 +770,7 @@ class PreprocessorReader < Reader
|
|
759
770
|
# FIXME slight hack to skip past conditional line
|
760
771
|
# but keep our synthetic line marked as processed
|
761
772
|
conditional_line = peek_line true
|
762
|
-
replace_line
|
773
|
+
replace_line text.rstrip
|
763
774
|
unshift conditional_line
|
764
775
|
return true
|
765
776
|
end
|
@@ -789,11 +800,11 @@ class PreprocessorReader < Reader
|
|
789
800
|
# target slot of the include::[] macro
|
790
801
|
#
|
791
802
|
# returns a Boolean indicating whether the line under the cursor has changed.
|
792
|
-
def preprocess_include
|
793
|
-
target = @document.sub_attributes
|
794
|
-
|
795
|
-
|
796
|
-
|
803
|
+
def preprocess_include raw_target, raw_attributes
|
804
|
+
if (target = @document.sub_attributes raw_target, :attribute_missing => 'drop-line').empty?
|
805
|
+
if @document.attributes.fetch('attribute-missing', Compliance.attribute_missing) == 'skip'
|
806
|
+
replace_line %(Unresolved directive in #{@path} - include::#{raw_target}[#{raw_attributes}])
|
807
|
+
true
|
797
808
|
else
|
798
809
|
advance
|
799
810
|
true
|
@@ -801,26 +812,34 @@ class PreprocessorReader < Reader
|
|
801
812
|
# assume that if an include processor is given, the developer wants
|
802
813
|
# to handle when and how to process the include
|
803
814
|
elsif include_processors? &&
|
804
|
-
(
|
815
|
+
(extension = @include_processor_extensions.find {|candidate| candidate.instance.handles? target })
|
805
816
|
advance
|
806
|
-
#
|
807
|
-
|
817
|
+
# FIXME parse attributes if requested by extension
|
818
|
+
extension.process_method[@document, self, target, AttributeList.new(raw_attributes).parse]
|
808
819
|
true
|
809
820
|
# if running in SafeMode::SECURE or greater, don't process this directive
|
810
821
|
# however, be friendly and at least make it a link to the source document
|
811
822
|
elsif @document.safe >= SafeMode::SECURE
|
812
|
-
|
813
|
-
|
814
|
-
#output_target = %(#{File.join(File.dirname(target), File.basename(target, File.extname(target)))}#{@document.attributes['outfilesuffix']})
|
815
|
-
#unshift "link:#{output_target}[]#{::Asciidoctor::EOL}"
|
823
|
+
# FIXME we don't want to use a link macro if we are in a verbatim context
|
824
|
+
replace_line %(link:#{target}[])
|
816
825
|
true
|
817
826
|
elsif (abs_maxdepth = @maxdepth[:abs]) > 0 && @include_stack.size >= abs_maxdepth
|
818
827
|
warn %(asciidoctor: ERROR: #{line_info}: maximum include depth of #{@maxdepth[:rel]} exceeded)
|
819
828
|
false
|
820
829
|
elsif abs_maxdepth > 0
|
821
|
-
if
|
830
|
+
if ::RUBY_ENGINE_OPAL
|
831
|
+
# NOTE resolves uri relative to currently loaded document
|
832
|
+
# NOTE we defer checking if file exists and catch the 404 error if it does not
|
833
|
+
# TODO only use this logic if env-browser is set
|
834
|
+
target_type = :file
|
835
|
+
include_file = path = if @include_stack.empty?
|
836
|
+
::Dir.pwd == @document.base_dir ? target : (::File.join @dir, target)
|
837
|
+
else
|
838
|
+
::File.join @dir, target
|
839
|
+
end
|
840
|
+
elsif target.include?(':') && UriSniffRx =~ target
|
822
841
|
unless @document.attributes.has_key? 'allow-uri-read'
|
823
|
-
replace_line
|
842
|
+
replace_line %(link:#{target}[])
|
824
843
|
return true
|
825
844
|
end
|
826
845
|
|
@@ -830,16 +849,17 @@ class PreprocessorReader < Reader
|
|
830
849
|
# caching requires the open-uri-cached gem to be installed
|
831
850
|
# processing will be automatically aborted if these libraries can't be opened
|
832
851
|
Helpers.require_library 'open-uri/cached', 'open-uri-cached'
|
833
|
-
|
834
|
-
|
852
|
+
elsif !::RUBY_ENGINE_OPAL
|
853
|
+
# autoload open-uri
|
854
|
+
::OpenURI
|
835
855
|
end
|
836
856
|
else
|
837
857
|
target_type = :file
|
838
858
|
# include file is resolved relative to dir of current include, or base_dir if within original docfile
|
839
859
|
include_file = @document.normalize_system_path(target, @dir, nil, :target_name => 'include file')
|
840
|
-
|
860
|
+
unless ::File.file? include_file
|
841
861
|
warn "asciidoctor: WARNING: #{line_info}: include file not found: #{include_file}"
|
842
|
-
|
862
|
+
replace_line %(Unresolved directive in #{@path} - include::#{target}[#{raw_attributes}])
|
843
863
|
return true
|
844
864
|
end
|
845
865
|
#path = @document.relative_path include_file
|
@@ -854,14 +874,14 @@ class PreprocessorReader < Reader
|
|
854
874
|
attributes = AttributeList.new(raw_attributes).parse
|
855
875
|
if attributes.has_key? 'lines'
|
856
876
|
inc_lines = []
|
857
|
-
attributes['lines'].split(
|
877
|
+
attributes['lines'].split(DataDelimiterRx).each do |linedef|
|
858
878
|
if linedef.include?('..')
|
859
879
|
from, to = linedef.split('..').map(&:to_i)
|
860
880
|
if to == -1
|
861
881
|
inc_lines << from
|
862
882
|
inc_lines << 1.0/0.0
|
863
883
|
else
|
864
|
-
inc_lines.concat Range.new(from, to).to_a
|
884
|
+
inc_lines.concat ::Range.new(from, to).to_a
|
865
885
|
end
|
866
886
|
else
|
867
887
|
inc_lines << linedef.to_i
|
@@ -869,9 +889,9 @@ class PreprocessorReader < Reader
|
|
869
889
|
end
|
870
890
|
inc_lines = inc_lines.sort.uniq
|
871
891
|
elsif attributes.has_key? 'tag'
|
872
|
-
tags = [attributes['tag']]
|
892
|
+
tags = [attributes['tag']].to_set
|
873
893
|
elsif attributes.has_key? 'tags'
|
874
|
-
tags = attributes['tags'].split(
|
894
|
+
tags = attributes['tags'].split(DataDelimiterRx).uniq.to_set
|
875
895
|
end
|
876
896
|
end
|
877
897
|
if !inc_lines.nil?
|
@@ -880,11 +900,11 @@ class PreprocessorReader < Reader
|
|
880
900
|
inc_line_offset = 0
|
881
901
|
inc_lineno = 0
|
882
902
|
begin
|
883
|
-
open(include_file) do |f|
|
903
|
+
open(include_file, 'r') do |f|
|
884
904
|
f.each_line do |l|
|
885
905
|
inc_lineno += 1
|
886
|
-
take = inc_lines
|
887
|
-
if take.is_a?(Float) && take.infinite?
|
906
|
+
take = inc_lines[0]
|
907
|
+
if take.is_a?(::Float) && take.infinite?
|
888
908
|
selected.push l
|
889
909
|
inc_line_offset = inc_lineno if inc_line_offset == 0
|
890
910
|
else
|
@@ -898,8 +918,8 @@ class PreprocessorReader < Reader
|
|
898
918
|
end
|
899
919
|
end
|
900
920
|
rescue
|
901
|
-
warn
|
902
|
-
|
921
|
+
warn %(asciidoctor: WARNING: #{line_info}: include #{target_type} not readable: #{include_file})
|
922
|
+
replace_line %(Unresolved directive in #{@path} - include::#{target}[#{raw_attributes}])
|
903
923
|
return true
|
904
924
|
end
|
905
925
|
advance
|
@@ -912,23 +932,26 @@ class PreprocessorReader < Reader
|
|
912
932
|
inc_line_offset = 0
|
913
933
|
inc_lineno = 0
|
914
934
|
active_tag = nil
|
935
|
+
tags_found = ::Set.new
|
915
936
|
begin
|
916
|
-
open(include_file) do |f|
|
937
|
+
open(include_file, 'r') do |f|
|
917
938
|
f.each_line do |l|
|
918
939
|
inc_lineno += 1
|
919
940
|
# must force encoding here since we're performing String operations on line
|
920
|
-
l.force_encoding(::Encoding::UTF_8) if
|
921
|
-
|
922
|
-
|
941
|
+
l.force_encoding(::Encoding::UTF_8) if FORCE_ENCODING
|
942
|
+
l = l.rstrip
|
943
|
+
if active_tag
|
944
|
+
if l.end_with?(%(end::#{active_tag}[])) && TagDirectiveRx =~ l
|
923
945
|
active_tag = nil
|
924
946
|
else
|
925
|
-
selected.push l
|
947
|
+
selected.push l unless l.end_with?('[]') && TagDirectiveRx =~ l
|
926
948
|
inc_line_offset = inc_lineno if inc_line_offset == 0
|
927
949
|
end
|
928
950
|
else
|
929
951
|
tags.each do |tag|
|
930
|
-
if l.
|
952
|
+
if l.end_with?(%(tag::#{tag}[])) && TagDirectiveRx =~ l
|
931
953
|
active_tag = tag
|
954
|
+
tags_found << tag
|
932
955
|
break
|
933
956
|
end
|
934
957
|
end
|
@@ -936,10 +959,13 @@ class PreprocessorReader < Reader
|
|
936
959
|
end
|
937
960
|
end
|
938
961
|
rescue
|
939
|
-
warn
|
940
|
-
|
962
|
+
warn %(asciidoctor: WARNING: #{line_info}: include #{target_type} not readable: #{include_file})
|
963
|
+
replace_line %(Unresolved directive in #{@path} - include::#{target}[#{raw_attributes}])
|
941
964
|
return true
|
942
965
|
end
|
966
|
+
unless (missing_tags = tags.to_a - tags_found.to_a).empty?
|
967
|
+
warn "asciidoctor: WARNING: #{line_info}: tag#{missing_tags.size > 1 ? 's' : nil} '#{missing_tags * ','}' not found in include #{target_type}: #{include_file}"
|
968
|
+
end
|
943
969
|
advance
|
944
970
|
# FIXME not accounting for skipped lines in reader line numbering
|
945
971
|
push_include selected, include_file, path, inc_line_offset, attributes
|
@@ -947,10 +973,10 @@ class PreprocessorReader < Reader
|
|
947
973
|
else
|
948
974
|
begin
|
949
975
|
advance
|
950
|
-
push_include open(include_file) {|f| f.read }, include_file, path, 1, attributes
|
976
|
+
push_include open(include_file, 'r') {|f| f.read }, include_file, path, 1, attributes
|
951
977
|
rescue
|
952
|
-
warn
|
953
|
-
|
978
|
+
warn %(asciidoctor: WARNING: #{line_info}: include #{target_type} not readable: #{include_file})
|
979
|
+
replace_line %(Unresolved directive in #{@path} - include::#{target}[#{raw_attributes}])
|
954
980
|
return true
|
955
981
|
end
|
956
982
|
end
|
@@ -960,31 +986,74 @@ class PreprocessorReader < Reader
|
|
960
986
|
end
|
961
987
|
end
|
962
988
|
|
989
|
+
# Public: Push source onto the front of the reader and switch the context
|
990
|
+
# based on the file, document-relative path and line information given.
|
991
|
+
#
|
992
|
+
# This method is typically used in an IncludeProcessor to add source
|
993
|
+
# read from the target specified.
|
994
|
+
#
|
995
|
+
# Examples
|
996
|
+
#
|
997
|
+
# path = 'partial.adoc'
|
998
|
+
# file = File.expand_path path
|
999
|
+
# data = IO.read file
|
1000
|
+
# reader.push_include data, file, path
|
1001
|
+
#
|
1002
|
+
# Returns nothing
|
963
1003
|
def push_include data, file = nil, path = nil, lineno = 1, attributes = {}
|
964
1004
|
@include_stack << [@lines, @file, @dir, @path, @lineno, @maxdepth, @process_lines]
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
1005
|
+
if file
|
1006
|
+
@file = file
|
1007
|
+
@dir = File.dirname file
|
1008
|
+
# only process lines in AsciiDoc files
|
1009
|
+
@process_lines = ASCIIDOC_EXTENSIONS[::File.extname(file)]
|
1010
|
+
else
|
1011
|
+
@file = nil
|
1012
|
+
@dir = '.' # right?
|
1013
|
+
# we don't know what file type we have, so assume AsciiDoc
|
1014
|
+
@process_lines = true
|
1015
|
+
end
|
1016
|
+
|
1017
|
+
@path = if path
|
1018
|
+
@includes << Helpers.rootname(path)
|
1019
|
+
path
|
1020
|
+
else
|
1021
|
+
'<stdin>'
|
1022
|
+
end
|
1023
|
+
|
969
1024
|
@lineno = lineno
|
970
|
-
|
971
|
-
@process_lines = ASCIIDOC_EXTENSIONS[File.extname(@file)]
|
1025
|
+
|
972
1026
|
if attributes.has_key? 'depth'
|
973
1027
|
depth = attributes['depth'].to_i
|
974
1028
|
depth = 1 if depth <= 0
|
975
1029
|
@maxdepth = {:abs => (@include_stack.size - 1) + depth, :rel => depth}
|
976
1030
|
end
|
1031
|
+
|
977
1032
|
# effectively fill the buffer
|
978
|
-
@lines = prepare_lines data, :condense => false, :indent => attributes['indent']
|
979
|
-
# FIXME kind of a hack
|
980
|
-
#Document::AttributeEntry.new('infile', @file).save_to_next_block @document
|
981
|
-
#Document::AttributeEntry.new('indir', File.dirname(@file)).save_to_next_block @document
|
982
|
-
if @lines.empty?
|
1033
|
+
if (@lines = prepare_lines data, :normalize => true, :condense => false, :indent => attributes['indent']).empty?
|
983
1034
|
pop_include
|
984
1035
|
else
|
1036
|
+
# FIXME we eventually want to handle leveloffset without affecting the lines
|
1037
|
+
if attributes.has_key? 'leveloffset'
|
1038
|
+
@lines.unshift ''
|
1039
|
+
@lines.unshift %(:leveloffset: #{attributes['leveloffset']})
|
1040
|
+
@lines.push ''
|
1041
|
+
if (old_leveloffset = @document.attr 'leveloffset')
|
1042
|
+
@lines.push %(:leveloffset: #{old_leveloffset})
|
1043
|
+
else
|
1044
|
+
@lines.push ':leveloffset!:'
|
1045
|
+
end
|
1046
|
+
# compensate for these extra lines
|
1047
|
+
@lineno -= 2
|
1048
|
+
end
|
1049
|
+
|
1050
|
+
# FIXME kind of a hack
|
1051
|
+
#Document::AttributeEntry.new('infile', @file).save_to_next_block @document
|
1052
|
+
#Document::AttributeEntry.new('indir', @dir).save_to_next_block @document
|
985
1053
|
@eof = false
|
986
1054
|
@look_ahead = 0
|
987
1055
|
end
|
1056
|
+
nil
|
988
1057
|
end
|
989
1058
|
|
990
1059
|
def pop_include
|
@@ -992,10 +1061,11 @@ class PreprocessorReader < Reader
|
|
992
1061
|
@lines, @file, @dir, @path, @lineno, @maxdepth, @process_lines = @include_stack.pop
|
993
1062
|
# FIXME kind of a hack
|
994
1063
|
#Document::AttributeEntry.new('infile', @file).save_to_next_block @document
|
995
|
-
#Document::AttributeEntry.new('indir', File.dirname(@file)).save_to_next_block @document
|
1064
|
+
#Document::AttributeEntry.new('indir', ::File.dirname(@file)).save_to_next_block @document
|
996
1065
|
@eof = @lines.empty?
|
997
1066
|
@look_ahead = 0
|
998
1067
|
end
|
1068
|
+
nil
|
999
1069
|
end
|
1000
1070
|
|
1001
1071
|
def include_depth
|
@@ -1025,12 +1095,12 @@ class PreprocessorReader < Reader
|
|
1025
1095
|
# Private: Ignore front-matter, commonly used in static site generators
|
1026
1096
|
def skip_front_matter! data, increment_linenos = true
|
1027
1097
|
front_matter = nil
|
1028
|
-
if data
|
1098
|
+
if data[0] == '---'
|
1029
1099
|
original_data = data.dup
|
1030
1100
|
front_matter = []
|
1031
1101
|
data.shift
|
1032
1102
|
@lineno += 1 if increment_linenos
|
1033
|
-
while !data.empty? && data
|
1103
|
+
while !data.empty? && data[0] != '---'
|
1034
1104
|
front_matter.push data.shift
|
1035
1105
|
@lineno += 1 if increment_linenos
|
1036
1106
|
end
|
@@ -1115,21 +1185,21 @@ class PreprocessorReader < Reader
|
|
1115
1185
|
end
|
1116
1186
|
|
1117
1187
|
def include_processors?
|
1118
|
-
if
|
1188
|
+
if !@include_processor_extensions
|
1119
1189
|
if @document.extensions? && @document.extensions.include_processors?
|
1120
|
-
@
|
1190
|
+
@include_processor_extensions = @document.extensions.include_processors
|
1121
1191
|
true
|
1122
1192
|
else
|
1123
|
-
@
|
1193
|
+
@include_processor_extensions = false
|
1124
1194
|
false
|
1125
1195
|
end
|
1126
1196
|
else
|
1127
|
-
@
|
1197
|
+
@include_processor_extensions != false
|
1128
1198
|
end
|
1129
1199
|
end
|
1130
1200
|
|
1131
1201
|
def to_s
|
1132
|
-
%(
|
1202
|
+
%(#<#{self.class}@#{object_id} {path: #{@path.inspect}, line #: #{@lineno}, include depth: #{@include_stack.size}, include stack: [#{@include_stack.map {|inc| inc.to_s}.join ', '}]}>)
|
1133
1203
|
end
|
1134
1204
|
end
|
1135
1205
|
end
|