asciidoctor 2.0.18 → 2.0.23

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