asciidoctor 2.0.13 → 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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +151 -30
  3. data/LICENSE +1 -1
  4. data/README-de.adoc +9 -12
  5. data/README-fr.adoc +9 -12
  6. data/README-jp.adoc +10 -13
  7. data/README-zh_CN.adoc +9 -12
  8. data/README.adoc +40 -19
  9. data/asciidoctor.gemspec +2 -9
  10. data/data/locale/attributes-fr.adoc +2 -2
  11. data/data/locale/attributes-th.adoc +23 -0
  12. data/data/locale/attributes-vi.adoc +23 -0
  13. data/data/stylesheets/asciidoctor-default.css +54 -53
  14. data/data/stylesheets/coderay-asciidoctor.css +9 -9
  15. data/lib/asciidoctor/abstract_block.rb +11 -9
  16. data/lib/asciidoctor/abstract_node.rb +9 -8
  17. data/lib/asciidoctor/attribute_list.rb +1 -1
  18. data/lib/asciidoctor/block.rb +6 -6
  19. data/lib/asciidoctor/cli/invoker.rb +1 -2
  20. data/lib/asciidoctor/cli/options.rb +25 -25
  21. data/lib/asciidoctor/convert.rb +1 -0
  22. data/lib/asciidoctor/converter/docbook5.rb +45 -26
  23. data/lib/asciidoctor/converter/html5.rb +130 -102
  24. data/lib/asciidoctor/converter/manpage.rb +69 -64
  25. data/lib/asciidoctor/converter/template.rb +12 -13
  26. data/lib/asciidoctor/converter.rb +6 -4
  27. data/lib/asciidoctor/core_ext/hash/merge.rb +1 -1
  28. data/lib/asciidoctor/document.rb +61 -57
  29. data/lib/asciidoctor/extensions.rb +20 -12
  30. data/lib/asciidoctor/list.rb +2 -6
  31. data/lib/asciidoctor/load.rb +11 -9
  32. data/lib/asciidoctor/logging.rb +10 -8
  33. data/lib/asciidoctor/parser.rb +177 -193
  34. data/lib/asciidoctor/path_resolver.rb +3 -3
  35. data/lib/asciidoctor/reader.rb +73 -72
  36. data/lib/asciidoctor/rx.rb +5 -4
  37. data/lib/asciidoctor/section.rb +7 -0
  38. data/lib/asciidoctor/substitutors.rb +121 -121
  39. data/lib/asciidoctor/syntax_highlighter/coderay.rb +2 -1
  40. data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +1 -1
  41. data/lib/asciidoctor/syntax_highlighter/pygments.rb +16 -7
  42. data/lib/asciidoctor/syntax_highlighter/rouge.rb +2 -1
  43. data/lib/asciidoctor/syntax_highlighter.rb +8 -11
  44. data/lib/asciidoctor/table.rb +18 -20
  45. data/lib/asciidoctor/timings.rb +3 -3
  46. data/lib/asciidoctor/version.rb +1 -1
  47. data/lib/asciidoctor.rb +10 -10
  48. data/man/asciidoctor.1 +26 -28
  49. data/man/asciidoctor.adoc +33 -27
  50. metadata +8 -62
@@ -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
@@ -59,8 +59,7 @@ class Reader
59
59
  end
60
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
@@ -576,8 +576,7 @@ class Reader
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
578
  if (normalize = opts[:normalize])
579
- trim_end = normalize == :chomp ? false : true
580
- ::Array === data ? (Helpers.prepare_source_array data, trim_end) : (Helpers.prepare_source_string data, trim_end)
579
+ ::Array === data ? (Helpers.prepare_source_array data, normalize != :chomp) : (Helpers.prepare_source_string data, normalize != :chomp)
581
580
  elsif ::Array === data
582
581
  data.drop 0
583
582
  elsif data
@@ -690,6 +689,7 @@ class PreprocessorReader < Reader
690
689
  @path = (path ||= ::File.basename file)
691
690
  # only process lines in AsciiDoc files
692
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
693
693
  @includes[path.slice 0, (path.rindex '.')] = attributes['partial-option'] ? nil : true
694
694
  end
695
695
  else
@@ -697,6 +697,7 @@ class PreprocessorReader < Reader
697
697
  # we don't know what file type we have, so assume AsciiDoc
698
698
  @process_lines = true
699
699
  if (@path = path)
700
+ # NOTE registering the include with a nil value tracks it while not making it visible to interdocument xrefs
700
701
  @includes[Helpers.rootname path] = attributes['partial-option'] ? nil : true
701
702
  else
702
703
  @path = '<stdin>'
@@ -723,16 +724,11 @@ class PreprocessorReader < Reader
723
724
  else
724
725
  # FIXME we eventually want to handle leveloffset without affecting the lines
725
726
  if attributes.key? 'leveloffset'
726
- @lines.unshift ''
727
- @lines.unshift %(:leveloffset: #{attributes['leveloffset']})
728
- @lines << ''
729
- if (old_leveloffset = @document.attr 'leveloffset')
730
- @lines << %(:leveloffset: #{old_leveloffset})
731
- else
732
- @lines << ':leveloffset!:'
733
- end
734
- # 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
735
729
  @lineno -= 2
730
+ else
731
+ @lines.reverse!
736
732
  end
737
733
 
738
734
  # FIXME kind of a hack
@@ -802,10 +798,8 @@ class PreprocessorReader < Reader
802
798
  result = super
803
799
 
804
800
  # QUESTION should this work for AsciiDoc table cell content? Currently it does not.
805
- if @document && @document.attributes['skip-front-matter']
806
- if (front_matter = skip_front_matter! result)
807
- @document.attributes['front-matter'] = front_matter.join LF
808
- end
801
+ if @document && @document.attributes['skip-front-matter'] && (front_matter = skip_front_matter! result)
802
+ @document.attributes['front-matter'] = front_matter.join LF
809
803
  end
810
804
 
811
805
  if opts.fetch :condense, true
@@ -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
@@ -1073,7 +1069,7 @@ class PreprocessorReader < Reader
1073
1069
  (split_delimited_value parsed_attrs['lines']).each do |linedef|
1074
1070
  if linedef.include? '..'
1075
1071
  from, _, to = linedef.partition '..'
1076
- 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
1077
1073
  else
1078
1074
  inc_linenos << linedef.to_i
1079
1075
  end
@@ -1129,18 +1125,23 @@ class PreprocessorReader < Reader
1129
1125
  push_include inc_lines, inc_path, relpath, inc_offset, parsed_attrs
1130
1126
  end
1131
1127
  elsif inc_tags
1132
- 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
1133
1129
  if inc_tags.key? '**'
1130
+ select = base_select = inc_tags.delete '**'
1134
1131
  if inc_tags.key? '*'
1135
- select = base_select = inc_tags.delete '**'
1136
1132
  wildcard = inc_tags.delete '*'
1137
- else
1138
- select = base_select = wildcard = inc_tags.delete '**'
1133
+ elsif !select && inc_tags.values.first == false
1134
+ wildcard = true
1139
1135
  end
1140
1136
  elsif inc_tags.key? '*'
1141
- select = base_select = !(wildcard = inc_tags.delete '*')
1137
+ if inc_tags.keys.first == '*'
1138
+ select = base_select = !(wildcard = inc_tags.delete '*')
1139
+ else
1140
+ select = base_select = false
1141
+ wildcard = inc_tags.delete '*'
1142
+ end
1142
1143
  else
1143
- select = base_select = false
1144
+ select = base_select = !(inc_tags.value? true)
1144
1145
  end
1145
1146
  begin
1146
1147
  reader.call inc_path, read_mode do |f|
@@ -1163,9 +1164,9 @@ class PreprocessorReader < Reader
1163
1164
  end
1164
1165
  end
1165
1166
  elsif inc_tags.key? this_tag
1166
- tags_used << this_tag
1167
+ tags_selected << this_tag if (select = inc_tags[this_tag])
1167
1168
  # QUESTION should we prevent tag from being selected when enclosing tag is excluded?
1168
- tag_stack << [(active_tag = this_tag), (select = inc_tags[this_tag]), inc_lineno]
1169
+ tag_stack << [(active_tag = this_tag), select, inc_lineno]
1169
1170
  elsif !wildcard.nil?
1170
1171
  select = active_tag && !select ? false : wildcard
1171
1172
  tag_stack << [(active_tag = this_tag), select, inc_lineno]
@@ -1186,12 +1187,12 @@ class PreprocessorReader < Reader
1186
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)
1187
1188
  end
1188
1189
  end
1189
- 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?
1190
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
1191
1192
  end
1192
1193
  shift
1193
1194
  if inc_offset
1194
- parsed_attrs['partial-option'] = '' unless base_select && wildcard && inc_tags.empty?
1195
+ parsed_attrs['partial-option'] = '' unless base_select && wildcard != false && inc_tags.empty?
1195
1196
  # FIXME not accounting for skipped lines in reader line numbering
1196
1197
  push_include inc_lines, inc_path, relpath, inc_offset, parsed_attrs
1197
1198
  end
@@ -1262,7 +1263,7 @@ class PreprocessorReader < Reader
1262
1263
  end
1263
1264
 
1264
1265
  def pop_include
1265
- if @include_stack.size > 0
1266
+ unless @include_stack.empty?
1266
1267
  @lines, @file, @dir, @path, @lineno, @maxdepth, @process_lines = @include_stack.pop
1267
1268
  # FIXME kind of a hack
1268
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).
@@ -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}*?[^\\]))\](?!<\/a>)/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
  #
@@ -592,7 +593,7 @@ module Asciidoctor
592
593
  # $$text$$
593
594
  # pass:quotes[text]
594
595
  #
595
- # 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
596
597
  InlinePassMacroRx = /(?:(?:(\\?)\[([^\]]+)\])?(\\{0,2})(\+\+\+?|\$\$)(#{CC_ALL}*?)\4|(\\?)pass:([a-z]+(?:,[a-z-]+)*)?\[(|#{CC_ALL}*?[^\\])\])/m
597
598
 
598
599
  # Matches an xref (i.e., cross-reference) inline macro, which may span multiple lines.
@@ -611,7 +612,7 @@ module Asciidoctor
611
612
  # Matches a trailing + preceded by at least one space character,
612
613
  # which forces a hard line break (<br> tag in HTML output).
613
614
  #
614
- # 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
615
616
  #
616
617
  # Examples
617
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