asciidoctor 2.0.10 → 2.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +294 -30
  3. data/LICENSE +1 -1
  4. data/README-de.adoc +16 -20
  5. data/README-fr.adoc +15 -22
  6. data/README-jp.adoc +15 -26
  7. data/README-zh_CN.adoc +21 -25
  8. data/README.adoc +161 -138
  9. data/asciidoctor.gemspec +6 -13
  10. data/data/locale/attributes-ar.adoc +4 -3
  11. data/data/locale/attributes-be.adoc +23 -0
  12. data/data/locale/attributes-bg.adoc +4 -3
  13. data/data/locale/attributes-ca.adoc +6 -5
  14. data/data/locale/attributes-cs.adoc +4 -3
  15. data/data/locale/attributes-da.adoc +6 -5
  16. data/data/locale/attributes-de.adoc +4 -4
  17. data/data/locale/attributes-en.adoc +4 -4
  18. data/data/locale/attributes-es.adoc +6 -5
  19. data/data/locale/attributes-fa.adoc +4 -3
  20. data/data/locale/attributes-fi.adoc +4 -3
  21. data/data/locale/attributes-fr.adoc +8 -7
  22. data/data/locale/attributes-hu.adoc +4 -3
  23. data/data/locale/attributes-id.adoc +4 -3
  24. data/data/locale/attributes-it.adoc +6 -5
  25. data/data/locale/attributes-ja.adoc +4 -3
  26. data/data/locale/{attributes-kr.adoc → attributes-ko.adoc} +4 -3
  27. data/data/locale/attributes-nb.adoc +4 -3
  28. data/data/locale/attributes-nl.adoc +6 -5
  29. data/data/locale/attributes-nn.adoc +4 -3
  30. data/data/locale/attributes-pl.adoc +8 -7
  31. data/data/locale/attributes-pt.adoc +6 -5
  32. data/data/locale/attributes-pt_BR.adoc +6 -5
  33. data/data/locale/attributes-ro.adoc +4 -3
  34. data/data/locale/attributes-ru.adoc +6 -5
  35. data/data/locale/attributes-sr.adoc +4 -4
  36. data/data/locale/attributes-sr_Latn.adoc +4 -4
  37. data/data/locale/attributes-sv.adoc +4 -4
  38. data/data/locale/attributes-th.adoc +23 -0
  39. data/data/locale/attributes-tr.adoc +4 -3
  40. data/data/locale/attributes-uk.adoc +6 -5
  41. data/data/locale/attributes-vi.adoc +23 -0
  42. data/data/locale/attributes-zh_CN.adoc +4 -3
  43. data/data/locale/attributes-zh_TW.adoc +4 -3
  44. data/data/reference/syntax.adoc +14 -7
  45. data/data/stylesheets/asciidoctor-default.css +76 -76
  46. data/data/stylesheets/coderay-asciidoctor.css +9 -9
  47. data/lib/asciidoctor/abstract_block.rb +20 -13
  48. data/lib/asciidoctor/abstract_node.rb +23 -12
  49. data/lib/asciidoctor/attribute_list.rb +64 -72
  50. data/lib/asciidoctor/block.rb +6 -6
  51. data/lib/asciidoctor/cli/invoker.rb +3 -2
  52. data/lib/asciidoctor/cli/options.rb +32 -31
  53. data/lib/asciidoctor/convert.rb +168 -162
  54. data/lib/asciidoctor/converter/docbook5.rb +49 -34
  55. data/lib/asciidoctor/converter/html5.rb +180 -139
  56. data/lib/asciidoctor/converter/manpage.rb +118 -90
  57. data/lib/asciidoctor/converter/template.rb +15 -13
  58. data/lib/asciidoctor/converter.rb +19 -16
  59. data/lib/asciidoctor/core_ext/hash/merge.rb +1 -1
  60. data/lib/asciidoctor/document.rb +77 -86
  61. data/lib/asciidoctor/extensions.rb +22 -16
  62. data/lib/asciidoctor/helpers.rb +20 -15
  63. data/lib/asciidoctor/list.rb +2 -6
  64. data/lib/asciidoctor/load.rb +103 -101
  65. data/lib/asciidoctor/logging.rb +10 -8
  66. data/lib/asciidoctor/parser.rb +211 -220
  67. data/lib/asciidoctor/path_resolver.rb +17 -15
  68. data/lib/asciidoctor/reader.rb +87 -79
  69. data/lib/asciidoctor/rx.rb +9 -7
  70. data/lib/asciidoctor/section.rb +7 -0
  71. data/lib/asciidoctor/substitutors.rb +167 -148
  72. data/lib/asciidoctor/syntax_highlighter/coderay.rb +3 -2
  73. data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +13 -5
  74. data/lib/asciidoctor/syntax_highlighter/prettify.rb +7 -4
  75. data/lib/asciidoctor/syntax_highlighter/pygments.rb +19 -11
  76. data/lib/asciidoctor/syntax_highlighter/rouge.rb +35 -20
  77. data/lib/asciidoctor/syntax_highlighter.rb +16 -16
  78. data/lib/asciidoctor/table.rb +70 -43
  79. data/lib/asciidoctor/timings.rb +3 -3
  80. data/lib/asciidoctor/version.rb +1 -1
  81. data/lib/asciidoctor.rb +45 -19
  82. data/man/asciidoctor.1 +29 -31
  83. data/man/asciidoctor.adoc +35 -29
  84. metadata +17 -70
@@ -109,7 +109,7 @@ class PathResolver
109
109
  SLASH = '/'
110
110
  BACKSLASH = '\\'
111
111
  DOUBLE_SLASH = '//'
112
- WindowsRootRx = /^(?:[a-zA-Z]:)?[\\\/]/
112
+ WindowsRootRx = %r(^(?:[a-zA-Z]:)?[\\/])
113
113
 
114
114
  attr_accessor :file_separator
115
115
  attr_accessor :working_dir
@@ -290,8 +290,8 @@ class PathResolver
290
290
  # ex. ./sample/path
291
291
  elsif posix_path.start_with? DOT_SLASH
292
292
  root = DOT_SLASH
293
- # else ex. sample/path
294
293
  end
294
+ # otherwise ex. sample/path
295
295
  elsif root? posix_path
296
296
  # ex. //sample/path
297
297
  if unc? posix_path
@@ -306,8 +306,8 @@ class PathResolver
306
306
  # ex. ./sample/path
307
307
  elsif posix_path.start_with? DOT_SLASH
308
308
  root = DOT_SLASH
309
- # else ex. sample/path
310
309
  end
310
+ # otherwise ex. sample/path
311
311
 
312
312
  path_segments = (root ? (posix_path.slice root.length, posix_path.length) : posix_path).split SLASH
313
313
  # strip out all dot entries
@@ -331,11 +331,12 @@ class PathResolver
331
331
 
332
332
  # Public: Securely resolve a system path
333
333
  #
334
- # Resolve a system path from the target relative to the start path, jail path, or working
335
- # directory (specified in the constructor), in that order. If a jail path is specified, enforce
336
- # that the resolved path descends from the jail path. If a jail path is not provided, the resolved
337
- # path may be any location on the system. If the resolved path is absolute, use it as is (unless
338
- # it breaches the jail path). Expand all parent and self references in the resolved path.
334
+ # Resolves the target to an absolute path on the current filesystem. The target is assumed to be
335
+ # relative to the start path, jail path, or working directory (specified in the constructor), in
336
+ # that order. If a jail path is specified, the resolved path is forced to descend from the jail
337
+ # path. If a jail path is not provided, the resolved path may be any location on the system. If
338
+ # the target is an absolute path, use it as is (unless it breaches the jail path). Expands all
339
+ # parent and self references in the resolved path.
339
340
  #
340
341
  # target - the String target path
341
342
  # start - the String start path from which to resolve a relative target; falls back to jail, if
@@ -347,8 +348,9 @@ class PathResolver
347
348
  # automatically recover when an illegal path is encountered
348
349
  # * :target_name is used in messages to refer to the path being resolved
349
350
  #
350
- # returns a String path relative to the start path, if specified, and confined to the jail path,
351
- # if specified. The path is posixified and all parent and self references in the path are expanded.
351
+ # Returns an absolute String path relative to the start path, if specified, and confined to the
352
+ # jail path, if specified. The path is posixified and all parent and self references in the path
353
+ # are expanded.
352
354
  def system_path target, start = nil, jail = nil, opts = {}
353
355
  if jail
354
356
  raise ::SecurityError, %(Jail is not an absolute path: #{jail}) unless root? jail
@@ -362,7 +364,7 @@ class PathResolver
362
364
  if jail && !(descends_from? target_path, jail)
363
365
  if opts.fetch :recover, true
364
366
  logger.warn %(#{opts[:target_name] || 'path'} is outside of jail; recovering automatically)
365
- target_segments, _ = partition_path target_path
367
+ target_segments, = partition_path target_path
366
368
  jail_segments, jail_root = partition_path jail
367
369
  return join_path jail_segments + target_segments, jail_root
368
370
  else
@@ -371,7 +373,7 @@ class PathResolver
371
373
  end
372
374
  return target_path
373
375
  else
374
- target_segments, _ = partition_path target
376
+ target_segments, = partition_path target
375
377
  end
376
378
  else
377
379
  target_segments = []
@@ -387,7 +389,7 @@ class PathResolver
387
389
  return expand_path start
388
390
  end
389
391
  else
390
- target_segments, _ = partition_path start
392
+ target_segments, = partition_path start
391
393
  start = jail || @working_dir
392
394
  end
393
395
  elsif start.nil_or_empty?
@@ -419,7 +421,7 @@ class PathResolver
419
421
  if (resolved_segments = start_segments + target_segments).include? DOT_DOT
420
422
  unresolved_segments, resolved_segments = resolved_segments, []
421
423
  if jail
422
- jail_segments, _ = partition_path jail unless jail_segments
424
+ jail_segments, = partition_path jail unless jail_segments
423
425
  warned = false
424
426
  unresolved_segments.each do |segment|
425
427
  if segment == DOT_DOT
@@ -450,7 +452,7 @@ class PathResolver
450
452
  target_path
451
453
  elsif opts.fetch :recover, true
452
454
  logger.warn %(#{opts[:target_name] || 'path'} is outside of jail; recovering automatically)
453
- jail_segments, _ = partition_path jail unless jail_segments
455
+ jail_segments, = partition_path jail unless jail_segments
454
456
  join_path jail_segments + target_segments, jail_root
455
457
  else
456
458
  raise ::SecurityError, %(#{opts[:target_name] || 'path'} #{target} is outside of jail: #{jail} (disallowed in safe mode))
@@ -44,11 +44,11 @@ class Reader
44
44
  @file = nil
45
45
  @dir = '.'
46
46
  @path = '<stdin>'
47
- @lineno = 1 # IMPORTANT lineno assignment must proceed prepare_lines call!
47
+ @lineno = 1
48
48
  elsif ::String === cursor
49
49
  @file = cursor
50
50
  @dir, @path = ::File.split @file
51
- @lineno = 1 # IMPORTANT lineno assignment must proceed prepare_lines call!
51
+ @lineno = 1
52
52
  else
53
53
  if (@file = cursor.file)
54
54
  @dir = cursor.dir || (::File.dirname @file)
@@ -57,10 +57,9 @@ class Reader
57
57
  @dir = cursor.dir || '.'
58
58
  @path = cursor.path || '<stdin>'
59
59
  end
60
- @lineno = cursor.lineno || 1 # IMPORTANT lineno assignment must proceed prepare_lines call!
60
+ @lineno = cursor.lineno || 1
61
61
  end
62
- @lines = prepare_lines data, opts
63
- @source_lines = @lines.drop 0
62
+ @lines = (@source_lines = prepare_lines data, opts).reverse
64
63
  @mark = nil
65
64
  @look_ahead = 0
66
65
  @process_lines = true
@@ -127,7 +126,7 @@ class Reader
127
126
  # Returns nothing if there is no more data.
128
127
  def peek_line direct = false
129
128
  if direct || @look_ahead > 0
130
- @unescape_next_line ? ((line = @lines[0]).slice 1, line.length) : @lines[0]
129
+ @unescape_next_line ? ((line = @lines[-1]).slice 1, line.length) : @lines[-1]
131
130
  elsif @lines.empty?
132
131
  @look_ahead = 0
133
132
  nil
@@ -135,7 +134,7 @@ class Reader
135
134
  # FIXME the problem with this approach is that we aren't
136
135
  # retaining the modified line (hence the @unescape_next_line tweak)
137
136
  # perhaps we need a stack of proxied lines
138
- (line = process_line @lines[0]) ? line : peek_line
137
+ (process_line @lines[-1]) || peek_line
139
138
  end
140
139
  end
141
140
 
@@ -192,9 +191,7 @@ class Reader
192
191
  def read_lines
193
192
  lines = []
194
193
  # has_more_lines? triggers preprocessor
195
- while has_more_lines?
196
- lines << shift
197
- end
194
+ lines << shift while has_more_lines?
198
195
  lines
199
196
  end
200
197
  alias readlines read_lines
@@ -241,7 +238,6 @@ class Reader
241
238
  # Returns nothing.
242
239
  def unshift_lines lines_to_restore
243
240
  unshift_all lines_to_restore
244
- nil
245
241
  end
246
242
  alias restore_lines unshift_lines
247
243
 
@@ -334,7 +330,7 @@ class Reader
334
330
  comment_lines = []
335
331
  # optimized code for shortest execution path
336
332
  while (next_line = peek_line) && !next_line.empty?
337
- if (next_line.start_with? '//')
333
+ if next_line.start_with? '//'
338
334
  comment_lines << shift
339
335
  else
340
336
  break
@@ -407,34 +403,22 @@ class Reader
407
403
  break_on_list_continuation = options[:break_on_list_continuation]
408
404
  end
409
405
  skip_comments = options[:skip_line_comments]
410
- complete = line_read = line_restored = nil
406
+ line_read = line_restored = nil
411
407
  shift if options[:skip_first_line]
412
- while !complete && (line = read_line)
413
- complete = while true
414
- break true if terminator && line == terminator
415
- # QUESTION: can we get away with line.empty? here?
416
- break true if break_on_blank_lines && line.empty?
417
- if break_on_list_continuation && line_read && line == LIST_CONTINUATION
418
- options[:preserve_last_line] = true
419
- break true
420
- end
421
- break true if block_given? && (yield line)
422
- break false
423
- end
424
- if complete
425
- if options[:read_last_line]
426
- result << line
427
- line_read = true
428
- end
408
+ while (line = read_line)
409
+ if terminator ? line == terminator : ((break_on_blank_lines && line.empty?) ||
410
+ (break_on_list_continuation && line_read && line == LIST_CONTINUATION && (options[:preserve_last_line] = true)) ||
411
+ (block_given? && (yield line)))
412
+ result << line if options[:read_last_line]
429
413
  if options[:preserve_last_line]
430
414
  unshift line
431
415
  line_restored = true
432
416
  end
433
- else
434
- unless skip_comments && (line.start_with? '//') && !(line.start_with? '///')
435
- result << line
436
- line_read = true
437
- end
417
+ break
418
+ end
419
+ unless skip_comments && (line.start_with? '//') && !(line.start_with? '///')
420
+ result << line
421
+ line_read = true
438
422
  end
439
423
  end
440
424
  if restore_process_lines
@@ -459,21 +443,37 @@ class Reader
459
443
  def shift
460
444
  @lineno += 1
461
445
  @look_ahead -= 1 unless @look_ahead == 0
462
- @lines.shift
446
+ @lines.pop
463
447
  end
464
448
 
465
449
  # Internal: Restore the line to the stack and decrement the lineno
466
450
  def unshift line
467
451
  @lineno -= 1
468
452
  @look_ahead += 1
469
- @lines.unshift line
453
+ @lines.push line
454
+ nil
470
455
  end
471
456
 
472
- # Internal: Restore the lines to the stack and decrement the lineno
473
- def unshift_all lines
474
- @lineno -= lines.size
475
- @look_ahead += lines.size
476
- @lines.unshift(*lines)
457
+ if ::RUBY_ENGINE == 'jruby'
458
+ # Internal: Restore the lines to the stack and decrement the lineno
459
+ def unshift_all lines_to_restore
460
+ @lineno -= lines_to_restore.size
461
+ @look_ahead += lines_to_restore.size
462
+ if lines_to_restore.respond_to? :reverse
463
+ @lines.push(*lines_to_restore.reverse)
464
+ else
465
+ lines_to_restore.reverse_each {|it| @lines.push it }
466
+ end
467
+ nil
468
+ end
469
+ else
470
+ # Internal: Restore the lines to the stack and decrement the lineno
471
+ def unshift_all lines_to_restore
472
+ @lineno -= lines_to_restore.size
473
+ @look_ahead += lines_to_restore.size
474
+ @lines.push(*lines_to_restore.reverse)
475
+ nil
476
+ end
477
477
  end
478
478
 
479
479
  def cursor
@@ -516,12 +516,12 @@ class Reader
516
516
  #
517
517
  # Returns A copy of the String Array of lines remaining in this Reader
518
518
  def lines
519
- @lines.drop 0
519
+ @lines.reverse
520
520
  end
521
521
 
522
522
  # Public: Get a copy of the remaining lines managed by this Reader joined as a String
523
523
  def string
524
- @lines.join LF
524
+ @lines.reverse.join LF
525
525
  end
526
526
 
527
527
  # Public: Get the source lines for this Reader joined as a String
@@ -570,17 +570,17 @@ class Reader
570
570
  #
571
571
  # data - A String Array or String of source data to be normalized.
572
572
  # opts - A Hash of options to control how lines are prepared.
573
- # :normalize - Enables line normalization, which coerces the encoding to UTF-8 and removes trailing whitespace
574
- # (optional, default: false).
573
+ # :normalize - Enables line normalization, which coerces the encoding to UTF-8 and removes trailing whitespace;
574
+ # :rstrip removes all trailing whitespace; :chomp removes trailing newline only (optional, not set).
575
575
  #
576
576
  # Returns A String Array of source lines. If the source data is an Array, this method returns a copy.
577
577
  def prepare_lines data, opts = {}
578
- if opts[:normalize]
579
- ::Array === data ? (Helpers.prepare_source_array data) : (Helpers.prepare_source_string data)
578
+ if (normalize = opts[:normalize])
579
+ ::Array === data ? (Helpers.prepare_source_array data, normalize != :chomp) : (Helpers.prepare_source_string data, normalize != :chomp)
580
580
  elsif ::Array === data
581
581
  data.drop 0
582
582
  elsif data
583
- data.split LF, -1
583
+ data.chomp.split LF, -1
584
584
  else
585
585
  []
586
586
  end
@@ -689,6 +689,7 @@ class PreprocessorReader < Reader
689
689
  @path = (path ||= ::File.basename file)
690
690
  # only process lines in AsciiDoc files
691
691
  if (@process_lines = file.end_with?(*ASCIIDOC_EXTENSIONS.keys))
692
+ # NOTE registering the include with a nil value tracks it while not making it visible to interdocument xrefs
692
693
  @includes[path.slice 0, (path.rindex '.')] = attributes['partial-option'] ? nil : true
693
694
  end
694
695
  else
@@ -696,6 +697,7 @@ class PreprocessorReader < Reader
696
697
  # we don't know what file type we have, so assume AsciiDoc
697
698
  @process_lines = true
698
699
  if (@path = path)
700
+ # NOTE registering the include with a nil value tracks it while not making it visible to interdocument xrefs
699
701
  @includes[Helpers.rootname path] = attributes['partial-option'] ? nil : true
700
702
  else
701
703
  @path = '<stdin>'
@@ -717,21 +719,16 @@ class PreprocessorReader < Reader
717
719
  end
718
720
 
719
721
  # effectively fill the buffer
720
- if (@lines = prepare_lines data, normalize: true, condense: false, indent: attributes['indent']).empty?
722
+ if (@lines = prepare_lines data, normalize: @process_lines || :chomp, condense: false, indent: attributes['indent']).empty?
721
723
  pop_include
722
724
  else
723
725
  # FIXME we eventually want to handle leveloffset without affecting the lines
724
726
  if attributes.key? 'leveloffset'
725
- @lines.unshift ''
726
- @lines.unshift %(:leveloffset: #{attributes['leveloffset']})
727
- @lines << ''
728
- if (old_leveloffset = @document.attr 'leveloffset')
729
- @lines << %(:leveloffset: #{old_leveloffset})
730
- else
731
- @lines << ':leveloffset!:'
732
- end
733
- # compensate for these extra lines
727
+ @lines = [((leveloffset = @document.attr 'leveloffset') ? %(:leveloffset: #{leveloffset}) : ':leveloffset!:'), ''] + @lines.reverse + ['', %(:leveloffset: #{attributes['leveloffset']})]
728
+ # compensate for these extra lines at the top
734
729
  @lineno -= 2
730
+ else
731
+ @lines.reverse!
735
732
  end
736
733
 
737
734
  # FIXME kind of a hack
@@ -801,14 +798,11 @@ class PreprocessorReader < Reader
801
798
  result = super
802
799
 
803
800
  # QUESTION should this work for AsciiDoc table cell content? Currently it does not.
804
- if @document && @document.attributes['skip-front-matter']
805
- if (front_matter = skip_front_matter! result)
806
- @document.attributes['front-matter'] = front_matter.join LF
807
- end
801
+ if @document && @document.attributes['skip-front-matter'] && (front_matter = skip_front_matter! result)
802
+ @document.attributes['front-matter'] = front_matter.join LF
808
803
  end
809
804
 
810
805
  if opts.fetch :condense, true
811
- result.shift && @lineno += 1 while (first = result[0]) && first.empty?
812
806
  result.pop while (last = result[-1]) && last.empty?
813
807
  end
814
808
 
@@ -955,11 +949,12 @@ class PreprocessorReader < Reader
955
949
  if no_target
956
950
  # the text in brackets must match a conditional expression
957
951
  if text && EvalExpressionRx =~ text.strip
952
+ # NOTE assignments must happen before call to resolve_expr_val for compatibility with Opal
958
953
  lhs = $1
954
+ # regex enforces a restricted set of math-related operations (==, !=, <=, >=, <, >)
959
955
  op = $2
960
956
  rhs = $3
961
- # regex enforces a restricted set of math-related operations (==, !=, <=, >=, <, >)
962
- skip = ((resolve_expr_val lhs).send op, (resolve_expr_val rhs)) ? false : true
957
+ skip = ((resolve_expr_val lhs).send op, (resolve_expr_val rhs)) ? false : true rescue true
963
958
  else
964
959
  logger.error message_with_context %(malformed preprocessor directive - #{text ? 'invalid expression' : 'missing expression'}: ifeval::[#{text}]), source_location: cursor
965
960
  return true
@@ -1049,10 +1044,11 @@ class PreprocessorReader < Reader
1049
1044
 
1050
1045
  parsed_attrs = doc.parse_attributes attrlist, [], sub_input: true
1051
1046
  inc_path, target_type, relpath = resolve_include_path expanded_target, attrlist, parsed_attrs
1052
- if target_type == :file
1047
+ case target_type
1048
+ when :file
1053
1049
  reader = ::File.method :open
1054
1050
  read_mode = FILE_READ_MODE
1055
- elsif target_type == :uri
1051
+ when :uri
1056
1052
  reader = ::OpenURI.method :open_uri
1057
1053
  read_mode = URI_READ_MODE
1058
1054
  else
@@ -1060,14 +1056,20 @@ class PreprocessorReader < Reader
1060
1056
  return inc_path
1061
1057
  end
1062
1058
 
1059
+ if (enc = parsed_attrs['encoding']) && (::Encoding.find enc rescue nil)
1060
+ (read_mode_params = read_mode.split ':')[1] = enc
1061
+ read_mode = read_mode_params.join ':'
1062
+ end unless RUBY_ENGINE_OPAL
1063
+
1063
1064
  inc_linenos = inc_tags = nil
1065
+ # NOTE attrlist is nil if missing from include directive
1064
1066
  if attrlist
1065
1067
  if parsed_attrs.key? 'lines'
1066
1068
  inc_linenos = []
1067
1069
  (split_delimited_value parsed_attrs['lines']).each do |linedef|
1068
1070
  if linedef.include? '..'
1069
1071
  from, _, to = linedef.partition '..'
1070
- inc_linenos += (to.empty? || (to = to.to_i) < 0) ? [from.to_i, 1.0/0.0] : (from.to_i..to).to_a
1072
+ inc_linenos += (to.empty? || (to = to.to_i) < 0) ? [from.to_i, ::Float::INFINITY] : (from.to_i..to).to_a
1071
1073
  else
1072
1074
  inc_linenos << linedef.to_i
1073
1075
  end
@@ -1123,17 +1125,23 @@ class PreprocessorReader < Reader
1123
1125
  push_include inc_lines, inc_path, relpath, inc_offset, parsed_attrs
1124
1126
  end
1125
1127
  elsif inc_tags
1126
- inc_lines, inc_offset, inc_lineno, tag_stack, tags_used, active_tag = [], nil, 0, [], ::Set.new, nil
1128
+ inc_lines, inc_offset, inc_lineno, tag_stack, tags_selected, active_tag = [], nil, 0, [], ::Set.new, nil
1127
1129
  if inc_tags.key? '**'
1130
+ select = base_select = inc_tags.delete '**'
1128
1131
  if inc_tags.key? '*'
1129
- select = base_select = inc_tags.delete '**'
1130
1132
  wildcard = inc_tags.delete '*'
1133
+ elsif !select && inc_tags.values.first == false
1134
+ wildcard = true
1135
+ end
1136
+ elsif inc_tags.key? '*'
1137
+ if inc_tags.keys.first == '*'
1138
+ select = base_select = !(wildcard = inc_tags.delete '*')
1131
1139
  else
1132
- select = base_select = wildcard = inc_tags.delete '**'
1140
+ select = base_select = false
1141
+ wildcard = inc_tags.delete '*'
1133
1142
  end
1134
1143
  else
1135
1144
  select = base_select = !(inc_tags.value? true)
1136
- wildcard = inc_tags.delete '*'
1137
1145
  end
1138
1146
  begin
1139
1147
  reader.call inc_path, read_mode do |f|
@@ -1148,7 +1156,7 @@ class PreprocessorReader < Reader
1148
1156
  active_tag, select = tag_stack.empty? ? [nil, base_select] : tag_stack[-1]
1149
1157
  elsif inc_tags.key? this_tag
1150
1158
  include_cursor = create_include_cursor inc_path, expanded_target, inc_lineno
1151
- if (idx = tag_stack.rindex {|key, _| key == this_tag })
1159
+ if (idx = tag_stack.rindex {|key,| key == this_tag })
1152
1160
  idx == 0 ? tag_stack.shift : (tag_stack.delete_at idx)
1153
1161
  logger.warn message_with_context %(mismatched end tag (expected '#{active_tag}' but found '#{this_tag}') at line #{inc_lineno} of include #{target_type}: #{inc_path}), source_location: cursor, include_location: include_cursor
1154
1162
  else
@@ -1156,9 +1164,9 @@ class PreprocessorReader < Reader
1156
1164
  end
1157
1165
  end
1158
1166
  elsif inc_tags.key? this_tag
1159
- tags_used << this_tag
1167
+ tags_selected << this_tag if (select = inc_tags[this_tag])
1160
1168
  # QUESTION should we prevent tag from being selected when enclosing tag is excluded?
1161
- tag_stack << [(active_tag = this_tag), (select = inc_tags[this_tag]), inc_lineno]
1169
+ tag_stack << [(active_tag = this_tag), select, inc_lineno]
1162
1170
  elsif !wildcard.nil?
1163
1171
  select = active_tag && !select ? false : wildcard
1164
1172
  tag_stack << [(active_tag = this_tag), select, inc_lineno]
@@ -1179,12 +1187,12 @@ class PreprocessorReader < Reader
1179
1187
  logger.warn message_with_context %(detected unclosed tag '#{tag_name}' starting at line #{tag_lineno} of include #{target_type}: #{inc_path}), source_location: cursor, include_location: (create_include_cursor inc_path, expanded_target, tag_lineno)
1180
1188
  end
1181
1189
  end
1182
- unless (missing_tags = inc_tags.keys - tags_used.to_a).empty?
1190
+ unless (missing_tags = inc_tags.keep_if {|_, v| v }.keys - tags_selected.to_a).empty?
1183
1191
  logger.warn message_with_context %(tag#{missing_tags.size > 1 ? 's' : ''} '#{missing_tags.join ', '}' not found in include #{target_type}: #{inc_path}), source_location: cursor
1184
1192
  end
1185
1193
  shift
1186
1194
  if inc_offset
1187
- parsed_attrs['partial-option'] = '' unless base_select && wildcard && inc_tags.empty?
1195
+ parsed_attrs['partial-option'] = '' unless base_select && wildcard != false && inc_tags.empty?
1188
1196
  # FIXME not accounting for skipped lines in reader line numbering
1189
1197
  push_include inc_lines, inc_path, relpath, inc_offset, parsed_attrs
1190
1198
  end
@@ -1255,7 +1263,7 @@ class PreprocessorReader < Reader
1255
1263
  end
1256
1264
 
1257
1265
  def pop_include
1258
- if @include_stack.size > 0
1266
+ unless @include_stack.empty?
1259
1267
  @lines, @file, @dir, @path, @lineno, @maxdepth, @process_lines = @include_stack.pop
1260
1268
  # FIXME kind of a hack
1261
1269
  #Document::AttributeEntry.new('infile', @file).save_to_next_block @document
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Asciidoctor
2
3
  # A collection of regular expression constants used by the parser. (For speed, these are not defined in the Rx module,
3
4
  # but rather directly in the Asciidoctor module).
@@ -269,7 +270,7 @@ module Asciidoctor
269
270
  #
270
271
  # NOTE we only have to check as far as the blank character because we know it means non-whitespace follows.
271
272
  # IMPORTANT if this regexp does not agree with the regexp for each list type, the parser will hang.
272
- AnyListRx = %r(^(?:[ \t]*(?:-|\*\**|\.\.*|\u2022|\d+\.|[a-zA-Z]\.|[IVXivx]+\))[ \t]|(?!//[^/])[ \t]*[^ \t]#{CC_ANY}*?(?::::{0,2}|;;)(?:$|[ \t])|<?\d+>[ \t]))
273
+ AnyListRx = %r(^(?:[ \t]*(?:-|\*\**|\.\.*|\u2022|\d+\.|[a-zA-Z]\.|[IVXivx]+\))[ \t]|(?!//[^/])[ \t]*[^ \t]#{CC_ANY}*?(?::::{0,2}|;;)(?:$|[ \t])|<(?:\d+|\.)>[ \t]))
273
274
 
274
275
  # Matches an unordered list item (one level for hyphens, up to 5 levels for asterisks).
275
276
  #
@@ -406,7 +407,7 @@ module Asciidoctor
406
407
  # gist::123456[]
407
408
  #
408
409
  #--
409
- # NOTE we've relaxed the match for target to accomodate the short format (e.g., name::[attrlist])
410
+ # NOTE we've relaxed the match for target to accommodate the short format (e.g., name::[attrlist])
410
411
  CustomBlockMacroRx = /^(#{CG_WORD}[#{CC_WORD}-]*)::(|\S|\S#{CC_ANY}*?\S)\[(#{CC_ANY}+)?\]$/
411
412
 
412
413
  # Matches an image, video or audio block macro.
@@ -469,7 +470,7 @@ module Asciidoctor
469
470
  # footnoteref:[id,text] (legacy)
470
471
  # footnoteref:[id] (legacy)
471
472
  #
472
- InlineFootnoteMacroRx = /\\?footnote(?:(ref):|:([#{CC_WORD}-]+)?)\[(?:|(#{CC_ALL}*?[^\\]))\]/m
473
+ InlineFootnoteMacroRx = %r(\\?footnote(?:(ref):|:([#{CC_WORD}-]+)?)\[(?:|(#{CC_ALL}*?[^\\]))\](?!</a>))m
473
474
 
474
475
  # Matches an image or icon inline macro.
475
476
  #
@@ -514,9 +515,10 @@ module Asciidoctor
514
515
  # https://github.com[GitHub]
515
516
  # <https://github.com>
516
517
  # link:https://github.com[]
518
+ # "https://github.com[]"
517
519
  #
518
520
  # FIXME revisit! the main issue is we need different rules for implicit vs explicit
519
- InlineLinkRx = %r((^|link:|#{CG_BLANK}|&lt;|[>\(\)\[\];])(\\?(?:https?|file|ftp|irc)://[^\s\[\]<]*([^\s.,\[\]<]))(?:\[(|#{CC_ALL}*?[^\\])\])?)m
521
+ InlineLinkRx = %r((^|link:|#{CG_BLANK}|&lt;|[>\(\)\[\];"'])(\\?(?:https?|file|ftp|irc)://[^\s\[\]<]*([^\s.,\[\]<]))(?:\[(|#{CC_ALL}*?[^\\])\])?)m
520
522
 
521
523
  # Match a link or e-mail inline macro.
522
524
  #
@@ -551,7 +553,7 @@ module Asciidoctor
551
553
  # menu:View[Page Style > No Style]
552
554
  # menu:View[Page Style, No Style]
553
555
  #
554
- InlineMenuMacroRx = /\\?menu:(#{CG_WORD}|[#{CC_WORD}&][^\n\[]*[^\s\[])\[ *(?:|(#{CC_ALL}*?[^\\]))?\]/m
556
+ InlineMenuMacroRx = /\\?menu:(#{CG_WORD}|[#{CC_WORD}&][^\n\[]*[^\s\[])\[ *(?:|(#{CC_ALL}*?[^\\]))\]/m
555
557
 
556
558
  # Matches an implicit menu inline macro.
557
559
  #
@@ -591,7 +593,7 @@ module Asciidoctor
591
593
  # $$text$$
592
594
  # pass:quotes[text]
593
595
  #
594
- # NOTE we have to support an empty pass:[] for compatibility with AsciiDoc Python
596
+ # NOTE we have to support an empty pass:[] for compatibility with AsciiDoc.py
595
597
  InlinePassMacroRx = /(?:(?:(\\?)\[([^\]]+)\])?(\\{0,2})(\+\+\+?|\$\$)(#{CC_ALL}*?)\4|(\\?)pass:([a-z]+(?:,[a-z-]+)*)?\[(|#{CC_ALL}*?[^\\])\])/m
596
598
 
597
599
  # Matches an xref (i.e., cross-reference) inline macro, which may span multiple lines.
@@ -610,7 +612,7 @@ module Asciidoctor
610
612
  # Matches a trailing + preceded by at least one space character,
611
613
  # which forces a hard line break (<br> tag in HTML output).
612
614
  #
613
- # NOTE AsciiDoc Python allows + to be preceded by TAB; Asciidoctor does not
615
+ # NOTE AsciiDoc.py allows + to be preceded by TAB; Asciidoctor does not
614
616
  #
615
617
  # Examples
616
618
  #
@@ -64,6 +64,13 @@ class Section < AbstractBlock
64
64
  Section.generate_id title, @document
65
65
  end
66
66
 
67
+ # Public: Check whether this Section has any child Section objects.
68
+ #
69
+ # Returns A [Boolean] to indicate whether this Section has child Section objects
70
+ def sections?
71
+ @next_section_index > 0
72
+ end
73
+
67
74
  # Public: Get the section number for the current Section
68
75
  #
69
76
  # The section number is a dot-separated String that uniquely describes the position of this