asciidoctor 1.5.5 → 1.5.6

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of asciidoctor might be problematic. Click here for more details.

Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +216 -1
  3. data/CONTRIBUTING.adoc +2 -2
  4. data/Gemfile +20 -1
  5. data/LICENSE.adoc +1 -1
  6. data/README-fr.adoc +4 -3
  7. data/README-jp.adoc +11 -10
  8. data/README-zh_CN.adoc +4 -3
  9. data/README.adoc +17 -202
  10. data/Rakefile +41 -25
  11. data/asciidoctor.gemspec +9 -10
  12. data/data/locale/attributes.adoc +216 -34
  13. data/data/stylesheets/asciidoctor-default.css +23 -16
  14. data/features/step_definitions.rb +15 -19
  15. data/features/xref.feature +584 -20
  16. data/lib/asciidoctor.rb +292 -278
  17. data/lib/asciidoctor/abstract_block.rb +155 -94
  18. data/lib/asciidoctor/abstract_node.rb +108 -94
  19. data/lib/asciidoctor/attribute_list.rb +30 -22
  20. data/lib/asciidoctor/block.rb +7 -7
  21. data/lib/asciidoctor/cli/invoker.rb +47 -34
  22. data/lib/asciidoctor/cli/options.rb +22 -11
  23. data/lib/asciidoctor/converter.rb +3 -3
  24. data/lib/asciidoctor/converter/base.rb +2 -2
  25. data/lib/asciidoctor/converter/composite.rb +1 -1
  26. data/lib/asciidoctor/converter/docbook45.rb +2 -2
  27. data/lib/asciidoctor/converter/docbook5.rb +132 -87
  28. data/lib/asciidoctor/converter/factory.rb +0 -1
  29. data/lib/asciidoctor/converter/html5.rb +116 -98
  30. data/lib/asciidoctor/converter/manpage.rb +51 -52
  31. data/lib/asciidoctor/converter/template.rb +47 -36
  32. data/lib/asciidoctor/core_ext.rb +8 -2
  33. data/lib/asciidoctor/core_ext/1.8.7/hash/key.rb +4 -0
  34. data/lib/asciidoctor/core_ext/1.8.7/io/binread.rb +6 -0
  35. data/lib/asciidoctor/core_ext/1.8.7/io/write.rb +5 -0
  36. data/lib/asciidoctor/core_ext/1.8.7/string/chr.rb +1 -1
  37. data/lib/asciidoctor/core_ext/1.8.7/string/{limit.rb → limit_bytesize.rb} +7 -6
  38. data/lib/asciidoctor/core_ext/1.8.7/symbol/empty.rb +6 -0
  39. data/lib/asciidoctor/core_ext/1.8.7/symbol/length.rb +1 -1
  40. data/lib/asciidoctor/core_ext/nil_or_empty.rb +5 -5
  41. data/lib/asciidoctor/core_ext/regexp/is_match.rb +3 -0
  42. data/lib/asciidoctor/core_ext/string/{limit.rb → limit_bytesize.rb} +2 -2
  43. data/lib/asciidoctor/document.rb +216 -213
  44. data/lib/asciidoctor/extensions.rb +318 -185
  45. data/lib/asciidoctor/helpers.rb +35 -35
  46. data/lib/asciidoctor/inline.rb +32 -1
  47. data/lib/asciidoctor/list.rb +22 -6
  48. data/lib/asciidoctor/parser.rb +1008 -1038
  49. data/lib/asciidoctor/path_resolver.rb +46 -50
  50. data/lib/asciidoctor/reader.rb +275 -251
  51. data/lib/asciidoctor/section.rb +86 -58
  52. data/lib/asciidoctor/stylesheets.rb +6 -6
  53. data/lib/asciidoctor/substitutors.rb +567 -649
  54. data/lib/asciidoctor/table.rb +163 -108
  55. data/lib/asciidoctor/version.rb +1 -1
  56. data/man/asciidoctor.1 +18 -16
  57. data/man/asciidoctor.adoc +15 -13
  58. data/test/attributes_test.rb +138 -22
  59. data/test/blocks_test.rb +377 -97
  60. data/test/converter_test.rb +13 -0
  61. data/test/document_test.rb +244 -34
  62. data/test/extensions_test.rb +409 -42
  63. data/test/fixtures/asciidoc_index.txt +521 -0
  64. data/test/fixtures/basic-docinfo-footer.html +6 -0
  65. data/test/fixtures/basic-docinfo-footer.xml +8 -0
  66. data/test/fixtures/basic-docinfo.html +1 -0
  67. data/test/fixtures/basic-docinfo.xml +4 -0
  68. data/test/fixtures/basic.asciidoc +5 -0
  69. data/test/fixtures/chapter-a.adoc +3 -0
  70. data/test/fixtures/child-include.adoc +5 -0
  71. data/test/fixtures/circle.svg +9 -0
  72. data/test/fixtures/custom-backends/erb/html5/block_paragraph.html.erb +6 -0
  73. data/test/fixtures/custom-backends/haml/docbook45/block_paragraph.xml.haml +6 -0
  74. data/test/fixtures/custom-backends/haml/html5-tweaks/block_paragraph.html.haml +1 -0
  75. data/test/fixtures/custom-backends/haml/html5/block_paragraph.html.haml +3 -0
  76. data/test/fixtures/custom-backends/haml/html5/block_sidebar.html.haml +5 -0
  77. data/test/fixtures/custom-backends/slim/docbook45/block_paragraph.xml.slim +6 -0
  78. data/test/fixtures/custom-backends/slim/html5/block_paragraph.html.slim +3 -0
  79. data/test/fixtures/custom-backends/slim/html5/block_sidebar.html.slim +5 -0
  80. data/test/fixtures/custom-docinfodir/basic-docinfo.html +1 -0
  81. data/test/fixtures/custom-docinfodir/docinfo.html +1 -0
  82. data/test/fixtures/docinfo-footer.html +1 -0
  83. data/test/fixtures/docinfo-footer.xml +9 -0
  84. data/test/fixtures/docinfo.html +1 -0
  85. data/test/fixtures/docinfo.xml +3 -0
  86. data/test/fixtures/dot.gif +0 -0
  87. data/test/fixtures/encoding.asciidoc +13 -0
  88. data/test/fixtures/grandchild-include.adoc +3 -0
  89. data/test/fixtures/hello-asciidoctor.pdf +69 -0
  90. data/test/fixtures/include-file.asciidoc +24 -0
  91. data/test/fixtures/include-file.ml +3 -0
  92. data/test/fixtures/include-file.xml +5 -0
  93. data/test/fixtures/master.adoc +5 -0
  94. data/test/fixtures/mismatched-end-tag.adoc +7 -0
  95. data/test/fixtures/parent-include-restricted.adoc +5 -0
  96. data/test/fixtures/parent-include.adoc +5 -0
  97. data/test/fixtures/sample.asciidoc +26 -0
  98. data/test/fixtures/stylesheets/custom.css +3 -0
  99. data/test/fixtures/subs-docinfo.html +2 -0
  100. data/test/fixtures/subs.adoc +7 -0
  101. data/test/fixtures/tagged-class-enclosed.rb +26 -0
  102. data/test/fixtures/tagged-class.rb +23 -0
  103. data/test/fixtures/tip.gif +0 -0
  104. data/test/invoker_test.rb +82 -4
  105. data/test/links_test.rb +312 -37
  106. data/test/lists_test.rb +204 -25
  107. data/test/manpage_test.rb +191 -4
  108. data/test/options_test.rb +18 -1
  109. data/test/paragraphs_test.rb +32 -7
  110. data/test/parser_test.rb +150 -30
  111. data/test/paths_test.rb +47 -13
  112. data/test/preamble_test.rb +1 -1
  113. data/test/reader_test.rb +366 -126
  114. data/test/sections_test.rb +203 -56
  115. data/test/substitutions_test.rb +339 -131
  116. data/test/tables_test.rb +315 -15
  117. data/test/test_helper.rb +400 -0
  118. data/test/text_test.rb +5 -5
  119. metadata +110 -22
@@ -128,28 +128,27 @@ class PathResolver
128
128
  else
129
129
  @working_dir = ::File.expand_path ::Dir.pwd
130
130
  end
131
- @_partition_path_sys = {}
132
- @_partition_path_web = {}
131
+ @_partition_path_sys, @_partition_path_web = {}, {}
133
132
  end
134
133
 
135
- # Public: Check if the specified path is an absolute root path
136
- # This operation correctly handles both posix and windows paths.
134
+ # Public: Check if the specified path is an absolute root path.
135
+ # This operation considers posix paths, windows paths, and file URLs.
136
+ #
137
+ # Unix absolute paths and UNC paths start with slash. Windows roots can start
138
+ # with a drive letter. Absolute paths in the browser start with file://.
137
139
  #
138
140
  # path - the String path to check
139
141
  #
140
142
  # returns a Boolean indicating whether the path is an absolute root path
141
- def is_root? path
142
- # Unix absolute paths and UNC paths start with slash
143
- if path.start_with? SLASH
144
- true
145
- # Windows roots can begin with drive letter
146
- elsif @file_separator == BACKSLASH && WindowsRootRx =~ path
147
- true
148
- # Absolute paths in the browser start with file:///
149
- elsif ::RUBY_ENGINE_OPAL && ::JAVASCRIPT_PLATFORM == 'browser' && (path.start_with? 'file:///')
150
- true
151
- else
152
- false
143
+ if RUBY_ENGINE == 'opal'
144
+ def is_root? path
145
+ (path.start_with? SLASH) ||
146
+ (::JAVASCRIPT_IO_MODULE == 'xmlhttprequest' && (path.start_with? 'file://')) ||
147
+ (@file_separator == BACKSLASH && (WindowsRootRx.match? path))
148
+ end
149
+ else
150
+ def is_root? path
151
+ (path.start_with? SLASH) || (@file_separator == BACKSLASH && (WindowsRootRx.match? path))
153
152
  end
154
153
  end
155
154
 
@@ -176,7 +175,7 @@ class PathResolver
176
175
  # path - the String path to normalize
177
176
  #
178
177
  # returns a String path with any backslashes replaced with forward slashes
179
- def posixfy path
178
+ def posixify path
180
179
  if path.nil_or_empty?
181
180
  ''
182
181
  elsif path.include? BACKSLASH
@@ -185,6 +184,7 @@ class PathResolver
185
184
  path
186
185
  end
187
186
  end
187
+ alias posixfy posixify
188
188
 
189
189
  # Public: Expand the path by resolving any parent references (..)
190
190
  # and cleaning self references (.).
@@ -206,23 +206,23 @@ class PathResolver
206
206
  # or segments that are self references (.). The path is converted to a posix
207
207
  # path before being partitioned.
208
208
  #
209
- # path - the String path to partition
210
- # web_path - a Boolean indicating whether the path should be handled
211
- # as a web path (optional, default: false)
209
+ # path - the String path to partition
210
+ # web - a Boolean indicating whether the path should be handled
211
+ # as a web path (optional, default: false)
212
212
  #
213
213
  # Returns a 3-item Array containing the Array of String path segments, the
214
214
  # path root (e.g., '/', './', 'c:/') if the path is absolute and the posix
215
215
  # version of the path.
216
216
  #--
217
217
  # QUESTION is it worth it to normalize slashes? it doubles the time elapsed
218
- def partition_path path, web_path = false
219
- if (result = web_path ? @_partition_path_web[path] : @_partition_path_sys[path])
218
+ def partition_path path, web = nil
219
+ if (result = (cache = web ? @_partition_path_web : @_partition_path_sys)[path])
220
220
  return result
221
221
  end
222
222
 
223
- posix_path = posixfy path
223
+ posix_path = posixify path
224
224
 
225
- root = if web_path
225
+ root = if web
226
226
  # ex. /sample/path
227
227
  if is_web_root? posix_path
228
228
  SLASH
@@ -241,9 +241,9 @@ class PathResolver
241
241
  # ex. /sample/path
242
242
  elsif posix_path.start_with? SLASH
243
243
  SLASH
244
- # ex. c:/sample/path (or file:///sample/path in browser environment)
244
+ # ex. C:/sample/path (or file:///sample/path in browser environment)
245
245
  else
246
- posix_path[0..(posix_path.index SLASH)]
246
+ posix_path.slice 0, (posix_path.index SLASH) + 1
247
247
  end
248
248
  # ex. ./sample/path
249
249
  elsif posix_path.start_with? DOT_SLASH
@@ -260,7 +260,7 @@ class PathResolver
260
260
  path_segments = path_segments[2..-1]
261
261
  # shift twice for a file:/// path and adjust root
262
262
  # NOTE technically file:/// paths work without this adjustment
263
- #elsif ::RUBY_ENGINE_OPAL && ::JAVASCRIPT_PLATFORM == 'browser' && root == 'file:/'
263
+ #elsif ::RUBY_ENGINE_OPAL && ::JAVASCRIPT_IO_MODULE == 'xmlhttprequest' && root == 'file:/'
264
264
  # root = 'file://'
265
265
  # path_segments = path_segments[2..-1]
266
266
  # shift once for any other root
@@ -269,9 +269,9 @@ class PathResolver
269
269
  end
270
270
  # strip out all dot entries
271
271
  path_segments.delete DOT
272
- # QUESTION should we chomp trailing /? (we pay a small fraction)
273
- #posix_path = posix_path.chomp '/'
274
- (web_path ? @_partition_path_web : @_partition_path_sys)[path] = [path_segments, root, posix_path]
272
+ # QUESTION should we chop trailing /? (we pay a small fraction)
273
+ #posix_path = posix_path.chop if posix_path.end_with? SLASH
274
+ cache[path] = [path_segments, root, posix_path]
275
275
  end
276
276
 
277
277
  # Public: Join the segments using the posix file separator (since Ruby knows
@@ -285,11 +285,7 @@ class PathResolver
285
285
  # returns a String path formed by joining the segments using the posix file
286
286
  # separator and prepending the root, if specified
287
287
  def join_path segments, root = nil
288
- if root
289
- %(#{root}#{segments * SLASH})
290
- else
291
- segments * SLASH
292
- end
288
+ root ? %(#{root}#{segments * SLASH}) : segments * SLASH
293
289
  end
294
290
 
295
291
  # Public: Resolve a system path from the target and start paths. If a jail
@@ -300,8 +296,8 @@ class PathResolver
300
296
  # specified in the constructor.
301
297
  #
302
298
  # target - the String target path
303
- # start - the String start (i.e., parent) path
304
- # jail - the String jail path to confine the resolved path
299
+ # start - the String start (i.e., parent) path (default: nil)
300
+ # jail - the String jail path to confine the resolved path (default: nil)
305
301
  # opts - an optional Hash of options to control processing (default: {}):
306
302
  # * :recover is used to control whether the processor should auto-recover
307
303
  # when an illegal path is encountered
@@ -310,12 +306,12 @@ class PathResolver
310
306
  # returns a String path that joins the target path with the start path with
311
307
  # any parent references resolved and self references removed and enforces
312
308
  # that the resolved path be contained within the jail, if provided
313
- def system_path target, start, jail = nil, opts = {}
309
+ def system_path target, start = nil, jail = nil, opts = {}
314
310
  if jail
315
311
  unless is_root? jail
316
312
  raise ::SecurityError, %(Jail is not an absolute path: #{jail})
317
313
  end
318
- jail = posixfy jail
314
+ jail = posixify jail
319
315
  end
320
316
 
321
317
  if target.nil_or_empty?
@@ -348,7 +344,7 @@ class PathResolver
348
344
  if start.nil_or_empty?
349
345
  start = jail ? jail : @working_dir
350
346
  elsif is_root? start
351
- start = posixfy start
347
+ start = posixify start
352
348
  else
353
349
  start = system_path start, jail, jail, opts
354
350
  end
@@ -379,7 +375,7 @@ class PathResolver
379
375
  target_segments.each do |segment|
380
376
  if segment == DOT_DOT
381
377
  if jail
382
- if resolved_segments.length > jail_segments.length
378
+ if resolved_segments.size > jail_segments.size
383
379
  resolved_segments.pop
384
380
  elsif !(recover ||= (opts.fetch :recover, true))
385
381
  raise ::SecurityError, %(#{opts[:target_name] || 'path'} #{target} refers to location outside jail: #{jail} (disallowed in safe mode))
@@ -391,7 +387,7 @@ class PathResolver
391
387
  resolved_segments.pop
392
388
  end
393
389
  else
394
- resolved_segments.push segment
390
+ resolved_segments << segment
395
391
  end
396
392
  end
397
393
 
@@ -412,12 +408,12 @@ class PathResolver
412
408
  # start path with any parent references resolved and self
413
409
  # references removed
414
410
  def web_path target, start = nil
415
- target = posixfy target
416
- start = posixfy start
411
+ target = posixify target
412
+ start = posixify start
417
413
  uri_prefix = nil
418
414
 
419
415
  unless start.nil_or_empty? || (is_web_root? target)
420
- target = %(#{start.chomp '/'}#{SLASH}#{target})
416
+ target = (start.end_with? SLASH) ? %(#{start}#{target}) : %(#{start}#{SLASH}#{target})
421
417
  if (uri_prefix = Helpers.uri_prefix target)
422
418
  target = target[uri_prefix.length..-1]
423
419
  end
@@ -453,11 +449,11 @@ class PathResolver
453
449
  end
454
450
  end
455
451
 
456
- if uri_prefix
457
- %(#{uri_prefix}#{join_path resolved_segments, target_root})
458
- else
459
- join_path resolved_segments, target_root
452
+ if (resolved_path = join_path resolved_segments, target_root).include? ' '
453
+ resolved_path = resolved_path.gsub ' ', '%20'
460
454
  end
455
+
456
+ uri_prefix ? %(#{uri_prefix}#{resolved_path}) : resolved_path
461
457
  end
462
458
 
463
459
  # Public: Calculate the relative path to this absolute filename from the specified base directory
@@ -470,7 +466,7 @@ class PathResolver
470
466
  # Return the relative path String of the filename calculated from the base directory
471
467
  def relative_path filename, base_directory
472
468
  if (is_root? filename) && (is_root? base_directory)
473
- offset = base_directory.chomp(@file_separator).length + 1
469
+ offset = (base_directory.end_with? @file_separator) ? base_directory.length : base_directory.length + 1
474
470
  filename[offset..-1]
475
471
  else
476
472
  filename
@@ -19,7 +19,7 @@ class Reader
19
19
  %(#{path}: line #{lineno})
20
20
  end
21
21
 
22
- alias :to_s :line_info
22
+ alias to_s line_info
23
23
  end
24
24
 
25
25
  attr_reader :file
@@ -36,7 +36,7 @@ class Reader
36
36
  attr_accessor :process_lines
37
37
 
38
38
  # Public: Initialize the Reader object
39
- def initialize data = nil, cursor = nil, opts = {:normalize => false}
39
+ def initialize data = nil, cursor = nil, opts = {}
40
40
  if !cursor
41
41
  @file = @dir = nil
42
42
  @path = '<stdin>'
@@ -88,7 +88,7 @@ class Reader
88
88
  if opts[:normalize]
89
89
  Helpers.normalize_lines_from_string data
90
90
  else
91
- data.split EOL
91
+ data.split LF, -1
92
92
  end
93
93
  else
94
94
  if opts[:normalize]
@@ -133,16 +133,19 @@ class Reader
133
133
  peek_line.nil_or_empty?
134
134
  end
135
135
 
136
- # Public: Peek at the next line of source data. Processes the line, if not
136
+ # Public: Peek at the next line of source data. Processes the line if not
137
137
  # already marked as processed, but does not consume it.
138
138
  #
139
139
  # This method will probe the reader for more lines. If there is a next line
140
140
  # that has not previously been visited, the line is passed to the
141
141
  # Reader#process_line method to be initialized. This call gives
142
- # sub-classess the opportunity to do preprocessing. If the return value of
142
+ # sub-classes the opportunity to do preprocessing. If the return value of
143
143
  # the Reader#process_line is nil, the data is assumed to be changed and
144
144
  # Reader#peek_line is invoked again to perform further processing.
145
145
  #
146
+ # If has_more_lines? is called immediately before peek_line, the direct flag
147
+ # is implicitly true (since the line is flagged as visited).
148
+ #
146
149
  # direct - A Boolean flag to bypasses the check for more lines and immediately
147
150
  # returns the first element of the internal @lines Array. (default: false)
148
151
  #
@@ -167,7 +170,7 @@ class Reader
167
170
  end
168
171
  end
169
172
 
170
- # Public: Peek at the next multiple lines of source data. Processes the lines, if not
173
+ # Public: Peek at the next multiple lines of source data. Processes the lines if not
171
174
  # already marked as processed, but does not consume them.
172
175
  #
173
176
  # This method delegates to Reader#read_line to process and collect the line, then
@@ -175,7 +178,7 @@ class Reader
175
178
  # be processed and marked as such so that subsequent reads will not need to process
176
179
  # the lines again.
177
180
  #
178
- # num - The Integer number of lines to peek.
181
+ # num - The positive Integer number of lines to peek (must be greater than 0).
179
182
  # direct - A Boolean indicating whether processing should be disabled when reading lines
180
183
  #
181
184
  # Returns A String Array of the next multiple lines of source data, or an empty Array
@@ -187,12 +190,13 @@ class Reader
187
190
  if (line = read_line direct)
188
191
  result << line
189
192
  else
193
+ @lineno -= 1 if direct
190
194
  break
191
195
  end
192
196
  end
193
197
 
194
198
  unless result.empty?
195
- result.reverse_each {|line| unshift line }
199
+ unshift_all result
196
200
  @look_ahead = old_look_ahead if direct
197
201
  end
198
202
 
@@ -229,7 +233,7 @@ class Reader
229
233
  end
230
234
  lines
231
235
  end
232
- alias :readlines :read_lines
236
+ alias readlines read_lines
233
237
 
234
238
  # Public: Get the remaining lines of source data joined as a String.
235
239
  #
@@ -237,7 +241,7 @@ class Reader
237
241
  #
238
242
  # Returns the lines read joined as a String
239
243
  def read
240
- read_lines * EOL
244
+ read_lines * LF
241
245
  end
242
246
 
243
247
  # Public: Advance to the next line by discarding the line at the front of the stack
@@ -252,8 +256,10 @@ class Reader
252
256
 
253
257
  # Public: Push the String line onto the beginning of the Array of source data.
254
258
  #
255
- # Since this line was (assumed to be) previously retrieved through the
256
- # reader, it is marked as seen.
259
+ # A line pushed on the reader using this method is not processed again. The
260
+ # method assumes the line was previously retrieved from the reader or does
261
+ # not otherwise contain preprocessor directives. Therefore, it is marked as
262
+ # processed immediately.
257
263
  #
258
264
  # line_to_restore - the line to restore onto the stack
259
265
  #
@@ -262,20 +268,21 @@ class Reader
262
268
  unshift line_to_restore
263
269
  nil
264
270
  end
265
- alias :restore_line :unshift_line
271
+ alias restore_line unshift_line
266
272
 
267
273
  # Public: Push an Array of lines onto the front of the Array of source data.
268
274
  #
269
- # Since these lines were (assumed to be) previously retrieved through the
270
- # reader, they are marked as seen.
275
+ # Lines pushed on the reader using this method are not processed again. The
276
+ # method assumes the lines were previously retrieved from the reader or do
277
+ # not otherwise contain preprocessor directives. Therefore, they are marked
278
+ # as processed immediately.
271
279
  #
272
280
  # Returns nothing.
273
281
  def unshift_lines lines_to_restore
274
- # QUESTION is it faster to use unshift(*lines_to_restore)?
275
- lines_to_restore.reverse_each {|line| unshift line }
282
+ unshift_all lines_to_restore
276
283
  nil
277
284
  end
278
- alias :restore_lines :unshift_lines
285
+ alias restore_lines unshift_lines
279
286
 
280
287
  # Public: Replace the next line with the specified line.
281
288
  #
@@ -292,7 +299,7 @@ class Reader
292
299
  nil
293
300
  end
294
301
  # deprecated
295
- alias :replace_line :replace_next_line
302
+ alias replace_line replace_next_line
296
303
 
297
304
  # Public: Strip off leading blank lines in the Array of lines.
298
305
  #
@@ -346,10 +353,10 @@ class Reader
346
353
  while (next_line = peek_line)
347
354
  if include_blank_lines && next_line.empty?
348
355
  comment_lines << shift
349
- elsif (commentish = next_line.start_with?('//')) && (match = CommentBlockRx.match(next_line))
356
+ elsif (commentish = next_line.start_with?('//')) && (CommentBlockRx.match? next_line)
350
357
  comment_lines << shift
351
- comment_lines.push(*(read_lines_until(:terminator => match[0], :read_last_line => true, :skip_processing => true)))
352
- elsif commentish && CommentLineRx =~ next_line
358
+ comment_lines.push(*(read_lines_until(:terminator => next_line, :read_last_line => true, :skip_processing => true)))
359
+ elsif commentish && (CommentLineRx.match? next_line)
353
360
  comment_lines << shift
354
361
  else
355
362
  break
@@ -366,7 +373,7 @@ class Reader
366
373
  comment_lines = []
367
374
  # optimized code for shortest execution path
368
375
  while (next_line = peek_line)
369
- if CommentLineRx =~ next_line
376
+ if CommentLineRx.match? next_line
370
377
  comment_lines << shift
371
378
  else
372
379
  break
@@ -393,7 +400,7 @@ class Reader
393
400
  def eof?
394
401
  !has_more_lines?
395
402
  end
396
- alias :empty? :eof?
403
+ alias empty? eof?
397
404
 
398
405
  # Public: Return all the lines from `@lines` until we (1) run out them,
399
406
  # (2) find a blank line with :break_on_blank_lines => true, or (3) find
@@ -470,7 +477,7 @@ class Reader
470
477
  line_restored = true
471
478
  end
472
479
  else
473
- unless skip_comments && line.start_with?('//') && CommentLineRx =~ line
480
+ unless skip_comments && (line.start_with? '//') && (CommentLineRx.match? line)
474
481
  result << line
475
482
  line_read = true
476
483
  end
@@ -504,6 +511,14 @@ class Reader
504
511
  @lines.unshift line
505
512
  end
506
513
 
514
+ # Internal: Restore the lines to the stack and decrement the lineno
515
+ def unshift_all lines
516
+ @lineno -= lines.size
517
+ @look_ahead += lines.size
518
+ @eof = false
519
+ @lines.unshift(*lines)
520
+ end
521
+
507
522
  def cursor
508
523
  Cursor.new @file, @dir, @path, @lineno
509
524
  end
@@ -514,7 +529,7 @@ class Reader
514
529
  def line_info
515
530
  %(#{@path}: line #{@lineno})
516
531
  end
517
- alias :next_line_info :line_info
532
+ alias next_line_info line_info
518
533
 
519
534
  def prev_line_info
520
535
  %(#{@path}: line #{@lineno - 1})
@@ -529,12 +544,12 @@ class Reader
529
544
 
530
545
  # Public: Get a copy of the remaining lines managed by this Reader joined as a String
531
546
  def string
532
- @lines * EOL
547
+ @lines * LF
533
548
  end
534
549
 
535
550
  # Public: Get the source lines for this Reader joined as a String
536
551
  def source
537
- @source_lines * EOL
552
+ @source_lines * LF
538
553
  end
539
554
 
540
555
  # Public: Get a summary of this Reader.
@@ -553,15 +568,15 @@ class PreprocessorReader < Reader
553
568
  attr_reader :includes
554
569
 
555
570
  # Public: Initialize the PreprocessorReader object
556
- def initialize document, data = nil, cursor = nil
571
+ def initialize document, data = nil, cursor = nil, opts = {}
557
572
  @document = document
558
- super data, cursor, :normalize => true
573
+ super data, cursor, opts
559
574
  include_depth_default = document.attributes.fetch('max-include-depth', 64).to_i
560
575
  include_depth_default = 0 if include_depth_default < 0
561
576
  # track both absolute depth for comparing to size of include stack and relative depth for reporting
562
577
  @maxdepth = {:abs => include_depth_default, :rel => include_depth_default}
563
578
  @include_stack = []
564
- @includes = (document.references[:includes] ||= [])
579
+ @includes = document.catalog[:includes]
565
580
  @skipping = false
566
581
  @conditional_stack = []
567
582
  @include_processor_extensions = nil
@@ -571,9 +586,9 @@ class PreprocessorReader < Reader
571
586
  result = super
572
587
 
573
588
  # QUESTION should this work for AsciiDoc table cell content? Currently it does not.
574
- if @document && (@document.attributes.has_key? 'skip-front-matter')
589
+ if @document && (@document.attributes.key? 'skip-front-matter')
575
590
  if (front_matter = skip_front_matter! result)
576
- @document.attributes['front-matter'] = front_matter * EOL
591
+ @document.attributes['front-matter'] = front_matter * LF
577
592
  end
578
593
  end
579
594
 
@@ -599,45 +614,41 @@ class PreprocessorReader < Reader
599
614
 
600
615
  # NOTE highly optimized
601
616
  if line.end_with?(']') && !line.start_with?('[') && line.include?('::')
602
- if line.include?('if') && (match = ConditionalDirectiveRx.match(line))
617
+ if (line.include? 'if') && ConditionalDirectiveRx =~ line
603
618
  # if escaped, mark as processed and return line unescaped
604
- if line.start_with?('\\')
619
+ if $1 == '\\'
605
620
  @unescape_next_line = true
606
621
  @look_ahead += 1
607
622
  line[1..-1]
623
+ elsif preprocess_conditional_directive $2, $3, $4, $5
624
+ # move the pointer past the conditional line
625
+ advance
626
+ # treat next line as uncharted territory
627
+ nil
608
628
  else
609
- if preprocess_conditional_inclusion(*match.captures)
610
- # move the pointer past the conditional line
611
- advance
612
- # treat next line as uncharted territory
613
- nil
614
- else
615
- # the line was not a valid conditional line
616
- # mark it as visited and return it
617
- @look_ahead += 1
618
- line
619
- end
629
+ # the line was not a valid conditional line
630
+ # mark it as visited and return it
631
+ @look_ahead += 1
632
+ line
620
633
  end
621
634
  elsif @skipping
622
635
  advance
623
636
  nil
624
- elsif ((escaped = line.start_with?('\\include::')) || line.start_with?('include::')) && (match = IncludeDirectiveRx.match(line))
637
+ elsif (line.start_with? 'inc', '\\inc') && IncludeDirectiveRx =~ line
625
638
  # if escaped, mark as processed and return line unescaped
626
- if escaped
639
+ if $1 == '\\'
627
640
  @unescape_next_line = true
628
641
  @look_ahead += 1
629
642
  line[1..-1]
643
+ # QUESTION should we strip whitespace from raw attributes in Substitutors#parse_attributes? (check perf)
644
+ elsif preprocess_include_directive $2, $3.strip
645
+ # peek again since the content has changed
646
+ nil
630
647
  else
631
- # QUESTION should we strip whitespace from raw attributes in Substitutors#parse_attributes? (check perf)
632
- if preprocess_include match[1], match[2].strip
633
- # peek again since the content has changed
634
- nil
635
- else
636
- # the line was not a valid include line and is unchanged
637
- # mark it as visited and return it
638
- @look_ahead += 1
639
- line
640
- end
648
+ # the line was not a valid include line and is unchanged
649
+ # mark it as visited and return it
650
+ @look_ahead += 1
651
+ line
641
652
  end
642
653
  else
643
654
  # NOTE optimization to inline super
@@ -672,95 +683,89 @@ class PreprocessorReader < Reader
672
683
  end
673
684
  end
674
685
 
675
- # Internal: Preprocess the directive (macro) to conditionally include content.
676
- #
677
- # Preprocess the conditional inclusion directive (ifdef, ifndef, ifeval,
678
- # endif) under the cursor. If the Reader is currently skipping content, then
679
- # simply track the open and close delimiters of any nested conditional
680
- # blocks. If the Reader is not skipping, mark whether the condition is
681
- # satisfied and continue preprocessing recursively until the next line of
682
- # available content is found.
683
- #
684
- # directive - The conditional inclusion directive (ifdef, ifndef, ifeval, endif)
685
- # target - The target, which is the name of one or more attributes that are
686
- # used in the condition (blank in the case of the ifeval directive)
687
- # delimiter - The conditional delimiter for multiple attributes ('+' means all
688
- # attributes must be defined or undefined, ',' means any of the attributes
689
- # can be defined or undefined.
690
- # text - The text associated with this directive (occurring between the square brackets)
691
- # Used for a single-line conditional block in the case of the ifdef or
692
- # ifndef directives, and for the conditional expression for the ifeval directive.
686
+ # Internal: Preprocess the directive to conditionally include or exclude content.
687
+ #
688
+ # Preprocess the conditional directive (ifdef, ifndef, ifeval, endif) under
689
+ # the cursor. If Reader is currently skipping content, then simply track the
690
+ # open and close delimiters of any nested conditional blocks. If Reader is
691
+ # not skipping, mark whether the condition is satisfied and continue
692
+ # preprocessing recursively until the next line of available content is
693
+ # found.
694
+ #
695
+ # keyword - The conditional inclusion directive (ifdef, ifndef, ifeval, endif)
696
+ # target - The target, which is the name of one or more attributes that are
697
+ # used in the condition (blank in the case of the ifeval directive)
698
+ # delimiter - The conditional delimiter for multiple attributes ('+' means all
699
+ # attributes must be defined or undefined, ',' means any of the attributes
700
+ # can be defined or undefined.
701
+ # text - The text associated with this directive (occurring between the square brackets)
702
+ # Used for a single-line conditional block in the case of the ifdef or
703
+ # ifndef directives, and for the conditional expression for the ifeval directive.
693
704
  #
694
705
  # Returns a Boolean indicating whether the cursor should be advanced
695
- def preprocess_conditional_inclusion directive, target, delimiter, text
706
+ def preprocess_conditional_directive keyword, target, delimiter, text
707
+ # attributes are case insensitive
708
+ target = target.downcase unless (no_target = target.empty?)
709
+
696
710
  # must have a target before brackets if ifdef or ifndef
697
711
  # must not have text between brackets if endif
698
- # don't honor match if it doesn't meet this criteria
712
+ # skip line if it doesn't meet this criteria
699
713
  # QUESTION should we warn for these bogus declarations?
700
- if ((directive == 'ifdef' || directive == 'ifndef') && target.empty?) ||
701
- (directive == 'endif' && text)
702
- return false
703
- end
714
+ return false if (no_target && (keyword == 'ifdef' || keyword == 'ifndef')) || (text && keyword == 'endif')
704
715
 
705
- # attributes are case insensitive
706
- target = target.downcase
707
-
708
- if directive == 'endif'
709
- stack_size = @conditional_stack.size
710
- if stack_size > 0
711
- pair = @conditional_stack[-1]
712
- if target.empty? || target == pair[:target]
713
- @conditional_stack.pop
714
- @skipping = @conditional_stack.empty? ? false : @conditional_stack[-1][:skipping]
715
- else
716
- warn %(asciidoctor: ERROR: #{line_info}: mismatched macro: endif::#{target}[], expected endif::#{pair[:target]}[])
717
- end
718
- else
716
+ if keyword == 'endif'
717
+ if @conditional_stack.empty?
719
718
  warn %(asciidoctor: ERROR: #{line_info}: unmatched macro: endif::#{target}[])
719
+ elsif no_target || target == (pair = @conditional_stack[-1])[:target]
720
+ @conditional_stack.pop
721
+ @skipping = @conditional_stack.empty? ? false : @conditional_stack[-1][:skipping]
722
+ else
723
+ warn %(asciidoctor: ERROR: #{line_info}: mismatched macro: endif::#{target}[], expected endif::#{pair[:target]}[])
720
724
  end
721
725
  return true
722
726
  end
723
727
 
724
- skip = false
725
- unless @skipping
728
+ if @skipping
729
+ skip = false
730
+ else
726
731
  # QUESTION any way to wrap ifdef & ifndef logic up together?
727
- case directive
732
+ case keyword
728
733
  when 'ifdef'
729
734
  case delimiter
730
- when nil
731
- # if the attribute is undefined, then skip
732
- skip = !@document.attributes.has_key?(target)
733
735
  when ','
734
- # if any attribute is defined, then don't skip
735
- skip = target.split(',').none? {|name| @document.attributes.has_key? name }
736
+ # skip if no attribute is defined
737
+ skip = target.split(',', -1).none? {|name| @document.attributes.key? name }
736
738
  when '+'
737
- # if any attribute is undefined, then skip
738
- skip = target.split('+').any? {|name| !@document.attributes.has_key? name }
739
+ # skip if any attribute is undefined
740
+ skip = target.split('+', -1).any? {|name| !@document.attributes.key? name }
741
+ else
742
+ # if the attribute is undefined, then skip
743
+ skip = !@document.attributes.key?(target)
739
744
  end
740
745
  when 'ifndef'
741
746
  case delimiter
742
- when nil
743
- # if the attribute is defined, then skip
744
- skip = @document.attributes.has_key?(target)
745
747
  when ','
746
- # if any attribute is undefined, then don't skip
747
- skip = target.split(',').none? {|name| !@document.attributes.has_key? name }
748
+ # skip if any attribute is defined
749
+ skip = target.split(',', -1).any? {|name| @document.attributes.key? name }
748
750
  when '+'
749
- # if any attribute is defined, then skip
750
- skip = target.split('+').any? {|name| @document.attributes.has_key? name }
751
+ # skip if all attributes are defined
752
+ skip = target.split('+', -1).all? {|name| @document.attributes.key? name }
753
+ else
754
+ # if the attribute is defined, then skip
755
+ skip = @document.attributes.key?(target)
751
756
  end
752
757
  when 'ifeval'
753
758
  # the text in brackets must match an expression
754
759
  # don't honor match if it doesn't meet this criteria
755
- if !target.empty? || !(expr_match = EvalExpressionRx.match(text.strip))
756
- return false
757
- end
760
+ return false unless no_target && EvalExpressionRx =~ text.strip
758
761
 
759
- lhs = resolve_expr_val expr_match[1]
760
- rhs = resolve_expr_val expr_match[3]
762
+ # NOTE save values eagerly for Ruby 1.8.7 compat
763
+ lhs, op, rhs = $1, $2, $3
764
+ lhs = resolve_expr_val lhs
765
+ rhs = resolve_expr_val rhs
761
766
 
762
767
  # regex enforces a restricted set of math-related operations
763
- if (op = expr_match[2]) == '!='
768
+ if op == '!='
764
769
  skip = lhs.send :==, rhs
765
770
  else
766
771
  skip = !(lhs.send op.to_sym, rhs)
@@ -769,26 +774,25 @@ class PreprocessorReader < Reader
769
774
  end
770
775
 
771
776
  # conditional inclusion block
772
- if directive == 'ifeval' || !text
777
+ if keyword == 'ifeval' || !text
773
778
  @skipping = true if skip
774
779
  @conditional_stack << {:target => target, :skip => skip, :skipping => @skipping}
775
780
  # single line conditional inclusion
776
781
  else
777
782
  unless @skipping || skip
778
- # FIXME slight hack to skip past conditional line
779
- # but keep our synthetic line marked as processed
780
- # QUESTION can we use read_line true and unshift twice instead?
781
- conditional_line = peek_line true
782
783
  replace_next_line text.rstrip
783
- unshift conditional_line
784
- return true
784
+ # HACK push dummy line to stand in for the opening conditional directive that's subsequently dropped
785
+ unshift ''
786
+ # NOTE force line to be processed again if it looks like an include directive
787
+ # QUESTION should we just call preprocess_include_directive here?
788
+ @look_ahead -= 1 if text.start_with? 'include::'
785
789
  end
786
790
  end
787
791
 
788
792
  true
789
793
  end
790
794
 
791
- # Internal: Preprocess the directive (macro) to include the target document.
795
+ # Internal: Preprocess the directive to include lines from another document.
792
796
  #
793
797
  # Preprocess the directive to include the target document. The scenarios
794
798
  # are as follows:
@@ -806,23 +810,21 @@ class PreprocessorReader < Reader
806
810
  # If none of the above apply, emit the include directive line verbatim.
807
811
  #
808
812
  # target - The name of the source document to include as specified in the
809
- # target slot of the include::[] macro
813
+ # target slot of the include::[] directive
810
814
  #
811
815
  # Returns a Boolean indicating whether the line under the cursor has changed.
812
- def preprocess_include raw_target, raw_attributes
813
- if (target = @document.sub_attributes raw_target, :attribute_missing => 'drop-line').empty?
816
+ def preprocess_include_directive raw_target, raw_attributes
817
+ if ((target = raw_target).include? '{') &&
818
+ (target = @document.sub_attributes raw_target, :attribute_missing => 'drop-line').empty?
814
819
  advance
815
820
  if @document.attributes.fetch('attribute-missing', Compliance.attribute_missing) == 'skip'
816
821
  unshift %(Unresolved directive in #{@path} - include::#{raw_target}[#{raw_attributes}])
817
822
  end
818
823
  true
819
- # assume that if an include processor is given, the developer wants
820
- # to handle when and how to process the include
821
- elsif include_processors? &&
822
- (extension = @include_processor_extensions.find {|candidate| candidate.instance.handles? target })
824
+ elsif include_processors? && (ext = @include_processor_extensions.find {|candidate| candidate.instance.handles? target })
823
825
  advance
824
- # FIXME parse attributes if requested by extension
825
- extension.process_method[@document, self, target, AttributeList.new(raw_attributes).parse]
826
+ # FIXME parse attributes only if requested by extension
827
+ ext.process_method[@document, self, target, AttributeList.new(raw_attributes).parse]
826
828
  true
827
829
  # if running in SafeMode::SECURE or greater, don't process this directive
828
830
  # however, be friendly and at least make it a link to the source document
@@ -835,25 +837,20 @@ class PreprocessorReader < Reader
835
837
  warn %(asciidoctor: ERROR: #{line_info}: maximum include depth of #{@maxdepth[:rel]} exceeded)
836
838
  return false
837
839
  end
838
- if ::RUBY_ENGINE_OPAL
840
+ if ::RUBY_ENGINE_OPAL && ::JAVASCRIPT_IO_MODULE == 'xmlhttprequest'
839
841
  # NOTE resolves uri relative to currently loaded document
840
842
  # NOTE we defer checking if file exists and catch the 404 error if it does not
841
- # TODO only use this logic if env-browser is set
842
843
  target_type = :file
843
- include_file = path = if @include_stack.empty?
844
- ::Dir.pwd == @document.base_dir ? target : (::File.join @dir, target)
845
- else
846
- ::File.join @dir, target
847
- end
844
+ inc_path = relpath = @include_stack.empty? && ::Dir.pwd == @document.base_dir ? target : (::File.join @dir, target)
848
845
  elsif Helpers.uriish? target
849
- unless @document.attributes.has_key? 'allow-uri-read'
846
+ unless @document.attributes.key? 'allow-uri-read'
850
847
  replace_next_line %(link:#{target}[])
851
848
  return true
852
849
  end
853
850
 
854
851
  target_type = :uri
855
- include_file = path = target
856
- if @document.attributes.has_key? 'cache-uri'
852
+ inc_path = relpath = target
853
+ if @document.attributes.key? 'cache-uri'
857
854
  # caching requires the open-uri-cached gem to be installed
858
855
  # processing will be automatically aborted if these libraries can't be opened
859
856
  Helpers.require_library 'open-uri/cached', 'open-uri-cached' unless defined? ::OpenURI::Cache
@@ -864,130 +861,159 @@ class PreprocessorReader < Reader
864
861
  else
865
862
  target_type = :file
866
863
  # include file is resolved relative to dir of current include, or base_dir if within original docfile
867
- include_file = @document.normalize_system_path(target, @dir, nil, :target_name => 'include file')
868
- unless ::File.file? include_file
869
- warn %(asciidoctor: WARNING: #{line_info}: include file not found: #{include_file})
864
+ inc_path = @document.normalize_system_path target, @dir, nil, :target_name => 'include file'
865
+ unless ::File.file? inc_path
866
+ warn %(asciidoctor: WARNING: #{line_info}: include file not found: #{inc_path})
870
867
  replace_next_line %(Unresolved directive in #{@path} - include::#{target}[#{raw_attributes}])
871
868
  return true
872
869
  end
873
- #path = @document.relative_path include_file
874
- path = PathResolver.new.relative_path include_file, @document.base_dir
870
+ # NOTE relpath is the path relative to the outermost document (or base_dir, if set)
871
+ #relpath = @document.relative_path inc_path
872
+ relpath = PathResolver.new.relative_path inc_path, @document.base_dir
875
873
  end
876
874
 
877
- inc_lines = nil
878
- tags = nil
879
- attributes = {}
880
- if !raw_attributes.empty?
875
+ inc_linenos, inc_tags, attributes = nil, nil, {}
876
+ unless raw_attributes.empty?
881
877
  # QUESTION should we use @document.parse_attribues?
882
878
  attributes = AttributeList.new(raw_attributes).parse
883
- if attributes.has_key? 'lines'
884
- inc_lines = []
879
+ if attributes.key? 'lines'
880
+ inc_linenos = []
885
881
  attributes['lines'].split(DataDelimiterRx).each do |linedef|
886
882
  if linedef.include?('..')
887
- from, to = linedef.split('..', 2).map(&:to_i)
883
+ from, to = linedef.split('..', 2).map {|it| it.to_i }
888
884
  if to == -1
889
- inc_lines << from
890
- inc_lines << 1.0/0.0
885
+ inc_linenos << from
886
+ inc_linenos << 1.0/0.0
891
887
  else
892
- inc_lines.concat ::Range.new(from, to).to_a
888
+ inc_linenos.concat ::Range.new(from, to).to_a
893
889
  end
894
890
  else
895
- inc_lines << linedef.to_i
891
+ inc_linenos << linedef.to_i
896
892
  end
897
893
  end
898
- inc_lines = inc_lines.sort.uniq
899
- elsif attributes.has_key? 'tag'
900
- tags = [attributes['tag']].to_set
901
- elsif attributes.has_key? 'tags'
902
- tags = attributes['tags'].split(DataDelimiterRx).to_set
894
+ inc_linenos = inc_linenos.empty? ? nil : inc_linenos.sort.uniq
895
+ elsif attributes.key? 'tag'
896
+ unless (tag = attributes['tag']).empty?
897
+ if tag.start_with? '!'
898
+ inc_tags = { (tag.slice 1, tag.length) => false } unless tag == '!'
899
+ else
900
+ inc_tags = { tag => true }
901
+ end
902
+ end
903
+ elsif attributes.key? 'tags'
904
+ inc_tags = {}
905
+ attributes['tags'].split(DataDelimiterRx).each do |tagdef|
906
+ if tagdef.start_with? '!'
907
+ inc_tags[tagdef.slice 1, tagdef.length] = false unless tagdef == '!'
908
+ else
909
+ inc_tags[tagdef] = true
910
+ end unless tagdef.empty?
911
+ end
912
+ inc_tags = nil if inc_tags.empty?
903
913
  end
904
914
  end
905
- if inc_lines
906
- unless inc_lines.empty?
907
- selected = []
908
- inc_line_offset = 0
909
- inc_lineno = 0
910
- begin
911
- open(include_file, 'r') do |f|
912
- f.each_line do |l|
913
- inc_lineno += 1
914
- take = inc_lines[0]
915
- if ::Float === take && take.infinite?
916
- selected.push l
917
- inc_line_offset = inc_lineno if inc_line_offset == 0
918
- else
919
- if f.lineno == take
920
- selected.push l
921
- inc_line_offset = inc_lineno if inc_line_offset == 0
922
- inc_lines.shift
923
- end
924
- break if inc_lines.empty?
915
+
916
+ if inc_linenos
917
+ inc_lines, inc_offset, inc_lineno = [], nil, 0
918
+ begin
919
+ open(inc_path, 'r') do |f|
920
+ f.each_line do |l|
921
+ inc_lineno += 1
922
+ select = inc_linenos[0]
923
+ if ::Float === select && select.infinite?
924
+ # NOTE record line where we started selecting
925
+ inc_offset ||= inc_lineno
926
+ inc_lines << l
927
+ else
928
+ if inc_lineno == select
929
+ # NOTE record line where we started selecting
930
+ inc_offset ||= inc_lineno
931
+ inc_lines << l
932
+ inc_linenos.shift
925
933
  end
934
+ break if inc_linenos.empty?
926
935
  end
927
936
  end
928
- rescue
929
- warn %(asciidoctor: WARNING: #{line_info}: include #{target_type} not readable: #{include_file})
930
- replace_next_line %(Unresolved directive in #{@path} - include::#{target}[#{raw_attributes}])
931
- return true
932
937
  end
933
- advance
934
- # FIXME not accounting for skipped lines in reader line numbering
935
- push_include selected, include_file, path, inc_line_offset, attributes
938
+ rescue
939
+ warn %(asciidoctor: WARNING: #{line_info}: include #{target_type} not readable: #{inc_path})
940
+ replace_next_line %(Unresolved directive in #{@path} - include::#{target}[#{raw_attributes}])
941
+ return true
936
942
  end
937
- elsif tags
938
- unless tags.empty?
939
- selected = []
940
- inc_line_offset = 0
941
- inc_lineno = 0
942
- active_tag = nil
943
- tags_found = ::Set.new
944
- begin
945
- open(include_file, 'r') do |f|
946
- f.each_line do |l|
947
- inc_lineno += 1
948
- # must force encoding here since we're performing String operations on line
949
- l.force_encoding(::Encoding::UTF_8) if FORCE_ENCODING
950
- l = l.rstrip
951
- # tagged lines in XML may end with '-->'
952
- tl = l.chomp('-->').rstrip
953
- if active_tag
954
- if tl.end_with?(%(end::#{active_tag}[]))
955
- active_tag = nil
956
- else
957
- selected.push l unless tl.end_with?('[]') && TagDirectiveRx =~ tl
958
- inc_line_offset = inc_lineno if inc_line_offset == 0
959
- end
960
- else
961
- tags.each do |tag|
962
- if tl.end_with?(%(tag::#{tag}[]))
963
- active_tag = tag
964
- tags_found << tag
965
- break
943
+ advance
944
+ # FIXME not accounting for skipped lines in reader line numbering
945
+ push_include inc_lines, inc_path, relpath, inc_offset, attributes if inc_offset
946
+ elsif inc_tags
947
+ inc_lines, inc_offset, inc_lineno, tag_stack, tags_used, active_tag = [], nil, 0, [], ::Set.new, nil
948
+ if inc_tags.key? '**'
949
+ if inc_tags.key? '*'
950
+ select = base_select = (inc_tags.delete '**')
951
+ wildcard = inc_tags.delete '*'
952
+ else
953
+ select = base_select = wildcard = (inc_tags.delete '**')
954
+ end
955
+ else
956
+ select = base_select = !(inc_tags.value? true)
957
+ wildcard = inc_tags.delete '*'
958
+ end
959
+ if (ext_idx = inc_path.rindex '.') && (circ_cmt = CIRCUMFIX_COMMENTS[inc_path.slice ext_idx, inc_path.length])
960
+ cmt_suffix_len = (tag_suffix = %([] #{circ_cmt[:suffix]})).length - 2
961
+ end
962
+ begin
963
+ open(inc_path, 'r') do |f|
964
+ f.each_line do |l|
965
+ inc_lineno += 1
966
+ # must force encoding since we're performing String operations on line
967
+ l.force_encoding ::Encoding::UTF_8 if FORCE_ENCODING
968
+ if (((tl = l.chomp).end_with? '[]') ||
969
+ (tag_suffix && (tl.end_with? tag_suffix) && (tl = tl.slice 0, tl.length - cmt_suffix_len))) &&
970
+ TagDirectiveRx =~ tl
971
+ if $1 # end tag
972
+ if (this_tag = $2) == active_tag
973
+ tag_stack.pop
974
+ active_tag, select = tag_stack.empty? ? [nil, base_select] : tag_stack[-1]
975
+ elsif inc_tags.key? this_tag
976
+ if (idx = tag_stack.rindex {|key, _| key == this_tag })
977
+ idx == 0 ? tag_stack.shift : (tag_stack.delete_at idx)
978
+ warn %(asciidoctor: WARNING: #{target}: line #{inc_lineno}: mismatched end tag in include: expected #{active_tag}, found #{this_tag})
979
+ else
980
+ warn %(asciidoctor: WARNING: #{target}: line #{inc_lineno}: unexpected end tag in include: #{this_tag})
966
981
  end
967
- end if tl.end_with?('[]') && TagDirectiveRx =~ tl
982
+ end
983
+ elsif inc_tags.key?(this_tag = $2)
984
+ tags_used << this_tag
985
+ # QUESTION should we prevent tag from being selected when enclosing tag is excluded?
986
+ tag_stack << [(active_tag = this_tag), (select = inc_tags[this_tag])]
987
+ elsif !wildcard.nil?
988
+ select = active_tag && !select ? false : wildcard
989
+ tag_stack << [(active_tag = this_tag), select]
968
990
  end
991
+ elsif select
992
+ # NOTE record the line where we started selecting
993
+ inc_offset ||= inc_lineno
994
+ inc_lines << l
969
995
  end
970
996
  end
971
- rescue
972
- warn %(asciidoctor: WARNING: #{line_info}: include #{target_type} not readable: #{include_file})
973
- replace_next_line %(Unresolved directive in #{@path} - include::#{target}[#{raw_attributes}])
974
- return true
975
- end
976
- unless (missing_tags = tags.to_a - tags_found.to_a).empty?
977
- warn %(asciidoctor: WARNING: #{line_info}: tag#{missing_tags.size > 1 ? 's' : nil} '#{missing_tags * ','}' not found in include #{target_type}: #{include_file})
978
997
  end
979
- advance
980
- # FIXME not accounting for skipped lines in reader line numbering
981
- push_include selected, include_file, path, inc_line_offset, attributes
998
+ rescue
999
+ warn %(asciidoctor: WARNING: #{line_info}: include #{target_type} not readable: #{inc_path})
1000
+ replace_next_line %(Unresolved directive in #{@path} - include::#{target}[#{raw_attributes}])
1001
+ return true
982
1002
  end
1003
+ unless (missing_tags = inc_tags.keys.to_a - tags_used.to_a).empty?
1004
+ warn %(asciidoctor: WARNING: #{line_info}: tag#{missing_tags.size > 1 ? 's' : nil} '#{missing_tags * ','}' not found in include #{target_type}: #{inc_path})
1005
+ end
1006
+ advance
1007
+ # FIXME not accounting for skipped lines in reader line numbering
1008
+ push_include inc_lines, inc_path, relpath, inc_offset, attributes if inc_offset
983
1009
  else
984
1010
  begin
985
1011
  # NOTE read content first so that we only advance cursor if IO operation succeeds
986
- include_content = open(include_file, 'r') {|f| f.read }
1012
+ inc_content = target_type == :file ? (::IO.read inc_path) : open(inc_path, 'r') {|f| f.read }
987
1013
  advance
988
- push_include include_content, include_file, path, 1, attributes
1014
+ push_include inc_content, inc_path, relpath, 1, attributes
989
1015
  rescue
990
- warn %(asciidoctor: WARNING: #{line_info}: include #{target_type} not readable: #{include_file})
1016
+ warn %(asciidoctor: WARNING: #{line_info}: include #{target_type} not readable: #{inc_path})
991
1017
  replace_next_line %(Unresolved directive in #{@path} - include::#{target}[#{raw_attributes}])
992
1018
  return true
993
1019
  end
@@ -1018,7 +1044,7 @@ class PreprocessorReader < Reader
1018
1044
  @file = file
1019
1045
  @dir = File.dirname file
1020
1046
  # only process lines in AsciiDoc files
1021
- @process_lines = ASCIIDOC_EXTENSIONS[::File.extname(file)]
1047
+ @process_lines = ASCIIDOC_EXTENSIONS[::File.extname file]
1022
1048
  else
1023
1049
  @file = nil
1024
1050
  @dir = '.' # right?
@@ -1026,16 +1052,16 @@ class PreprocessorReader < Reader
1026
1052
  @process_lines = true
1027
1053
  end
1028
1054
 
1029
- @path = if path
1055
+ if path
1030
1056
  @includes << Helpers.rootname(path)
1031
- path
1057
+ @path = path
1032
1058
  else
1033
- '<stdin>'
1059
+ @path = '<stdin>'
1034
1060
  end
1035
1061
 
1036
1062
  @lineno = lineno
1037
1063
 
1038
- if attributes.has_key? 'depth'
1064
+ if attributes.key? 'depth'
1039
1065
  depth = attributes['depth'].to_i
1040
1066
  depth = 1 if depth <= 0
1041
1067
  @maxdepth = {:abs => (@include_stack.size - 1) + depth, :rel => depth}
@@ -1046,14 +1072,14 @@ class PreprocessorReader < Reader
1046
1072
  pop_include
1047
1073
  else
1048
1074
  # FIXME we eventually want to handle leveloffset without affecting the lines
1049
- if attributes.has_key? 'leveloffset'
1075
+ if attributes.key? 'leveloffset'
1050
1076
  @lines.unshift ''
1051
1077
  @lines.unshift %(:leveloffset: #{attributes['leveloffset']})
1052
- @lines.push ''
1078
+ @lines << ''
1053
1079
  if (old_leveloffset = @document.attr 'leveloffset')
1054
- @lines.push %(:leveloffset: #{old_leveloffset})
1080
+ @lines << %(:leveloffset: #{old_leveloffset})
1055
1081
  else
1056
- @lines.push ':leveloffset!:'
1082
+ @lines << ':leveloffset!:'
1057
1083
  end
1058
1084
  # compensate for these extra lines
1059
1085
  @lineno -= 2
@@ -1113,7 +1139,7 @@ class PreprocessorReader < Reader
1113
1139
  data.shift
1114
1140
  @lineno += 1 if increment_linenos
1115
1141
  while !data.empty? && data[0] != '---'
1116
- front_matter.push data.shift
1142
+ front_matter << data.shift
1117
1143
  @lineno += 1 if increment_linenos
1118
1144
  end
1119
1145
 
@@ -1171,9 +1197,7 @@ class PreprocessorReader < Reader
1171
1197
 
1172
1198
  # QUESTION should we substitute first?
1173
1199
  # QUESTION should we also require string to be single quoted (like block attribute values?)
1174
- if val.include? '{'
1175
- val = @document.sub_attributes val, :attribute_missing => 'drop'
1176
- end
1200
+ val = @document.sub_attributes val, :attribute_missing => 'drop' if val.include? '{'
1177
1201
 
1178
1202
  if quoted
1179
1203
  val
@@ -1209,7 +1233,7 @@ class PreprocessorReader < Reader
1209
1233
  end
1210
1234
 
1211
1235
  def to_s
1212
- %(#<#{self.class}@#{object_id} {path: #{@path.inspect}, line #: #{@lineno}, include depth: #{@include_stack.size}, include stack: [#{@include_stack.map {|inc| inc.to_s}.join ', '}]}>)
1236
+ %(#<#{self.class}@#{object_id} {path: #{@path.inspect}, line #: #{@lineno}, include depth: #{@include_stack.size}, include stack: [#{@include_stack.map {|inc| inc.to_s } * ', '}]}>)
1213
1237
  end
1214
1238
  end
1215
1239
  end