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,232 +1,417 @@
1
- # encoding: UTF-8
1
+ # frozen_string_literal: true
2
2
  module Asciidoctor
3
- # A base module for defining converters that can be used to convert {AbstractNode}
4
- # objects in a parsed AsciiDoc document to a backend format such as HTML or
5
- # DocBook.
3
+ # A module for defining converters that are used to convert {AbstractNode} objects in a parsed AsciiDoc document to an
4
+ # output (aka backend) format such as HTML or DocBook.
5
+ #
6
+ # A {Converter} is typically instantiated each time an AsciiDoc document is processed (i.e., parsed and converted).
7
+ # Implementing a custom converter entails:
8
+ #
9
+ # * Including the {Converter} module in a converter class and implementing the {Converter#convert} method or extending
10
+ # the {Converter::Base Base} class and implementing the dispatch methods that map to each node.
11
+ # * Optionally registering the converter with one or more backend names statically using the +register_for+ DSL method
12
+ # contributed by the {Converter::Config Config} module.
13
+ #
14
+ # Examples
15
+ #
16
+ # class TextConverter
17
+ # include Asciidoctor::Converter
18
+ # register_for 'text'
19
+ # def initialize *args
20
+ # super
21
+ # outfilesuffix '.txt'
22
+ # end
23
+ # def convert node, transform = node.node_name, opts = nil
24
+ # case transform
25
+ # when 'document', 'section'
26
+ # [node.title, node.content].join %(\n\n)
27
+ # when 'paragraph'
28
+ # (node.content.tr ?\n, ' ') << ?\n
29
+ # else
30
+ # (transform.start_with? 'inline_') ? node.text : node.content
31
+ # end
32
+ # end
33
+ # end
34
+ # puts Asciidoctor.convert_file 'sample.adoc', backend: :text, safe: :safe
35
+ #
36
+ # class Html5Converter < (Asciidoctor::Converter.for 'html5')
37
+ # register_for 'html5'
38
+ # def convert_paragraph node
39
+ # %(<p>#{node.content}</p>)
40
+ # end
41
+ # end
42
+ # puts Asciidoctor.convert_file 'sample.adoc', safe: :safe
43
+ module Converter
44
+ autoload :CompositeConverter, %(#{__dir__}/converter/composite)
45
+ autoload :TemplateConverter, %(#{__dir__}/converter/template) unless RUBY_ENGINE == 'opal'
46
+
47
+ # Public: The String backend name that this converter is handling.
48
+ attr_reader :backend
49
+
50
+ # Public: Creates a new instance of this {Converter}.
51
+ #
52
+ # backend - The String backend name (aka format) to which this converter converts.
53
+ # opts - An options Hash (optional, default: {})
54
+ #
55
+ # Returns a new [Converter] instance.
56
+ def initialize backend, opts = {}
57
+ @backend = backend
58
+ end
59
+
60
+ # Public: Converts an {AbstractNode} using the given transform.
61
+ #
62
+ # This method must be implemented by a concrete converter class.
63
+ #
64
+ # node - The concrete instance of AbstractNode to convert.
65
+ # transform - An optional String transform that hints at which transformation should be applied to this node. If a
66
+ # transform is not given, the transform is often derived from the value of the {AbstractNode#node_name}
67
+ # property. (optional, default: nil)
68
+ # opts - An optional Hash of options hints about how to convert the node. (optional, default: nil)
69
+ #
70
+ # Returns the [String] result.
71
+ def convert node, transform = nil, opts = nil
72
+ raise ::NotImplementedError, %(#{self.class} (backend: #{@backend}) must implement the ##{__method__} method)
73
+ end
74
+
75
+ # Public: Reports whether the current converter is able to convert this node (by its transform name). Used by the
76
+ # {CompositeConverter} to select which converter to use to handle a given node. Returns true by default.
77
+ #
78
+ # transform - the String name of the node transformation (typically the node name).
79
+ #
80
+ # Returns a [Boolean] indicating whether this converter can handle the specified transform.
81
+ def handles? transform
82
+ true
83
+ end
84
+
85
+ # Public: Derive backend traits (basebackend, filetype, outfilesuffix, htmlsyntax) from the given backend.
86
+ #
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)
89
+ #
90
+ # Returns the backend traits for the given backend as a [Hash].
91
+ def self.derive_backend_traits backend, basebackend = nil
92
+ return {} unless backend
93
+ if (outfilesuffix = DEFAULT_EXTENSIONS[(basebackend ||= backend.sub TrailingDigitsRx, '')])
94
+ filetype = outfilesuffix.slice 1, outfilesuffix.length
95
+ else
96
+ outfilesuffix = %(.#{filetype = basebackend})
97
+ end
98
+ filetype == 'html' ?
99
+ { basebackend: basebackend, filetype: filetype, htmlsyntax: 'html', outfilesuffix: outfilesuffix } :
100
+ { basebackend: basebackend, filetype: filetype, outfilesuffix: outfilesuffix }
101
+ end
102
+
103
+ module BackendTraits
104
+ def basebackend value = nil
105
+ value ? ((backend_traits value)[:basebackend] = value) : backend_traits[:basebackend]
106
+ end
107
+
108
+ def filetype value = nil
109
+ value ? (backend_traits[:filetype] = value) : backend_traits[:filetype]
110
+ end
111
+
112
+ def htmlsyntax value = nil
113
+ value ? (backend_traits[:htmlsyntax] = value) : backend_traits[:htmlsyntax]
114
+ end
115
+
116
+ def outfilesuffix value = nil
117
+ value ? (backend_traits[:outfilesuffix] = value) : backend_traits[:outfilesuffix]
118
+ end
119
+
120
+ def supports_templates value = true
121
+ backend_traits[:supports_templates] = value
122
+ end
123
+
124
+ def supports_templates?
125
+ backend_traits[:supports_templates]
126
+ end
127
+
128
+ def init_backend_traits value = nil
129
+ @backend_traits = value || {}
130
+ end
131
+
132
+ def backend_traits basebackend = nil
133
+ @backend_traits ||= Converter.derive_backend_traits @backend, basebackend
134
+ end
135
+
136
+ alias backend_info backend_traits
137
+
138
+ # Deprecated: Use {Converter.derive_backend_traits} instead.
139
+ def self.derive_backend_traits backend, basebackend = nil
140
+ Converter.derive_backend_traits backend, basebackend
141
+ end
142
+ end
143
+
144
+ # A module that contributes the +register_for+ method for registering a converter with the default registry.
145
+ module Config
146
+ # Public: Registers this {Converter} class with the default registry to handle the specified backend name(s).
147
+ #
148
+ # backends - One or more String backend names with which to associate this {Converter} class.
149
+ #
150
+ # Returns nothing.
151
+ def register_for *backends
152
+ Converter.register self, *(backends.map {|backend| backend.to_s })
153
+ end
154
+ end
155
+
156
+ # A reusable module for registering and instantiating {Converter Converter} classes used to convert an {AbstractNode}
157
+ # to an output (aka backend) format such as HTML or DocBook.
6
158
  #
7
- # Implementing a converter involves:
159
+ # {Converter Converter} objects are instantiated by passing a String backend name and, optionally, an options Hash to
160
+ # the {Factory#create} method. The backend can be thought of as an intent to convert a document to a specified format.
8
161
  #
9
- # * including this module in a {Converter} implementation class
10
- # * overriding the {Converter#convert} method
11
- # * optionally associating the converter with one or more backends using
12
- # the {#register_for} DSL method imported by the {Config Converter::Config} module
162
+ # Applications interact with the factory either through the global, static registry mixed into the {Converter
163
+ # Converter} module or a concrete class that includes this module such as {CustomFactory}. For example:
13
164
  #
14
165
  # Examples
15
166
  #
16
- # class TextConverter
17
- # include Asciidoctor::Converter
18
- # register_for 'text'
19
- # def initialize backend, opts
20
- # super
21
- # outfilesuffix '.txt'
22
- # end
23
- # def convert node, transform = nil, opts = {}
24
- # case (transform ||= node.node_name)
25
- # when 'document'
26
- # node.content
27
- # when 'section'
28
- # [node.title, node.content].join "\n\n"
29
- # when 'paragraph'
30
- # node.content.tr("\n", ' ') << "\n"
31
- # else
32
- # if transform.start_with? 'inline_'
33
- # node.text
34
- # else
35
- # %(<#{transform}>\n)
36
- # end
37
- # end
38
- # end
39
- # end
40
- #
41
- # puts Asciidoctor.convert_file 'sample.adoc', backend: :text
42
- module Converter
43
- # A module that provides the {#register_for} method for statically
44
- # registering a converter with the default {Factory Converter::Factory} instance.
45
- module Config
46
- # Public: Statically registers the current {Converter} class with the default
47
- # {Factory Converter::Factory} to handle conversion to the specified backends.
48
- #
49
- # This method also defines the converts? method on the class which returns whether
50
- # the class is registered to convert a specified backend.
51
- #
52
- # backends - A String Array of backends with which to associate this {Converter} class.
53
- #
54
- # Returns nothing
55
- def register_for *backends
56
- Factory.register self, backends
57
- metaclass = class << self; self; end
58
- if backends == ['*']
59
- metaclass.send :define_method, :converts? do |name|
60
- true
61
- end
167
+ # converter = Asciidoctor::Converter.create 'html5', htmlsyntax: 'xml'
168
+ module Factory
169
+ # Public: Create an instance of DefaultProxyFactory or CustomFactory, depending on whether the proxy_default keyword
170
+ # arg is set (true by default), and optionally seed it with the specified converters map. If proxy_default is set,
171
+ # entries in the proxy registry are preferred over matching entries from the default registry.
172
+ #
173
+ # converters - An optional Hash of converters to use in place of ones in the default registry. The keys are
174
+ # backend names and the values are converter classes or instances.
175
+ # proxy_default - A Boolean keyword arg indicating whether to proxy the default registry (optional, default: true).
176
+ #
177
+ # Returns a Factory instance (DefaultFactoryProxy or CustomFactory) seeded with the optional converters map.
178
+ def self.new converters = nil, proxy_default: true
179
+ proxy_default ? (DefaultFactoryProxy.new converters) : (CustomFactory.new converters)
180
+ end
181
+
182
+ # Deprecated: Maps the old default factory instance holder to the Converter module.
183
+ def self.default *args
184
+ Converter
185
+ end
186
+
187
+ # Deprecated: Maps the create method on the old default factory instance holder to the Converter module.
188
+ def self.create backend, opts = {}
189
+ default.create backend, opts
190
+ end
191
+
192
+ # Public: Register a custom converter with this factory to handle conversion for the specified backends. If the
193
+ # backend is an asterisk (i.e., +*+), the converter will handle any backend for which a converter is not registered.
194
+ #
195
+ # converter - The Converter class to register.
196
+ # backends - One or more String backend names that this converter should be registered to handle.
197
+ #
198
+ # Returns nothing
199
+ def register converter, *backends
200
+ backends.each {|backend| backend == '*' ? (registry.default = converter) : (registry[backend] = converter) }
201
+ end
202
+
203
+ # Public: Lookup the custom converter registered with this factory to handle the specified backend.
204
+ #
205
+ # backend - The String backend name.
206
+ #
207
+ # Returns the [Converter] class registered to convert the specified backend or nil if no match is found.
208
+ def for backend
209
+ registry[backend]
210
+ end
211
+
212
+ # Public: Create a new Converter object that can be used to convert {AbstractNode}s to the format associated with
213
+ # the backend. This method accepts an optional Hash of options that are passed to the converter's constructor.
214
+ #
215
+ # If a custom Converter is found to convert the specified backend, it's instantiated (if necessary) and returned
216
+ # immediately. If a custom Converter is not found, an attempt is made to find a built-in converter. If the
217
+ # +:template_dirs+ key is found in the Hash passed as the second argument, a {CompositeConverter} is created that
218
+ # delegates to a {TemplateConverter} and, if found, the built-in converter. If the +:template_dirs+ key is not
219
+ # found, the built-in converter is returned or nil if no converter is found.
220
+ #
221
+ # backend - the String backend name.
222
+ # opts - a Hash of options to customize creation; also passed to the converter's constructor:
223
+ # :template_dirs - a String Array of directories used to instantiate a {TemplateConverter} (optional).
224
+ # :delegate_backend - a backend String of the last converter in the {CompositeConverter} chain (optional).
225
+ #
226
+ # Returns the [Converter] instance.
227
+ def create backend, opts = {}
228
+ if (converter = self.for backend)
229
+ converter = converter.new backend, opts if ::Class === converter
230
+ if (template_dirs = opts[:template_dirs]) && BackendTraits === converter && converter.supports_templates?
231
+ CompositeConverter.new backend, (TemplateConverter.new backend, template_dirs, opts), converter, backend_traits_source: converter
232
+ else
233
+ converter
234
+ end
235
+ elsif (template_dirs = opts[:template_dirs])
236
+ if (delegate_backend = opts[:delegate_backend]) && (converter = self.for delegate_backend)
237
+ converter = converter.new delegate_backend, opts if ::Class === converter
238
+ CompositeConverter.new backend, (TemplateConverter.new backend, template_dirs, opts), converter, backend_traits_source: converter
62
239
  else
63
- metaclass.send :define_method, :converts? do |name|
64
- backends.include? name
65
- end
240
+ TemplateConverter.new backend, template_dirs, opts
66
241
  end
67
- nil
68
242
  end
69
243
  end
70
244
 
71
- module BackendInfo
72
- def backend_info
73
- @backend_info ||= setup_backend_info
74
- end
245
+ # Public: Get the Hash of Converter classes keyed by backend name. Intended for testing only.
246
+ def converters
247
+ registry.merge
248
+ end
75
249
 
76
- def setup_backend_info
77
- raise ::ArgumentError, %(Cannot determine backend for converter: #{self.class}) unless @backend
78
- base = @backend.sub TrailingDigitsRx, ''
79
- if (ext = DEFAULT_EXTENSIONS[base])
80
- type = ext.slice 1, ext.length
81
- else
82
- # QUESTION should we be forcing the basebackend to html if unknown?
83
- base = 'html'
84
- ext = '.html'
85
- type = 'html'
86
- syntax = 'html'
87
- end
88
- {
89
- :basebackend => base,
90
- :outfilesuffix => ext,
91
- :filetype => type,
92
- :htmlsyntax => syntax
93
- }
250
+ private
251
+
252
+ def registry
253
+ raise ::NotImplementedError, %(#{Factory} subclass #{self.class} must implement the ##{__method__} method)
254
+ end
255
+ end
256
+
257
+ class CustomFactory
258
+ include Factory
259
+
260
+ def initialize seed_registry = nil
261
+ if seed_registry
262
+ seed_registry.default = seed_registry.delete '*'
263
+ @registry = seed_registry
264
+ else
265
+ @registry = {}
94
266
  end
267
+ end
268
+
269
+ # Public: Unregister all Converter classes that are registered with this factory. Intended for testing only.
270
+ #
271
+ # Returns nothing.
272
+ def unregister_all
273
+ registry.clear.default = nil
274
+ end
275
+
276
+ private
277
+
278
+ attr_reader :registry
279
+ end
280
+
281
+ # Mixed into the {Converter} module to provide the global registry of converters that are registered statically.
282
+ #
283
+ # This registry includes built-in converters for {Html5Converter HTML 5}, {DocBook5Converter DocBook 5} and
284
+ # {ManPageConverter man(ual) page}, as well as any custom converters that have been discovered or explicitly
285
+ # registered. Converter registration is synchronized (where applicable) and is thus guaranteed to be thread safe.
286
+ module DefaultFactory
287
+ include Factory
288
+
289
+ private
290
+
291
+ @@registry = {}
95
292
 
96
- def filetype value = nil
97
- if value
98
- backend_info[:filetype] = value
293
+ def registry
294
+ @@registry
295
+ end
296
+
297
+ unless RUBY_ENGINE == 'opal' # the following block adds support for synchronization and lazy registration
298
+ public
299
+
300
+ def register converter, *backends
301
+ if @@mutex.owned?
302
+ backends.each {|backend| backend == '*' ? (@@catch_all = converter) : (@@registry = @@registry.merge backend => converter) }
99
303
  else
100
- backend_info[:filetype]
304
+ @@mutex.synchronize { register converter, *backends }
101
305
  end
102
306
  end
103
307
 
104
- def basebackend value = nil
105
- if value
106
- backend_info[:basebackend] = value
107
- else
108
- backend_info[:basebackend]
308
+ def unregister_all
309
+ @@mutex.synchronize do
310
+ @@registry = @@registry.select {|backend| PROVIDED[backend] }
311
+ @@catch_all = nil
109
312
  end
110
313
  end
111
314
 
112
- def outfilesuffix value = nil
113
- if value
114
- backend_info[:outfilesuffix] = value
115
- else
116
- backend_info[:outfilesuffix]
315
+ def for backend
316
+ @@registry.fetch backend do
317
+ PROVIDED[backend] ? (@@mutex.synchronize do
318
+ # require is thread-safe, so no reason to refetch
319
+ require PROVIDED[backend]
320
+ @@registry[backend]
321
+ end) : catch_all
117
322
  end
118
323
  end
119
324
 
120
- def htmlsyntax value = nil
121
- if value
122
- backend_info[:htmlsyntax] = value
123
- else
124
- backend_info[:htmlsyntax]
125
- end
325
+ PROVIDED = {
326
+ 'docbook5' => %(#{__dir__}/converter/docbook5),
327
+ 'html5' => %(#{__dir__}/converter/html5),
328
+ 'manpage' => %(#{__dir__}/converter/manpage),
329
+ }
330
+
331
+ private
332
+
333
+ def catch_all
334
+ @@catch_all
126
335
  end
127
336
 
128
- def supports_templates
129
- backend_info[:supports_templates] = true
337
+ @@catch_all = nil
338
+ @@mutex = ::Mutex.new
339
+ end
340
+ end
341
+
342
+ class DefaultFactoryProxy < CustomFactory
343
+ include DefaultFactory # inserts module into ancestors immediately after superclass
344
+
345
+ unless RUBY_ENGINE == 'opal'
346
+ def unregister_all
347
+ super
348
+ @registry.clear.default = nil
130
349
  end
131
350
 
132
- def supports_templates?
133
- backend_info[:supports_templates]
351
+ def for backend
352
+ @registry.fetch(backend) { super }
134
353
  end
135
- end
136
354
 
137
- class << self
138
- # Mixes the {Config Converter::Config} module into any class that includes the {Converter} module.
139
- #
140
- # converter - The Class that includes the {Converter} module
141
- #
142
- # Returns nothing
143
- def included converter
144
- converter.extend Config
355
+ private
356
+
357
+ def catch_all
358
+ @registry.default || super
145
359
  end
146
360
  end
361
+ end
147
362
 
148
- include Config
149
- include BackendInfo
363
+ # Internal: Mixes the {Config} module into any class that includes the {Converter} module. Additionally, mixes the
364
+ # {BackendTraits} method into instances of this class.
365
+ #
366
+ # into - The Class into which the {Converter} module is being included.
367
+ #
368
+ # Returns nothing.
369
+ def self.included into
370
+ into.send :include, BackendTraits
371
+ into.extend Config
372
+ end
373
+ private_class_method :included # use separate declaration for Ruby 2.0.x
374
+
375
+ # An abstract base class for defining converters that can be used to convert {AbstractNode} objects in a parsed
376
+ # AsciiDoc document to a backend format such as HTML or DocBook.
377
+ class Base
378
+ include Logging
379
+ include Converter
150
380
 
151
- # Public: Creates a new instance of Converter
381
+ # Public: Converts an {AbstractNode} by delegating to a method that matches the transform value.
152
382
  #
153
- # backend - The String backend format to which this converter converts.
154
- # opts - An options Hash (optional, default: {})
383
+ # This method looks for a method whose name matches the transform prefixed with "convert_" to dispatch to. If the
384
+ # +opts+ argument is non-nil, this method assumes the dispatch method accepts two arguments, the node and an options
385
+ # Hash. The options Hash may be used by converters to delegate back to the top-level converter. Currently, this
386
+ # feature is used for the outline transform. If the +opts+ argument is nil, this method assumes the dispatch method
387
+ # accepts the node as its only argument.
155
388
  #
156
- # Returns a new instance of [Converter]
157
- def initialize backend, opts = {}
158
- @backend = backend
159
- setup_backend_info
389
+ # See {Converter#convert} for details about the arguments and return value.
390
+ def convert node, transform = node.node_name, opts = nil
391
+ opts ? (send 'convert_' + transform, node, opts) : (send 'convert_' + transform, node)
392
+ rescue
393
+ raise unless ::NoMethodError === (ex = $!) && ex.receiver == self && ex.name.to_s == transform
394
+ logger.warn %(missing convert handler for #{ex.name} node in #{@backend} backend (#{self.class}))
395
+ nil
160
396
  end
161
397
 
162
- =begin
163
- # Public: Invoked when this converter is added to the chain of converters in a {CompositeConverter}.
164
- #
165
- # owner - The CompositeConverter instance
166
- #
167
- # Returns nothing
168
- def composed owner
398
+ def handles? transform
399
+ respond_to? %(convert_#{transform})
169
400
  end
170
- =end
171
401
 
172
- # Public: Converts an {AbstractNode} using the specified transform along
173
- # with additional options. If a transform is not specified, implementations
174
- # typically derive one from the {AbstractNode#node_name} property.
402
+ # Public: Converts the {AbstractNode} using only its converted content.
175
403
  #
176
- # Implementations are free to decide how to carry out the conversion. In
177
- # the case of the built-in converters, the tranform value is used to
178
- # dispatch to a handler method. The {TemplateConverter} uses the value of
179
- # the transform to select a template to render.
180
- #
181
- # node - The concrete instance of AbstractNode to convert
182
- # transform - An optional String transform that hints at which transformation
183
- # should be applied to this node. If a transform is not specified,
184
- # the transform is typically derived from the value of the
185
- # node's node_name property. (optional, default: nil)
186
- # opts - An optional Hash of options that provide additional hints about
187
- # how to convert the node. (optional, default: {})
188
- #
189
- # Returns the [String] result
190
- def convert node, transform = nil, opts = {}
191
- raise ::NotImplementedError
404
+ # Returns the converted [String] content.
405
+ def content_only node
406
+ node.content
192
407
  end
193
408
 
194
- alias handles? respond_to?
195
-
196
- # Alias for backward compatibility.
197
- alias convert_with_options convert
198
- end
199
-
200
- # A module that can be used to mix the {#write} method into a {Converter}
201
- # implementation to allow the converter to control how the output is written
202
- # to disk.
203
- module Writer
204
- # Public: Writes the output to the specified target file name or stream.
205
- #
206
- # output - The output String to write
207
- # target - The String file name or stream object to which the output should
208
- # be written.
409
+ # Public: Skips conversion of the {AbstractNode}.
209
410
  #
210
- # Returns nothing
211
- def write output, target
212
- if target.respond_to? :write
213
- target.write output.chomp
214
- # ensure there's a trailing endline to be nice to terminals
215
- target.write LF
216
- else
217
- ::IO.write target, output
218
- end
219
- nil
220
- end
411
+ # Returns nothing.
412
+ def skip node; end
221
413
  end
222
414
 
223
- module VoidWriter
224
- include Writer
225
- # Public: Does not write output
226
- def write output, target
227
- end
228
- end
415
+ extend DefaultFactory # exports static methods
416
+ end
229
417
  end
230
-
231
- require 'asciidoctor/converter/base'
232
- require 'asciidoctor/converter/factory'
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+ # NOTE remove once minimum required Ruby version is at least 2.4
3
+ # NOTE use `send :prepend` to be nice to Ruby 2.0
4
+ Float.send :prepend, (Module.new do
5
+ def truncate *args
6
+ if args.length == 1
7
+ if (precision = Integer args.shift) == 0
8
+ super
9
+ elsif precision > 0
10
+ precision_factor = 10.0 ** precision
11
+ (self * precision_factor).to_i / precision_factor
12
+ else
13
+ precision_factor = 10 ** precision.abs
14
+ (self / precision_factor).to_i * precision_factor
15
+ end
16
+ else
17
+ super
18
+ end
19
+ end
20
+ end) if (Float.instance_method :truncate).arity == 0
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+ # NOTE remove once minimum required Ruby version is at least 2.6
3
+ # NOTE use `send :prepend` to be nice to Ruby 2.0
4
+ Hash.send :prepend, (Module.new do
5
+ def merge *args
6
+ (len = args.length) < 1 ? dup : (len > 1 ? args.inject(self) {|acc, arg| acc.merge arg } : (super args[0]))
7
+ end
8
+ end) if (Hash.instance_method :merge).arity == 1
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+ # NOTE remove once implemented in Opal; see https://github.com/opal/opal/issues/1964
3
+ class MatchData
4
+ def names
5
+ []
6
+ end
7
+ end unless MatchData.method_defined? :names
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # A core library extension that defines the method nil_or_empty? as an alias to
2
3
  # optimize checks for nil? or empty? on common object types such as NilClass,
3
4
  # String, Array, Hash, and Numeric.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+ # NOTE remove once minimum required Ruby version is at least 2.4
1
3
  class Regexp
2
- alias match? === unless method_defined? :match?
3
- end
4
+ alias match? ===
5
+ end unless Regexp.method_defined? :match?