asciidoctor 2.0.9 → 2.0.14

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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +193 -16
  3. data/LICENSE +1 -1
  4. data/README-de.adoc +12 -13
  5. data/README-fr.adoc +11 -15
  6. data/README-jp.adoc +242 -185
  7. data/README-zh_CN.adoc +17 -18
  8. data/README.adoc +133 -131
  9. data/asciidoctor.gemspec +6 -6
  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 +6 -5
  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-tr.adoc +4 -3
  39. data/data/locale/attributes-uk.adoc +6 -5
  40. data/data/locale/attributes-zh_CN.adoc +4 -3
  41. data/data/locale/attributes-zh_TW.adoc +4 -3
  42. data/data/reference/syntax.adoc +14 -7
  43. data/data/stylesheets/asciidoctor-default.css +30 -30
  44. data/lib/asciidoctor.rb +40 -14
  45. data/lib/asciidoctor/abstract_block.rb +9 -4
  46. data/lib/asciidoctor/abstract_node.rb +16 -6
  47. data/lib/asciidoctor/attribute_list.rb +63 -71
  48. data/lib/asciidoctor/cli/invoker.rb +2 -0
  49. data/lib/asciidoctor/cli/options.rb +10 -9
  50. data/lib/asciidoctor/convert.rb +167 -162
  51. data/lib/asciidoctor/converter.rb +13 -12
  52. data/lib/asciidoctor/converter/docbook5.rb +5 -9
  53. data/lib/asciidoctor/converter/html5.rb +58 -45
  54. data/lib/asciidoctor/converter/manpage.rb +61 -38
  55. data/lib/asciidoctor/converter/template.rb +3 -0
  56. data/lib/asciidoctor/document.rb +44 -51
  57. data/lib/asciidoctor/extensions.rb +2 -4
  58. data/lib/asciidoctor/helpers.rb +20 -15
  59. data/lib/asciidoctor/load.rb +102 -101
  60. data/lib/asciidoctor/parser.rb +40 -32
  61. data/lib/asciidoctor/path_resolver.rb +14 -12
  62. data/lib/asciidoctor/reader.rb +20 -13
  63. data/lib/asciidoctor/rx.rb +7 -6
  64. data/lib/asciidoctor/substitutors.rb +69 -50
  65. data/lib/asciidoctor/syntax_highlighter.rb +15 -7
  66. data/lib/asciidoctor/syntax_highlighter/coderay.rb +1 -1
  67. data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +12 -4
  68. data/lib/asciidoctor/syntax_highlighter/prettify.rb +7 -4
  69. data/lib/asciidoctor/syntax_highlighter/pygments.rb +6 -7
  70. data/lib/asciidoctor/syntax_highlighter/rouge.rb +33 -19
  71. data/lib/asciidoctor/table.rb +52 -23
  72. data/lib/asciidoctor/version.rb +1 -1
  73. data/man/asciidoctor.1 +8 -8
  74. data/man/asciidoctor.adoc +4 -4
  75. metadata +16 -15
@@ -119,7 +119,7 @@ class Parser
119
119
  # returns the Hash of orphan block attributes captured above the header
120
120
  def self.parse_document_header(reader, document)
121
121
  # capture lines of block-level metadata and plow away comment lines that precede first block
122
- block_attrs = parse_block_metadata_lines reader, document
122
+ block_attrs = reader.skip_blank_lines ? (parse_block_metadata_lines reader, document) : {}
123
123
  doc_attrs = document.attributes
124
124
 
125
125
  # special case, block title is not allowed above document title,
@@ -144,7 +144,10 @@ class Parser
144
144
  l0_section_title = nil
145
145
  else
146
146
  document.title = l0_section_title
147
- doc_attrs['doctitle'] = doctitle_attr_val = document.apply_header_subs l0_section_title
147
+ if (doc_attrs['doctitle'] = doctitle_attr_val = document.sub_specialchars l0_section_title).include? ATTR_REF_HEAD
148
+ # QUESTION should we defer substituting attributes until the end of the header? or should we substitute again if necessary?
149
+ doc_attrs['doctitle'] = doctitle_attr_val = document.sub_attributes doctitle_attr_val, attribute_missing: 'skip'
150
+ end
148
151
  end
149
152
  document.header.source_location = source_location if source_location
150
153
  # default to compat-mode if document has setext doctitle
@@ -215,9 +218,6 @@ class Parser
215
218
  name_section = initialize_section reader, document, {}
216
219
  name_section_buffer = (reader.read_lines_until break_on_blank_lines: true, skip_line_comments: true).map {|l| l.lstrip }.join ' '
217
220
  if ManpageNamePurposeRx =~ name_section_buffer
218
- doc_attrs['manname-title'] ||= name_section.title
219
- doc_attrs['manname-id'] = name_section.id if name_section.id
220
- doc_attrs['manpurpose'] = $2
221
221
  if (manname = $1).include? ATTR_REF_HEAD
222
222
  manname = document.sub_attributes manname
223
223
  end
@@ -226,8 +226,14 @@ class Parser
226
226
  else
227
227
  mannames = [manname]
228
228
  end
229
+ if (manpurpose = $2).include? ATTR_REF_HEAD
230
+ manpurpose = document.sub_attributes manpurpose
231
+ end
232
+ doc_attrs['manname-title'] ||= name_section.title
233
+ doc_attrs['manname-id'] = name_section.id if name_section.id
229
234
  doc_attrs['manname'] = manname
230
235
  doc_attrs['mannames'] = mannames
236
+ doc_attrs['manpurpose'] = manpurpose
231
237
  if document.backend == 'manpage'
232
238
  doc_attrs['docname'] = manname
233
239
  doc_attrs['outfilesuffix'] = %(.#{manvolnum})
@@ -327,7 +333,7 @@ class Parser
327
333
  if current_level == 0
328
334
  part = book
329
335
  elsif current_level == 1 && section.special
330
- # NOTE technically preface and abstract sections are only permitted in the book doctype
336
+ # NOTE technically preface sections are only permitted in the book doctype
331
337
  unless (sectname = section.sectname) == 'appendix' || sectname == 'preface' || sectname == 'abstract'
332
338
  expected_next_level = nil
333
339
  end
@@ -433,8 +439,10 @@ class Parser
433
439
  # is treated like an untitled section
434
440
  elsif preamble # implies parent == document
435
441
  if preamble.blocks?
442
+ if book || document.blocks[1] || !Compliance.unwrap_standalone_preamble
443
+ preamble.source_location = preamble.blocks[0].source_location if document.sourcemap
436
444
  # unwrap standalone preamble (i.e., document has no sections) except for books, if permissible
437
- unless book || document.blocks[1] || !Compliance.unwrap_standalone_preamble
445
+ else
438
446
  document.blocks.shift
439
447
  while (child_block = preamble.blocks.shift)
440
448
  document << child_block
@@ -858,6 +866,7 @@ class Parser
858
866
  when :literal
859
867
  block = build_block(block_context, :verbatim, terminator, parent, reader, attributes)
860
868
  when :example
869
+ attributes['caption'] = '' if attributes['collapsible-option']
861
870
  block = build_block(block_context, :compound, terminator, parent, reader, attributes)
862
871
  when :quote, :verse
863
872
  AttributeList.rekey(attributes, [nil, 'attribution', 'citetitle'])
@@ -899,9 +908,7 @@ class Parser
899
908
  # FIXME title and caption should be assigned when block is constructed (though we need to handle all cases)
900
909
  if attributes['title']
901
910
  block.title = block_title = attributes.delete 'title'
902
- if (caption_attr_name = CAPTION_ATTR_NAMES[block.context]) && document.attributes[caption_attr_name]
903
- block.assign_caption (attributes.delete 'caption')
904
- end
911
+ block.assign_caption (attributes.delete 'caption') if CAPTION_ATTRIBUTE_NAMES[block.context]
905
912
  end
906
913
  # TODO eventually remove the style attribute from the attributes hash
907
914
  #block.style = attributes.delete 'style'
@@ -1148,14 +1155,16 @@ class Parser
1148
1155
  def self.catalog_inline_anchors text, block, document, reader
1149
1156
  text.scan InlineAnchorScanRx do
1150
1157
  if (id = $1)
1151
- if (reftext = $2)
1152
- next if (reftext.include? ATTR_REF_HEAD) && (reftext = document.sub_attributes reftext).empty?
1153
- end
1158
+ next if (reftext = $2) && (reftext.include? ATTR_REF_HEAD) && (reftext = document.sub_attributes reftext).empty?
1154
1159
  else
1155
1160
  id = $3
1156
1161
  if (reftext = $4)
1157
- reftext = reftext.gsub '\]', ']' if reftext.include? ']'
1158
- next if (reftext.include? ATTR_REF_HEAD) && (reftext = document.sub_attributes reftext).empty?
1162
+ if reftext.include? ']'
1163
+ reftext = reftext.gsub '\]', ']'
1164
+ reftext = document.sub_attributes reftext if reftext.include? ATTR_REF_HEAD
1165
+ elsif (reftext.include? ATTR_REF_HEAD) && (reftext = document.sub_attributes reftext).empty?
1166
+ next
1167
+ end
1159
1168
  end
1160
1169
  end
1161
1170
  unless document.register :refs, [id, (Inline.new block, :anchor, reftext, type: :ref, id: id)]
@@ -1311,7 +1320,7 @@ class Parser
1311
1320
  list_item.marker = sibling_trait
1312
1321
  if ordinal == 0 && !style
1313
1322
  # using list level makes more sense, but we don't track it
1314
- # basing style on marker level is compliant with AsciiDoc Python
1323
+ # basing style on marker level is compliant with AsciiDoc.py
1315
1324
  list_block.style = implicit_style || ((ORDERED_LIST_STYLES[sibling_trait.length - 1] || 'arabic').to_s)
1316
1325
  end
1317
1326
  if item_text.start_with?('[[') && LeadingInlineAnchorRx =~ item_text
@@ -2119,6 +2128,8 @@ class Parser
2119
2128
  name = 'sectnums'
2120
2129
  elsif name == 'hardbreaks'
2121
2130
  name = 'hardbreaks-option'
2131
+ elsif name == 'showtitle'
2132
+ store_attribute 'notitle', (value ? nil : ''), doc, attrs
2122
2133
  end
2123
2134
 
2124
2135
  if doc
@@ -2273,9 +2284,15 @@ class Parser
2273
2284
  end
2274
2285
 
2275
2286
  skipped = table_reader.skip_blank_lines || 0
2287
+ if attributes['header-option']
2288
+ table.has_header_option = true
2289
+ elsif skipped == 0 && !attributes['noheader-option']
2290
+ # NOTE: assume table has header until we know otherwise; if it doesn't (nil), cells in first row get reprocessed
2291
+ table.has_header_option = :implicit
2292
+ implicit_header = true
2293
+ end
2276
2294
  parser_ctx = Table::ParserContext.new table_reader, table, attributes
2277
2295
  format, loop_idx, implicit_header_boundary = parser_ctx.format, -1, nil
2278
- implicit_header = true unless skipped > 0 || attributes['header-option'] || attributes['noheader-option']
2279
2296
 
2280
2297
  while (line = table_reader.read_line)
2281
2298
  if (beyond_first = (loop_idx += 1) > 0) && line.empty?
@@ -2295,7 +2312,7 @@ class Parser
2295
2312
  implicit_header_boundary = nil if implicit_header_boundary
2296
2313
  # otherwise, the cell continues from previous line
2297
2314
  elsif implicit_header_boundary && implicit_header_boundary == loop_idx
2298
- implicit_header, implicit_header_boundary = false, nil
2315
+ table.has_header_option = implicit_header = implicit_header_boundary = nil
2299
2316
  end
2300
2317
  end
2301
2318
  end
@@ -2307,7 +2324,7 @@ class Parser
2307
2324
  if table_reader.has_more_lines? && table_reader.peek_line.empty?
2308
2325
  implicit_header_boundary = 1
2309
2326
  else
2310
- implicit_header = false
2327
+ table.has_header_option = implicit_header = nil
2311
2328
  end
2312
2329
  end
2313
2330
  end
@@ -2358,7 +2375,7 @@ class Parser
2358
2375
  case format
2359
2376
  when 'csv'
2360
2377
  if parser_ctx.buffer_has_unclosed_quotes?
2361
- implicit_header, implicit_header_boundary = false, nil if implicit_header_boundary && loop_idx == 0
2378
+ table.has_header_option = implicit_header = implicit_header_boundary = nil if implicit_header_boundary && loop_idx == 0
2362
2379
  parser_ctx.keep_cell_open
2363
2380
  else
2364
2381
  parser_ctx.close_cell true
@@ -2380,15 +2397,8 @@ class Parser
2380
2397
  end
2381
2398
  end
2382
2399
 
2383
- unless (table.attributes['colcount'] ||= table.columns.size) == 0 || explicit_colspecs
2384
- table.assign_column_widths
2385
- end
2386
-
2387
- if implicit_header
2388
- table.has_header_option = true
2389
- attributes['header-option'] = ''
2390
- end
2391
-
2400
+ table.assign_column_widths unless (table.attributes['colcount'] ||= table.columns.size) == 0 || explicit_colspecs
2401
+ table.has_header_option = true if implicit_header
2392
2402
  table.partition_header_footer attributes
2393
2403
 
2394
2404
  table
@@ -2579,9 +2589,7 @@ class Parser
2579
2589
  attributes['role'] = (existing_role = attributes['role']).nil_or_empty? ? (parsed_attrs[:role].join ' ') : %(#{existing_role} #{parsed_attrs[:role].join ' '})
2580
2590
  end
2581
2591
 
2582
- if parsed_attrs.key? :option
2583
- (opts = parsed_attrs[:option]).each {|opt| attributes[%(#{opt}-option)] = '' }
2584
- end
2592
+ parsed_attrs[:option].each {|opt| attributes[%(#{opt}-option)] = '' } if parsed_attrs.key? :option
2585
2593
 
2586
2594
  parsed_style
2587
2595
  else
@@ -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,7 +57,7 @@ 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
62
  @lines = prepare_lines data, opts
63
63
  @source_lines = @lines.drop 0
@@ -570,17 +570,18 @@ 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
+ trim_end = normalize == :chomp ? false : true
580
+ ::Array === data ? (Helpers.prepare_source_array data, trim_end) : (Helpers.prepare_source_string data, trim_end)
580
581
  elsif ::Array === data
581
582
  data.drop 0
582
583
  elsif data
583
- data.split LF, -1
584
+ data.chomp.split LF, -1
584
585
  else
585
586
  []
586
587
  end
@@ -717,7 +718,7 @@ class PreprocessorReader < Reader
717
718
  end
718
719
 
719
720
  # effectively fill the buffer
720
- if (@lines = prepare_lines data, normalize: true, condense: false, indent: attributes['indent']).empty?
721
+ if (@lines = prepare_lines data, normalize: @process_lines || :chomp, condense: false, indent: attributes['indent']).empty?
721
722
  pop_include
722
723
  else
723
724
  # FIXME we eventually want to handle leveloffset without affecting the lines
@@ -808,7 +809,6 @@ class PreprocessorReader < Reader
808
809
  end
809
810
 
810
811
  if opts.fetch :condense, true
811
- result.shift && @lineno += 1 while (first = result[0]) && first.empty?
812
812
  result.pop while (last = result[-1]) && last.empty?
813
813
  end
814
814
 
@@ -1060,7 +1060,13 @@ class PreprocessorReader < Reader
1060
1060
  return inc_path
1061
1061
  end
1062
1062
 
1063
+ if (enc = parsed_attrs['encoding']) && (::Encoding.find enc rescue nil)
1064
+ (read_mode_params = read_mode.split ':')[1] = enc
1065
+ read_mode = read_mode_params.join ':'
1066
+ end unless RUBY_ENGINE_OPAL
1067
+
1063
1068
  inc_linenos = inc_tags = nil
1069
+ # NOTE attrlist is nil if missing from include directive
1064
1070
  if attrlist
1065
1071
  if parsed_attrs.key? 'lines'
1066
1072
  inc_linenos = []
@@ -1131,9 +1137,10 @@ class PreprocessorReader < Reader
1131
1137
  else
1132
1138
  select = base_select = wildcard = inc_tags.delete '**'
1133
1139
  end
1140
+ elsif inc_tags.key? '*'
1141
+ select = base_select = !(wildcard = inc_tags.delete '*')
1134
1142
  else
1135
- select = base_select = !(inc_tags.value? true)
1136
- wildcard = inc_tags.delete '*'
1143
+ select = base_select = false
1137
1144
  end
1138
1145
  begin
1139
1146
  reader.call inc_path, read_mode do |f|
@@ -1148,7 +1155,7 @@ class PreprocessorReader < Reader
1148
1155
  active_tag, select = tag_stack.empty? ? [nil, base_select] : tag_stack[-1]
1149
1156
  elsif inc_tags.key? this_tag
1150
1157
  include_cursor = create_include_cursor inc_path, expanded_target, inc_lineno
1151
- if (idx = tag_stack.rindex {|key, _| key == this_tag })
1158
+ if (idx = tag_stack.rindex {|key,| key == this_tag })
1152
1159
  idx == 0 ? tag_stack.shift : (tag_stack.delete_at idx)
1153
1160
  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
1161
  else
@@ -269,7 +269,7 @@ module Asciidoctor
269
269
  #
270
270
  # NOTE we only have to check as far as the blank character because we know it means non-whitespace follows.
271
271
  # 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]))
272
+ AnyListRx = %r(^(?:[ \t]*(?:-|\*\**|\.\.*|\u2022|\d+\.|[a-zA-Z]\.|[IVXivx]+\))[ \t]|(?!//[^/])[ \t]*[^ \t]#{CC_ANY}*?(?::::{0,2}|;;)(?:$|[ \t])|<(?:\d+|\.)>[ \t]))
273
273
 
274
274
  # Matches an unordered list item (one level for hyphens, up to 5 levels for asterisks).
275
275
  #
@@ -469,7 +469,7 @@ module Asciidoctor
469
469
  # footnoteref:[id,text] (legacy)
470
470
  # footnoteref:[id] (legacy)
471
471
  #
472
- InlineFootnoteMacroRx = /\\?footnote(?:(ref):|:([#{CC_WORD}-]+)?)\[(?:|(#{CC_ALL}*?[^\\]))\]/m
472
+ InlineFootnoteMacroRx = /\\?footnote(?:(ref):|:([#{CC_WORD}-]+)?)\[(?:|(#{CC_ALL}*?[^\\]))\](?!<\/a>)/m
473
473
 
474
474
  # Matches an image or icon inline macro.
475
475
  #
@@ -514,9 +514,10 @@ module Asciidoctor
514
514
  # https://github.com[GitHub]
515
515
  # <https://github.com>
516
516
  # link:https://github.com[]
517
+ # "https://github.com[]"
517
518
  #
518
519
  # 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
520
+ InlineLinkRx = %r((^|link:|#{CG_BLANK}|&lt;|[>\(\)\[\];"'])(\\?(?:https?|file|ftp|irc)://[^\s\[\]<]*([^\s.,\[\]<]))(?:\[(|#{CC_ALL}*?[^\\])\])?)m
520
521
 
521
522
  # Match a link or e-mail inline macro.
522
523
  #
@@ -551,7 +552,7 @@ module Asciidoctor
551
552
  # menu:View[Page Style > No Style]
552
553
  # menu:View[Page Style, No Style]
553
554
  #
554
- InlineMenuMacroRx = /\\?menu:(#{CG_WORD}|[#{CC_WORD}&][^\n\[]*[^\s\[])\[ *(?:|(#{CC_ALL}*?[^\\]))?\]/m
555
+ InlineMenuMacroRx = /\\?menu:(#{CG_WORD}|[#{CC_WORD}&][^\n\[]*[^\s\[])\[ *(?:|(#{CC_ALL}*?[^\\]))\]/m
555
556
 
556
557
  # Matches an implicit menu inline macro.
557
558
  #
@@ -591,7 +592,7 @@ module Asciidoctor
591
592
  # $$text$$
592
593
  # pass:quotes[text]
593
594
  #
594
- # NOTE we have to support an empty pass:[] for compatibility with AsciiDoc Python
595
+ # NOTE we have to support an empty pass:[] for compatibility with AsciiDoc.py
595
596
  InlinePassMacroRx = /(?:(?:(\\?)\[([^\]]+)\])?(\\{0,2})(\+\+\+?|\$\$)(#{CC_ALL}*?)\4|(\\?)pass:([a-z]+(?:,[a-z-]+)*)?\[(|#{CC_ALL}*?[^\\])\])/m
596
597
 
597
598
  # Matches an xref (i.e., cross-reference) inline macro, which may span multiple lines.
@@ -610,7 +611,7 @@ module Asciidoctor
610
611
  # Matches a trailing + preceded by at least one space character,
611
612
  # which forces a hard line break (<br> tag in HTML output).
612
613
  #
613
- # NOTE AsciiDoc Python allows + to be preceded by TAB; Asciidoctor does not
614
+ # NOTE AsciiDoc.py allows + to be preceded by TAB; Asciidoctor does not
614
615
  #
615
616
  # Examples
616
617
  #
@@ -331,8 +331,8 @@ module Substitutors
331
331
  target ||= ext_config[:format] == :short ? content : target
332
332
  end
333
333
  if (Inline === (replacement = extension.process_method[self, target, attributes]))
334
- if (inline_subs = replacement.attributes.delete 'subs')
335
- replacement.text = apply_subs replacement.text, (expand_subs inline_subs)
334
+ if (inline_subs = replacement.attributes.delete 'subs') && (inline_subs = expand_subs inline_subs, 'custom inline macro')
335
+ replacement.text = apply_subs replacement.text, inline_subs
336
336
  end
337
337
  replacement.convert
338
338
  elsif replacement
@@ -542,14 +542,17 @@ module Substitutors
542
542
  end
543
543
 
544
544
  prefix, suffix = $1, ''
545
- # NOTE if $4 is set, then we're looking at a formal macro
545
+ # NOTE if $4 is set, we're looking at a formal macro (e.g., https://example.org[])
546
546
  if $4
547
547
  prefix = '' if prefix == 'link:'
548
548
  text = $4
549
549
  else
550
- # invalid macro syntax (link: prefix w/o trailing square brackets)
551
- # FIXME we probably shouldn't even get here...our regex is doing too much
552
- next $& if prefix == 'link:'
550
+ # invalid macro syntax (link: prefix w/o trailing square brackets or enclosed in double quotes)
551
+ # FIXME we probably shouldn't even get here when the link: prefix is present; the regex is doing too much
552
+ case prefix
553
+ when 'link:', ?", ?'
554
+ next $&
555
+ end
553
556
  text = ''
554
557
  case $3
555
558
  when ')'
@@ -591,7 +594,8 @@ module Substitutors
591
594
  unless text.empty?
592
595
  text = text.gsub ESC_R_SB, R_SB if text.include? R_SB
593
596
  if !doc.compat_mode && (text.include? '=')
594
- text = (attrs = (AttributeList.new text, self).parse)[1] || ''
597
+ # NOTE if an equals sign (=) is present, extract attributes from text
598
+ text, attrs = extract_attributes_from_text text, ''
595
599
  link_opts[:id] = attrs['id']
596
600
  end
597
601
 
@@ -637,7 +641,8 @@ module Substitutors
637
641
  text = text.gsub ESC_R_SB, R_SB if text.include? R_SB
638
642
  if mailto
639
643
  if !doc.compat_mode && (text.include? ',')
640
- text = (attrs = (AttributeList.new text, self).parse)[1] || ''
644
+ # NOTE if a comma (,) is present, extract attributes from text
645
+ text, attrs = extract_attributes_from_text text, ''
641
646
  link_opts[:id] = attrs['id']
642
647
  if attrs.key? 2
643
648
  if attrs.key? 3
@@ -648,7 +653,8 @@ module Substitutors
648
653
  end
649
654
  end
650
655
  elsif !doc.compat_mode && (text.include? '=')
651
- text = (attrs = (AttributeList.new text, self).parse)[1] || ''
656
+ # NOTE if an equals sign (=) is present, extract attributes from text
657
+ text, attrs = extract_attributes_from_text text, ''
652
658
  link_opts[:id] = attrs['id']
653
659
  end
654
660
 
@@ -739,8 +745,8 @@ module Substitutors
739
745
  refid = $2
740
746
  if (text = $3)
741
747
  text = text.gsub ESC_R_SB, R_SB if text.include? R_SB
742
- # NOTE if an equal sign (=) is present, parse text as attributes
743
- text = ((AttributeList.new text, self).parse_into attrs)[1] if !doc.compat_mode && (text.include? '=')
748
+ # NOTE if an equals sign (=) is present, extract attributes from text
749
+ text, attrs = extract_attributes_from_text text if !doc.compat_mode && (text.include? '=')
744
750
  end
745
751
  end
746
752
 
@@ -804,7 +810,7 @@ module Substitutors
804
810
  # handles: id (in compat mode or when natural xrefs are disabled)
805
811
  elsif doc.compat_mode || !Compliance.natural_xrefs
806
812
  refid, target = fragment, %(##{fragment})
807
- logger.info %(possible invalid reference: #{refid}) if logger.info? && doc.catalog[:refs][refid]
813
+ logger.info %(possible invalid reference: #{refid}) if logger.info? && !doc.catalog[:refs][refid]
808
814
  # handles: id
809
815
  elsif doc.catalog[:refs][fragment]
810
816
  refid, target = fragment, %(##{fragment})
@@ -843,19 +849,17 @@ module Substitutors
843
849
  end
844
850
 
845
851
  if id
846
- if text
852
+ if (footnote = doc.footnotes.find {|candidate| candidate.id == id })
853
+ index, text = footnote.index, footnote.text
854
+ type, target, id = :xref, id, nil
855
+ elsif text
847
856
  text = restore_passthroughs(normalize_text text, true, true)
848
857
  index = doc.counter('footnote-number')
849
858
  doc.register(:footnotes, Document::Footnote.new(index, id, text))
850
859
  type, target = :ref, nil
851
860
  else
852
- if (footnote = doc.footnotes.find {|candidate| candidate.id == id })
853
- index, text = footnote.index, footnote.text
854
- else
855
- logger.warn %(invalid footnote reference: #{id})
856
- index, text = nil, id
857
- end
858
- type, target, id = :xref, id, nil
861
+ logger.warn %(invalid footnote reference: #{id})
862
+ type, target, text, id = :xref, id, id, nil
859
863
  end
860
864
  elsif text
861
865
  text = restore_passthroughs(normalize_text text, true, true)
@@ -917,7 +921,7 @@ module Substitutors
917
921
  # use sub since it might be behind a line comment
918
922
  $&.sub RS, ''
919
923
  else
920
- Inline.new(self, :callout, $4 == '.' ? (autonum += 1).to_s : $4, id: @document.callouts.read_next_id, attributes: { 'guard' => $1 }).convert
924
+ Inline.new(self, :callout, $4 == '.' ? (autonum += 1).to_s : $4, id: @document.callouts.read_next_id, attributes: { 'guard' => $1 || ($3 == '--' ? ['<!--', '-->'] : nil) }).convert
921
925
  end
922
926
  end
923
927
  end
@@ -945,7 +949,7 @@ module Substitutors
945
949
  if (linenums_mode = (attr? 'linenums') ? (doc_attrs[%(#{syntax_hl_name}-linenums-mode)] || :table).to_sym : nil)
946
950
  start_line_number = 1 if (start_line_number = (attr 'start', 1).to_i) < 1
947
951
  end
948
- highlight_lines = resolve_lines_to_highlight source, (attr 'highlight') if attr? 'highlight'
952
+ highlight_lines = resolve_lines_to_highlight source, (attr 'highlight'), start_line_number if attr? 'highlight'
949
953
 
950
954
  highlighted, source_offset = syntax_hl.highlight self, source, (attr 'language'),
951
955
  callouts: callout_marks,
@@ -968,9 +972,10 @@ module Substitutors
968
972
  #
969
973
  # source - The String source.
970
974
  # spec - The lines specifier (e.g., "1-5, !2, 10" or "1..5;!2;10")
975
+ # start - The line number of the first line (optional, default: false)
971
976
  #
972
977
  # Returns an [Array] of unique, sorted line numbers.
973
- def resolve_lines_to_highlight source, spec
978
+ def resolve_lines_to_highlight source, spec, start = nil
974
979
  lines = []
975
980
  spec = spec.delete ' ' if spec.include? ' '
976
981
  ((spec.include? ',') ? (spec.split ',') : (spec.split ';')).map do |entry|
@@ -981,21 +986,22 @@ module Substitutors
981
986
  if (delim = (entry.include? '..') ? '..' : ((entry.include? '-') ? '-' : nil))
982
987
  from, delim, to = entry.partition delim
983
988
  to = (source.count LF) + 1 if to.empty? || (to = to.to_i) < 0
984
- line_nums = (from.to_i..to).to_a
985
- if negate
986
- lines -= line_nums
987
- else
988
- lines.concat line_nums
989
- end
990
- else
991
989
  if negate
992
- lines.delete entry.to_i
990
+ lines -= (from.to_i..to).to_a
993
991
  else
994
- lines << entry.to_i
992
+ lines |= (from.to_i..to).to_a
995
993
  end
994
+ elsif negate
995
+ lines.delete entry.to_i
996
+ elsif !lines.include?(line = entry.to_i)
997
+ lines << line
996
998
  end
997
999
  end
998
- lines.sort.uniq
1000
+ # If the start attribute is defined, then the lines to highlight specified by the provided spec should be relative to the start value.
1001
+ unless (shift = start ? start - 1 : 0) == 0
1002
+ lines = lines.map {|it| it - shift }
1003
+ end
1004
+ lines.sort
999
1005
  end
1000
1006
 
1001
1007
  # Public: Extract the passthrough text from the document for reinsertion after processing.
@@ -1112,7 +1118,7 @@ module Substitutors
1112
1118
  end
1113
1119
  subs = $2
1114
1120
  content = normalize_text $3, nil, true
1115
- # NOTE drop enclosing $ signs around latexmath for backwards compatibility with AsciiDoc Python
1121
+ # NOTE drop enclosing $ signs around latexmath for backwards compatibility with AsciiDoc.py
1116
1122
  content = content.slice 1, content.length - 2 if type == :latexmath && (content.start_with? '$') && (content.end_with? '$')
1117
1123
  subs = subs ? (resolve_pass_subs subs) : ((@document.basebackend? 'html') ? BASIC_SUBS : nil)
1118
1124
  passthrus[passthru_key = passthrus.size] = { text: content, subs: subs, type: type }
@@ -1226,17 +1232,16 @@ module Substitutors
1226
1232
  resolve_subs subs, :inline, nil, 'passthrough macro'
1227
1233
  end
1228
1234
 
1229
- # Public: Expand all groups in the subs list and return. If no subs are resolve, return nil.
1235
+ # Public: Expand all groups in the subs list and return. If no subs are resolved, return nil.
1230
1236
  #
1231
- # subs - The substitutions to expand; can be a Symbol, Symbol Array or nil
1237
+ # subs - The substitutions to expand; can be a Symbol, Symbol Array, or String
1238
+ # subject - The String to use in log messages to communicate the subject for which subs are being resolved (default: nil)
1232
1239
  #
1233
1240
  # Returns a Symbol Array of substitutions to pass to apply_subs or nil if no substitutions were resolved.
1234
- def expand_subs subs
1241
+ def expand_subs subs, subject = nil
1235
1242
  if ::Symbol === subs
1236
- unless subs == :none
1237
- SUB_GROUPS[subs] || [subs]
1238
- end
1239
- else
1243
+ subs == :none ? nil : SUB_GROUPS[subs] || [subs]
1244
+ elsif ::Array === subs
1240
1245
  expanded_subs = []
1241
1246
  subs.each do |key|
1242
1247
  unless key == :none
@@ -1247,8 +1252,9 @@ module Substitutors
1247
1252
  end
1248
1253
  end
1249
1254
  end
1250
-
1251
1255
  expanded_subs.empty? ? nil : expanded_subs
1256
+ else
1257
+ resolve_subs subs, :inline, nil, subject
1252
1258
  end
1253
1259
  end
1254
1260
 
@@ -1270,7 +1276,7 @@ module Substitutors
1270
1276
  # NOTE :literal with listparagraph-option gets folded into text of list item later
1271
1277
  default_subs = @context == :verse ? NORMAL_SUBS : VERBATIM_SUBS
1272
1278
  when :raw
1273
- # TODO make pass subs a compliance setting; AsciiDoc Python performs :attributes and :macros on a pass block
1279
+ # TODO make pass subs a compliance setting; AsciiDoc.py performs :attributes and :macros on a pass block
1274
1280
  default_subs = @context == :stem ? BASIC_SUBS : NO_SUBS
1275
1281
  else
1276
1282
  return @subs
@@ -1321,10 +1327,23 @@ module Substitutors
1321
1327
 
1322
1328
  private
1323
1329
 
1330
+ # This method is used in cases when the attrlist can be mixed with the text of a macro.
1331
+ # If no attributes are detected aside from the first positional attribute, and the first positional
1332
+ # attribute matches the attrlist, then the original text is returned.
1333
+ def extract_attributes_from_text text, default_text = nil
1334
+ attrlist = (text.include? LF) ? (text.tr LF, ' ') : text
1335
+ if (resolved_text = (attrs = (AttributeList.new attrlist, self).parse)[1])
1336
+ # NOTE if resolved text remains unchanged, clear attributes and return unparsed text
1337
+ resolved_text == attrlist ? [text, attrs.clear] : [resolved_text, attrs]
1338
+ else
1339
+ [default_text, attrs]
1340
+ end
1341
+ end
1342
+
1324
1343
  # Internal: Extract the callout numbers from the source to prepare it for syntax highlighting.
1325
1344
  def extract_callouts source
1326
1345
  callout_marks = {}
1327
- lineno = 0
1346
+ autonum = lineno = 0
1328
1347
  last_lineno = nil
1329
1348
  callout_rx = (attr? 'line-comment') ? CalloutExtractRxMap[attr 'line-comment'] : CalloutExtractRx
1330
1349
  # extract callout marks, indexed by line number
@@ -1336,7 +1355,7 @@ module Substitutors
1336
1355
  # use sub since it might be behind a line comment
1337
1356
  $&.sub RS, ''
1338
1357
  else
1339
- (callout_marks[lineno] ||= []) << [$1, $4]
1358
+ (callout_marks[lineno] ||= []) << [$1 || ($3 == '--' ? ['<!--', '-->'] : nil), $4 == '.' ? (autonum += 1).to_s : $4]
1340
1359
  last_lineno = lineno
1341
1360
  ''
1342
1361
  end
@@ -1358,15 +1377,15 @@ module Substitutors
1358
1377
  else
1359
1378
  preamble = ''
1360
1379
  end
1361
- autonum = lineno = 0
1380
+ lineno = 0
1362
1381
  preamble + ((source.split LF, -1).map do |line|
1363
1382
  if (conums = callout_marks.delete lineno += 1)
1364
1383
  if conums.size == 1
1365
- guard, conum = conums[0]
1366
- %(#{line}#{Inline.new(self, :callout, conum == '.' ? (autonum += 1).to_s : conum, id: @document.callouts.read_next_id, attributes: { 'guard' => guard }).convert})
1384
+ guard, numeral = conums[0]
1385
+ %(#{line}#{Inline.new(self, :callout, numeral, id: @document.callouts.read_next_id, attributes: { 'guard' => guard }).convert})
1367
1386
  else
1368
- %(#{line}#{conums.map do |guard_it, conum_it|
1369
- Inline.new(self, :callout, conum_it == '.' ? (autonum += 1).to_s : conum_it, id: @document.callouts.read_next_id, attributes: { 'guard' => guard_it }).convert
1387
+ %(#{line}#{conums.map do |guard_it, numeral_it|
1388
+ Inline.new(self, :callout, numeral_it, id: @document.callouts.read_next_id, attributes: { 'guard' => guard_it }).convert
1370
1389
  end.join ' '})
1371
1390
  end
1372
1391
  else