asciidoctor 2.0.10 → 2.0.17

Sign up to get free protection for your applications and to get access to all the features.
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