asciidoctor 1.5.8 → 2.0.0.rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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