asciidoctor 1.5.8 → 2.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (158) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +162 -17
  3. data/LICENSE +1 -1
  4. data/README-de.adoc +12 -13
  5. data/README-fr.adoc +11 -12
  6. data/README-jp.adoc +11 -12
  7. data/README-zh_CN.adoc +12 -13
  8. data/README.adoc +6 -7
  9. data/asciidoctor.gemspec +19 -24
  10. data/bin/asciidoctor +5 -4
  11. data/data/reference/syntax.adoc +283 -0
  12. data/data/stylesheets/asciidoctor-default.css +56 -52
  13. data/data/stylesheets/coderay-asciidoctor.css +7 -9
  14. data/lib/asciidoctor.rb +171 -232
  15. data/lib/asciidoctor/abstract_block.rb +96 -105
  16. data/lib/asciidoctor/abstract_node.rb +118 -139
  17. data/lib/asciidoctor/attribute_list.rb +10 -14
  18. data/lib/asciidoctor/block.rb +20 -19
  19. data/lib/asciidoctor/callouts.rb +4 -2
  20. data/lib/asciidoctor/cli.rb +3 -2
  21. data/lib/asciidoctor/cli/invoker.rb +14 -21
  22. data/lib/asciidoctor/cli/options.rb +64 -54
  23. data/lib/asciidoctor/converter.rb +357 -185
  24. data/lib/asciidoctor/converter/composite.rb +40 -48
  25. data/lib/asciidoctor/converter/docbook5.rb +604 -640
  26. data/lib/asciidoctor/converter/html5.rb +949 -963
  27. data/lib/asciidoctor/converter/manpage.rb +569 -548
  28. data/lib/asciidoctor/converter/template.rb +231 -272
  29. data/lib/asciidoctor/core_ext.rb +5 -18
  30. data/lib/asciidoctor/core_ext/float/truncate.rb +19 -0
  31. data/lib/asciidoctor/core_ext/match_data/names.rb +7 -0
  32. data/lib/asciidoctor/core_ext/nil_or_empty.rb +1 -0
  33. data/lib/asciidoctor/core_ext/regexp/is_match.rb +4 -2
  34. data/lib/asciidoctor/document.rb +399 -377
  35. data/lib/asciidoctor/extensions.rb +72 -140
  36. data/lib/asciidoctor/helpers.rb +122 -83
  37. data/lib/asciidoctor/inline.rb +5 -1
  38. data/lib/asciidoctor/list.rb +13 -11
  39. data/lib/asciidoctor/logging.rb +17 -16
  40. data/lib/asciidoctor/parser.rb +390 -423
  41. data/lib/asciidoctor/path_resolver.rb +10 -5
  42. data/lib/asciidoctor/reader.rb +286 -263
  43. data/lib/asciidoctor/rouge_ext.rb +39 -0
  44. data/lib/asciidoctor/section.rb +9 -8
  45. data/lib/asciidoctor/stylesheets.rb +19 -37
  46. data/lib/asciidoctor/substitutors.rb +364 -509
  47. data/lib/asciidoctor/syntax_highlighter.rb +238 -0
  48. data/lib/asciidoctor/syntax_highlighter/coderay.rb +87 -0
  49. data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +26 -0
  50. data/lib/asciidoctor/syntax_highlighter/html_pipeline.rb +10 -0
  51. data/lib/asciidoctor/syntax_highlighter/prettify.rb +27 -0
  52. data/lib/asciidoctor/syntax_highlighter/pygments.rb +149 -0
  53. data/lib/asciidoctor/syntax_highlighter/rouge.rb +129 -0
  54. data/lib/asciidoctor/table.rb +73 -66
  55. data/lib/asciidoctor/timings.rb +4 -2
  56. data/lib/asciidoctor/version.rb +2 -1
  57. data/lib/asciidoctor/writer.rb +30 -0
  58. data/man/asciidoctor.1 +19 -15
  59. data/man/asciidoctor.adoc +14 -12
  60. metadata +69 -216
  61. data/CONTRIBUTING.adoc +0 -185
  62. data/Gemfile +0 -60
  63. data/Rakefile +0 -129
  64. data/bin/asciidoctor-safe +0 -15
  65. data/features/open_block.feature +0 -92
  66. data/features/pass_block.feature +0 -66
  67. data/features/step_definitions.rb +0 -49
  68. data/features/text_formatting.feature +0 -57
  69. data/features/xref.feature +0 -1039
  70. data/lib/asciidoctor/converter/base.rb +0 -59
  71. data/lib/asciidoctor/converter/docbook45.rb +0 -93
  72. data/lib/asciidoctor/converter/factory.rb +0 -226
  73. data/lib/asciidoctor/core_ext/1.8.7/base64/strict_encode64.rb +0 -6
  74. data/lib/asciidoctor/core_ext/1.8.7/concurrent/hash.rb +0 -5
  75. data/lib/asciidoctor/core_ext/1.8.7/hash/key.rb +0 -4
  76. data/lib/asciidoctor/core_ext/1.8.7/io/binread.rb +0 -6
  77. data/lib/asciidoctor/core_ext/1.8.7/io/write.rb +0 -5
  78. data/lib/asciidoctor/core_ext/1.8.7/string/chr.rb +0 -6
  79. data/lib/asciidoctor/core_ext/1.8.7/string/limit_bytesize.rb +0 -29
  80. data/lib/asciidoctor/core_ext/1.8.7/symbol/empty.rb +0 -6
  81. data/lib/asciidoctor/core_ext/1.8.7/symbol/length.rb +0 -6
  82. data/lib/asciidoctor/core_ext/string/limit_bytesize.rb +0 -10
  83. data/test/api_test.rb +0 -1240
  84. data/test/attribute_list_test.rb +0 -242
  85. data/test/attributes_test.rb +0 -1623
  86. data/test/blocks_test.rb +0 -3870
  87. data/test/converter_test.rb +0 -470
  88. data/test/document_test.rb +0 -1853
  89. data/test/extensions_test.rb +0 -1560
  90. data/test/fixtures/asciidoc_index.txt +0 -521
  91. data/test/fixtures/basic-docinfo-footer.html +0 -6
  92. data/test/fixtures/basic-docinfo-footer.xml +0 -8
  93. data/test/fixtures/basic-docinfo.html +0 -1
  94. data/test/fixtures/basic-docinfo.xml +0 -4
  95. data/test/fixtures/basic.asciidoc +0 -5
  96. data/test/fixtures/chapter-a.adoc +0 -3
  97. data/test/fixtures/child-include.adoc +0 -5
  98. data/test/fixtures/circle.svg +0 -9
  99. data/test/fixtures/custom-backends/erb/html5/block_paragraph.html.erb +0 -6
  100. data/test/fixtures/custom-backends/haml/docbook45/block_paragraph.xml.haml +0 -6
  101. data/test/fixtures/custom-backends/haml/html5-tweaks/block_paragraph.html.haml +0 -1
  102. data/test/fixtures/custom-backends/haml/html5/block_paragraph.html.haml +0 -3
  103. data/test/fixtures/custom-backends/haml/html5/block_sidebar.html.haml +0 -5
  104. data/test/fixtures/custom-backends/slim/docbook45/block_paragraph.xml.slim +0 -6
  105. data/test/fixtures/custom-backends/slim/html5/block_paragraph.html.slim +0 -3
  106. data/test/fixtures/custom-backends/slim/html5/block_sidebar.html.slim +0 -5
  107. data/test/fixtures/custom-docinfodir/basic-docinfo.html +0 -1
  108. data/test/fixtures/custom-docinfodir/docinfo.html +0 -1
  109. data/test/fixtures/docinfo-footer.html +0 -1
  110. data/test/fixtures/docinfo-footer.xml +0 -9
  111. data/test/fixtures/docinfo.html +0 -1
  112. data/test/fixtures/docinfo.xml +0 -3
  113. data/test/fixtures/doctime-localtime.adoc +0 -2
  114. data/test/fixtures/dot.gif +0 -0
  115. data/test/fixtures/encoding.asciidoc +0 -13
  116. data/test/fixtures/file-with-missing-include.adoc +0 -1
  117. data/test/fixtures/grandchild-include.adoc +0 -3
  118. data/test/fixtures/hello-asciidoctor.pdf +0 -69
  119. data/test/fixtures/include-file.asciidoc +0 -24
  120. data/test/fixtures/include-file.jsx +0 -8
  121. data/test/fixtures/include-file.ml +0 -3
  122. data/test/fixtures/include-file.xml +0 -5
  123. data/test/fixtures/lists.adoc +0 -96
  124. data/test/fixtures/master.adoc +0 -5
  125. data/test/fixtures/mismatched-end-tag.adoc +0 -7
  126. data/test/fixtures/other-chapters.adoc +0 -11
  127. data/test/fixtures/outer-include.adoc +0 -5
  128. data/test/fixtures/parent-include-restricted.adoc +0 -5
  129. data/test/fixtures/parent-include.adoc +0 -5
  130. data/test/fixtures/sample.asciidoc +0 -30
  131. data/test/fixtures/section-a.adoc +0 -4
  132. data/test/fixtures/stylesheets/custom.css +0 -3
  133. data/test/fixtures/subdir/index.adoc +0 -3
  134. data/test/fixtures/subdir/inner-include.adoc +0 -3
  135. data/test/fixtures/subdir/middle-include.adoc +0 -5
  136. data/test/fixtures/subs-docinfo.html +0 -2
  137. data/test/fixtures/subs.adoc +0 -6
  138. data/test/fixtures/tagged-class-enclosed.rb +0 -25
  139. data/test/fixtures/tagged-class.rb +0 -23
  140. data/test/fixtures/tip.gif +0 -0
  141. data/test/fixtures/unclosed-tag.adoc +0 -3
  142. data/test/fixtures/unexpected-end-tag.adoc +0 -4
  143. data/test/invoker_test.rb +0 -745
  144. data/test/links_test.rb +0 -855
  145. data/test/lists_test.rb +0 -5151
  146. data/test/logger_test.rb +0 -211
  147. data/test/manpage_test.rb +0 -660
  148. data/test/options_test.rb +0 -262
  149. data/test/paragraphs_test.rb +0 -562
  150. data/test/parser_test.rb +0 -742
  151. data/test/paths_test.rb +0 -395
  152. data/test/preamble_test.rb +0 -173
  153. data/test/reader_test.rb +0 -2161
  154. data/test/sections_test.rb +0 -3575
  155. data/test/substitutions_test.rb +0 -2066
  156. data/test/tables_test.rb +0 -2036
  157. data/test/test_helper.rb +0 -447
  158. data/test/text_test.rb +0 -309
@@ -1,232 +1,404 @@
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
+ # Implementing a custom converter involves:
7
+ #
8
+ # * Including the {Converter} module in a converter class and implementing the {Converter#convert} method or extending
9
+ # the {Converter::Base Base} class and implementing the dispatch methods that map to each node.
10
+ # * Optionally registering the converter with one or more backend names statically using the +register_for+ DSL method
11
+ # contributed by the {Converter::Config Config} module.
12
+ #
13
+ # {Converter} instances are typically instantiated for each instance of a parsed AsciiDoc document.
14
+ #
15
+ # Examples
16
+ #
17
+ # class TextConverter
18
+ # include Asciidoctor::Converter
19
+ # register_for 'text'
20
+ # def initialize *args
21
+ # super
22
+ # outfilesuffix '.txt'
23
+ # end
24
+ # def convert node, transform = nil, opts = nil
25
+ # case (transform ||= node.node_name)
26
+ # when 'document', 'section'
27
+ # [node.title, node.content].join %(\n\n)
28
+ # when 'paragraph'
29
+ # (node.content.tr ?\n, ' ') << ?\n
30
+ # else
31
+ # (transform.start_with? 'inline_') ? node.text : node.content
32
+ # end
33
+ # end
34
+ # end
35
+ # puts Asciidoctor.convert_file 'sample.adoc', backend: :text
36
+ #
37
+ # class Html5Converter < (Asciidoctor::Converter.for 'html5')
38
+ # register_for 'html5'
39
+ # def paragraph node
40
+ # %(<p>#{node.content}</p>)
41
+ # end
42
+ # end
43
+ # puts Asciidoctor.convert_file 'sample.adoc'
44
+ module Converter
45
+ autoload :CompositeConverter, %(#{__dir__}/converter/composite)
46
+ autoload :TemplateConverter, %(#{__dir__}/converter/template)
47
+
48
+ # Public: The String backend name that this converter is handling.
49
+ attr_reader :backend
50
+
51
+ # Public: Creates a new instance of this {Converter}.
6
52
  #
7
- # Implementing a converter involves:
53
+ # backend - The String backend name (aka format) to which this converter converts.
54
+ # opts - An options Hash (optional, default: {})
8
55
  #
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
56
+ # Returns a new [Converter] instance.
57
+ def initialize backend, opts = {}
58
+ @backend = backend
59
+ end
60
+
61
+ # Public: Converts an {AbstractNode} using the given transform.
13
62
  #
14
- # Examples
63
+ # This method must be implemented by a concrete converter class.
15
64
  #
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
65
+ # node - The concrete instance of AbstractNode to convert.
66
+ # transform - An optional String transform that hints at which transformation should be applied to this node. If a
67
+ # transform is not given, the transform is often derived from the value of the {AbstractNode#node_name}
68
+ # property. (optional, default: nil)
69
+ # opts - An optional Hash of options hints about how to convert the node. (optional, default: nil)
40
70
  #
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
62
- else
63
- metaclass.send :define_method, :converts? do |name|
64
- backends.include? name
65
- end
66
- end
67
- nil
68
- end
71
+ # Returns the [String] result.
72
+ def convert node, transform = nil, opts = nil
73
+ raise ::NotImplementedError, %(#{self.class} (backend: #{@backend}) must implement the ##{__method__} method)
74
+ end
75
+
76
+ # Public: Reports whether the current converter is able to convert this node (by its name). Used by the
77
+ # {CompositeConverter} to select which converter to use to handle a given node. Returns true by default.
78
+ #
79
+ # name - the String name of the node to convert.
80
+ #
81
+ # Returns a [Boolean] indicating whether this converter can handle the specified node by name.
82
+ def handles? name
83
+ true
84
+ end
85
+
86
+ module BackendTraits
87
+ def basebackend value = nil
88
+ value ? (backend_traits[:basebackend] = value) : backend_traits[:basebackend]
89
+ end
90
+
91
+ def filetype value = nil
92
+ value ? (backend_traits[:filetype] = value) : backend_traits[:filetype]
69
93
  end
70
94
 
71
- module BackendInfo
72
- def backend_info
73
- @backend_info ||= setup_backend_info
95
+ def htmlsyntax value = nil
96
+ value ? (backend_traits[:htmlsyntax] = value) : backend_traits[:htmlsyntax]
97
+ end
98
+
99
+ def outfilesuffix value = nil
100
+ value ? (backend_traits[:outfilesuffix] = value) : backend_traits[:outfilesuffix]
101
+ end
102
+
103
+ def supports_templates value = true
104
+ backend_traits[:supports_templates] = value
105
+ end
106
+
107
+ def supports_templates?
108
+ backend_traits[:supports_templates]
109
+ end
110
+
111
+ def init_backend_traits value = nil
112
+ @backend_traits = value || {}
113
+ end
114
+
115
+ def backend_traits
116
+ @backend_traits ||= derive_backend_traits
117
+ end
118
+
119
+ alias backend_info backend_traits
120
+
121
+ private def derive_backend_traits
122
+ BackendTraits.derive_backend_traits @backend
123
+ end
124
+
125
+ def self.derive_backend_traits backend
126
+ return {} unless backend
127
+ if (t_outfilesuffix = DEFAULT_EXTENSIONS[(t_basebackend = backend.sub TrailingDigitsRx, '')])
128
+ t_filetype = t_outfilesuffix.slice 1, t_outfilesuffix.length
129
+ else
130
+ t_outfilesuffix = %(.#{t_filetype = t_basebackend})
74
131
  end
132
+ t_filetype == 'html' ?
133
+ { basebackend: t_basebackend, filetype: t_filetype, htmlsyntax: 'html', outfilesuffix: t_outfilesuffix } :
134
+ { basebackend: t_basebackend, filetype: t_filetype, outfilesuffix: t_outfilesuffix }
135
+ end
136
+ end
137
+
138
+ # A module that contributes the +register_for+ method for registering a converter with the default registry.
139
+ module Config
140
+ # Public: Registers this {Converter} class with the default registry to handle the specified backend name(s).
141
+ #
142
+ # backends - One or more String backend names with which to associate this {Converter} class.
143
+ #
144
+ # Returns nothing.
145
+ private def register_for *backends
146
+ Converter.register self, *backends
147
+ end
148
+ end
149
+
150
+ # A reusable module for registering and instantiating {Converter Converter} classes used to convert an {AbstractNode}
151
+ # to an output (aka backend) format such as HTML or DocBook.
152
+ #
153
+ # {Converter Converter} objects are instantiated by passing a String backend name and, optionally, an options Hash to
154
+ # the {Factory#create} method. The backend can be thought of as an intent to convert a document to a specified format.
155
+ #
156
+ # Applications interact with the factory either through the global, static registry mixed into the {Converter
157
+ # Converter} module or a concrete class that includes this module such as {CustomFactory}. For example:
158
+ #
159
+ # Examples
160
+ #
161
+ # converter = Asciidoctor::Converter.create 'html5', htmlsyntax: 'xml'
162
+ module Factory
163
+ # Public: Create an instance of DefaultProxyFactory or CustomFactory, depending on whether the proxy_default keyword
164
+ # arg is set (true by default), and optionally seed it with the specified converters map. If proxy_default is set,
165
+ # entries in the proxy registry are preferred over matching entries from the default registry.
166
+ #
167
+ # converters - An optional Hash of converters to use in place of ones in the default registry. The keys are
168
+ # backend names and the values are converter classes or instances.
169
+ # proxy_default - A Boolean keyword arg indicating whether to proxy the default registry (optional, default: true).
170
+ #
171
+ # Returns a Factory instance (DefaultFactoryProxy or CustomFactory) seeded with the optional converters map.
172
+ def self.new converters = nil, proxy_default: true
173
+ proxy_default ? (DefaultFactoryProxy.new converters) : (CustomFactory.new converters)
174
+ end
75
175
 
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
176
+ # Deprecated: Maps the old default factory instance holder to the Converter module.
177
+ def self.default *args
178
+ Converter
179
+ end
180
+
181
+ # Deprecated: Maps the create method on the old default factory instance holder to the Converter module.
182
+ def self.create backend, opts = {}
183
+ default.create backend, opts
184
+ end
185
+
186
+ # Public: Register a custom converter with this factory to handle conversion for the specified backends. If the
187
+ # backend is an asterisk (i.e., +*+), the converter will handle any backend for which a converter is not registered.
188
+ #
189
+ # converter - The Converter class to register.
190
+ # backends - One or more String backend names that this converter should be registered to handle.
191
+ #
192
+ # Returns nothing
193
+ def register converter, *backends
194
+ backends.each {|backend| backend == '*' ? (registry.default = converter) : (registry[backend] = converter) }
195
+ end
196
+
197
+ # Public: Lookup the custom converter registered with this factory to handle the specified backend.
198
+ #
199
+ # backend - The String backend name.
200
+ #
201
+ # Returns the [Converter] class registered to convert the specified backend or nil if no match is found.
202
+ def for backend
203
+ registry[backend]
204
+ end
205
+
206
+ # Public: Create a new Converter object that can be used to convert {AbstractNode}s to the format associated with
207
+ # the backend. This method accepts an optional Hash of options that are passed to the converter's constructor.
208
+ #
209
+ # If a custom Converter is found to convert the specified backend, it's instantiated (if necessary) and returned
210
+ # immediately. If a custom Converter is not found, an attempt is made to find a built-in converter. If the
211
+ # +:template_dirs+ key is found in the Hash passed as the second argument, a {CompositeConverter} is created that
212
+ # delegates to a {TemplateConverter} and, if found, the built-in converter. If the +:template_dirs+ key is not
213
+ # found, the built-in converter is returned or nil if no converter is found.
214
+ #
215
+ # backend - the String backend name.
216
+ # opts - a Hash of options to customize creation; also passed to the converter's constructor:
217
+ # :template_dirs - a String Array of directories used to instantiate a {TemplateConverter} (optional).
218
+ # :delegate_backend - a backend String of the last converter in the {CompositeConverter} chain (optional).
219
+ #
220
+ # Returns the [Converter] instance.
221
+ def create backend, opts = {}
222
+ if (converter = self.for backend)
223
+ converter = converter.new backend, opts if ::Class === converter
224
+ if (template_dirs = opts[:template_dirs]) && BackendTraits === converter && converter.supports_templates?
225
+ CompositeConverter.new backend, (TemplateConverter.new backend, template_dirs, opts), converter, backend_traits_source: converter
81
226
  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'
227
+ converter
87
228
  end
88
- {
89
- :basebackend => base,
90
- :outfilesuffix => ext,
91
- :filetype => type,
92
- :htmlsyntax => syntax
93
- }
94
- end
95
-
96
- def filetype value = nil
97
- if value
98
- backend_info[:filetype] = value
229
+ elsif (template_dirs = opts[:template_dirs])
230
+ if (delegate_backend = opts[:delegate_backend]) && (converter = self.for delegate_backend)
231
+ converter = converter.new delegate_backend, opts if ::Class === converter
232
+ CompositeConverter.new backend, (TemplateConverter.new backend, template_dirs, opts), converter, backend_traits_source: converter
99
233
  else
100
- backend_info[:filetype]
234
+ TemplateConverter.new backend, template_dirs, opts
101
235
  end
102
236
  end
237
+ end
103
238
 
104
- def basebackend value = nil
105
- if value
106
- backend_info[:basebackend] = value
107
- else
108
- backend_info[:basebackend]
109
- end
239
+ # Public: Get the Hash of Converter classes keyed by backend name. Intended for testing only.
240
+ def converters
241
+ registry.dup
242
+ end
243
+
244
+ private def registry
245
+ raise ::NotImplementedError, %(#{Factory} subclass #{self.class} must implement the ##{__method__} method)
246
+ end
247
+ end
248
+
249
+ class CustomFactory
250
+ include Factory
251
+
252
+ def initialize seed_registry = nil
253
+ if seed_registry
254
+ seed_registry.default = seed_registry.delete '*'
255
+ else
256
+ seed_registry = {}
110
257
  end
258
+ @registry = seed_registry
259
+ end
260
+
261
+ # Public: Unregister all Converter classes that are registered with this factory. Intended for testing only.
262
+ #
263
+ # Returns nothing.
264
+ def unregister_all
265
+ registry.clear.default = nil
266
+ end
267
+
268
+ private
269
+
270
+ attr_reader :registry
271
+ end
272
+
273
+ # Mixed into the {Converter} module to provide the global registry of converters that are registered statically.
274
+ #
275
+ # This registry includes built-in converters for {Html5Converter HTML 5}, {DocBook5Converter DocBook 5} and
276
+ # {ManPageConverter man(ual) page}, as well as any custom converters that have been discovered or explicitly
277
+ # registered. Converter registration is synchronized (where applicable) and is thus guaranteed to be thread safe.
278
+ module DefaultFactory
279
+ include Factory
280
+
281
+ private
282
+
283
+ @@registry = {}
111
284
 
112
- def outfilesuffix value = nil
113
- if value
114
- backend_info[:outfilesuffix] = value
285
+ def registry
286
+ @@registry
287
+ end
288
+
289
+ unless RUBY_ENGINE == 'opal' # the following block adds support for synchronization and lazy registration
290
+ public
291
+
292
+ def register converter, *backends
293
+ if @@mutex.owned?
294
+ backends.each {|backend| backend == '*' ? (@@catch_all = converter) : (@@registry = @@registry.merge backend => converter) }
115
295
  else
116
- backend_info[:outfilesuffix]
296
+ @@mutex.synchronize { register converter, *backends }
117
297
  end
118
298
  end
119
299
 
120
- def htmlsyntax value = nil
121
- if value
122
- backend_info[:htmlsyntax] = value
123
- else
124
- backend_info[:htmlsyntax]
300
+ def unregister_all
301
+ @@mutex.synchronize do
302
+ @@registry = @@registry.select {|backend| PROVIDED[backend] }
303
+ @@catch_all = nil
125
304
  end
126
305
  end
127
306
 
128
- def supports_templates
129
- backend_info[:supports_templates] = true
307
+ def for backend
308
+ @@registry.fetch backend do
309
+ PROVIDED[backend] ? @@mutex.synchronize do
310
+ # require is thread-safe, so no reason to refetch
311
+ require PROVIDED[backend]
312
+ @@registry[backend]
313
+ end : catch_all
314
+ end
130
315
  end
131
316
 
132
- def supports_templates?
133
- backend_info[:supports_templates]
317
+ PROVIDED = {
318
+ 'docbook5' => %(#{__dir__}/converter/docbook5),
319
+ 'html5' => %(#{__dir__}/converter/html5),
320
+ 'manpage' => %(#{__dir__}/converter/manpage),
321
+ }
322
+
323
+ private
324
+
325
+ def catch_all
326
+ @@catch_all
134
327
  end
328
+
329
+ @@catch_all = nil
330
+ @@mutex = ::Mutex.new
135
331
  end
332
+ end
333
+
334
+ class DefaultFactoryProxy < CustomFactory
335
+ include DefaultFactory # inserts module into ancestors immediately after superclass
136
336
 
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
337
+ unless RUBY_ENGINE == 'opal'
338
+ def unregister_all
339
+ super
340
+ @registry.clear.default = nil
145
341
  end
146
- end
147
342
 
148
- include Config
149
- include BackendInfo
343
+ def for backend
344
+ @registry.fetch(backend) { super }
345
+ end
150
346
 
151
- # Public: Creates a new instance of Converter
152
- #
153
- # backend - The String backend format to which this converter converts.
154
- # opts - An options Hash (optional, default: {})
155
- #
156
- # Returns a new instance of [Converter]
157
- def initialize backend, opts = {}
158
- @backend = backend
159
- setup_backend_info
347
+ private def catch_all
348
+ @registry.default || super
349
+ end
160
350
  end
351
+ end
161
352
 
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
169
- end
170
- =end
353
+ # Internal: Mixes the {Config} module into any class that includes the {Converter} module. Additionally, mixes the
354
+ # {BackendTraits} method into instances of this class.
355
+ #
356
+ # into - The Class into which the {Converter} module is being included.
357
+ #
358
+ # Returns nothing.
359
+ private_class_method def self.included into
360
+ into.include BackendTraits
361
+ into.extend Config
362
+ end
171
363
 
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.
175
- #
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.
364
+ # An abstract base class for defining converters that can be used to convert {AbstractNode} objects in a parsed
365
+ # AsciiDoc document to a backend format such as HTML or DocBook.
366
+ class Base
367
+ include Converter, Logging
368
+
369
+ # Public: Converts an {AbstractNode} by delegating to a method that matches the transform value.
180
370
  #
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: {})
371
+ # This method looks for a method that matches the name of the transform to dispatch to. If the +opts+ argument is
372
+ # non-nil, this method assumes the dispatch method accepts two arguments, the node and an options Hash. The options
373
+ # Hash may be used by converters to delegate back to the top-level converter. Currently, it's used for the outline
374
+ # transform. If the +opts+ argument is nil, this method assumes the dispatch method accepts the node as its only
375
+ # argument. To distiguish from node dispatch methods, the convention is to prefix the name of helper method with
376
+ # underscore and mark them as private. Implementations may override this method to provide different behavior.
188
377
  #
189
- # Returns the [String] result
190
- def convert node, transform = nil, opts = {}
191
- raise ::NotImplementedError
378
+ # See {Converter#convert} for details about the arguments and return value.
379
+ def convert node, transform = nil, opts = nil
380
+ opts ? (send transform || node.node_name, node, opts) : (send transform || node.node_name, node)
381
+ rescue
382
+ raise unless ::NoMethodError === (ex = $!) && ex.receiver == self && ex.name.to_s == (transform || node.node_name)
383
+ logger.warn %(missing convert handler for #{ex.name} node in #{@backend} backend (#{self.class}))
384
+ nil
192
385
  end
193
386
 
194
387
  alias handles? respond_to?
195
388
 
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.
389
+ # Public: Converts the {AbstractNode} using only its converted content.
205
390
  #
206
- # output - The output String to write
207
- # target - The String file name or stream object to which the output should
208
- # be written.
209
- #
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
391
+ # Returns the converted [String] content.
392
+ def _content_only node
393
+ node.content
220
394
  end
221
- end
222
395
 
223
- module VoidWriter
224
- include Writer
225
- # Public: Does not write output
226
- def write output, target
227
- end
396
+ # Public: Skips conversion of the {AbstractNode}.
397
+ #
398
+ # Returns nothing.
399
+ def _skip node; end
228
400
  end
229
- end
230
401
 
231
- require 'asciidoctor/converter/base'
232
- require 'asciidoctor/converter/factory'
402
+ extend DefaultFactory # exports static methods
403
+ end
404
+ end