asciidoctor 2.0.10 → 2.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
@@ -2,8 +2,12 @@
2
2
  module Asciidoctor
3
3
  # A built-in {Converter} implementation that generates the man page (troff) format.
4
4
  #
5
- # The output follows the groff man page definition while also trying to be
6
- # consistent with the output produced by the a2x tool from AsciiDoc Python.
5
+ # The output of this converter adheres to the man definition as defined by
6
+ # groff and uses the manpage output of the DocBook toolchain as a foundation.
7
+ # That means if you've previously been generating man pages using the a2x tool
8
+ # from AsciiDoc.py, you should be able to achieve a very similar result
9
+ # using this converter. Though you'll also get to enjoy some notable
10
+ # enhancements that have been added since, such as the customizable linkstyle.
7
11
  #
8
12
  # See http://www.gnu.org/software/groff/manual/html_node/Man-usage.html#Man-usage
9
13
  class Converter::ManPageConverter < Converter::Base
@@ -15,13 +19,16 @@ class Converter::ManPageConverter < Converter::Base
15
19
  ESC_BS = %(#{ESC}\\) # escaped backslash (indicates troff formatting sequence)
16
20
  ESC_FS = %(#{ESC}.) # escaped full stop (indicates troff macro)
17
21
 
18
- LiteralBackslashRx = /(?:\A|[^#{ESC}])\\/
22
+ LiteralBackslashRx = /\A\\|(#{ESC})?\\/
19
23
  LeadingPeriodRx = /^\./
20
24
  EscapedMacroRx = /^(?:#{ESC}\\c\n)?#{ESC}\.((?:URL|MTO) "#{CC_ANY}*?" "#{CC_ANY}*?" )( |[^\s]*)(#{CC_ANY}*?)(?: *#{ESC}\\c)?$/
21
- MockBoundaryRx = /<\/?BOUNDARY>/
25
+ MalformedEscapedMacroRx = /(#{ESC}\\c) (#{ESC}\.(?:URL|MTO) )/
26
+ MockMacroRx = %r(</?(#{ESC}\\[^>]+)>)
22
27
  EmDashCharRefRx = /&#8212;(?:&#8203;)?/
23
28
  EllipsisCharRefRx = /&#8230;(?:&#8203;)?/
24
29
  WrappedIndentRx = /#{CG_BLANK}*#{LF}#{CG_BLANK}*/
30
+ XMLMarkupRx = /&#?[a-z\d]+;|</
31
+ PCDATAFilterRx = /(&#?[a-z\d]+;|<[^>]+>)|([^&<]+)/
25
32
 
26
33
  def initialize backend, opts = {}
27
34
  @backend = backend
@@ -91,17 +98,14 @@ class Converter::ManPageConverter < Converter::Base
91
98
  if node.attr? 'manpurpose'
92
99
  mannames = node.attr 'mannames', [manname]
93
100
  result << %(.SH "#{(node.attr 'manname-title', 'NAME').upcase}"
94
- #{mannames.map {|n| manify n }.join ', '} \\- #{manify node.attr('manpurpose'), whitespace: :normalize})
101
+ #{mannames.map {|n| (manify n).gsub '\-', '-' }.join ', '} \\- #{manify node.attr('manpurpose'), whitespace: :normalize})
95
102
  end
96
103
  end
97
104
 
98
105
  result << node.content
99
106
 
100
107
  # QUESTION should NOTES come after AUTHOR(S)?
101
- if node.footnotes? && !(node.attr? 'nofootnotes')
102
- result << '.SH "NOTES"'
103
- result.concat(node.footnotes.map {|fn| %(#{fn.index}. #{fn.text}) })
104
- end
108
+ append_footnotes result, node
105
109
 
106
110
  unless (authors = node.authors).empty?
107
111
  if authors.size > 1
@@ -124,10 +128,7 @@ class Converter::ManPageConverter < Converter::Base
124
128
  def convert_embedded node
125
129
  result = [node.content]
126
130
 
127
- if node.footnotes? && !(node.attr? 'nofootnotes')
128
- result << '.SH "NOTES"'
129
- result.concat(node.footnotes.map {|fn| %(#{fn.index}. #{fn.text}) })
130
- end
131
+ append_footnotes result, node
131
132
 
132
133
  # QUESTION should we add an AUTHOR(S) section?
133
134
 
@@ -142,7 +143,7 @@ class Converter::ManPageConverter < Converter::Base
142
143
  stitle = node.captioned_title
143
144
  else
144
145
  macro = 'SH'
145
- stitle = node.title.upcase
146
+ stitle = uppercase_pcdata node.title
146
147
  end
147
148
  result << %(.#{macro} "#{manify stitle}"
148
149
  #{node.content})
@@ -247,7 +248,9 @@ r lw(\n(.lu*75u/100u).'
247
248
  result << %(.sp
248
249
  .if n .RS 4
249
250
  .nf
251
+ .fam C
250
252
  #{manify node.content, whitespace: :preserve}
253
+ .fam
251
254
  .fi
252
255
  .if n .RE)
253
256
  result.join LF
@@ -261,7 +264,9 @@ r lw(\n(.lu*75u/100u).'
261
264
  result << %(.sp
262
265
  .if n .RS 4
263
266
  .nf
267
+ .fam C
264
268
  #{manify node.content, whitespace: :preserve}
269
+ .fam
265
270
  .fi
266
271
  .if n .RE)
267
272
  result.join LF
@@ -284,15 +289,16 @@ r lw(\n(.lu*75u/100u).'
284
289
  .B #{manify node.title}
285
290
  .br) if node.title?
286
291
 
292
+ start = (node.attr 'start', 1).to_i
287
293
  node.items.each_with_index do |item, idx|
288
294
  result << %(.sp
289
295
  .RS 4
290
296
  .ie n \\{\\
291
- \\h'-04' #{idx + 1}.\\h'+01'\\c
297
+ \\h'-04' #{numeral = idx + start}.\\h'+01'\\c
292
298
  .\\}
293
299
  .el \\{\\
294
300
  . sp -1
295
- . IP " #{idx + 1}." 4.2
301
+ . IP " #{numeral}." 4.2
296
302
  .\\}
297
303
  #{manify item.text, whitespace: :normalize})
298
304
  result << item.content if item.blocks?
@@ -310,8 +316,9 @@ r lw(\n(.lu*75u/100u).'
310
316
  end
311
317
  end
312
318
 
313
- # TODO use Page Control https://www.gnu.org/software/groff/manual/html_node/Page-Control.html#Page-Control
314
- alias convert_page_break skip
319
+ def convert_page_break node
320
+ '.bp'
321
+ end
315
322
 
316
323
  def convert_paragraph node
317
324
  if node.title?
@@ -412,15 +419,7 @@ allbox tab(:);'
412
419
  end
413
420
  row_text[row_index] << %(T{#{LF}.sp#{LF})
414
421
  cell_halign = (cell.attr 'halign', 'left').chr
415
- if tsec == :head
416
- if row_header[row_index].empty? || row_header[row_index][cell_index].empty?
417
- row_header[row_index][cell_index] << %(#{cell_halign}tB)
418
- else
419
- row_header[row_index][cell_index + 1] ||= []
420
- row_header[row_index][cell_index + 1] << %(#{cell_halign}tB)
421
- end
422
- row_text[row_index] << %(#{manify cell.text, whitespace: :normalize}#{LF})
423
- elsif tsec == :body
422
+ if tsec == :body
424
423
  if row_header[row_index].empty? || row_header[row_index][cell_index].empty?
425
424
  row_header[row_index][cell_index] << %(#{cell_halign}t)
426
425
  else
@@ -436,7 +435,7 @@ allbox tab(:);'
436
435
  cell_content = manify cell.content.join, whitespace: :normalize
437
436
  end
438
437
  row_text[row_index] << %(#{cell_content}#{LF})
439
- elsif tsec == :foot
438
+ else # tsec == :head || tsec == :foot
440
439
  if row_header[row_index].empty? || row_header[row_index][cell_index].empty?
441
440
  row_header[row_index][cell_index] << %(#{cell_halign}tB)
442
441
  else
@@ -526,12 +525,13 @@ allbox tab(:);'
526
525
  result.join LF
527
526
  end
528
527
 
529
- # FIXME git uses [verse] for the synopsis; detect this special case
530
528
  def convert_verse node
531
529
  result = []
532
- result << (node.title? ? %(.sp
530
+ if node.title?
531
+ result << %(.sp
533
532
  .B #{manify node.title}
534
- .br) : '.sp')
533
+ .br)
534
+ end
535
535
  attribution_line = (node.attr? 'citetitle') ? %(#{node.attr 'citetitle'} ) : nil
536
536
  attribution_line = (node.attr? 'attribution') ? %[#{attribution_line}\\(em #{node.attr 'attribution'}] : nil
537
537
  result << %(.sp
@@ -579,9 +579,13 @@ allbox tab(:);'
579
579
  %(#{ESC_BS}c#{LF}#{ESC_FS}#{macro} "#{target}" "#{text}" )
580
580
  when :xref
581
581
  unless (text = node.text)
582
- refid = node.attributes['refid']
583
- if AbstractNode === (ref = (@refs ||= node.document.catalog[:refs])[refid])
584
- text = (ref.xreftext node.attr('xrefstyle', nil, true)) || %([#{refid}])
582
+ if AbstractNode === (ref = (@refs ||= node.document.catalog[:refs])[refid = node.attributes['refid']] || (refid.nil_or_empty? ? (top = get_root_document node) : nil))
583
+ if (@resolving_xref ||= (outer = true)) && outer && (text = ref.xreftext node.attr 'xrefstyle', nil, true)
584
+ text = uppercase_pcdata text if ref.context === :section && ref.level < 2 && text == ref.title
585
+ else
586
+ text = top ? '[^top]' : %([#{refid}])
587
+ end
588
+ @resolving_xref = nil if outer
585
589
  else
586
590
  text = %([#{refid}])
587
591
  end
@@ -601,14 +605,13 @@ allbox tab(:);'
601
605
  end
602
606
 
603
607
  def convert_inline_button node
604
- %(#{ESC_BS}fB[#{ESC_BS}0#{node.text}#{ESC_BS}0]#{ESC_BS}fP)
608
+ %(<#{ESC_BS}fB>[#{ESC_BS}0#{node.text}#{ESC_BS}0]</#{ESC_BS}fP>)
605
609
  end
606
610
 
607
611
  def convert_inline_callout node
608
- %(#{ESC_BS}fB(#{node.text})#{ESC_BS}fP)
612
+ %(<#{ESC_BS}fB>(#{node.text})<#{ESC_BS}fP>)
609
613
  end
610
614
 
611
- # TODO supposedly groff has footnotes, but we're in search of an example
612
615
  def convert_inline_footnote node
613
616
  if (index = node.attr 'index')
614
617
  %([#{index}])
@@ -626,57 +629,68 @@ allbox tab(:);'
626
629
  end
627
630
 
628
631
  def convert_inline_kbd node
629
- if (keys = node.attr 'keys').size == 1
630
- keys[0]
631
- else
632
- keys.join %(#{ESC_BS}0+#{ESC_BS}0)
633
- end
632
+ %[<#{ESC_BS}f(CR>#{(keys = node.attr 'keys').size == 1 ? keys[0] : (keys.join "#{ESC_BS}0+#{ESC_BS}0")}</#{ESC_BS}fP>]
634
633
  end
635
634
 
636
635
  def convert_inline_menu node
637
636
  caret = %[#{ESC_BS}0#{ESC_BS}(fc#{ESC_BS}0]
638
637
  menu = node.attr 'menu'
639
638
  if !(submenus = node.attr 'submenus').empty?
640
- submenu_path = submenus.map {|item| %(#{ESC_BS}fI#{item}#{ESC_BS}fP) }.join caret
641
- %(#{ESC_BS}fI#{menu}#{ESC_BS}fP#{caret}#{submenu_path}#{caret}#{ESC_BS}fI#{node.attr 'menuitem'}#{ESC_BS}fP)
639
+ submenu_path = submenus.map {|item| %(<#{ESC_BS}fI>#{item}</#{ESC_BS}fP>) }.join caret
640
+ %(<#{ESC_BS}fI>#{menu}</#{ESC_BS}fP>#{caret}#{submenu_path}#{caret}<#{ESC_BS}fI>#{node.attr 'menuitem'}</#{ESC_BS}fP>)
642
641
  elsif (menuitem = node.attr 'menuitem')
643
- %(#{ESC_BS}fI#{menu}#{caret}#{menuitem}#{ESC_BS}fP)
642
+ %(<#{ESC_BS}fI>#{menu}#{caret}#{menuitem}</#{ESC_BS}fP>)
644
643
  else
645
- %(#{ESC_BS}fI#{menu}#{ESC_BS}fP)
644
+ %(<#{ESC_BS}fI>#{menu}</#{ESC_BS}fP>)
646
645
  end
647
646
  end
648
647
 
649
- # NOTE use fake <BOUNDARY> element to prevent creating artificial word boundaries
648
+ # NOTE use fake XML elements to prevent creating artificial word boundaries
650
649
  def convert_inline_quoted node
651
650
  case node.type
652
651
  when :emphasis
653
- %(#{ESC_BS}fI<BOUNDARY>#{node.text}</BOUNDARY>#{ESC_BS}fP)
652
+ %(<#{ESC_BS}fI>#{node.text}</#{ESC_BS}fP>)
654
653
  when :strong
655
- %(#{ESC_BS}fB<BOUNDARY>#{node.text}</BOUNDARY>#{ESC_BS}fP)
654
+ %(<#{ESC_BS}fB>#{node.text}</#{ESC_BS}fP>)
656
655
  when :monospaced
657
- %[#{ESC_BS}f(CR<BOUNDARY>#{node.text}</BOUNDARY>#{ESC_BS}fP]
656
+ %[<#{ESC_BS}f(CR>#{node.text}</#{ESC_BS}fP>]
658
657
  when :single
659
- %[#{ESC_BS}(oq<BOUNDARY>#{node.text}</BOUNDARY>#{ESC_BS}(cq]
658
+ %[<#{ESC_BS}(oq>#{node.text}</#{ESC_BS}(cq>]
660
659
  when :double
661
- %[#{ESC_BS}(lq<BOUNDARY>#{node.text}</BOUNDARY>#{ESC_BS}(rq]
660
+ %[<#{ESC_BS}(lq>#{node.text}</#{ESC_BS}(rq>]
662
661
  else
663
662
  node.text
664
663
  end
665
664
  end
666
665
 
667
666
  def self.write_alternate_pages mannames, manvolnum, target
668
- if mannames && mannames.size > 1
669
- mannames.shift
670
- manvolext = %(.#{manvolnum})
671
- dir, basename = ::File.split target
672
- mannames.each do |manname|
673
- ::File.write ::File.join(dir, %(#{manname}#{manvolext})), %(.so #{basename}), mode: FILE_WRITE_MODE
674
- end
667
+ return unless mannames && mannames.size > 1
668
+ mannames.shift
669
+ manvolext = %(.#{manvolnum})
670
+ dir, basename = ::File.split target
671
+ mannames.each do |manname|
672
+ ::File.write ::File.join(dir, %(#{manname}#{manvolext})), %(.so #{basename}), mode: FILE_WRITE_MODE
675
673
  end
676
674
  end
677
675
 
678
676
  private
679
677
 
678
+ def append_footnotes result, node
679
+ if node.footnotes? && !(node.attr? 'nofootnotes')
680
+ result << '.SH "NOTES"'
681
+ node.footnotes.each do |fn|
682
+ result << %(.IP [#{fn.index}])
683
+ # NOTE restore newline in escaped macro that gets removed by normalize_text in substitutor
684
+ if (text = fn.text).include? %(#{ESC}\\c #{ESC}.)
685
+ text = (manify %(#{text.gsub MalformedEscapedMacroRx, %(\\1#{LF}\\2)} ), whitespace: :normalize).chomp ' '
686
+ else
687
+ text = manify text, whitespace: :normalize
688
+ end
689
+ result << text
690
+ end
691
+ end
692
+ end
693
+
680
694
  # Converts HTML entity references back to their original form, escapes
681
695
  # special man characters and strips trailing whitespace.
682
696
  #
@@ -698,42 +712,56 @@ allbox tab(:);'
698
712
  else
699
713
  str = str.tr_s WHITESPACE, ' '
700
714
  end
701
- str = str.
702
- gsub(LiteralBackslashRx, '\&(rs'). # literal backslash (not a troff escape sequence)
703
- gsub(LeadingPeriodRx, '\\\&.'). # leading . is used in troff for macro call or other formatting; replace with \&.
704
- # drop orphaned \c escape lines, unescape troff macro, quote adjacent character, isolate macro line
705
- gsub(EscapedMacroRx) { (rest = $3.lstrip).empty? ? %(.#$1"#$2") : %(.#$1"#$2"#{LF}#{rest}) }.
706
- gsub('-', '\-').
707
- gsub('&lt;', '<').
708
- gsub('&gt;', '>').
709
- gsub('&#160;', '\~'). # non-breaking space
710
- gsub('&#169;', '\(co'). # copyright sign
711
- gsub('&#174;', '\(rg'). # registered sign
712
- gsub('&#8482;', '\(tm'). # trademark sign
713
- gsub('&#8201;', ' '). # thin space
714
- gsub('&#8211;', '\(en'). # en dash
715
- gsub(EmDashCharRefRx, '\(em'). # em dash
716
- gsub('&#8216;', '\(oq'). # left single quotation mark
717
- gsub('&#8217;', '\(cq'). # right single quotation mark
718
- gsub('&#8220;', '\(lq'). # left double quotation mark
719
- gsub('&#8221;', '\(rq'). # right double quotation mark
720
- gsub(EllipsisCharRefRx, '...'). # horizontal ellipsis
721
- gsub('&#8592;', '\(<-'). # leftwards arrow
722
- gsub('&#8594;', '\(->'). # rightwards arrow
723
- gsub('&#8656;', '\(lA'). # leftwards double arrow
724
- gsub('&#8658;', '\(rA'). # rightwards double arrow
725
- gsub('&#8203;', '\:'). # zero width space
726
- gsub('&amp;','&'). # literal ampersand (NOTE must take place after any other replacement that includes &)
727
- gsub('\'', '\(aq'). # apostrophe-quote
728
- gsub(MockBoundaryRx, ''). # mock boundary
729
- gsub(ESC_BS, '\\'). # unescape troff backslash (NOTE update if more escapes are added)
730
- gsub(ESC_FS, '.'). # unescape full stop in troff commands (NOTE must take place after gsub(LeadingPeriodRx))
731
- rstrip # strip trailing space
715
+ str = str
716
+ .gsub(LiteralBackslashRx) { $1 ? $& : '\\(rs' } # literal backslash (not a troff escape sequence)
717
+ .gsub(EllipsisCharRefRx, '...') # horizontal ellipsis
718
+ .gsub(LeadingPeriodRx, '\\\&.') # leading . is used in troff for macro call or other formatting; replace with \&.
719
+ .gsub(EscapedMacroRx) do # drop orphaned \c escape lines, unescape troff macro, quote adjacent character, isolate macro line
720
+ (rest = $3.lstrip).empty? ? %(.#{$1}"#{$2}") : %(.#{$1}"#{$2.rstrip}"#{LF}#{rest})
721
+ end
722
+ .gsub('-', '\-')
723
+ .gsub('&lt;', '<')
724
+ .gsub('&gt;', '>')
725
+ .gsub('&#43;', '+') # plus sign; alternately could use \c(pl
726
+ .gsub('&#160;', '\~') # non-breaking space
727
+ .gsub('&#169;', '\(co') # copyright sign
728
+ .gsub('&#174;', '\(rg') # registered sign
729
+ .gsub('&#8482;', '\(tm') # trademark sign
730
+ .gsub('&#176;', '\(de') # degree sign
731
+ .gsub('&#8201;', ' ') # thin space
732
+ .gsub('&#8211;', '\(en') # en dash
733
+ .gsub(EmDashCharRefRx, '\(em') # em dash
734
+ .gsub('&#8216;', '\(oq') # left single quotation mark
735
+ .gsub('&#8217;', '\(cq') # right single quotation mark
736
+ .gsub('&#8220;', '\(lq') # left double quotation mark
737
+ .gsub('&#8221;', '\(rq') # right double quotation mark
738
+ .gsub('&#8592;', '\(<-') # leftwards arrow
739
+ .gsub('&#8594;', '\(->') # rightwards arrow
740
+ .gsub('&#8656;', '\(lA') # leftwards double arrow
741
+ .gsub('&#8658;', '\(rA') # rightwards double arrow
742
+ .gsub('&#8203;', '\:') # zero width space
743
+ .gsub('&amp;', '&') # literal ampersand (NOTE must take place after any other replacement that includes &)
744
+ .gsub('\'', '\*(Aq') # apostrophe / neutral single quote
745
+ .gsub(MockMacroRx, '\1') # mock boundary
746
+ .gsub(ESC_BS, '\\') # unescape troff backslash (NOTE update if more escapes are added)
747
+ .gsub(ESC_FS, '.') # unescape full stop in troff commands (NOTE must take place after gsub(LeadingPeriodRx))
748
+ .rstrip # strip trailing space
732
749
  opts[:append_newline] ? %(#{str}#{LF}) : str
733
750
  end
734
751
 
752
+ def uppercase_pcdata string
753
+ (XMLMarkupRx.match? string) ? string.gsub(PCDATAFilterRx) { $2 ? $2.upcase : $1 } : string.upcase
754
+ end
755
+
735
756
  def enclose_content node
736
757
  node.content_model == :compound ? node.content : %(.sp#{LF}#{manify node.content, whitespace: :normalize})
737
758
  end
759
+
760
+ def get_root_document node
761
+ while (node = node.document).nested?
762
+ node = node.parent_document
763
+ end
764
+ node
765
+ end
738
766
  end
739
767
  end
@@ -40,13 +40,13 @@ class Converter::TemplateConverter < Converter::Base
40
40
  @caches = { scans: {}, templates: {} }
41
41
  end
42
42
 
43
- def self.caches
44
- @caches
45
- end
43
+ class << self
44
+ attr_reader :caches
46
45
 
47
- def self.clear_caches
48
- @caches[:scans].clear if @caches[:scans]
49
- @caches[:templates].clear if @caches[:templates]
46
+ def clear_caches
47
+ @caches[:scans].clear
48
+ @caches[:templates].clear
49
+ end
50
50
  end
51
51
 
52
52
  def initialize backend, template_dirs, opts = {}
@@ -134,12 +134,10 @@ class Converter::TemplateConverter < Converter::Base
134
134
  #
135
135
  # Returns the Tilt template object
136
136
  def register name, template
137
- @templates[name] = if (template_cache = @caches[:templates])
137
+ if (template_cache = @caches[:templates])
138
138
  template_cache[template.file] = template
139
- else
140
- template
141
139
  end
142
- #create_handler name, template
140
+ @templates[name] = template
143
141
  end
144
142
 
145
143
  private
@@ -155,6 +153,7 @@ class Converter::TemplateConverter < Converter::Base
155
153
  engine = @engine
156
154
  @template_dirs.each do |template_dir|
157
155
  # FIXME need to think about safe mode restrictions here
156
+ # Ruby 2.3 requires the extra brackets around the path_resolver.system_path method call
158
157
  next unless ::File.directory?(template_dir = (path_resolver.system_path template_dir))
159
158
 
160
159
  if engine
@@ -186,8 +185,8 @@ class Converter::TemplateConverter < Converter::Base
186
185
  else
187
186
  @templates.update scan_dir(template_dir, pattern, @caches[:templates])
188
187
  end
189
- nil
190
188
  end
189
+ nil
191
190
  end
192
191
 
193
192
  # Internal: Scan the specified directory for template files matching pattern and instantiate
@@ -197,7 +196,7 @@ class Converter::TemplateConverter < Converter::Base
197
196
  def scan_dir template_dir, pattern, template_cache = nil
198
197
  result, helpers = {}, nil
199
198
  # Grab the files in the top level of the directory (do not recurse)
200
- ::Dir.glob(pattern).select {|match| ::File.file? match }.each do |file|
199
+ ::Dir.glob(pattern).keep_if {|match| ::File.file? match }.each do |file|
201
200
  if (basename = ::File.basename file) == 'helpers.rb'
202
201
  helpers = file
203
202
  next
@@ -257,9 +256,12 @@ class Converter::TemplateConverter < Converter::Base
257
256
  if !name || name == 'erb'
258
257
  require 'erb' unless defined? ::ERB.version
259
258
  [::Tilt::ERBTemplate, {}]
259
+ elsif name == 'erubi'
260
+ Helpers.require_library 'erubi' unless defined? ::Erubis::Engine
261
+ [::Tilt::ErubiTemplate, {}]
260
262
  elsif name == 'erubis'
261
263
  Helpers.require_library 'erubis' unless defined? ::Erubis::FastEruby
262
- [::Tilt::ErubisTemplate, { engine_class: ::Erubis::FastEruby }]
264
+ [::Tilt::ErubisTemplate, engine_class: ::Erubis::FastEruby]
263
265
  else
264
266
  raise ::ArgumentError, %(Unknown ERB implementation: #{name})
265
267
  end
@@ -42,7 +42,7 @@ module Asciidoctor
42
42
  # puts Asciidoctor.convert_file 'sample.adoc', safe: :safe
43
43
  module Converter
44
44
  autoload :CompositeConverter, %(#{__dir__}/converter/composite)
45
- autoload :TemplateConverter, %(#{__dir__}/converter/template)
45
+ autoload :TemplateConverter, %(#{__dir__}/converter/template) unless RUBY_ENGINE == 'opal'
46
46
 
47
47
  # Public: The String backend name that this converter is handling.
48
48
  attr_reader :backend
@@ -85,23 +85,24 @@ module Converter
85
85
  # Public: Derive backend traits (basebackend, filetype, outfilesuffix, htmlsyntax) from the given backend.
86
86
  #
87
87
  # backend - the String backend from which to derive the traits
88
+ # basebackend - the String basebackend to use in favor of deriving one from the backend (optional, default: nil)
88
89
  #
89
90
  # Returns the backend traits for the given backend as a [Hash].
90
- def self.derive_backend_traits backend
91
+ def self.derive_backend_traits backend, basebackend = nil
91
92
  return {} unless backend
92
- if (t_outfilesuffix = DEFAULT_EXTENSIONS[(t_basebackend = backend.sub TrailingDigitsRx, '')])
93
- t_filetype = t_outfilesuffix.slice 1, t_outfilesuffix.length
93
+ if (outfilesuffix = DEFAULT_EXTENSIONS[(basebackend ||= backend.sub TrailingDigitsRx, '')])
94
+ filetype = outfilesuffix.slice 1, outfilesuffix.length
94
95
  else
95
- t_outfilesuffix = %(.#{t_filetype = t_basebackend})
96
+ outfilesuffix = %(.#{filetype = basebackend})
96
97
  end
97
- t_filetype == 'html' ?
98
- { basebackend: t_basebackend, filetype: t_filetype, htmlsyntax: 'html', outfilesuffix: t_outfilesuffix } :
99
- { basebackend: t_basebackend, filetype: t_filetype, outfilesuffix: t_outfilesuffix }
98
+ filetype == 'html' ?
99
+ { basebackend: basebackend, filetype: filetype, htmlsyntax: 'html', outfilesuffix: outfilesuffix } :
100
+ { basebackend: basebackend, filetype: filetype, outfilesuffix: outfilesuffix }
100
101
  end
101
102
 
102
103
  module BackendTraits
103
104
  def basebackend value = nil
104
- value ? (backend_traits[:basebackend] = value) : backend_traits[:basebackend]
105
+ value ? ((backend_traits value)[:basebackend] = value) : backend_traits[:basebackend]
105
106
  end
106
107
 
107
108
  def filetype value = nil
@@ -128,15 +129,15 @@ module Converter
128
129
  @backend_traits = value || {}
129
130
  end
130
131
 
131
- def backend_traits
132
- @backend_traits ||= Converter.derive_backend_traits @backend
132
+ def backend_traits basebackend = nil
133
+ @backend_traits ||= Converter.derive_backend_traits @backend, basebackend
133
134
  end
134
135
 
135
136
  alias backend_info backend_traits
136
137
 
137
138
  # Deprecated: Use {Converter.derive_backend_traits} instead.
138
- def self.derive_backend_traits backend
139
- Converter.derive_backend_traits backend
139
+ def self.derive_backend_traits backend, basebackend = nil
140
+ Converter.derive_backend_traits backend, basebackend
140
141
  end
141
142
  end
142
143
 
@@ -365,15 +366,17 @@ module Converter
365
366
  # into - The Class into which the {Converter} module is being included.
366
367
  #
367
368
  # Returns nothing.
368
- private_class_method def self.included into
369
+ def self.included into
369
370
  into.send :include, BackendTraits
370
371
  into.extend Config
371
- end || :included
372
+ end
373
+ private_class_method :included # use separate declaration for Ruby 2.0.x
372
374
 
373
375
  # An abstract base class for defining converters that can be used to convert {AbstractNode} objects in a parsed
374
376
  # AsciiDoc document to a backend format such as HTML or DocBook.
375
377
  class Base
376
- include Converter, Logging
378
+ include Logging
379
+ include Converter
377
380
 
378
381
  # Public: Converts an {AbstractNode} by delegating to a method that matches the transform value.
379
382
  #
@@ -3,6 +3,6 @@
3
3
  # NOTE use `send :prepend` to be nice to Ruby 2.0
4
4
  Hash.send :prepend, (Module.new do
5
5
  def merge *args
6
- (len = args.length) < 1 ? super({}) : (len > 1 ? args.inject(self) {|acc, arg| acc.merge arg } : (super args[0]))
6
+ (len = args.length) < 1 ? dup : (len > 1 ? args.inject(self) {|acc, arg| acc.merge arg } : (super args[0]))
7
7
  end
8
8
  end) if (Hash.instance_method :merge).arity == 1