asciidoctor 1.5.8 → 2.0.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (197) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +11 -0
  3. data/CHANGELOG.adoc +628 -45
  4. data/LICENSE +2 -1
  5. data/README-de.adoc +28 -38
  6. data/README-fr.adoc +30 -43
  7. data/README-jp.adoc +255 -201
  8. data/README-zh_CN.adoc +40 -44
  9. data/README.adoc +170 -143
  10. data/asciidoctor.gemspec +22 -34
  11. data/bin/asciidoctor +5 -4
  12. data/data/locale/attributes-ar.adoc +4 -3
  13. data/data/locale/attributes-be.adoc +23 -0
  14. data/data/locale/attributes-bg.adoc +4 -3
  15. data/data/locale/attributes-ca.adoc +6 -5
  16. data/data/locale/attributes-cs.adoc +4 -3
  17. data/data/locale/attributes-da.adoc +6 -5
  18. data/data/locale/attributes-de.adoc +6 -5
  19. data/data/locale/attributes-en.adoc +4 -4
  20. data/data/locale/attributes-es.adoc +6 -5
  21. data/data/locale/attributes-fa.adoc +4 -3
  22. data/data/locale/attributes-fi.adoc +4 -3
  23. data/data/locale/attributes-fr.adoc +8 -7
  24. data/data/locale/attributes-hu.adoc +4 -3
  25. data/data/locale/attributes-id.adoc +4 -3
  26. data/data/locale/attributes-it.adoc +6 -5
  27. data/data/locale/attributes-ja.adoc +4 -3
  28. data/data/locale/{attributes-kr.adoc → attributes-ko.adoc} +4 -3
  29. data/data/locale/attributes-nb.adoc +4 -3
  30. data/data/locale/attributes-nl.adoc +6 -5
  31. data/data/locale/attributes-nn.adoc +4 -3
  32. data/data/locale/attributes-pl.adoc +8 -7
  33. data/data/locale/attributes-pt.adoc +6 -5
  34. data/data/locale/attributes-pt_BR.adoc +6 -5
  35. data/data/locale/attributes-ro.adoc +4 -3
  36. data/data/locale/attributes-ru.adoc +6 -5
  37. data/data/locale/attributes-sr.adoc +4 -4
  38. data/data/locale/attributes-sr_Latn.adoc +4 -4
  39. data/data/locale/attributes-sv.adoc +4 -4
  40. data/data/locale/attributes-th.adoc +23 -0
  41. data/data/locale/attributes-tr.adoc +4 -3
  42. data/data/locale/attributes-uk.adoc +6 -5
  43. data/data/locale/attributes-vi.adoc +23 -0
  44. data/data/locale/attributes-zh_CN.adoc +4 -3
  45. data/data/locale/attributes-zh_TW.adoc +4 -3
  46. data/data/reference/syntax.adoc +296 -0
  47. data/data/stylesheets/asciidoctor-default.css +120 -114
  48. data/data/stylesheets/coderay-asciidoctor.css +15 -17
  49. data/lib/asciidoctor/abstract_block.rb +146 -140
  50. data/lib/asciidoctor/abstract_node.rb +152 -170
  51. data/lib/asciidoctor/attribute_list.rb +77 -89
  52. data/lib/asciidoctor/block.rb +29 -28
  53. data/lib/asciidoctor/callouts.rb +4 -2
  54. data/lib/asciidoctor/cli/invoker.rb +20 -24
  55. data/lib/asciidoctor/cli/options.rb +107 -96
  56. data/lib/asciidoctor/cli.rb +3 -2
  57. data/lib/asciidoctor/convert.rb +199 -0
  58. data/lib/asciidoctor/converter/composite.rb +40 -48
  59. data/lib/asciidoctor/converter/docbook5.rb +627 -644
  60. data/lib/asciidoctor/converter/html5.rb +1053 -951
  61. data/lib/asciidoctor/converter/manpage.rb +581 -532
  62. data/lib/asciidoctor/converter/template.rb +232 -271
  63. data/lib/asciidoctor/converter.rb +370 -185
  64. data/lib/asciidoctor/core_ext/float/truncate.rb +20 -0
  65. data/lib/asciidoctor/core_ext/hash/merge.rb +8 -0
  66. data/lib/asciidoctor/core_ext/match_data/names.rb +7 -0
  67. data/lib/asciidoctor/core_ext/nil_or_empty.rb +1 -0
  68. data/lib/asciidoctor/core_ext/regexp/is_match.rb +4 -2
  69. data/lib/asciidoctor/core_ext.rb +8 -17
  70. data/lib/asciidoctor/document.rb +503 -461
  71. data/lib/asciidoctor/extensions.rb +127 -174
  72. data/lib/asciidoctor/helpers.rb +184 -107
  73. data/lib/asciidoctor/inline.rb +9 -12
  74. data/lib/asciidoctor/list.rb +11 -29
  75. data/lib/asciidoctor/load.rb +119 -0
  76. data/lib/asciidoctor/logging.rb +22 -17
  77. data/lib/asciidoctor/parser.rb +673 -719
  78. data/lib/asciidoctor/path_resolver.rb +48 -33
  79. data/lib/asciidoctor/reader.rb +383 -338
  80. data/lib/asciidoctor/rouge_ext.rb +39 -0
  81. data/lib/asciidoctor/rx.rb +723 -0
  82. data/lib/asciidoctor/section.rb +17 -16
  83. data/lib/asciidoctor/stylesheets.rb +19 -37
  84. data/lib/asciidoctor/substitutors.rb +926 -1022
  85. data/lib/asciidoctor/syntax_highlighter/coderay.rb +88 -0
  86. data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +34 -0
  87. data/lib/asciidoctor/syntax_highlighter/html_pipeline.rb +10 -0
  88. data/lib/asciidoctor/syntax_highlighter/prettify.rb +30 -0
  89. data/lib/asciidoctor/syntax_highlighter/pygments.rb +157 -0
  90. data/lib/asciidoctor/syntax_highlighter/rouge.rb +143 -0
  91. data/lib/asciidoctor/syntax_highlighter.rb +253 -0
  92. data/lib/asciidoctor/table.rb +152 -114
  93. data/lib/asciidoctor/timings.rb +7 -5
  94. data/lib/asciidoctor/version.rb +2 -1
  95. data/lib/asciidoctor/writer.rb +30 -0
  96. data/lib/asciidoctor.rb +266 -1340
  97. data/man/asciidoctor.1 +49 -47
  98. data/man/asciidoctor.adoc +54 -45
  99. metadata +50 -245
  100. data/CONTRIBUTING.adoc +0 -185
  101. data/Gemfile +0 -60
  102. data/Rakefile +0 -129
  103. data/bin/asciidoctor-safe +0 -15
  104. data/features/open_block.feature +0 -92
  105. data/features/pass_block.feature +0 -66
  106. data/features/step_definitions.rb +0 -49
  107. data/features/text_formatting.feature +0 -57
  108. data/features/xref.feature +0 -1039
  109. data/lib/asciidoctor/converter/base.rb +0 -59
  110. data/lib/asciidoctor/converter/docbook45.rb +0 -93
  111. data/lib/asciidoctor/converter/factory.rb +0 -226
  112. data/lib/asciidoctor/core_ext/1.8.7/base64/strict_encode64.rb +0 -6
  113. data/lib/asciidoctor/core_ext/1.8.7/concurrent/hash.rb +0 -5
  114. data/lib/asciidoctor/core_ext/1.8.7/hash/key.rb +0 -4
  115. data/lib/asciidoctor/core_ext/1.8.7/io/binread.rb +0 -6
  116. data/lib/asciidoctor/core_ext/1.8.7/io/write.rb +0 -5
  117. data/lib/asciidoctor/core_ext/1.8.7/string/chr.rb +0 -6
  118. data/lib/asciidoctor/core_ext/1.8.7/string/limit_bytesize.rb +0 -29
  119. data/lib/asciidoctor/core_ext/1.8.7/symbol/empty.rb +0 -6
  120. data/lib/asciidoctor/core_ext/1.8.7/symbol/length.rb +0 -6
  121. data/lib/asciidoctor/core_ext/string/limit_bytesize.rb +0 -10
  122. data/test/api_test.rb +0 -1240
  123. data/test/attribute_list_test.rb +0 -242
  124. data/test/attributes_test.rb +0 -1623
  125. data/test/blocks_test.rb +0 -3870
  126. data/test/converter_test.rb +0 -470
  127. data/test/document_test.rb +0 -1853
  128. data/test/extensions_test.rb +0 -1560
  129. data/test/fixtures/asciidoc_index.txt +0 -521
  130. data/test/fixtures/basic-docinfo-footer.html +0 -6
  131. data/test/fixtures/basic-docinfo-footer.xml +0 -8
  132. data/test/fixtures/basic-docinfo.html +0 -1
  133. data/test/fixtures/basic-docinfo.xml +0 -4
  134. data/test/fixtures/basic.asciidoc +0 -5
  135. data/test/fixtures/chapter-a.adoc +0 -3
  136. data/test/fixtures/child-include.adoc +0 -5
  137. data/test/fixtures/circle.svg +0 -9
  138. data/test/fixtures/custom-backends/erb/html5/block_paragraph.html.erb +0 -6
  139. data/test/fixtures/custom-backends/haml/docbook45/block_paragraph.xml.haml +0 -6
  140. data/test/fixtures/custom-backends/haml/html5/block_paragraph.html.haml +0 -3
  141. data/test/fixtures/custom-backends/haml/html5/block_sidebar.html.haml +0 -5
  142. data/test/fixtures/custom-backends/haml/html5-tweaks/block_paragraph.html.haml +0 -1
  143. data/test/fixtures/custom-backends/slim/docbook45/block_paragraph.xml.slim +0 -6
  144. data/test/fixtures/custom-backends/slim/html5/block_paragraph.html.slim +0 -3
  145. data/test/fixtures/custom-backends/slim/html5/block_sidebar.html.slim +0 -5
  146. data/test/fixtures/custom-docinfodir/basic-docinfo.html +0 -1
  147. data/test/fixtures/custom-docinfodir/docinfo.html +0 -1
  148. data/test/fixtures/docinfo-footer.html +0 -1
  149. data/test/fixtures/docinfo-footer.xml +0 -9
  150. data/test/fixtures/docinfo.html +0 -1
  151. data/test/fixtures/docinfo.xml +0 -3
  152. data/test/fixtures/doctime-localtime.adoc +0 -2
  153. data/test/fixtures/dot.gif +0 -0
  154. data/test/fixtures/encoding.asciidoc +0 -13
  155. data/test/fixtures/file-with-missing-include.adoc +0 -1
  156. data/test/fixtures/grandchild-include.adoc +0 -3
  157. data/test/fixtures/hello-asciidoctor.pdf +0 -69
  158. data/test/fixtures/include-file.asciidoc +0 -24
  159. data/test/fixtures/include-file.jsx +0 -8
  160. data/test/fixtures/include-file.ml +0 -3
  161. data/test/fixtures/include-file.xml +0 -5
  162. data/test/fixtures/lists.adoc +0 -96
  163. data/test/fixtures/master.adoc +0 -5
  164. data/test/fixtures/mismatched-end-tag.adoc +0 -7
  165. data/test/fixtures/other-chapters.adoc +0 -11
  166. data/test/fixtures/outer-include.adoc +0 -5
  167. data/test/fixtures/parent-include-restricted.adoc +0 -5
  168. data/test/fixtures/parent-include.adoc +0 -5
  169. data/test/fixtures/sample.asciidoc +0 -30
  170. data/test/fixtures/section-a.adoc +0 -4
  171. data/test/fixtures/stylesheets/custom.css +0 -3
  172. data/test/fixtures/subdir/index.adoc +0 -3
  173. data/test/fixtures/subdir/inner-include.adoc +0 -3
  174. data/test/fixtures/subdir/middle-include.adoc +0 -5
  175. data/test/fixtures/subs-docinfo.html +0 -2
  176. data/test/fixtures/subs.adoc +0 -6
  177. data/test/fixtures/tagged-class-enclosed.rb +0 -25
  178. data/test/fixtures/tagged-class.rb +0 -23
  179. data/test/fixtures/tip.gif +0 -0
  180. data/test/fixtures/unclosed-tag.adoc +0 -3
  181. data/test/fixtures/unexpected-end-tag.adoc +0 -4
  182. data/test/invoker_test.rb +0 -745
  183. data/test/links_test.rb +0 -855
  184. data/test/lists_test.rb +0 -5151
  185. data/test/logger_test.rb +0 -211
  186. data/test/manpage_test.rb +0 -660
  187. data/test/options_test.rb +0 -262
  188. data/test/paragraphs_test.rb +0 -562
  189. data/test/parser_test.rb +0 -742
  190. data/test/paths_test.rb +0 -395
  191. data/test/preamble_test.rb +0 -173
  192. data/test/reader_test.rb +0 -2161
  193. data/test/sections_test.rb +0 -3575
  194. data/test/substitutions_test.rb +0 -2066
  195. data/test/tables_test.rb +0 -2036
  196. data/test/test_helper.rb +0 -447
  197. data/test/text_test.rb +0 -309
@@ -1,132 +1,85 @@
1
+ # frozen_string_literal: true
1
2
  module Asciidoctor
2
- # A built-in {Converter} implementation that generates the man page (troff) format.
3
- #
4
- # The output follows the groff man page definition while also trying to be
5
- # consistent with the output produced by the a2x tool from AsciiDoc Python.
6
- #
7
- # See http://www.gnu.org/software/groff/manual/html_node/Man-usage.html#Man-usage
8
- class Converter::ManPageConverter < Converter::BuiltIn
9
- LF = %(\n)
10
- TAB = %(\t)
11
- WHITESPACE = %(#{LF}#{TAB} )
12
- ET = ' ' * 8
13
- ESC = %(\u001b) # troff leader marker
14
- ESC_BS = %(#{ESC}\\) # escaped backslash (indicates troff formatting sequence)
15
- ESC_FS = %(#{ESC}.) # escaped full stop (indicates troff macro)
16
-
17
- LiteralBackslashRx = /(?:\A|[^#{ESC}])\\/
18
- LeadingPeriodRx = /^\./
19
- EscapedMacroRx = /^(?:#{ESC}\\c\n)?#{ESC}\.((?:URL|MTO) ".*?" ".*?" )( |[^\s]*)(.*?)(?: *#{ESC}\\c)?$/
20
- MockBoundaryRx = /<\/?BOUNDARY>/
21
- EmDashCharRefRx = /&#8212;(?:&#8203;)?/
22
- EllipsisCharRefRx = /&#8230;(?:&#8203;)?/
23
- WrappedIndentRx = /#{CG_BLANK}*#{LF}#{CG_BLANK}*/
24
-
25
- # Converts HTML entity references back to their original form, escapes
26
- # special man characters and strips trailing whitespace.
27
- #
28
- # It's crucial that text only ever pass through manify once.
29
- #
30
- # str - the String to convert
31
- # opts - an Hash of options to control processing (default: {})
32
- # * :whitespace an enum that indicates how to handle whitespace; supported options are:
33
- # :preserve - preserve spaces (only expanding tabs); :normalize - normalize whitespace
34
- # (remove spaces around newlines); :collapse - collapse adjacent whitespace to a single
35
- # space (default: :collapse)
36
- # * :append_newline a Boolean that indicates whether to append an endline to the result (default: false)
37
- def manify str, opts = {}
38
- case opts.fetch :whitespace, :collapse
39
- when :preserve
40
- str = str.gsub TAB, ET
41
- when :normalize
42
- str = str.gsub WrappedIndentRx, LF
43
- else
44
- str = str.tr_s WHITESPACE, ' '
45
- end
46
- str = str.
47
- gsub(LiteralBackslashRx, '\&(rs'). # literal backslash (not a troff escape sequence)
48
- gsub(LeadingPeriodRx, '\\\&.'). # leading . is used in troff for macro call or other formatting; replace with \&.
49
- # drop orphaned \c escape lines, unescape troff macro, quote adjacent character, isolate macro line
50
- gsub(EscapedMacroRx) { (rest = $3.lstrip).empty? ? %(.#$1"#$2") : %(.#$1"#$2"#{LF}#{rest}) }.
51
- gsub('-', '\-').
52
- gsub('&lt;', '<').
53
- gsub('&gt;', '>').
54
- gsub('&#160;', '\~'). # non-breaking space
55
- gsub('&#169;', '\(co'). # copyright sign
56
- gsub('&#174;', '\(rg'). # registered sign
57
- gsub('&#8482;', '\(tm'). # trademark sign
58
- gsub('&#8201;', ' '). # thin space
59
- gsub('&#8211;', '\(en'). # en dash
60
- gsub(EmDashCharRefRx, '\(em'). # em dash
61
- gsub('&#8216;', '\(oq'). # left single quotation mark
62
- gsub('&#8217;', '\(cq'). # right single quotation mark
63
- gsub('&#8220;', '\(lq'). # left double quotation mark
64
- gsub('&#8221;', '\(rq'). # right double quotation mark
65
- gsub(EllipsisCharRefRx, '...'). # horizontal ellipsis
66
- gsub('&#8592;', '\(<-'). # leftwards arrow
67
- gsub('&#8594;', '\(->'). # rightwards arrow
68
- gsub('&#8656;', '\(lA'). # leftwards double arrow
69
- gsub('&#8658;', '\(rA'). # rightwards double arrow
70
- gsub('&#8203;', '\:'). # zero width space
71
- gsub('&amp;','&'). # literal ampersand (NOTE must take place after any other replacement that includes &)
72
- gsub('\'', '\(aq'). # apostrophe-quote
73
- gsub(MockBoundaryRx, ''). # mock boundary
74
- gsub(ESC_BS, '\\'). # unescape troff backslash (NOTE update if more escapes are added)
75
- gsub(ESC_FS, '.'). # unescape full stop in troff commands (NOTE must take place after gsub(LeadingPeriodRx))
76
- rstrip # strip trailing space
77
- opts[:append_newline] ? %(#{str}#{LF}) : str
78
- end
79
-
80
- def skip_with_warning node, name = nil
81
- logger.warn %(converter missing for #{name || node.node_name} node in manpage backend)
82
- nil
83
- end
3
+ # A built-in {Converter} implementation that generates the man page (troff) format.
4
+ #
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.
11
+ #
12
+ # See http://www.gnu.org/software/groff/manual/html_node/Man-usage.html#Man-usage
13
+ class Converter::ManPageConverter < Converter::Base
14
+ register_for 'manpage'
15
+
16
+ WHITESPACE = %(#{LF}#{TAB} )
17
+ ET = ' ' * 8
18
+ ESC = ?\u001b # troff leader marker
19
+ ESC_BS = %(#{ESC}\\) # escaped backslash (indicates troff formatting sequence)
20
+ ESC_FS = %(#{ESC}.) # escaped full stop (indicates troff macro)
21
+
22
+ LiteralBackslashRx = /\A\\|(#{ESC})?\\/
23
+ LeadingPeriodRx = /^\./
24
+ EscapedMacroRx = /^(?:#{ESC}\\c\n)?#{ESC}\.((?:URL|MTO) "#{CC_ANY}*?" "#{CC_ANY}*?" )( |[^\s]*)(#{CC_ANY}*?)(?: *#{ESC}\\c)?$/
25
+ MalformedEscapedMacroRx = /(#{ESC}\\c) (#{ESC}\.(?:URL|MTO) )/
26
+ MockMacroRx = %r(</?(#{ESC}\\[^>]+)>)
27
+ EmDashCharRefRx = /&#8212;(?:&#8203;)?/
28
+ EllipsisCharRefRx = /&#8230;(?:&#8203;)?/
29
+ WrappedIndentRx = /#{CG_BLANK}*#{LF}#{CG_BLANK}*/
30
+ XMLMarkupRx = /&#?[a-z\d]+;|</
31
+ PCDATAFilterRx = /(&#?[a-z\d]+;|<[^>]+>)|([^&<]+)/
32
+
33
+ def initialize backend, opts = {}
34
+ @backend = backend
35
+ init_backend_traits basebackend: 'manpage', filetype: 'man', outfilesuffix: '.man', supports_templates: true
36
+ end
84
37
 
85
- def document node
86
- unless node.attr? 'mantitle'
87
- raise 'asciidoctor: ERROR: doctype must be set to manpage when using manpage backend'
88
- end
89
- mantitle = node.attr 'mantitle'
90
- manvolnum = node.attr 'manvolnum', '1'
91
- manname = node.attr 'manname', mantitle
92
- manmanual = node.attr 'manmanual'
93
- mansource = node.attr 'mansource'
94
- docdate = (node.attr? 'reproducible') ? nil : (node.attr 'docdate')
95
- # NOTE the first line enables the table (tbl) preprocessor, necessary for non-Linux systems
96
- result = [%('\\" t
38
+ def convert_document node
39
+ unless node.attr? 'mantitle'
40
+ raise 'asciidoctor: ERROR: doctype must be set to manpage when using manpage backend'
41
+ end
42
+ mantitle = node.attr 'mantitle'
43
+ manvolnum = node.attr 'manvolnum', '1'
44
+ manname = node.attr 'manname', mantitle
45
+ manmanual = node.attr 'manmanual'
46
+ mansource = node.attr 'mansource'
47
+ docdate = (node.attr? 'reproducible') ? nil : (node.attr 'docdate')
48
+ # NOTE the first line enables the table (tbl) preprocessor, necessary for non-Linux systems
49
+ result = [%('\\" t
97
50
  .\\" Title: #{mantitle}
98
51
  .\\" Author: #{(node.attr? 'authors') ? (node.attr 'authors') : '[see the "AUTHOR(S)" section]'}
99
52
  .\\" Generator: Asciidoctor #{node.attr 'asciidoctor-version'})]
100
- result << %(.\\" Date: #{docdate}) if docdate
101
- result << %(.\\" Manual: #{manmanual ? (manmanual.tr_s WHITESPACE, ' ') : '\ \&'}
53
+ result << %(.\\" Date: #{docdate}) if docdate
54
+ result << %(.\\" Manual: #{manmanual ? (manmanual.tr_s WHITESPACE, ' ') : '\ \&'}
102
55
  .\\" Source: #{mansource ? (mansource.tr_s WHITESPACE, ' ') : '\ \&'}
103
56
  .\\" Language: English
104
57
  .\\")
105
- # TODO add document-level setting to disable capitalization of manname
106
- result << %(.TH "#{manify manname.upcase}" "#{manvolnum}" "#{docdate}" "#{mansource ? (manify mansource) : '\ \&'}" "#{manmanual ? (manify manmanual) : '\ \&'}")
107
- # define portability settings
108
- # see http://bugs.debian.org/507673
109
- # see http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
110
- result << '.ie \n(.g .ds Aq \(aq'
111
- result << '.el .ds Aq \''
112
- # set sentence_space_size to 0 to prevent extra space between sentences separated by a newline
113
- # the alternative is to add \& at the end of the line
114
- result << '.ss \n[.ss] 0'
115
- # disable hyphenation
116
- result << '.nh'
117
- # disable justification (adjust text to left margin only)
118
- result << '.ad l'
119
- # define URL macro for portability
120
- # see http://web.archive.org/web/20060102165607/http://people.debian.org/~branden/talks/wtfm/wtfm.pdf
121
- #
122
- # Usage
123
- #
124
- # .URL "http://www.debian.org" "Debian" "."
125
- #
126
- # * First argument: the URL
127
- # * Second argument: text to be hyperlinked
128
- # * Third (optional) argument: text that needs to immediately trail the hyperlink without intervening whitespace
129
- result << '.de URL
58
+ # TODO add document-level setting to disable capitalization of manname
59
+ result << %(.TH "#{manify manname.upcase}" "#{manvolnum}" "#{docdate}" "#{mansource ? (manify mansource) : '\ \&'}" "#{manmanual ? (manify manmanual) : '\ \&'}")
60
+ # define portability settings
61
+ # see http://bugs.debian.org/507673
62
+ # see http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
63
+ result << '.ie \n(.g .ds Aq \(aq'
64
+ result << '.el .ds Aq \''
65
+ # set sentence_space_size to 0 to prevent extra space between sentences separated by a newline
66
+ # the alternative is to add \& at the end of the line
67
+ result << '.ss \n[.ss] 0'
68
+ # disable hyphenation
69
+ result << '.nh'
70
+ # disable justification (adjust text to left margin only)
71
+ result << '.ad l'
72
+ # define URL macro for portability
73
+ # see http://web.archive.org/web/20060102165607/http://people.debian.org/~branden/talks/wtfm/wtfm.pdf
74
+ #
75
+ # Usage
76
+ #
77
+ # .URL "http://www.debian.org" "Debian" "."
78
+ #
79
+ # * First argument: the URL
80
+ # * Second argument: text to be hyperlinked
81
+ # * Third (optional) argument: text that needs to immediately trail the hyperlink without intervening whitespace
82
+ result << '.de URL
130
83
  \\fI\\\\$2\\fP <\\\\$1>\\\\$3
131
84
  ..
132
85
  .als MTO URL
@@ -138,74 +91,68 @@ module Asciidoctor
138
91
  . am MTO
139
92
  . ad l
140
93
  . .'
141
- result << %(. LINKSTYLE #{node.attr 'man-linkstyle', 'blue R < >'})
142
- result << '.\}'
143
-
144
- unless node.noheader
145
- if node.attr? 'manpurpose'
146
- mannames = node.attr 'mannames', [manname]
147
- result << %(.SH "#{(node.attr 'manname-title', 'NAME').upcase}"
148
- #{mannames.map {|n| manify n }.join ', '} \\- #{manify node.attr('manpurpose'), :whitespace => :normalize})
149
- end
94
+ result << %(. LINKSTYLE #{node.attr 'man-linkstyle', 'blue R < >'})
95
+ result << '.\}'
96
+
97
+ unless node.noheader
98
+ if node.attr? 'manpurpose'
99
+ mannames = node.attr 'mannames', [manname]
100
+ result << %(.SH "#{(node.attr 'manname-title', 'NAME').upcase}"
101
+ #{mannames.map {|n| (manify n).gsub '\-', '-' }.join ', '} \\- #{manify node.attr('manpurpose'), whitespace: :normalize})
150
102
  end
103
+ end
151
104
 
152
- result << node.content
105
+ result << node.content
153
106
 
154
- # QUESTION should NOTES come after AUTHOR(S)?
155
- if node.footnotes? && !(node.attr? 'nofootnotes')
156
- result << '.SH "NOTES"'
157
- result.concat(node.footnotes.map {|fn| %(#{fn.index}. #{fn.text}) })
158
- end
107
+ # QUESTION should NOTES come after AUTHOR(S)?
108
+ append_footnotes result, node
159
109
 
160
- unless (authors = node.authors).empty?
161
- if authors.size > 1
162
- result << '.SH "AUTHORS"'
163
- authors.each do |author|
164
- result << %(.sp
110
+ unless (authors = node.authors).empty?
111
+ if authors.size > 1
112
+ result << '.SH "AUTHORS"'
113
+ authors.each do |author|
114
+ result << %(.sp
165
115
  #{author.name})
166
- end
167
- else
168
- result << %(.SH "AUTHOR"
116
+ end
117
+ else
118
+ result << %(.SH "AUTHOR"
169
119
  .sp
170
120
  #{authors[0].name})
171
- end
172
121
  end
173
-
174
- result.join LF
175
122
  end
176
123
 
177
- # NOTE embedded doesn't really make sense in the manpage backend
178
- def embedded node
179
- result = [node.content]
124
+ result.join LF
125
+ end
180
126
 
181
- if node.footnotes? && !(node.attr? 'nofootnotes')
182
- result << '.SH "NOTES"'
183
- result.concat(node.footnotes.map {|fn| %(#{fn.index}. #{fn.text}) })
184
- end
127
+ # NOTE embedded doesn't really make sense in the manpage backend
128
+ def convert_embedded node
129
+ result = [node.content]
185
130
 
186
- # QUESTION should we add an AUTHOR(S) section?
131
+ append_footnotes result, node
187
132
 
188
- result.join LF
189
- end
133
+ # QUESTION should we add an AUTHOR(S) section?
190
134
 
191
- def section node
192
- result = []
193
- if node.level > 1
194
- macro = 'SS'
195
- # QUESTION why captioned title? why not when level == 1?
196
- stitle = node.captioned_title
197
- else
198
- macro = 'SH'
199
- stitle = node.title.upcase
200
- end
201
- result << %(.#{macro} "#{manify stitle}"
135
+ result.join LF
136
+ end
137
+
138
+ def convert_section node
139
+ result = []
140
+ if node.level > 1
141
+ macro = 'SS'
142
+ # QUESTION why captioned title? why not when level == 1?
143
+ stitle = node.captioned_title
144
+ else
145
+ macro = 'SH'
146
+ stitle = uppercase_pcdata node.title
147
+ end
148
+ result << %(.#{macro} "#{manify stitle}"
202
149
  #{node.content})
203
- result.join LF
204
- end
150
+ result.join LF
151
+ end
205
152
 
206
- def admonition node
207
- result = []
208
- result << %(.if n .sp
153
+ def convert_admonition node
154
+ result = []
155
+ result << %(.if n .sp
209
156
  .RS 4
210
157
  .it 1 an-trap
211
158
  .nr an-no-space-flag 1
@@ -215,343 +162,354 @@ module Asciidoctor
215
162
  .B #{node.attr 'textlabel'}#{node.title? ? "\\fP: #{manify node.title}" : ''}
216
163
  .ps -1
217
164
  .br
218
- #{resolve_content node}
165
+ #{enclose_content node}
219
166
  .sp .5v
220
167
  .RE)
221
- result.join LF
222
- end
223
-
224
- alias audio skip_with_warning
168
+ result.join LF
169
+ end
225
170
 
226
- def colist node
227
- result = []
228
- result << %(.sp
171
+ def convert_colist node
172
+ result = []
173
+ result << %(.sp
229
174
  .B #{manify node.title}
230
175
  .br) if node.title?
231
- result << '.TS
176
+ result << '.TS
232
177
  tab(:);
233
178
  r lw(\n(.lu*75u/100u).'
234
179
 
235
- num = 0
236
- node.items.each do |item|
237
- result << %(\\fB(#{num += 1})\\fP\\h'-2n':T{)
238
- result << (manify item.text, :whitespace => :normalize)
239
- result << item.content if item.blocks?
240
- result << 'T}'
241
- end
242
- result << '.TE'
243
- result.join LF
180
+ num = 0
181
+ node.items.each do |item|
182
+ result << %(\\fB(#{num += 1})\\fP\\h'-2n':T{)
183
+ result << (manify item.text, whitespace: :normalize)
184
+ result << item.content if item.blocks?
185
+ result << 'T}'
244
186
  end
187
+ result << '.TE'
188
+ result.join LF
189
+ end
245
190
 
246
- # TODO implement horizontal (if it makes sense)
247
- def dlist node
248
- result = []
249
- result << %(.sp
191
+ # TODO implement horizontal (if it makes sense)
192
+ def convert_dlist node
193
+ result = []
194
+ result << %(.sp
250
195
  .B #{manify node.title}
251
196
  .br) if node.title?
252
- counter = 0
253
- node.items.each do |terms, dd|
254
- counter += 1
255
- case node.style
256
- when 'qanda'
257
- result << %(.sp
258
- #{counter}. #{manify [*terms].map {|dt| dt.text }.join ' '}
197
+ counter = 0
198
+ node.items.each do |terms, dd|
199
+ counter += 1
200
+ case node.style
201
+ when 'qanda'
202
+ result << %(.sp
203
+ #{counter}. #{manify terms.map {|dt| dt.text }.join ' '}
259
204
  .RS 4)
260
- else
261
- result << %(.sp
262
- #{manify [*terms].map {|dt| dt.text }.join(', '), :whitespace => :normalize}
205
+ else
206
+ result << %(.sp
207
+ #{manify terms.map {|dt| dt.text }.join(', '), whitespace: :normalize}
263
208
  .RS 4)
264
- end
265
- if dd
266
- result << (manify dd.text, :whitespace => :normalize) if dd.text?
267
- result << dd.content if dd.blocks?
268
- end
269
- result << '.RE'
270
209
  end
271
- result.join LF
210
+ if dd
211
+ result << (manify dd.text, whitespace: :normalize) if dd.text?
212
+ result << dd.content if dd.blocks?
213
+ end
214
+ result << '.RE'
272
215
  end
216
+ result.join LF
217
+ end
273
218
 
274
- def example node
275
- result = []
276
- result << %(.sp
219
+ def convert_example node
220
+ result = []
221
+ result << (node.title? ? %(.sp
277
222
  .B #{manify node.captioned_title}
278
- .br) if node.title?
279
- result << %(.RS 4
280
- #{resolve_content node}
223
+ .br) : '.sp')
224
+ result << %(.RS 4
225
+ #{enclose_content node}
281
226
  .RE)
282
- result.join LF
283
- end
227
+ result.join LF
228
+ end
284
229
 
285
- def floating_title node
286
- %(.SS "#{manify node.title}")
287
- end
230
+ def convert_floating_title node
231
+ %(.SS "#{manify node.title}")
232
+ end
288
233
 
289
- alias image skip_with_warning
234
+ def convert_image node
235
+ result = []
236
+ result << (node.title? ? %(.sp
237
+ .B #{manify node.captioned_title}
238
+ .br) : '.sp')
239
+ result << %([#{node.alt}])
240
+ result.join LF
241
+ end
290
242
 
291
- def listing node
292
- result = []
293
- result << %(.sp
243
+ def convert_listing node
244
+ result = []
245
+ result << %(.sp
294
246
  .B #{manify node.captioned_title}
295
247
  .br) if node.title?
296
- result << %(.sp
248
+ result << %(.sp
297
249
  .if n .RS 4
298
250
  .nf
299
- #{manify node.content, :whitespace => :preserve}
251
+ .fam C
252
+ #{manify node.content, whitespace: :preserve}
253
+ .fam
300
254
  .fi
301
255
  .if n .RE)
302
- result.join LF
303
- end
256
+ result.join LF
257
+ end
304
258
 
305
- def literal node
306
- result = []
307
- result << %(.sp
259
+ def convert_literal node
260
+ result = []
261
+ result << %(.sp
308
262
  .B #{manify node.title}
309
263
  .br) if node.title?
310
- result << %(.sp
264
+ result << %(.sp
311
265
  .if n .RS 4
312
266
  .nf
313
- #{manify node.content, :whitespace => :preserve}
267
+ .fam C
268
+ #{manify node.content, whitespace: :preserve}
269
+ .fam
314
270
  .fi
315
271
  .if n .RE)
316
- result.join LF
317
- end
272
+ result.join LF
273
+ end
318
274
 
319
- def olist node
320
- result = []
321
- result << %(.sp
275
+ def convert_sidebar node
276
+ result = []
277
+ result << (node.title? ? %(.sp
278
+ .B #{manify node.title}
279
+ .br) : '.sp')
280
+ result << %(.RS 4
281
+ #{enclose_content node}
282
+ .RE)
283
+ result.join LF
284
+ end
285
+
286
+ def convert_olist node
287
+ result = []
288
+ result << %(.sp
322
289
  .B #{manify node.title}
323
290
  .br) if node.title?
324
291
 
325
- node.items.each_with_index do |item, idx|
326
- result << %(.sp
292
+ start = (node.attr 'start', 1).to_i
293
+ node.items.each_with_index do |item, idx|
294
+ result << %(.sp
327
295
  .RS 4
328
296
  .ie n \\{\\
329
- \\h'-04' #{idx + 1}.\\h'+01'\\c
297
+ \\h'-04' #{numeral = idx + start}.\\h'+01'\\c
330
298
  .\\}
331
299
  .el \\{\\
332
300
  . sp -1
333
- . IP " #{idx + 1}." 4.2
301
+ . IP " #{numeral}." 4.2
334
302
  .\\}
335
- #{manify item.text, :whitespace => :normalize})
336
- result << item.content if item.blocks?
337
- result << '.RE'
338
- end
339
- result.join LF
303
+ #{manify item.text, whitespace: :normalize})
304
+ result << item.content if item.blocks?
305
+ result << '.RE'
340
306
  end
307
+ result.join LF
308
+ end
341
309
 
342
- def open node
343
- case node.style
344
- when 'abstract', 'partintro'
345
- resolve_content node
346
- else
347
- node.content
348
- end
310
+ def convert_open node
311
+ case node.style
312
+ when 'abstract', 'partintro'
313
+ enclose_content node
314
+ else
315
+ node.content
349
316
  end
317
+ end
350
318
 
351
- # TODO use Page Control https://www.gnu.org/software/groff/manual/html_node/Page-Control.html#Page-Control
352
- alias page_break skip
319
+ def convert_page_break node
320
+ '.bp'
321
+ end
353
322
 
354
- def paragraph node
355
- if node.title?
356
- %(.sp
323
+ def convert_paragraph node
324
+ if node.title?
325
+ %(.sp
357
326
  .B #{manify node.title}
358
327
  .br
359
- #{manify node.content, :whitespace => :normalize})
360
- else
361
- %(.sp
362
- #{manify node.content, :whitespace => :normalize})
363
- end
328
+ #{manify node.content, whitespace: :normalize})
329
+ else
330
+ %(.sp
331
+ #{manify node.content, whitespace: :normalize})
364
332
  end
333
+ end
365
334
 
366
- alias preamble content
335
+ alias convert_pass content_only
336
+ alias convert_preamble content_only
367
337
 
368
- def quote node
369
- result = []
370
- if node.title?
371
- result << %(.sp
338
+ def convert_quote node
339
+ result = []
340
+ if node.title?
341
+ result << %(.sp
372
342
  .RS 3
373
343
  .B #{manify node.title}
374
344
  .br
375
345
  .RE)
376
- end
377
- attribution_line = (node.attr? 'citetitle') ? %(#{node.attr 'citetitle'} ) : nil
378
- attribution_line = (node.attr? 'attribution') ? %[#{attribution_line}\\(em #{node.attr 'attribution'}] : nil
379
- result << %(.RS 3
346
+ end
347
+ attribution_line = (node.attr? 'citetitle') ? %(#{node.attr 'citetitle'} ) : nil
348
+ attribution_line = (node.attr? 'attribution') ? %[#{attribution_line}\\(em #{node.attr 'attribution'}] : nil
349
+ result << %(.RS 3
380
350
  .ll -.6i
381
- #{resolve_content node}
351
+ #{enclose_content node}
382
352
  .br
383
353
  .RE
384
354
  .ll)
385
- if attribution_line
386
- result << %(.RS 5
355
+ if attribution_line
356
+ result << %(.RS 5
387
357
  .ll -.10i
388
358
  #{attribution_line}
389
359
  .RE
390
360
  .ll)
391
- end
392
- result.join LF
393
361
  end
362
+ result.join LF
363
+ end
394
364
 
395
- alias sidebar skip_with_warning
396
-
397
- def stem node
398
- title_element = node.title? ? %(.sp
365
+ def convert_stem node
366
+ result = []
367
+ result << (node.title? ? %(.sp
399
368
  .B #{manify node.title}
400
- .br) : ''
401
- open, close = BLOCK_MATH_DELIMITERS[node.style.to_sym]
402
-
403
- unless ((equation = node.content).start_with? open) && (equation.end_with? close)
404
- equation = %(#{open}#{equation}#{close})
405
- end
406
-
407
- %(#{title_element}#{equation})
369
+ .br) : '.sp')
370
+ open, close = BLOCK_MATH_DELIMITERS[node.style.to_sym]
371
+ if ((equation = node.content).start_with? open) && (equation.end_with? close)
372
+ equation = equation.slice open.length, equation.length - open.length - close.length
408
373
  end
374
+ result << %(#{manify equation, whitespace: :preserve} (#{node.style}))
375
+ result.join LF
376
+ end
409
377
 
410
- # FIXME: The reason this method is so complicated is because we are not
411
- # receiving empty(marked) cells when there are colspans or rowspans. This
412
- # method has to create a map of all cells and in the case of rowspans
413
- # create empty cells as placeholders of the span.
414
- # To fix this, asciidoctor needs to provide an API to tell the user if a
415
- # given cell is being used as a colspan or rowspan.
416
- def table node
417
- result = []
418
- if node.title?
419
- result << %(.sp
378
+ # FIXME: The reason this method is so complicated is because we are not
379
+ # receiving empty(marked) cells when there are colspans or rowspans. This
380
+ # method has to create a map of all cells and in the case of rowspans
381
+ # create empty cells as placeholders of the span.
382
+ # To fix this, asciidoctor needs to provide an API to tell the user if a
383
+ # given cell is being used as a colspan or rowspan.
384
+ def convert_table node
385
+ result = []
386
+ if node.title?
387
+ result << %(.sp
420
388
  .it 1 an-trap
421
389
  .nr an-no-space-flag 1
422
390
  .nr an-break-flag 1
423
391
  .br
424
392
  .B #{manify node.captioned_title}
425
393
  )
426
- end
427
- result << '.TS
394
+ end
395
+ result << '.TS
428
396
  allbox tab(:);'
429
- row_header = []
430
- row_text = []
431
- row_index = 0
432
- node.rows.by_section.each do |tsec, rows|
433
- rows.each do |row|
434
- row_header[row_index] ||= []
435
- row_text[row_index] ||= []
436
- # result << LF
437
- # l left-adjusted
438
- # r right-adjusted
439
- # c centered-adjusted
440
- # n numerical align
441
- # a alphabetic align
442
- # s spanned
443
- # ^ vertically spanned
444
- remaining_cells = row.size
445
- row.each_with_index do |cell, cell_index|
446
- remaining_cells -= 1
447
- row_header[row_index][cell_index] ||= []
448
- # Add an empty cell if this is a rowspan cell
449
- if row_header[row_index][cell_index] == ['^t']
450
- row_text[row_index] << %(T{#{LF}.sp#{LF}T}:)
397
+ row_header = []
398
+ row_text = []
399
+ row_index = 0
400
+ node.rows.to_h.each do |tsec, rows|
401
+ rows.each do |row|
402
+ row_header[row_index] ||= []
403
+ row_text[row_index] ||= []
404
+ # result << LF
405
+ # l left-adjusted
406
+ # r right-adjusted
407
+ # c centered-adjusted
408
+ # n numerical align
409
+ # a alphabetic align
410
+ # s spanned
411
+ # ^ vertically spanned
412
+ remaining_cells = row.size
413
+ row.each_with_index do |cell, cell_index|
414
+ remaining_cells -= 1
415
+ row_header[row_index][cell_index] ||= []
416
+ # Add an empty cell if this is a rowspan cell
417
+ if row_header[row_index][cell_index] == ['^t']
418
+ row_text[row_index] << %(T{#{LF}.sp#{LF}T}:)
419
+ end
420
+ row_text[row_index] << %(T{#{LF}.sp#{LF})
421
+ cell_halign = (cell.attr 'halign', 'left').chr
422
+ if tsec == :body
423
+ if row_header[row_index].empty? || row_header[row_index][cell_index].empty?
424
+ row_header[row_index][cell_index] << %(#{cell_halign}t)
425
+ else
426
+ row_header[row_index][cell_index + 1] ||= []
427
+ row_header[row_index][cell_index + 1] << %(#{cell_halign}t)
451
428
  end
452
- row_text[row_index] << %(T{#{LF}.sp#{LF})
453
- cell_halign = (cell.attr 'halign', 'left').chr
454
- if tsec == :head
455
- if row_header[row_index].empty? || row_header[row_index][cell_index].empty?
456
- row_header[row_index][cell_index] << %(#{cell_halign}tB)
457
- else
458
- row_header[row_index][cell_index + 1] ||= []
459
- row_header[row_index][cell_index + 1] << %(#{cell_halign}tB)
460
- end
461
- row_text[row_index] << %(#{manify cell.text, :whitespace => :normalize}#{LF})
462
- elsif tsec == :body
463
- if row_header[row_index].empty? || row_header[row_index][cell_index].empty?
464
- row_header[row_index][cell_index] << %(#{cell_halign}t)
465
- else
466
- row_header[row_index][cell_index + 1] ||= []
467
- row_header[row_index][cell_index + 1] << %(#{cell_halign}t)
468
- end
469
- case cell.style
470
- when :asciidoc
471
- cell_content = cell.content
472
- when :literal
473
- cell_content = %(.nf#{LF}#{manify cell.text, :whitespace => :preserve}#{LF}.fi)
474
- when :verse
475
- cell_content = %(.nf#{LF}#{manify cell.text, :whitespace => :preserve}#{LF}.fi)
476
- else
477
- cell_content = manify cell.content.join, :whitespace => :normalize
478
- end
479
- row_text[row_index] << %(#{cell_content}#{LF})
480
- elsif tsec == :foot
429
+ case cell.style
430
+ when :asciidoc
431
+ cell_content = cell.content
432
+ when :literal
433
+ cell_content = %(.nf#{LF}#{manify cell.text, whitespace: :preserve}#{LF}.fi)
434
+ else
435
+ cell_content = manify cell.content.join, whitespace: :normalize
436
+ end
437
+ row_text[row_index] << %(#{cell_content}#{LF})
438
+ else # tsec == :head || tsec == :foot
439
+ if row_header[row_index].empty? || row_header[row_index][cell_index].empty?
440
+ row_header[row_index][cell_index] << %(#{cell_halign}tB)
441
+ else
442
+ row_header[row_index][cell_index + 1] ||= []
443
+ row_header[row_index][cell_index + 1] << %(#{cell_halign}tB)
444
+ end
445
+ row_text[row_index] << %(#{manify cell.text, whitespace: :normalize}#{LF})
446
+ end
447
+ if cell.colspan && cell.colspan > 1
448
+ (cell.colspan - 1).times do |i|
481
449
  if row_header[row_index].empty? || row_header[row_index][cell_index].empty?
482
- row_header[row_index][cell_index] << %(#{cell_halign}tB)
450
+ row_header[row_index][cell_index + i] << 'st'
483
451
  else
484
- row_header[row_index][cell_index + 1] ||= []
485
- row_header[row_index][cell_index + 1] << %(#{cell_halign}tB)
486
- end
487
- row_text[row_index] << %(#{manify cell.text, :whitespace => :normalize}#{LF})
488
- end
489
- if cell.colspan && cell.colspan > 1
490
- (cell.colspan - 1).times do |i|
491
- if row_header[row_index].empty? || row_header[row_index][cell_index].empty?
492
- row_header[row_index][cell_index + i] << 'st'
493
- else
494
- row_header[row_index][cell_index + 1 + i] ||= []
495
- row_header[row_index][cell_index + 1 + i] << 'st'
496
- end
452
+ row_header[row_index][cell_index + 1 + i] ||= []
453
+ row_header[row_index][cell_index + 1 + i] << 'st'
497
454
  end
498
455
  end
499
- if cell.rowspan && cell.rowspan > 1
500
- (cell.rowspan - 1).times do |i|
501
- row_header[row_index + 1 + i] ||= []
502
- if row_header[row_index + 1 + i].empty? || row_header[row_index + 1 + i][cell_index].empty?
503
- row_header[row_index + 1 + i][cell_index] ||= []
504
- row_header[row_index + 1 + i][cell_index] << '^t'
505
- else
506
- row_header[row_index + 1 + i][cell_index + 1] ||= []
507
- row_header[row_index + 1 + i][cell_index + 1] << '^t'
508
- end
456
+ end
457
+ if cell.rowspan && cell.rowspan > 1
458
+ (cell.rowspan - 1).times do |i|
459
+ row_header[row_index + 1 + i] ||= []
460
+ if row_header[row_index + 1 + i].empty? || row_header[row_index + 1 + i][cell_index].empty?
461
+ row_header[row_index + 1 + i][cell_index] ||= []
462
+ row_header[row_index + 1 + i][cell_index] << '^t'
463
+ else
464
+ row_header[row_index + 1 + i][cell_index + 1] ||= []
465
+ row_header[row_index + 1 + i][cell_index + 1] << '^t'
509
466
  end
510
467
  end
511
- if remaining_cells >= 1
512
- row_text[row_index] << 'T}:'
513
- else
514
- row_text[row_index] << %(T}#{LF})
515
- end
516
468
  end
517
- row_index += 1
518
- end unless rows.empty?
519
- end
520
-
521
- #row_header.each do |row|
522
- # result << LF
523
- # row.each_with_index do |cell, i|
524
- # result << (cell.join ' ')
525
- # result << ' ' if row.size > i + 1
526
- # end
527
- #end
528
- # FIXME temporary fix to get basic table to display
529
- result << LF
530
- result << ('lt ' * row_header[0].size).chop
531
-
532
- result << %(.#{LF})
533
- row_text.each do |row|
534
- result << row.join
535
- end
536
- result << %(.TE#{LF}.sp)
537
- result.join
538
- end
469
+ if remaining_cells >= 1
470
+ row_text[row_index] << 'T}:'
471
+ else
472
+ row_text[row_index] << %(T}#{LF})
473
+ end
474
+ end
475
+ row_index += 1
476
+ end unless rows.empty?
477
+ end
478
+
479
+ #row_header.each do |row|
480
+ # result << LF
481
+ # row.each_with_index do |cell, i|
482
+ # result << (cell.join ' ')
483
+ # result << ' ' if row.size > i + 1
484
+ # end
485
+ #end
486
+ # FIXME temporary fix to get basic table to display
487
+ result << LF
488
+ result << ('lt ' * row_header[0].size).chop
489
+
490
+ result << %(.#{LF})
491
+ row_text.each do |row|
492
+ result << row.join
493
+ end
494
+ result << %(.TE#{LF}.sp)
495
+ result.join
496
+ end
539
497
 
540
- def thematic_break node
541
- '.sp
498
+ def convert_thematic_break node
499
+ '.sp
542
500
  .ce
543
501
  \l\'\n(.lu*25u/100u\(ap\''
544
- end
502
+ end
545
503
 
546
- alias toc skip
504
+ alias convert_toc skip
547
505
 
548
- def ulist node
549
- result = []
550
- result << %(.sp
506
+ def convert_ulist node
507
+ result = []
508
+ result << %(.sp
551
509
  .B #{manify node.title}
552
510
  .br) if node.title?
553
- node.items.map {|item|
554
- result << %[.sp
511
+ node.items.map do |item|
512
+ result << %[.sp
555
513
  .RS 4
556
514
  .ie n \\{\\
557
515
  \\h'-04'\\(bu\\h'+03'\\c
@@ -560,159 +518,250 @@ allbox tab(:);'
560
518
  . sp -1
561
519
  . IP \\(bu 2.3
562
520
  .\\}
563
- #{manify item.text, :whitespace => :normalize}]
564
- result << item.content if item.blocks?
565
- result << '.RE'
566
- }
567
- result.join LF
521
+ #{manify item.text, whitespace: :normalize}]
522
+ result << item.content if item.blocks?
523
+ result << '.RE'
568
524
  end
525
+ result.join LF
526
+ end
569
527
 
570
- # FIXME git uses [verse] for the synopsis; detect this special case
571
- def verse node
572
- result = []
573
- if node.title?
574
- result << %(.sp
528
+ def convert_verse node
529
+ result = []
530
+ if node.title?
531
+ result << %(.sp
575
532
  .B #{manify node.title}
576
533
  .br)
577
- end
578
- attribution_line = (node.attr? 'citetitle') ? %(#{node.attr 'citetitle'} ) : nil
579
- attribution_line = (node.attr? 'attribution') ? %[#{attribution_line}\\(em #{node.attr 'attribution'}] : nil
580
- result << %(.sp
534
+ end
535
+ attribution_line = (node.attr? 'citetitle') ? %(#{node.attr 'citetitle'} ) : nil
536
+ attribution_line = (node.attr? 'attribution') ? %[#{attribution_line}\\(em #{node.attr 'attribution'}] : nil
537
+ result << %(.sp
581
538
  .nf
582
- #{manify node.content, :whitespace => :preserve}
539
+ #{manify node.content, whitespace: :preserve}
583
540
  .fi
584
541
  .br)
585
- if attribution_line
586
- result << %(.in +.5i
542
+ if attribution_line
543
+ result << %(.in +.5i
587
544
  .ll -.5i
588
545
  #{attribution_line}
589
546
  .in
590
547
  .ll)
591
- end
592
- result.join LF
593
548
  end
549
+ result.join LF
550
+ end
594
551
 
595
- def video node
596
- start_param = (node.attr? 'start', nil, false) ? %(&start=#{node.attr 'start'}) : ''
597
- end_param = (node.attr? 'end', nil, false) ? %(&end=#{node.attr 'end'}) : ''
598
- result = []
599
- result << %(.sp
552
+ def convert_video node
553
+ start_param = (node.attr? 'start') ? %(&start=#{node.attr 'start'}) : ''
554
+ end_param = (node.attr? 'end') ? %(&end=#{node.attr 'end'}) : ''
555
+ result = []
556
+ result << (node.title? ? %(.sp
600
557
  .B #{manify node.title}
601
- .br) if node.title?
602
- result << %(<#{node.media_uri(node.attr 'target')}#{start_param}#{end_param}> (video))
603
- result.join LF
604
- end
558
+ .br) : '.sp')
559
+ result << %(<#{node.media_uri(node.attr 'target')}#{start_param}#{end_param}> (video))
560
+ result.join LF
561
+ end
605
562
 
606
- def inline_anchor node
607
- target = node.target
608
- case node.type
609
- when :link
610
- if target.start_with? 'mailto:'
611
- macro = 'MTO'
612
- target = target.slice 7, target.length
613
- else
614
- macro = 'URL'
615
- end
616
- if (text = node.text) == target
617
- text = ''
563
+ def convert_inline_anchor node
564
+ target = node.target
565
+ case node.type
566
+ when :link
567
+ if target.start_with? 'mailto:'
568
+ macro = 'MTO'
569
+ target = target.slice 7, target.length
570
+ else
571
+ macro = 'URL'
572
+ end
573
+ if (text = node.text) == target
574
+ text = ''
575
+ else
576
+ text = text.gsub '"', %[#{ESC_BS}(dq]
577
+ end
578
+ target = target.sub '@', %[#{ESC_BS}(at] if macro == 'MTO'
579
+ %(#{ESC_BS}c#{LF}#{ESC_FS}#{macro} "#{target}" "#{text}" )
580
+ when :xref
581
+ unless (text = node.text)
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
618
589
  else
619
- text = text.gsub '"', %[#{ESC_BS}(dq]
590
+ text = %([#{refid}])
620
591
  end
621
- target = target.sub '@', %[#{ESC_BS}(at] if macro == 'MTO'
622
- %(#{ESC_BS}c#{LF}#{ESC_FS}#{macro} "#{target}" "#{text}" )
623
- when :xref
624
- refid = (node.attr 'refid') || target
625
- node.text || (node.document.catalog[:ids][refid] || %([#{refid}]))
626
- when :ref, :bibref
627
- # These are anchor points, which shouldn't be visible
628
- ''
629
- else
630
- logger.warn %(unknown anchor type: #{node.type.inspect})
631
- nil
632
592
  end
593
+ text
594
+ when :ref, :bibref
595
+ # These are anchor points, which shouldn't be visible
596
+ ''
597
+ else
598
+ logger.warn %(unknown anchor type: #{node.type.inspect})
599
+ nil
633
600
  end
601
+ end
634
602
 
635
- def inline_break node
636
- %(#{node.text}#{LF}#{ESC_FS}br)
637
- end
603
+ def convert_inline_break node
604
+ %(#{node.text}#{LF}#{ESC_FS}br)
605
+ end
638
606
 
639
- def inline_button node
640
- %(#{ESC_BS}fB[#{ESC_BS}0#{node.text}#{ESC_BS}0]#{ESC_BS}fP)
641
- end
607
+ def convert_inline_button node
608
+ %(<#{ESC_BS}fB>[#{ESC_BS}0#{node.text}#{ESC_BS}0]</#{ESC_BS}fP>)
609
+ end
642
610
 
643
- def inline_callout node
644
- %(#{ESC_BS}fB(#{node.text})#{ESC_BS}fP)
645
- end
611
+ def convert_inline_callout node
612
+ %(<#{ESC_BS}fB>(#{node.text})<#{ESC_BS}fP>)
613
+ end
646
614
 
647
- # TODO supposedly groff has footnotes, but we're in search of an example
648
- def inline_footnote node
649
- if (index = node.attr 'index')
650
- %([#{index}])
651
- elsif node.type == :xref
652
- %([#{node.text}])
653
- end
615
+ def convert_inline_footnote node
616
+ if (index = node.attr 'index')
617
+ %([#{index}])
618
+ elsif node.type == :xref
619
+ %([#{node.text}])
654
620
  end
621
+ end
622
+
623
+ def convert_inline_image node
624
+ (node.attr? 'link') ? %([#{node.alt}] <#{node.attr 'link'}>) : %([#{node.alt}])
625
+ end
655
626
 
656
- def inline_image node
657
- (node.attr? 'link') ? %([#{node.alt}] <#{node.attr 'link'}>) : %([#{node.alt}])
627
+ def convert_inline_indexterm node
628
+ node.type == :visible ? node.text : ''
629
+ end
630
+
631
+ def convert_inline_kbd node
632
+ %[<#{ESC_BS}f(CR>#{(keys = node.attr 'keys').size == 1 ? keys[0] : (keys.join "#{ESC_BS}0+#{ESC_BS}0")}</#{ESC_BS}fP>]
633
+ end
634
+
635
+ def convert_inline_menu node
636
+ caret = %[#{ESC_BS}0#{ESC_BS}(fc#{ESC_BS}0]
637
+ menu = node.attr 'menu'
638
+ if !(submenus = node.attr 'submenus').empty?
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>)
641
+ elsif (menuitem = node.attr 'menuitem')
642
+ %(<#{ESC_BS}fI>#{menu}#{caret}#{menuitem}</#{ESC_BS}fP>)
643
+ else
644
+ %(<#{ESC_BS}fI>#{menu}</#{ESC_BS}fP>)
658
645
  end
646
+ end
659
647
 
660
- def inline_indexterm node
661
- node.type == :visible ? node.text : ''
648
+ # NOTE use fake XML elements to prevent creating artificial word boundaries
649
+ def convert_inline_quoted node
650
+ case node.type
651
+ when :emphasis
652
+ %(<#{ESC_BS}fI>#{node.text}</#{ESC_BS}fP>)
653
+ when :strong
654
+ %(<#{ESC_BS}fB>#{node.text}</#{ESC_BS}fP>)
655
+ when :monospaced
656
+ %[<#{ESC_BS}f(CR>#{node.text}</#{ESC_BS}fP>]
657
+ when :single
658
+ %[<#{ESC_BS}(oq>#{node.text}</#{ESC_BS}(cq>]
659
+ when :double
660
+ %[<#{ESC_BS}(lq>#{node.text}</#{ESC_BS}(rq>]
661
+ else
662
+ node.text
662
663
  end
664
+ end
663
665
 
664
- def inline_kbd node
665
- if (keys = node.attr 'keys').size == 1
666
- keys[0]
667
- else
668
- keys.join %(#{ESC_BS}0+#{ESC_BS}0)
669
- end
666
+ def self.write_alternate_pages mannames, manvolnum, target
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
670
673
  end
674
+ end
671
675
 
672
- def inline_menu node
673
- caret = %[#{ESC_BS}0#{ESC_BS}(fc#{ESC_BS}0]
674
- menu = node.attr 'menu'
675
- if !(submenus = node.attr 'submenus').empty?
676
- submenu_path = submenus.map {|item| %(#{ESC_BS}fI#{item}#{ESC_BS}fP) }.join caret
677
- %(#{ESC_BS}fI#{menu}#{ESC_BS}fP#{caret}#{submenu_path}#{caret}#{ESC_BS}fI#{node.attr 'menuitem'}#{ESC_BS}fP)
678
- elsif (menuitem = node.attr 'menuitem')
679
- %(#{ESC_BS}fI#{menu}#{caret}#{menuitem}#{ESC_BS}fP)
680
- else
681
- %(#{ESC_BS}fI#{menu}#{ESC_BS}fP)
676
+ private
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
682
690
  end
683
691
  end
692
+ end
684
693
 
685
- # NOTE use fake <BOUNDARY> element to prevent creating artificial word boundaries
686
- def inline_quoted node
687
- case node.type
688
- when :emphasis
689
- %(#{ESC_BS}fI<BOUNDARY>#{node.text}</BOUNDARY>#{ESC_BS}fP)
690
- when :strong
691
- %(#{ESC_BS}fB<BOUNDARY>#{node.text}</BOUNDARY>#{ESC_BS}fP)
692
- when :monospaced
693
- %[#{ESC_BS}f(CR<BOUNDARY>#{node.text}</BOUNDARY>#{ESC_BS}fP]
694
- when :single
695
- %[#{ESC_BS}(oq<BOUNDARY>#{node.text}</BOUNDARY>#{ESC_BS}(cq]
696
- when :double
697
- %[#{ESC_BS}(lq<BOUNDARY>#{node.text}</BOUNDARY>#{ESC_BS}(rq]
698
- else
699
- node.text
694
+ # Converts HTML entity references back to their original form, escapes
695
+ # special man characters and strips trailing whitespace.
696
+ #
697
+ # It's crucial that text only ever pass through manify once.
698
+ #
699
+ # str - the String to convert
700
+ # opts - an Hash of options to control processing (default: {})
701
+ # * :whitespace an enum that indicates how to handle whitespace; supported options are:
702
+ # :preserve - preserve spaces (only expanding tabs); :normalize - normalize whitespace
703
+ # (remove spaces around newlines); :collapse - collapse adjacent whitespace to a single
704
+ # space (default: :collapse)
705
+ # * :append_newline a Boolean that indicates whether to append a newline to the result (default: false)
706
+ def manify str, opts = {}
707
+ case opts.fetch :whitespace, :collapse
708
+ when :preserve
709
+ str = str.gsub TAB, ET
710
+ when :normalize
711
+ str = str.gsub WrappedIndentRx, LF
712
+ else
713
+ str = str.tr_s WHITESPACE, ' '
714
+ end
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})
700
721
  end
701
- 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
749
+ opts[:append_newline] ? %(#{str}#{LF}) : str
750
+ end
702
751
 
703
- def resolve_content node
704
- node.content_model == :compound ? node.content : %(.sp#{LF}#{manify node.content, :whitespace => :normalize})
705
- end
752
+ def uppercase_pcdata string
753
+ (XMLMarkupRx.match? string) ? string.gsub(PCDATAFilterRx) { $2 ? $2.upcase : $1 } : string.upcase
754
+ end
706
755
 
707
- def write_alternate_pages mannames, manvolnum, target
708
- if mannames && mannames.size > 1
709
- mannames.shift
710
- manvolext = %(.#{manvolnum})
711
- dir, basename = ::File.split target
712
- mannames.each do |manname|
713
- ::IO.write ::File.join(dir, %(#{manname}#{manvolext})), %(.so #{basename})
714
- end
715
- end
756
+ def enclose_content node
757
+ node.content_model == :compound ? node.content : %(.sp#{LF}#{manify node.content, whitespace: :normalize})
758
+ end
759
+
760
+ def get_root_document node
761
+ while (node = node.document).nested?
762
+ node = node.parent_document
716
763
  end
764
+ node
717
765
  end
718
766
  end
767
+ end