asciidoctor 2.0.10 → 2.0.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +294 -30
  3. data/LICENSE +1 -1
  4. data/README-de.adoc +16 -20
  5. data/README-fr.adoc +15 -22
  6. data/README-jp.adoc +15 -26
  7. data/README-zh_CN.adoc +21 -25
  8. data/README.adoc +161 -138
  9. data/asciidoctor.gemspec +6 -13
  10. data/data/locale/attributes-ar.adoc +4 -3
  11. data/data/locale/attributes-be.adoc +23 -0
  12. data/data/locale/attributes-bg.adoc +4 -3
  13. data/data/locale/attributes-ca.adoc +6 -5
  14. data/data/locale/attributes-cs.adoc +4 -3
  15. data/data/locale/attributes-da.adoc +6 -5
  16. data/data/locale/attributes-de.adoc +4 -4
  17. data/data/locale/attributes-en.adoc +4 -4
  18. data/data/locale/attributes-es.adoc +6 -5
  19. data/data/locale/attributes-fa.adoc +4 -3
  20. data/data/locale/attributes-fi.adoc +4 -3
  21. data/data/locale/attributes-fr.adoc +8 -7
  22. data/data/locale/attributes-hu.adoc +4 -3
  23. data/data/locale/attributes-id.adoc +4 -3
  24. data/data/locale/attributes-it.adoc +6 -5
  25. data/data/locale/attributes-ja.adoc +4 -3
  26. data/data/locale/{attributes-kr.adoc → attributes-ko.adoc} +4 -3
  27. data/data/locale/attributes-nb.adoc +4 -3
  28. data/data/locale/attributes-nl.adoc +6 -5
  29. data/data/locale/attributes-nn.adoc +4 -3
  30. data/data/locale/attributes-pl.adoc +8 -7
  31. data/data/locale/attributes-pt.adoc +6 -5
  32. data/data/locale/attributes-pt_BR.adoc +6 -5
  33. data/data/locale/attributes-ro.adoc +4 -3
  34. data/data/locale/attributes-ru.adoc +6 -5
  35. data/data/locale/attributes-sr.adoc +4 -4
  36. data/data/locale/attributes-sr_Latn.adoc +4 -4
  37. data/data/locale/attributes-sv.adoc +4 -4
  38. data/data/locale/attributes-th.adoc +23 -0
  39. data/data/locale/attributes-tr.adoc +4 -3
  40. data/data/locale/attributes-uk.adoc +6 -5
  41. data/data/locale/attributes-vi.adoc +23 -0
  42. data/data/locale/attributes-zh_CN.adoc +4 -3
  43. data/data/locale/attributes-zh_TW.adoc +4 -3
  44. data/data/reference/syntax.adoc +14 -7
  45. data/data/stylesheets/asciidoctor-default.css +76 -76
  46. data/data/stylesheets/coderay-asciidoctor.css +9 -9
  47. data/lib/asciidoctor/abstract_block.rb +20 -13
  48. data/lib/asciidoctor/abstract_node.rb +23 -12
  49. data/lib/asciidoctor/attribute_list.rb +64 -72
  50. data/lib/asciidoctor/block.rb +6 -6
  51. data/lib/asciidoctor/cli/invoker.rb +3 -2
  52. data/lib/asciidoctor/cli/options.rb +32 -31
  53. data/lib/asciidoctor/convert.rb +168 -162
  54. data/lib/asciidoctor/converter/docbook5.rb +49 -34
  55. data/lib/asciidoctor/converter/html5.rb +180 -139
  56. data/lib/asciidoctor/converter/manpage.rb +118 -90
  57. data/lib/asciidoctor/converter/template.rb +15 -13
  58. data/lib/asciidoctor/converter.rb +19 -16
  59. data/lib/asciidoctor/core_ext/hash/merge.rb +1 -1
  60. data/lib/asciidoctor/document.rb +77 -86
  61. data/lib/asciidoctor/extensions.rb +22 -16
  62. data/lib/asciidoctor/helpers.rb +20 -15
  63. data/lib/asciidoctor/list.rb +2 -6
  64. data/lib/asciidoctor/load.rb +103 -101
  65. data/lib/asciidoctor/logging.rb +10 -8
  66. data/lib/asciidoctor/parser.rb +211 -220
  67. data/lib/asciidoctor/path_resolver.rb +17 -15
  68. data/lib/asciidoctor/reader.rb +87 -79
  69. data/lib/asciidoctor/rx.rb +9 -7
  70. data/lib/asciidoctor/section.rb +7 -0
  71. data/lib/asciidoctor/substitutors.rb +167 -148
  72. data/lib/asciidoctor/syntax_highlighter/coderay.rb +3 -2
  73. data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +13 -5
  74. data/lib/asciidoctor/syntax_highlighter/prettify.rb +7 -4
  75. data/lib/asciidoctor/syntax_highlighter/pygments.rb +19 -11
  76. data/lib/asciidoctor/syntax_highlighter/rouge.rb +35 -20
  77. data/lib/asciidoctor/syntax_highlighter.rb +16 -16
  78. data/lib/asciidoctor/table.rb +70 -43
  79. data/lib/asciidoctor/timings.rb +3 -3
  80. data/lib/asciidoctor/version.rb +1 -1
  81. data/lib/asciidoctor.rb +45 -19
  82. data/man/asciidoctor.1 +29 -31
  83. data/man/asciidoctor.adoc +35 -29
  84. metadata +17 -70
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module Asciidoctor
3
3
  # Public: Methods to perform substitutions on lines of AsciiDoc text. This module
4
- # is intented to be mixed-in to Section and Block to provide operations for performing
4
+ # is intended to be mixed-in to Section and Block to provide operations for performing
5
5
  # the necessary substitutions.
6
6
  module Substitutors
7
7
  SpecialCharsRx = /[<&>]/
@@ -330,13 +330,13 @@ module Substitutors
330
330
  # NOTE for convenience, map content (unparsed attrlist) to target when format is short
331
331
  target ||= ext_config[:format] == :short ? content : target
332
332
  end
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)
333
+ if Inline === (replacement = extension.process_method[self, target, attributes])
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
339
- logger.info %(expected substitution value for custom inline macro to be of type Inline; got #{replacement.class}: #{match})
339
+ logger.info { %(expected substitution value for custom inline macro to be of type Inline; got #{replacement.class}: #{match}) }
340
340
  replacement
341
341
  else
342
342
  ''
@@ -445,23 +445,16 @@ module Substitutors
445
445
  # indexterm:[Tigers,Big cats]
446
446
  if (attrlist = normalize_text $2, true, true).include? '='
447
447
  if (primary = (attrs = (AttributeList.new attrlist, self).parse)[1])
448
- attrs['terms'] = terms = [primary]
449
- if (secondary = attrs[2])
450
- terms << secondary
451
- if (tertiary = attrs[3])
452
- terms << tertiary
453
- end
454
- end
448
+ attrs['terms'] = [primary]
455
449
  if (see_also = attrs['see-also'])
456
450
  attrs['see-also'] = (see_also.include? ',') ? (see_also.split ',').map {|it| it.lstrip } : [see_also]
457
451
  end
458
452
  else
459
- attrs = { 'terms' => (terms = attrlist) }
453
+ attrs = { 'terms' => attrlist }
460
454
  end
461
455
  else
462
- attrs = { 'terms' => (terms = split_simple_csv attrlist) }
456
+ attrs = { 'terms' => (split_simple_csv attrlist) }
463
457
  end
464
- #doc.register :indexterms, terms
465
458
  (Inline.new self, :indexterm, nil, attributes: attrs).convert
466
459
  when 'indexterm2'
467
460
  # honor the escape
@@ -474,34 +467,33 @@ module Substitutors
474
467
  attrs['see-also'] = (see_also.include? ',') ? (see_also.split ',').map {|it| it.lstrip } : [see_also]
475
468
  end
476
469
  end
477
- #doc.register :indexterms, [term]
478
470
  (Inline.new self, :indexterm, term, attributes: attrs, type: :visible).convert
479
471
  else
480
- text = $3
472
+ encl_text = $3
481
473
  # honor the escape
482
474
  if $&.start_with? RS
483
475
  # escape concealed index term, but process nested flow index term
484
- if (text.start_with? '(') && (text.end_with? ')')
485
- text = text.slice 1, text.length - 2
476
+ if (encl_text.start_with? '(') && (encl_text.end_with? ')')
477
+ encl_text = encl_text.slice 1, encl_text.length - 2
486
478
  visible, before, after = true, '(', ')'
487
479
  else
488
480
  next $&.slice 1, $&.length
489
481
  end
490
482
  else
491
483
  visible = true
492
- if text.start_with? '('
493
- if text.end_with? ')'
494
- text, visible = (text.slice 1, text.length - 2), false
484
+ if encl_text.start_with? '('
485
+ if encl_text.end_with? ')'
486
+ encl_text, visible = (encl_text.slice 1, encl_text.length - 2), false
495
487
  else
496
- text, before, after = (text.slice 1, text.length), '(', ''
488
+ encl_text, before, after = (encl_text.slice 1, encl_text.length), '(', ''
497
489
  end
498
- elsif text.end_with? ')'
499
- text, before, after = text.chop, '', ')'
490
+ elsif encl_text.end_with? ')'
491
+ encl_text, before, after = encl_text.chop, '', ')'
500
492
  end
501
493
  end
502
494
  if visible
503
495
  # ((Tigers))
504
- if (term = normalize_text text, true).include? ';&'
496
+ if (term = normalize_text encl_text, true).include? ';&'
505
497
  if term.include? ' &gt;&gt; '
506
498
  term, _, see = term.partition ' &gt;&gt; '
507
499
  attrs = { 'see' => see }
@@ -510,12 +502,11 @@ module Substitutors
510
502
  attrs = { 'see-also' => see_also }
511
503
  end
512
504
  end
513
- #doc.register :indexterms, [term]
514
505
  subbed_term = (Inline.new self, :indexterm, term, attributes: attrs, type: :visible).convert
515
506
  else
516
507
  # (((Tigers,Big cats)))
517
508
  attrs = {}
518
- if (terms = normalize_text text, true).include? ';&'
509
+ if (terms = normalize_text encl_text, true).include? ';&'
519
510
  if terms.include? ' &gt;&gt; '
520
511
  terms, _, see = terms.partition ' &gt;&gt; '
521
512
  attrs['see'] = see
@@ -524,8 +515,7 @@ module Substitutors
524
515
  attrs['see-also'] = see_also
525
516
  end
526
517
  end
527
- attrs['terms'] = terms = split_simple_csv terms
528
- #doc.register :indexterms, terms
518
+ attrs['terms'] = split_simple_csv terms
529
519
  subbed_term = (Inline.new self, :indexterm, nil, attributes: attrs).convert
530
520
  end
531
521
  before ? %(#{before}#{subbed_term}#{after}) : subbed_term
@@ -542,20 +532,24 @@ module Substitutors
542
532
  end
543
533
 
544
534
  prefix, suffix = $1, ''
545
- # NOTE if $4 is set, then we're looking at a formal macro
535
+ # NOTE if $4 is set, we're looking at a formal macro (e.g., https://example.org[])
546
536
  if $4
547
537
  prefix = '' if prefix == 'link:'
548
- text = $4
538
+ link_text = nil if (link_text = $4).empty?
549
539
  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:'
553
- text = ''
540
+ # invalid macro syntax (link: prefix w/o trailing square brackets or enclosed in double quotes)
541
+ # FIXME we probably shouldn't even get here when the link: prefix is present; the regex is doing too much
542
+ case prefix
543
+ when 'link:', ?", ?'
544
+ next $&
545
+ end
554
546
  case $3
555
- when ')'
556
- # move trailing ) out of URL
547
+ when ')', '?', '!'
557
548
  target = target.chop
558
- suffix = ')'
549
+ if (suffix = $3) == ')' && (target.end_with? '.', '?', '!')
550
+ suffix = target[-1] + suffix
551
+ target = target.chop
552
+ end
559
553
  # NOTE handle case when modified target is a URI scheme (e.g., http://)
560
554
  next $& if target.end_with? '://'
561
555
  when ';'
@@ -588,26 +582,37 @@ module Substitutors
588
582
  end
589
583
 
590
584
  attrs, link_opts = nil, { type: :link }
591
- unless text.empty?
592
- text = text.gsub ESC_R_SB, R_SB if text.include? R_SB
593
- if !doc.compat_mode && (text.include? '=')
594
- text = (attrs = (AttributeList.new text, self).parse)[1] || ''
585
+
586
+ if link_text
587
+ new_link_text = link_text = link_text.gsub ESC_R_SB, R_SB if link_text.include? R_SB
588
+ if !doc.compat_mode && (link_text.include? '=')
589
+ # NOTE if an equals sign (=) is present, extract attributes from link text
590
+ link_text, attrs = extract_attributes_from_text link_text, ''
591
+ new_link_text = link_text
595
592
  link_opts[:id] = attrs['id']
596
593
  end
597
594
 
598
- if text.end_with? '^'
599
- text = text.chop
595
+ if link_text.end_with? '^'
596
+ new_link_text = link_text = link_text.chop
600
597
  if attrs
601
598
  attrs['window'] ||= '_blank'
602
599
  else
603
600
  attrs = { 'window' => '_blank' }
604
601
  end
605
602
  end
606
- end
607
603
 
608
- if text.empty?
604
+ if new_link_text && new_link_text.empty?
605
+ # NOTE it's not possible for the URI scheme to be bare in this case
606
+ link_text = (doc_attrs.key? 'hide-uri-scheme') ? (target.sub UriSniffRx, '') : target
607
+ bare = true
608
+ end
609
+ else
609
610
  # NOTE it's not possible for the URI scheme to be bare in this case
610
- text = (doc_attrs.key? 'hide-uri-scheme') ? (target.sub UriSniffRx, '') : target
611
+ link_text = (doc_attrs.key? 'hide-uri-scheme') ? (target.sub UriSniffRx, '') : target
612
+ bare = true
613
+ end
614
+
615
+ if bare
611
616
  if attrs
612
617
  attrs['role'] = (attrs.key? 'role') ? %(bare #{attrs['role']}) : 'bare'
613
618
  else
@@ -617,7 +622,7 @@ module Substitutors
617
622
 
618
623
  doc.register :links, (link_opts[:target] = target)
619
624
  link_opts[:attributes] = attrs if attrs
620
- %(#{prefix}#{(Inline.new self, :anchor, text, link_opts).convert}#{suffix})
625
+ %(#{prefix}#{(Inline.new self, :anchor, link_text, link_opts).convert}#{suffix})
621
626
  end
622
627
  end
623
628
 
@@ -633,11 +638,12 @@ module Substitutors
633
638
  target = $2
634
639
  end
635
640
  attrs, link_opts = nil, { type: :link }
636
- unless (text = $3).empty?
637
- text = text.gsub ESC_R_SB, R_SB if text.include? R_SB
641
+ unless (link_text = $3).empty?
642
+ link_text = link_text.gsub ESC_R_SB, R_SB if link_text.include? R_SB
638
643
  if mailto
639
- if !doc.compat_mode && (text.include? ',')
640
- text = (attrs = (AttributeList.new text, self).parse)[1] || ''
644
+ if !doc.compat_mode && (link_text.include? ',')
645
+ # NOTE if a comma (,) is present, extract attributes from link text
646
+ link_text, attrs = extract_attributes_from_text link_text, ''
641
647
  link_opts[:id] = attrs['id']
642
648
  if attrs.key? 2
643
649
  if attrs.key? 3
@@ -647,13 +653,14 @@ module Substitutors
647
653
  end
648
654
  end
649
655
  end
650
- elsif !doc.compat_mode && (text.include? '=')
651
- text = (attrs = (AttributeList.new text, self).parse)[1] || ''
656
+ elsif !doc.compat_mode && (link_text.include? '=')
657
+ # NOTE if an equals sign (=) is present, extract attributes from link text
658
+ link_text, attrs = extract_attributes_from_text link_text, ''
652
659
  link_opts[:id] = attrs['id']
653
660
  end
654
661
 
655
- if text.end_with? '^'
656
- text = text.chop
662
+ if link_text.end_with? '^'
663
+ link_text = link_text.chop
657
664
  if attrs
658
665
  attrs['window'] ||= '_blank'
659
666
  else
@@ -662,17 +669,17 @@ module Substitutors
662
669
  end
663
670
  end
664
671
 
665
- if text.empty?
672
+ if link_text.empty?
666
673
  # mailto is a special case, already processed
667
674
  if mailto
668
- text = mailto_text
675
+ link_text = mailto_text
669
676
  else
670
677
  if doc_attrs.key? 'hide-uri-scheme'
671
- if (text = target.sub UriSniffRx, '').empty?
672
- text = target
678
+ if (link_text = target.sub UriSniffRx, '').empty?
679
+ link_text = target
673
680
  end
674
681
  else
675
- text = target
682
+ link_text = target
676
683
  end
677
684
  if attrs
678
685
  attrs['role'] = (attrs.key? 'role') ? %(bare #{attrs['role']}) : 'bare'
@@ -685,7 +692,7 @@ module Substitutors
685
692
  # QUESTION should a mailto be registered as an e-mail address?
686
693
  doc.register :links, (link_opts[:target] = target)
687
694
  link_opts[:attributes] = attrs if attrs
688
- Inline.new(self, :anchor, text, link_opts).convert
695
+ Inline.new(self, :anchor, link_text, link_opts).convert
689
696
  end
690
697
  end
691
698
 
@@ -732,15 +739,17 @@ module Substitutors
732
739
 
733
740
  attrs = {}
734
741
  if (refid = $1)
735
- refid, text = refid.split ',', 2
736
- text = text.lstrip if text
742
+ if refid.include? ','
743
+ refid, _, link_text = refid.partition ','
744
+ link_text = nil if (link_text = link_text.lstrip).empty?
745
+ end
737
746
  else
738
747
  macro = true
739
748
  refid = $2
740
- if (text = $3)
741
- 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? '=')
749
+ if (link_text = $3)
750
+ link_text = link_text.gsub ESC_R_SB, R_SB if link_text.include? R_SB
751
+ # NOTE if an equals sign (=) is present, extract attributes from link text
752
+ link_text, attrs = extract_attributes_from_text link_text if !doc.compat_mode && (link_text.include? '=')
744
753
  end
745
754
  end
746
755
 
@@ -794,7 +803,7 @@ module Substitutors
794
803
  refid, path, target = nil, nil, '#'
795
804
  end
796
805
  else
797
- refid, path = path, %(#{doc.attributes['relfileprefix']}#{path}#{src2src ? (doc.attributes.fetch 'relfilesuffix', doc.outfilesuffix) : ''})
806
+ refid, path = path, %(#{doc.attributes['relfileprefix'] || ''}#{path}#{src2src ? (doc.attributes.fetch 'relfilesuffix', doc.outfilesuffix) : ''})
798
807
  if fragment
799
808
  refid, target = %(#{refid}##{fragment}), %(#{path}##{fragment})
800
809
  else
@@ -804,7 +813,7 @@ module Substitutors
804
813
  # handles: id (in compat mode or when natural xrefs are disabled)
805
814
  elsif doc.compat_mode || !Compliance.natural_xrefs
806
815
  refid, target = fragment, %(##{fragment})
807
- logger.info %(possible invalid reference: #{refid}) if logger.info? && doc.catalog[:refs][refid]
816
+ logger.info %(possible invalid reference: #{refid}) if logger.info? && !doc.catalog[:refs][refid]
808
817
  # handles: id
809
818
  elsif doc.catalog[:refs][fragment]
810
819
  refid, target = fragment, %(##{fragment})
@@ -819,7 +828,7 @@ module Substitutors
819
828
  attrs['path'] = path
820
829
  attrs['fragment'] = fragment
821
830
  attrs['refid'] = refid
822
- Inline.new(self, :anchor, text, type: :xref, target: target, attributes: attrs).convert
831
+ Inline.new(self, :anchor, link_text, type: :xref, target: target, attributes: attrs).convert
823
832
  end
824
833
  end
825
834
 
@@ -831,7 +840,7 @@ module Substitutors
831
840
  # footnoteref
832
841
  if $1
833
842
  if $3
834
- id, text = $3.split ',', 2
843
+ id, content = $3.split ',', 2
835
844
  logger.warn %(found deprecated footnoteref macro: #{$&}; use footnote macro with target instead) unless doc.compat_mode
836
845
  else
837
846
  next $&
@@ -839,33 +848,31 @@ module Substitutors
839
848
  # footnote
840
849
  else
841
850
  id = $2
842
- text = $3
851
+ content = $3
843
852
  end
844
853
 
845
854
  if id
846
- if text
847
- text = restore_passthroughs(normalize_text text, true, true)
855
+ if (footnote = doc.footnotes.find {|candidate| candidate.id == id })
856
+ index, content = footnote.index, footnote.text
857
+ type, target, id = :xref, id, nil
858
+ elsif content
859
+ content = restore_passthroughs(normalize_text content, true, true)
848
860
  index = doc.counter('footnote-number')
849
- doc.register(:footnotes, Document::Footnote.new(index, id, text))
861
+ doc.register(:footnotes, Document::Footnote.new(index, id, content))
850
862
  type, target = :ref, nil
851
863
  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
864
+ logger.warn %(invalid footnote reference: #{id})
865
+ type, target, content, id = :xref, id, id, nil
859
866
  end
860
- elsif text
861
- text = restore_passthroughs(normalize_text text, true, true)
867
+ elsif content
868
+ content = restore_passthroughs(normalize_text content, true, true)
862
869
  index = doc.counter('footnote-number')
863
- doc.register(:footnotes, Document::Footnote.new(index, id, text))
870
+ doc.register(:footnotes, Document::Footnote.new(index, id, content))
864
871
  type = target = nil
865
872
  else
866
873
  next $&
867
874
  end
868
- Inline.new(self, :footnote, text, attributes: { 'index' => index }, id: id, target: target, type: type).convert
875
+ Inline.new(self, :footnote, content, attributes: { 'index' => index }, id: id, target: target, type: type).convert
869
876
  end
870
877
  end
871
878
 
@@ -917,7 +924,7 @@ module Substitutors
917
924
  # use sub since it might be behind a line comment
918
925
  $&.sub RS, ''
919
926
  else
920
- Inline.new(self, :callout, $4 == '.' ? (autonum += 1).to_s : $4, id: @document.callouts.read_next_id, attributes: { 'guard' => $1 }).convert
927
+ Inline.new(self, :callout, $4 == '.' ? (autonum += 1).to_s : $4, id: @document.callouts.read_next_id, attributes: { 'guard' => $1 || ($3 == '--' ? ['<!--', '-->'] : nil) }).convert
921
928
  end
922
929
  end
923
930
  end
@@ -933,7 +940,7 @@ module Substitutors
933
940
  # process_callouts - a Boolean flag indicating whether callout marks should be located and substituted
934
941
  #
935
942
  # Returns the highlighted source code, if a syntax highlighter is defined on the document, otherwise the source with
936
- # verbatim substituions applied
943
+ # verbatim substitutions applied
937
944
  def highlight_source source, process_callouts
938
945
  # NOTE the call to highlight? is a defensive check since, normally, we wouldn't arrive here unless it returns true
939
946
  return sub_source source, process_callouts unless (syntax_hl = @document.syntax_highlighter) && syntax_hl.highlight?
@@ -942,10 +949,11 @@ module Substitutors
942
949
 
943
950
  doc_attrs = @document.attributes
944
951
  syntax_hl_name = syntax_hl.name
945
- if (linenums_mode = (attr? 'linenums') ? (doc_attrs[%(#{syntax_hl_name}-linenums-mode)] || :table).to_sym : nil)
946
- start_line_number = 1 if (start_line_number = (attr 'start', 1).to_i) < 1
952
+ if (linenums_mode = (attr? 'linenums') ? (doc_attrs[%(#{syntax_hl_name}-linenums-mode)] || :table).to_sym : nil) &&
953
+ (start_line_number = (attr 'start', 1).to_i) < 1
954
+ start_line_number = 1
947
955
  end
948
- highlight_lines = resolve_lines_to_highlight source, (attr 'highlight') if attr? 'highlight'
956
+ highlight_lines = resolve_lines_to_highlight source, (attr 'highlight'), start_line_number if attr? 'highlight'
949
957
 
950
958
  highlighted, source_offset = syntax_hl.highlight self, source, (attr 'language'),
951
959
  callouts: callout_marks,
@@ -968,9 +976,10 @@ module Substitutors
968
976
  #
969
977
  # source - The String source.
970
978
  # spec - The lines specifier (e.g., "1-5, !2, 10" or "1..5;!2;10")
979
+ # start - The line number of the first line (optional, default: false)
971
980
  #
972
981
  # Returns an [Array] of unique, sorted line numbers.
973
- def resolve_lines_to_highlight source, spec
982
+ def resolve_lines_to_highlight source, spec, start = nil
974
983
  lines = []
975
984
  spec = spec.delete ' ' if spec.include? ' '
976
985
  ((spec.include? ',') ? (spec.split ',') : (spec.split ';')).map do |entry|
@@ -979,28 +988,29 @@ module Substitutors
979
988
  negate = true
980
989
  end
981
990
  if (delim = (entry.include? '..') ? '..' : ((entry.include? '-') ? '-' : nil))
982
- from, delim, to = entry.partition delim
991
+ from, _, to = entry.partition delim
983
992
  to = (source.count LF) + 1 if to.empty? || (to = to.to_i) < 0
984
- line_nums = (from.to_i..to).to_a
985
993
  if negate
986
- lines -= line_nums
994
+ lines -= (from.to_i..to).to_a
987
995
  else
988
- lines.concat line_nums
989
- end
990
- else
991
- if negate
992
- lines.delete entry.to_i
993
- else
994
- lines << entry.to_i
996
+ lines |= (from.to_i..to).to_a
995
997
  end
998
+ elsif negate
999
+ lines.delete entry.to_i
1000
+ elsif !lines.include?(line = entry.to_i)
1001
+ lines << line
996
1002
  end
997
1003
  end
998
- lines.sort.uniq
1004
+ # If the start attribute is defined, then the lines to highlight specified by the provided spec should be relative to the start value.
1005
+ unless (shift = start ? start - 1 : 0) == 0
1006
+ lines = lines.map {|it| it - shift }
1007
+ end
1008
+ lines.sort
999
1009
  end
1000
1010
 
1001
1011
  # Public: Extract the passthrough text from the document for reinsertion after processing.
1002
1012
  #
1003
- # text - The String from which to extract passthrough fragements
1013
+ # text - The String from which to extract passthrough fragments
1004
1014
  #
1005
1015
  # Returns the String text with passthrough regions substituted with placeholders
1006
1016
  def extract_passthroughs text
@@ -1112,7 +1122,7 @@ module Substitutors
1112
1122
  end
1113
1123
  subs = $2
1114
1124
  content = normalize_text $3, nil, true
1115
- # NOTE drop enclosing $ signs around latexmath for backwards compatibility with AsciiDoc Python
1125
+ # NOTE drop enclosing $ signs around latexmath for backwards compatibility with AsciiDoc.py
1116
1126
  content = content.slice 1, content.length - 2 if type == :latexmath && (content.start_with? '$') && (content.end_with? '$')
1117
1127
  subs = subs ? (resolve_pass_subs subs) : ((@document.basebackend? 'html') ? BASIC_SUBS : nil)
1118
1128
  passthrus[passthru_key = passthrus.size] = { text: content, subs: subs, type: type }
@@ -1207,7 +1217,7 @@ module Substitutors
1207
1217
  end
1208
1218
  end
1209
1219
  return unless candidates
1210
- # weed out invalid options and remove duplicates (order is preserved; first occurence wins)
1220
+ # weed out invalid options and remove duplicates (order is preserved; first occurrence wins)
1211
1221
  resolved = candidates & SUB_OPTIONS[type]
1212
1222
  unless (candidates - resolved).empty?
1213
1223
  invalid = candidates - resolved
@@ -1226,17 +1236,17 @@ module Substitutors
1226
1236
  resolve_subs subs, :inline, nil, 'passthrough macro'
1227
1237
  end
1228
1238
 
1229
- # Public: Expand all groups in the subs list and return. If no subs are resolve, return nil.
1239
+ # Public: Expand all groups in the subs list and return. If no subs are resolved, return nil.
1230
1240
  #
1231
- # subs - The substitutions to expand; can be a Symbol, Symbol Array or nil
1241
+ # subs - The substitutions to expand; can be a Symbol, Symbol Array, or String
1242
+ # subject - The String to use in log messages to communicate the subject for which subs are being resolved (default: nil)
1232
1243
  #
1233
1244
  # Returns a Symbol Array of substitutions to pass to apply_subs or nil if no substitutions were resolved.
1234
- def expand_subs subs
1235
- if ::Symbol === subs
1236
- unless subs == :none
1237
- SUB_GROUPS[subs] || [subs]
1238
- end
1239
- else
1245
+ def expand_subs subs, subject = nil
1246
+ case subs
1247
+ when ::Symbol
1248
+ subs == :none ? nil : SUB_GROUPS[subs] || [subs]
1249
+ when ::Array
1240
1250
  expanded_subs = []
1241
1251
  subs.each do |key|
1242
1252
  unless key == :none
@@ -1247,8 +1257,9 @@ module Substitutors
1247
1257
  end
1248
1258
  end
1249
1259
  end
1250
-
1251
1260
  expanded_subs.empty? ? nil : expanded_subs
1261
+ else
1262
+ resolve_subs subs, :inline, nil, subject
1252
1263
  end
1253
1264
  end
1254
1265
 
@@ -1270,7 +1281,7 @@ module Substitutors
1270
1281
  # NOTE :literal with listparagraph-option gets folded into text of list item later
1271
1282
  default_subs = @context == :verse ? NORMAL_SUBS : VERBATIM_SUBS
1272
1283
  when :raw
1273
- # TODO make pass subs a compliance setting; AsciiDoc Python performs :attributes and :macros on a pass block
1284
+ # TODO make pass subs a compliance setting; AsciiDoc.py performs :attributes and :macros on a pass block
1274
1285
  default_subs = @context == :stem ? BASIC_SUBS : NO_SUBS
1275
1286
  else
1276
1287
  return @subs
@@ -1321,10 +1332,23 @@ module Substitutors
1321
1332
 
1322
1333
  private
1323
1334
 
1335
+ # This method is used in cases when the attrlist can be mixed with the text of a macro.
1336
+ # If no attributes are detected aside from the first positional attribute, and the first positional
1337
+ # attribute matches the attrlist, then the original text is returned.
1338
+ def extract_attributes_from_text text, default_text = nil
1339
+ attrlist = (text.include? LF) ? (text.tr LF, ' ') : text
1340
+ if (resolved_text = (attrs = (AttributeList.new attrlist, self).parse)[1])
1341
+ # NOTE if resolved text remains unchanged, clear attributes and return unparsed text
1342
+ resolved_text == attrlist ? [text, attrs.clear] : [resolved_text, attrs]
1343
+ else
1344
+ [default_text, attrs]
1345
+ end
1346
+ end
1347
+
1324
1348
  # Internal: Extract the callout numbers from the source to prepare it for syntax highlighting.
1325
1349
  def extract_callouts source
1326
1350
  callout_marks = {}
1327
- lineno = 0
1351
+ autonum = lineno = 0
1328
1352
  last_lineno = nil
1329
1353
  callout_rx = (attr? 'line-comment') ? CalloutExtractRxMap[attr 'line-comment'] : CalloutExtractRx
1330
1354
  # extract callout marks, indexed by line number
@@ -1336,7 +1360,7 @@ module Substitutors
1336
1360
  # use sub since it might be behind a line comment
1337
1361
  $&.sub RS, ''
1338
1362
  else
1339
- (callout_marks[lineno] ||= []) << [$1, $4]
1363
+ (callout_marks[lineno] ||= []) << [$1 || ($3 == '--' ? ['<!--', '-->'] : nil), $4 == '.' ? (autonum += 1).to_s : $4]
1340
1364
  last_lineno = lineno
1341
1365
  ''
1342
1366
  end
@@ -1358,15 +1382,15 @@ module Substitutors
1358
1382
  else
1359
1383
  preamble = ''
1360
1384
  end
1361
- autonum = lineno = 0
1385
+ lineno = 0
1362
1386
  preamble + ((source.split LF, -1).map do |line|
1363
1387
  if (conums = callout_marks.delete lineno += 1)
1364
1388
  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})
1389
+ guard, numeral = conums[0]
1390
+ %(#{line}#{Inline.new(self, :callout, numeral, id: @document.callouts.read_next_id, attributes: { 'guard' => guard }).convert})
1367
1391
  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
1392
+ %(#{line}#{conums.map do |guard_it, numeral_it|
1393
+ Inline.new(self, :callout, numeral_it, id: @document.callouts.read_next_id, attributes: { 'guard' => guard_it }).convert
1370
1394
  end.join ' '})
1371
1395
  end
1372
1396
  else
@@ -1453,33 +1477,28 @@ module Substitutors
1453
1477
  #
1454
1478
  # Returns a Hash of attributes (role and id only)
1455
1479
  def parse_quoted_text_attributes str
1456
- return {} if (str = str.rstrip).empty?
1457
1480
  # NOTE attributes are typically resolved after quoted text, so substitute eagerly
1458
1481
  str = sub_attributes str if str.include? ATTR_REF_HEAD
1459
1482
  # for compliance, only consider first positional attribute (very unlikely)
1460
1483
  str = str.slice 0, (str.index ',') if str.include? ','
1461
-
1462
- if (str.start_with? '.', '#') && Compliance.shorthand_property_syntax
1463
- segments = str.split '#', 2
1464
-
1465
- if segments.size > 1
1466
- id, *more_roles = segments[1].split('.')
1484
+ if (str = str.strip).empty?
1485
+ {}
1486
+ elsif (str.start_with? '.', '#') && Compliance.shorthand_property_syntax
1487
+ before, _, after = str.partition '#'
1488
+ attrs = {}
1489
+ if after.empty?
1490
+ attrs['role'] = (before.tr '.', ' ').lstrip if before.length > 1
1467
1491
  else
1468
- more_roles = []
1469
- end
1470
-
1471
- roles = segments[0].empty? ? [] : segments[0].split('.')
1472
- if roles.size > 1
1473
- roles.shift
1474
- end
1475
-
1476
- if more_roles.size > 0
1477
- roles.concat more_roles
1492
+ id, _, roles = after.partition '.'
1493
+ attrs['id'] = id unless id.empty?
1494
+ if roles.empty?
1495
+ attrs['role'] = (before.tr '.', ' ').lstrip if before.length > 1
1496
+ elsif before.length > 1
1497
+ attrs['role'] = ((before + '.' + roles).tr '.', ' ').lstrip
1498
+ else
1499
+ attrs['role'] = roles.tr '.', ' '
1500
+ end
1478
1501
  end
1479
-
1480
- attrs = {}
1481
- attrs['id'] = id if id
1482
- attrs['role'] = roles.join ' ' unless roles.empty?
1483
1502
  attrs
1484
1503
  else
1485
1504
  { 'role' => str }
@@ -1513,7 +1532,7 @@ module Substitutors
1513
1532
  case c
1514
1533
  when ','
1515
1534
  if quote_open
1516
- accum = accum + c
1535
+ accum += c
1517
1536
  else
1518
1537
  values << accum.strip
1519
1538
  accum = ''
@@ -1521,7 +1540,7 @@ module Substitutors
1521
1540
  when '"'
1522
1541
  quote_open = !quote_open
1523
1542
  else
1524
- accum = accum + c
1543
+ accum += c
1525
1544
  end
1526
1545
  end
1527
1546
  values << accum.strip