asciidoctor 2.0.18 → 2.0.23

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.
@@ -43,6 +43,12 @@ class Parser
43
43
 
44
44
  AuthorKeys = ['author', 'authorinitials', 'firstname', 'middlename', 'lastname', 'email']
45
45
 
46
+ ListContinuationMarker = ::Module.new
47
+
48
+ ListContinuationPlaceholder = ::String.new.extend ListContinuationMarker
49
+
50
+ ListContinuationString = (::String.new LIST_CONTINUATION).extend ListContinuationMarker
51
+
46
52
  # Internal: A Hash mapping horizontal alignment abbreviations to alignments
47
53
  # that can be applied to a table cell (or to all cells in a column)
48
54
  TableCellHorzAlignments = {
@@ -398,18 +404,22 @@ class Parser
398
404
  # REVIEW this may be doing too much
399
405
  if part
400
406
  if !section.blocks?
401
- # if this block wasn't marked as [partintro], emulate behavior as if it had
407
+ # if this not a [partintro] open block, enclose it in a [partintro] open block
402
408
  if new_block.style != 'partintro'
403
- # emulate [partintro] paragraph
404
- if new_block.context == :paragraph
405
- new_block.context = :open
409
+ # if this is already a normal open block, simply add the partintro style
410
+ if new_block.style == 'open' && new_block.context == :open
406
411
  new_block.style = 'partintro'
407
- # emulate [partintro] open block
408
412
  else
409
413
  new_block.parent = (intro = Block.new section, :open, content_model: :compound)
410
414
  intro.style = 'partintro'
411
415
  section.blocks << intro
412
416
  end
417
+ # if this is a [partintro] paragraph, convert it to a [partintro] open block w/ single paragraph
418
+ elsif new_block.content_model == :simple
419
+ new_block.content_model = :compound
420
+ new_block << (Block.new new_block, :paragraph, source: new_block.lines, subs: new_block.subs)
421
+ new_block.lines.clear
422
+ new_block.subs.clear
413
423
  end
414
424
  elsif section.blocks.size == 1
415
425
  first_block = section.blocks[0]
@@ -419,12 +429,11 @@ class Parser
419
429
  # rebuild [partintro] paragraph as an open block
420
430
  elsif first_block.content_model != :compound
421
431
  new_block.parent = (intro = Block.new section, :open, content_model: :compound)
422
- intro.style = 'partintro'
423
- section.blocks.shift
424
- if first_block.style == 'partintro'
432
+ if first_block.style == (intro.style = 'partintro')
425
433
  first_block.context = :paragraph
426
434
  first_block.style = nil
427
435
  end
436
+ section.blocks.shift
428
437
  intro << first_block
429
438
  section.blocks << intro
430
439
  end
@@ -552,6 +561,7 @@ class Parser
552
561
  # process lines verbatim
553
562
  if style && Compliance.strict_verbatim_paragraphs && (VERBATIM_STYLES.include? style)
554
563
  block_context = style.to_sym
564
+ cloaked_context = :paragraph
555
565
  reader.unshift_line this_line
556
566
  # advance to block parsing =>
557
567
  break
@@ -809,16 +819,17 @@ class Parser
809
819
  unless block
810
820
  case block_context
811
821
  when :listing, :source
812
- if block_context == :source || (!attributes[1] && (language = attributes[2] || doc_attrs['source-language']))
813
- if language
822
+ if block_context == :source || (language = attributes[1] ? nil : attributes[2] || doc_attrs['source-language'])
823
+ if language # :listing
814
824
  attributes['style'] = 'source'
815
825
  attributes['language'] = language
816
826
  AttributeList.rekey attributes, [nil, nil, 'linenums']
817
- else
827
+ else # :source
818
828
  AttributeList.rekey attributes, [nil, 'language', 'linenums']
819
829
  if doc_attrs.key? 'source-language'
820
830
  attributes['language'] = doc_attrs['source-language']
821
831
  end unless attributes.key? 'language'
832
+ attributes['cloaked-context'] = cloaked_context unless cloaked_context == :listing
822
833
  end
823
834
  if attributes['linenums-option'] || doc_attrs['source-linenums-option']
824
835
  attributes['linenums'] = ''
@@ -847,6 +858,7 @@ class Parser
847
858
  else
848
859
  attributes['language'] = language
849
860
  end
861
+ attributes['cloaked-context'] = cloaked_context
850
862
  if attributes['linenums-option'] || doc_attrs['source-linenums-option']
851
863
  attributes['linenums'] = ''
852
864
  end unless attributes.key? 'linenums'
@@ -1168,8 +1180,8 @@ class Parser
1168
1180
  if reftext.include? ']'
1169
1181
  reftext = reftext.gsub '\]', ']'
1170
1182
  reftext = document.sub_attributes reftext if reftext.include? ATTR_REF_HEAD
1171
- elsif (reftext.include? ATTR_REF_HEAD) && (reftext = document.sub_attributes reftext).empty?
1172
- next
1183
+ elsif reftext.include? ATTR_REF_HEAD
1184
+ reftext = nil if (reftext = document.sub_attributes reftext).empty?
1173
1185
  end
1174
1186
  end
1175
1187
  end
@@ -1417,17 +1429,18 @@ class Parser
1417
1429
  # the termination of the list
1418
1430
  break if is_sibling_list_item?(this_line, list_type, sibling_trait)
1419
1431
 
1432
+ this_line = ListContinuationString if this_line == LIST_CONTINUATION
1420
1433
  prev_line = buffer.empty? ? nil : buffer[-1]
1421
1434
 
1422
- if prev_line == LIST_CONTINUATION
1435
+ if ListContinuationMarker === prev_line
1423
1436
  if continuation == :inactive
1424
1437
  continuation = :active
1425
1438
  has_text = true
1426
- buffer[-1] = '' unless within_nested_list
1439
+ buffer[-1] = ListContinuationPlaceholder unless within_nested_list
1427
1440
  end
1428
1441
 
1429
1442
  # dealing with adjacent list continuations (which is really a syntax error)
1430
- if this_line == LIST_CONTINUATION
1443
+ if ListContinuationMarker === this_line
1431
1444
  if continuation != :frozen
1432
1445
  continuation = :frozen
1433
1446
  buffer << this_line
@@ -1439,21 +1452,34 @@ class Parser
1439
1452
 
1440
1453
  # a delimited block immediately breaks the list unless preceded
1441
1454
  # by a list continuation (they are harsh like that ;0)
1442
- if (match = is_delimited_block?(this_line, true))
1443
- if continuation == :active
1444
- buffer << this_line
1445
- # grab all the lines in the block, leaving the delimiters in place
1446
- # we're being more strict here about the terminator, but I think that's a good thing
1447
- buffer.concat reader.read_lines_until(terminator: match.terminator, read_last_line: true, context: nil)
1448
- continuation = :inactive
1449
- else
1455
+ if (match = is_delimited_block? this_line, true)
1456
+ break unless continuation == :active
1457
+ buffer << this_line
1458
+ # grab all the lines in the block, leaving the delimiters in place
1459
+ # we're being more strict here about the terminator, but I think that's a good thing
1460
+ buffer.concat reader.read_lines_until terminator: match.terminator, read_last_line: true, context: nil
1461
+ continuation = :inactive
1462
+ # BlockAttributeLineRx only breaks dlist if ensuing line is not a list item
1463
+ elsif dlist && continuation != :active && (this_line.start_with? '[') && (BlockAttributeLineRx.match? this_line)
1464
+ block_attribute_lines = [this_line]
1465
+ while (next_line = reader.peek_line)
1466
+ if is_delimited_block? next_line
1467
+ interrupt = true
1468
+ elsif next_line.empty? || ((next_line.start_with? '[') && (BlockAttributeLineRx.match? next_line))
1469
+ block_attribute_lines << reader.read_line
1470
+ next
1471
+ elsif (AnyListRx.match? next_line) && !(is_sibling_list_item? next_line, list_type, sibling_trait)
1472
+ buffer.concat block_attribute_lines
1473
+ else # rubocop:disable Lint/DuplicateBranch
1474
+ interrupt = true
1475
+ end
1476
+ break
1477
+ end
1478
+ if interrupt
1479
+ this_line = nil
1480
+ reader.unshift_lines block_attribute_lines
1450
1481
  break
1451
1482
  end
1452
- # technically BlockAttributeLineRx only breaks if ensuing line is not a list item
1453
- # which really means BlockAttributeLineRx only breaks if it's acting as a block delimiter
1454
- # FIXME to be AsciiDoc compliant, we shouldn't break if style in attribute line is "literal" (i.e., [literal])
1455
- elsif dlist && continuation != :active && (BlockAttributeLineRx.match? this_line)
1456
- break
1457
1483
  elsif continuation == :active && !this_line.empty?
1458
1484
  # literal paragraphs have special considerations (and this is one of
1459
1485
  # two entry points into one)
@@ -1470,10 +1496,11 @@ class Parser
1470
1496
  end
1471
1497
  continuation = :inactive
1472
1498
  # let block metadata play out until we find the block
1473
- elsif (BlockTitleRx.match? this_line) || (BlockAttributeLineRx.match? this_line) || (AttributeEntryRx.match? this_line)
1499
+ elsif ((ch0 = this_line.chr) == '.' && (BlockTitleRx.match? this_line)) ||
1500
+ (ch0 == '[' && (BlockAttributeLineRx.match? this_line)) || (ch0 == ':' && (AttributeEntryRx.match? this_line))
1474
1501
  buffer << this_line
1475
1502
  else
1476
- if (nested_list_type = (within_nested_list ? [:dlist] : NESTABLE_LIST_CONTEXTS).find {|ctx| ListRxMap[ctx].match? this_line })
1503
+ if (nested_list_type = (within_nested_list ? [:dlist] : NESTABLE_LIST_CONTEXTS).find {|ctx| ListRxMap[ctx] =~ this_line })
1477
1504
  within_nested_list = true
1478
1505
  if nested_list_type == :dlist && $3.nil_or_empty?
1479
1506
  # get greedy again
@@ -1494,7 +1521,7 @@ class Parser
1494
1521
 
1495
1522
  if this_line == LIST_CONTINUATION
1496
1523
  detached_continuation = buffer.size
1497
- buffer << this_line
1524
+ buffer << ListContinuationString
1498
1525
  elsif has_text # has_text only relevant for dlist, which is more greedy until it has text for an item; has_text is always true for all other lists
1499
1526
  # in this block, we have to see whether we stay in the list
1500
1527
  # TODO any way to combine this with the check after skipping blank lines?
@@ -1527,6 +1554,9 @@ class Parser
1527
1554
  buffer << this_line
1528
1555
  has_text = true
1529
1556
  end
1557
+ elsif ListContinuationMarker === this_line
1558
+ has_text = true
1559
+ buffer << this_line
1530
1560
  else
1531
1561
  has_text = true unless this_line.empty?
1532
1562
  if (nested_list_type = (within_nested_list ? [:dlist] : NESTABLE_LIST_CONTEXTS).find {|ctx| ListRxMap[ctx] =~ this_line })
@@ -1543,16 +1573,17 @@ class Parser
1543
1573
 
1544
1574
  reader.unshift_line this_line if this_line
1545
1575
 
1546
- buffer[detached_continuation] = '' if detached_continuation
1576
+ buffer[detached_continuation] = ListContinuationPlaceholder if detached_continuation
1547
1577
 
1548
1578
  until buffer.empty?
1579
+ # drop optional trailing continuation
1580
+ if ListContinuationMarker === (last_line = buffer[-1])
1581
+ buffer.pop
1582
+ break
1549
1583
  # strip trailing blank lines to prevent empty blocks
1550
- if (last_line = buffer[-1]).empty?
1584
+ elsif last_line.empty?
1551
1585
  buffer.pop
1552
1586
  else
1553
- # drop optional trailing continuation
1554
- # (a blank line would have served the same purpose in the document)
1555
- buffer.pop if last_line == LIST_CONTINUATION
1556
1587
  break
1557
1588
  end
1558
1589
  end
@@ -2383,6 +2414,7 @@ class Parser
2383
2414
  end
2384
2415
  end
2385
2416
 
2417
+ parser_ctx.close_table
2386
2418
  table.assign_column_widths unless (table.attributes['colcount'] ||= table.columns.size) == 0 || explicit_colspecs
2387
2419
  table.has_header_option = true if implicit_header
2388
2420
  table.partition_header_footer attributes
@@ -109,6 +109,7 @@ class PathResolver
109
109
  SLASH = '/'
110
110
  BACKSLASH = '\\'
111
111
  DOUBLE_SLASH = '//'
112
+ URI_CLASSLOADER = 'uri:classloader:'
112
113
  WindowsRootRx = %r(^(?:[a-zA-Z]:)?[\\/])
113
114
 
114
115
  attr_accessor :file_separator
@@ -148,8 +149,9 @@ class PathResolver
148
149
  # Public: Check if the specified path is an absolute root path (or, in the
149
150
  # browser environment, an absolute URI as well)
150
151
  #
151
- # This operation considers both posix paths and Windows paths. If the JavaScript IO
152
- # module is xmlhttprequest, this operation also considers absolute URIs.
152
+ # This operation considers both POSIX and Windows paths. If the JavaScript IO module
153
+ # is xmlhttprequest, this operation also considers absolute URIs. If running on JRuby,
154
+ # this operation also considers classloader URIs (starts with uri:classloader:).
153
155
  #
154
156
  # Unix absolute paths and UNC paths start with slash. Windows roots can
155
157
  # start with a drive letter. When the IO module is xmlhttprequest (Opal
@@ -164,6 +166,10 @@ class PathResolver
164
166
  def root? path
165
167
  (absolute_path? path) || (path.start_with? 'file://', 'http://', 'https://')
166
168
  end
169
+ elsif ::RUBY_ENGINE == 'jruby'
170
+ def root? path
171
+ (absolute_path? path) || (path.start_with? URI_CLASSLOADER)
172
+ end
167
173
  else
168
174
  alias root? absolute_path?
169
175
  end
@@ -299,6 +305,9 @@ class PathResolver
299
305
  # ex. /sample/path
300
306
  elsif posix_path.start_with? SLASH
301
307
  root = SLASH
308
+ # ex. uri:classloader:sample/path (or uri:classloader:/sample/path)
309
+ elsif posix_path.start_with? URI_CLASSLOADER
310
+ root = posix_path.slice 0, URI_CLASSLOADER.length
302
311
  # ex. C:/sample/path (or file:///sample/path in browser environment)
303
312
  else
304
313
  root = posix_path.slice 0, (posix_path.index SLASH) + 1
@@ -125,16 +125,21 @@ class Reader
125
125
  # Returns the next line of the source data as a String if there are lines remaining.
126
126
  # Returns nothing if there is no more data.
127
127
  def peek_line direct = false
128
- if direct || @look_ahead > 0
129
- @unescape_next_line ? ((line = @lines[-1]).slice 1, line.length) : @lines[-1]
130
- elsif @lines.empty?
131
- @look_ahead = 0
132
- nil
133
- else
134
- # FIXME the problem with this approach is that we aren't
135
- # retaining the modified line (hence the @unescape_next_line tweak)
136
- # perhaps we need a stack of proxied lines
137
- (process_line @lines[-1]) || peek_line
128
+ while true
129
+ next_line = @lines[-1]
130
+ if direct || @look_ahead > 0
131
+ return @unescape_next_line ? (next_line.slice 1, next_line.length) : next_line
132
+ elsif next_line
133
+ # FIXME the problem with this approach is that we aren't
134
+ # retaining the modified line (hence the @unescape_next_line tweak)
135
+ # perhaps we need a stack of proxied lines
136
+ if (line = process_line next_line)
137
+ return line
138
+ end
139
+ else
140
+ @look_ahead = 0
141
+ return
142
+ end
138
143
  end
139
144
  end
140
145
 
@@ -815,6 +820,10 @@ class PreprocessorReader < Reader
815
820
  return line unless @process_lines
816
821
 
817
822
  if line.empty?
823
+ if @skipping
824
+ shift
825
+ return
826
+ end
818
827
  @look_ahead += 1
819
828
  return line
820
829
  end
@@ -1034,6 +1043,7 @@ class PreprocessorReader < Reader
1034
1043
  # if running in SafeMode::SECURE or greater, don't process this directive
1035
1044
  # however, be friendly and at least make it a link to the source document
1036
1045
  elsif doc.safe >= SafeMode::SECURE
1046
+ expanded_target = %(pass:c[#{expanded_target}]) if expanded_target.include? ' '
1037
1047
  # FIXME we don't want to use a link macro if we are in a verbatim context
1038
1048
  replace_next_line %(link:#{expanded_target}[role=include])
1039
1049
  elsif @maxdepth
@@ -1197,15 +1207,16 @@ class PreprocessorReader < Reader
1197
1207
  push_include inc_lines, inc_path, relpath, inc_offset, parsed_attrs
1198
1208
  end
1199
1209
  else
1210
+ inc_content = nil
1200
1211
  begin
1201
1212
  # NOTE read content before shift so cursor is only advanced if IO operation succeeds
1202
1213
  inc_content = reader.call(inc_path, read_mode) {|f| f.read }
1203
1214
  shift
1204
- push_include inc_content, inc_path, relpath, 1, parsed_attrs
1205
1215
  rescue
1206
1216
  logger.error message_with_context %(include #{target_type} not readable: #{inc_path}), source_location: cursor
1207
1217
  return replace_next_line %(Unresolved directive in #{@path} - include::#{expanded_target}[#{attrlist}])
1208
1218
  end
1219
+ push_include inc_content, inc_path, relpath, 1, parsed_attrs
1209
1220
  end
1210
1221
  true
1211
1222
  end
@@ -1232,7 +1243,10 @@ class PreprocessorReader < Reader
1232
1243
  def resolve_include_path target, attrlist, attributes
1233
1244
  doc = @document
1234
1245
  if (Helpers.uriish? target) || (::String === @dir ? nil : (target = %(#{@dir}/#{target})))
1235
- return replace_next_line %(link:#{target}[role=include]) unless doc.attr? 'allow-uri-read'
1246
+ unless doc.attr? 'allow-uri-read'
1247
+ target = %(pass:c[#{target}]) if target.include? ' '
1248
+ return replace_next_line %(link:#{target}[role=include])
1249
+ end
1236
1250
  if doc.attr? 'cache-uri'
1237
1251
  # caching requires the open-uri-cached gem to be installed
1238
1252
  # processing will be automatically aborted if these libraries can't be opened
@@ -89,7 +89,7 @@ module Asciidoctor
89
89
  # include::chapter1.ad[]
90
90
  # include::example.txt[lines=1;2;5..10]
91
91
  #
92
- IncludeDirectiveRx = /^(\\)?include::([^\[][^\[]*)\[(#{CC_ANY}+)?\]$/
92
+ IncludeDirectiveRx = /^(\\)?include::([^\s\[](?:[^\[]*[^\s\[])?)\[(#{CC_ANY}+)?\]$/
93
93
 
94
94
  # Matches a trailing tag directive in an include file.
95
95
  #
@@ -103,6 +103,7 @@ module Asciidoctor
103
103
  # }
104
104
  # // end::try-catch[]
105
105
  # NOTE m flag is required for Asciidoctor.js
106
+ # NOTE the regex checks for \r to account of include files that use Windows newlines
106
107
  TagDirectiveRx = /\b(?:tag|(e)nd)::(\S+?)\[\](?=$|[ \r])/m
107
108
 
108
109
  ## Attribute entries and references
@@ -513,12 +514,17 @@ module Asciidoctor
513
514
  #
514
515
  # https://github.com
515
516
  # https://github.com[GitHub]
516
- # <https://github.com>
517
+ # <https://github.com> <= angle brackets not included in autolink
517
518
  # link:https://github.com[]
518
519
  # "https://github.com[]"
520
+ # (https://github.com) <= parenthesis not included in autolink
519
521
  #
520
- # FIXME revisit! the main issue is we need different rules for implicit vs explicit
521
- InlineLinkRx = %r((^|link:|#{CG_BLANK}|&lt;|[>\(\)\[\];"'])(\\?(?:https?|file|ftp|irc)://[^\s\[\]<]*([^\s.,\[\]<]))(?:\[(|#{CC_ALL}*?[^\\])\])?)m
522
+ if RUBY_ENGINE == 'opal'
523
+ # NOTE In JavaScript, a back reference succeeds if not set; invert the logic to give it a match to refute
524
+ InlineLinkRx = %r((^|link:|#{CG_BLANK}|\\?&lt;(?=\\?(?:https?|file|ftp|irc)(:))|[>\(\)\[\];"'])(\\?(?:https?|file|ftp|irc)://)(?:([^\s\[\]]+)\[(|#{CC_ALL}*?[^\\])\]|(?!\2)([^\s]+?)&gt;|([^\s\[\]<]*([^\s,.?!\[\]<\)]))))
525
+ else
526
+ InlineLinkRx = %r((^|link:|#{CG_BLANK}|\\?&lt;()|[>\(\)\[\];"'])(\\?(?:https?|file|ftp|irc)://)(?:([^\s\[\]]+)\[(|#{CC_ALL}*?[^\\])\]|\2([^\s]+?)&gt;|([^\s\[\]<]*([^\s,.?!\[\]<\)]))))m
527
+ end
522
528
 
523
529
  # Match a link or e-mail inline macro.
524
530
  #
@@ -568,23 +574,17 @@ module Asciidoctor
568
574
  # Examples
569
575
  #
570
576
  # +text+
571
- # `text` (compat)
577
+ # [x-]+text+
578
+ # [x-]`text`
579
+ # `text` (compat only)
580
+ # [role]`text` (compat only)
572
581
  #
573
582
  # NOTE we always capture the attributes so we know when to use compatible (i.e., legacy) behavior
574
583
  InlinePassRx = {
575
- false => ['+', '`', /(^|[^#{CC_WORD};:])(?:\[([^\]]+)\])?(\\?(\+|`)(\S|\S#{CC_ALL}*?\S)\4)(?!#{CG_WORD})/m],
576
- true => ['`', nil, /(^|[^`#{CC_WORD}])(?:\[([^\]]+)\])?(\\?(`)([^`\s]|[^`\s]#{CC_ALL}*?\S)\4)(?![`#{CC_WORD}])/m]
584
+ false => ['+', '-]', /((?:^|[^#{CC_WORD};:\\])(?=(\[)|\+)|\\(?=\[)|(?=\\\+))(?:\2(x-|[^\]]+ x-)\]|(?:\[([^\]]+)\])?(?=(\\)?\+))(\5?(\+|`)(\S|\S#{CC_ALL}*?\S)\7)(?!#{CG_WORD})/m],
585
+ true => ['`', nil, /(^|[^`#{CC_WORD}])(?:(\Z)()|\[([^\]]+)\](?=(\\))?)?(\5?(`)([^`\s]|[^`\s]#{CC_ALL}*?\S)\7)(?![`#{CC_WORD}])/m],
577
586
  }
578
587
 
579
- # Matches an inline plus passthrough spanning multiple lines, but only when it occurs directly
580
- # inside constrained monospaced formatting in non-compat mode.
581
- #
582
- # Examples
583
- #
584
- # +text+
585
- #
586
- SinglePlusInlinePassRx = /^(\\)?\+(\S|\S#{CC_ALL}*?\S)\+$/m
587
-
588
588
  # Matches several variants of the passthrough inline macro, which may span multiple lines.
589
589
  #
590
590
  # Examples