-
-
Notifications
You must be signed in to change notification settings - Fork 787
/
factory.rb
231 lines (216 loc) · 9.41 KB
/
factory.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# encoding: UTF-8
module Asciidoctor
module Converter
# A factory for instantiating converters that are used to convert a
# {Document} (i.e., a parsed AsciiDoc tree structure) or {AbstractNode} to
# a backend format such as HTML or DocBook. {Factory Converter::Factory} is
# the primary entry point for creating, registering and accessing
# converters.
#
# {Converter} objects are instantiated by passing a String backend name
# and, optionally, an options Hash to the {Factory#create} method. The
# backend can be thought of as an intent to convert a document to a
# specified format. For example:
#
# converter = Asciidoctor::Converter::Factory.create 'html5', :htmlsyntax => 'xml'
#
# Converter objects are thread safe. They only survive the lifetime of a single conversion.
#
# A singleton instance of {Factory Converter::Factory} can be accessed
# using the {Factory.default} method. This instance maintains the global
# registry of statically registered converters. The registery includes
# built-in converters for {Html5Converter HTML 5}, {DocBook5Converter
# DocBook 5} and {DocBook45Converter DocBook 4.5}, as well as any custom
# converters that have been discovered or explicitly registered.
#
# If the {https://rubygems.org/gems/thread_safe thread_safe} gem is
# installed, access to the default factory is guaranteed to be thread safe.
# Otherwise, a warning is issued to the user.
class Factory
@__default__ = nil
class << self
# Public: Retrieves a singleton instance of {Factory Converter::Factory}.
#
# If the thread_safe gem is installed, the registry of converters is
# initialized as a ThreadSafe::Cache. Otherwise, a warning is issued and
# the registry of converters is initialized using a normal Hash.
#
# initialize_singleton - A Boolean to indicate whether the singleton should
# be initialize if it has not already been created.
# If false, and a singleton has not been previously
# initialized, a fresh instance is returned.
#
# Returns the default [Factory] singleton instance
def default initialize_singleton = true
return @__default__ || new unless initialize_singleton
# FIXME this assignment is not thread_safe, may need to use a ::Threadsafe helper here
@__default__ ||= begin
require 'thread_safe'.to_s unless defined? ::ThreadSafe
new ::ThreadSafe::Cache.new
rescue ::LoadError
warn 'asciidoctor: WARNING: gem \'thread_safe\' is not installed. This gem recommended when registering custom converters.'
new
end
end
# Public: Register a custom converter in the global converter factory to
# handle conversion to the specified backends. If the backend value is an
# asterisk, the converter is used to handle any backend that does not have
# an explicit converter.
#
# converter - The Converter class to register
# backends - A String Array of backend names that this converter should
# be registered to handle (optional, default: ['*'])
#
# Returns nothing
def register converter, backends = ['*']
default.register converter, backends
end
# Public: Lookup the custom converter for the specified backend in the
# global factory.
#
# This method does not resolve the built-in converters.
#
# backend - The String backend name
#
# Returns the [Converter] class registered to convert the specified backend
# or nil if no match is found
def resolve backend
default.resolve backend
end
# Public: Lookup the converter for the specified backend in the global
# factory and instantiate it, forwarding the Hash of options to the
# constructor of the converter class.
#
# If the custom converter is not found, an attempt will be made to find
# and instantiate a built-in converter.
#
#
# backend - The String backend name
# opts - A Hash of options to pass to the converter
#
# Returns an instance of [Converter] for converting the specified backend or
# nil if no match is found.
def create backend, opts = {}
default.create backend, opts
end
# Public: Retrieve the global Hash of custom Converter classes keyed by backend.
#
# Returns the the global [Hash] of custom Converter classes
def converters
default.converters
end
# Public: Unregister all Converter classes in the global factory.
#
# Returns nothing
def unregister_all
default.unregister_all
end
end
# Public: Get the Hash of Converter classes keyed by backend name
attr_reader :converters
def initialize converters = nil
@converters = converters || {}
@star_converter = nil
end
# Public: Register a custom converter with this factory to handle conversion
# to the specified backends. If the backend value is an asterisk, the
# converter is used to handle any backend that does not have an explicit
# converter.
#
# converter - The Converter class to register
# backends - A String Array of backend names that this converter should
# be registered to handle (optional, default: ['*'])
#
# Returns nothing
def register converter, backends = ['*']
backends.each do |backend|
@converters[backend] = converter
if backend == '*'
@star_converter = converter
end
end
nil
end
# Public: Lookup the custom converter registered with this factory to handle
# the specified backend.
#
# backend - The String backend name
#
# Returns the [Converter] class registered to convert the specified backend
# or nil if no match is found
def resolve backend
@converters && (@converters[backend] || @star_converter)
end
# Public: Unregister all Converter classes that are registered with this
# factory.
#
# Returns nothing
def unregister_all
@converters.clear
@star_converter = nil
end
# Public: Create a new Converter object that can be used to convert the
# {AbstractNode} (typically a {Document}) to the specified String backend.
# This method accepts an optional Hash of options that are passed to the
# converter's constructor.
#
# If a custom Converter is found to convert the specified backend, it is
# instantiated (if necessary) and returned immediately. If a custom
# Converter is not found, an attempt is made to resolve a built-in
# converter. If the `:template_dirs` key is found in the Hash passed as the
# second argument, a {CompositeConverter} is created that delegates to a
# {TemplateConverter} and, if resolved, the built-in converter. If the
# `:template_dirs` key is not found, the built-in converter is returned
# or nil if no converter is resolved.
#
# backend - the String backend name
# opts - an optional Hash of options that get passed on to the converter's
# constructor. If the :template_dirs key is found in the options
# Hash, this method returns a {CompositeConverter} that delegates
# to a {TemplateConverter}. (optional, default: {})
#
# Returns the [Converter] object
def create backend, opts = {}
if (converter = resolve backend)
if converter.is_a? ::Class
return converter.new backend, opts
else
return converter
end
end
base_converter = case backend
when 'html5'
unless defined? ::Asciidoctor::Converter::Html5Converter
require 'asciidoctor/converter/html5'.to_s
end
Html5Converter.new backend, opts
when 'docbook5'
unless defined? ::Asciidoctor::Converter::DocBook5Converter
require 'asciidoctor/converter/docbook5'.to_s
end
DocBook5Converter.new backend, opts
when 'docbook45'
unless defined? ::Asciidoctor::Converter::DocBook45Converter
require 'asciidoctor/converter/docbook45'.to_s
end
DocBook45Converter.new backend, opts
when 'manpage'
unless defined? ::Asciidoctor::Converter::ManPageConverter
require 'asciidoctor/converter/manpage'.to_s
end
ManPageConverter.new backend, opts
end
return base_converter unless opts.key? :template_dirs
unless defined? ::Asciidoctor::Converter::TemplateConverter
require 'asciidoctor/converter/template'.to_s
end
unless defined? ::Asciidoctor::Converter::CompositeConverter
require 'asciidoctor/converter/composite'.to_s
end
template_converter = TemplateConverter.new backend, opts[:template_dirs], opts
# QUESTION should we omit the composite converter if built_in_converter is nil?
CompositeConverter.new backend, template_converter, base_converter
end
end
end
end