pluggability 0.0.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.
- data.tar.gz.sig +0 -0
- data/.gemtest +0 -0
- data/ChangeLog +690 -0
- data/History.rdoc +4 -0
- data/Manifest.txt +8 -0
- data/README.rdoc +237 -0
- data/Rakefile +31 -0
- data/lib/pluggability.rb +343 -0
- data/spec/lib/helpers.rb +31 -0
- data/spec/pluggability_spec.rb +198 -0
- metadata +212 -0
- metadata.gz.sig +1 -0
data/History.rdoc
ADDED
data/Manifest.txt
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,237 @@
|
|
1
|
+
= pluggability
|
2
|
+
|
3
|
+
* http://deveiate.org/projects/Pluggability
|
4
|
+
|
5
|
+
|
6
|
+
== Description
|
7
|
+
|
8
|
+
Pluggability is a mixin module that turns an including class into a factory for
|
9
|
+
its derivatives, capable of searching for and loading them by name. This is
|
10
|
+
useful when you have an abstract base class which defines an interface and basic
|
11
|
+
functionality for a part of a larger system, and a collection of subclasses
|
12
|
+
which implement the interface for different underlying functionality.
|
13
|
+
|
14
|
+
An example of where this might be useful is in a program which talks to a
|
15
|
+
database. To avoid coupling it to a specific database, you use a Driver class
|
16
|
+
which encapsulates your program's interaction with the database behind a useful
|
17
|
+
interface. Now you can create a concrete implementation of the Driver class for
|
18
|
+
each kind of database you wish to talk to. If you make the base Driver class a
|
19
|
+
Pluggability, too, you can add new drivers simply by dropping them in a
|
20
|
+
directory and using the Driver's `create` method to instantiate them:
|
21
|
+
|
22
|
+
=== Synopsis
|
23
|
+
|
24
|
+
in driver.rb:
|
25
|
+
|
26
|
+
require "Pluggability"
|
27
|
+
|
28
|
+
class Driver
|
29
|
+
include Pluggability
|
30
|
+
def self::derivative_dirs
|
31
|
+
["drivers"]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
in drivers/mysql.rb:
|
36
|
+
|
37
|
+
require 'driver'
|
38
|
+
|
39
|
+
class MysqlDriver < Driver
|
40
|
+
...implementation...
|
41
|
+
end
|
42
|
+
|
43
|
+
in /usr/lib/ruby/1.8/PostgresDriver.rb:
|
44
|
+
|
45
|
+
require 'driver'
|
46
|
+
|
47
|
+
class PostgresDriver < Driver
|
48
|
+
...implementation...
|
49
|
+
end
|
50
|
+
|
51
|
+
elsewhere
|
52
|
+
|
53
|
+
require 'driver'
|
54
|
+
|
55
|
+
config[:driver_type] #=> "mysql"
|
56
|
+
driver = Driver.create( config[:driver_type] )
|
57
|
+
driver.class #=> MysqlDriver
|
58
|
+
pgdriver = Driver.create( "PostGresDriver" )
|
59
|
+
|
60
|
+
=== How Plugins Are Loaded
|
61
|
+
|
62
|
+
The +create+ class method added to your class by Pluggability searches for your
|
63
|
+
module using several different strategies. It tries various permutations of the
|
64
|
+
base class's name in combination with the derivative requested. For example,
|
65
|
+
assume we want to make a +DataDriver+ base class, and then use plugins to define
|
66
|
+
drivers for different kinds of data sources:
|
67
|
+
|
68
|
+
require 'pluggability'
|
69
|
+
|
70
|
+
class DataDriver
|
71
|
+
include Pluggability
|
72
|
+
end
|
73
|
+
|
74
|
+
When you attempt to load the 'socket' data-driver class like so:
|
75
|
+
|
76
|
+
DataDriver.create( 'socket' )
|
77
|
+
|
78
|
+
Pluggability searches for modules with the following names:
|
79
|
+
|
80
|
+
'socketdatadriver'
|
81
|
+
'socket_datadriver'
|
82
|
+
'socketDataDriver'
|
83
|
+
'socket_DataDriver'
|
84
|
+
'SocketDataDriver'
|
85
|
+
'Socket_DataDriver'
|
86
|
+
'socket'
|
87
|
+
'Socket'
|
88
|
+
|
89
|
+
Obviously the last one will load something other than what is intended, so you
|
90
|
+
can also tell Pluggability that plugins should be loaded from a subdirectory by
|
91
|
+
declaring a class method called `derivative_dirs` in the base class. It should
|
92
|
+
return an Array that contains a list of subdirectories to try:
|
93
|
+
|
94
|
+
class DataDriver
|
95
|
+
include Pluggability
|
96
|
+
|
97
|
+
def self::derivative_dirs
|
98
|
+
['drivers']
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
This will change the list that is required to:
|
103
|
+
|
104
|
+
'drivers/socketdatadriver'
|
105
|
+
'drivers/socket_datadriver'
|
106
|
+
'drivers/socketDataDriver'
|
107
|
+
'drivers/socket_DataDriver'
|
108
|
+
'drivers/SocketDataDriver'
|
109
|
+
'drivers/Socket_DataDriver'
|
110
|
+
'drivers/socket'
|
111
|
+
'drivers/Socket'
|
112
|
+
|
113
|
+
If you return more than one subdirectory, each of them will be tried in turn:
|
114
|
+
|
115
|
+
class DataDriver
|
116
|
+
include Pluggability
|
117
|
+
|
118
|
+
def self::derivative_dirs
|
119
|
+
['drivers', 'datadriver']
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
will change the search to include:
|
124
|
+
|
125
|
+
'drivers/socketdatadriver'
|
126
|
+
'drivers/socket_datadriver'
|
127
|
+
'drivers/socketDataDriver'
|
128
|
+
'drivers/socket_DataDriver'
|
129
|
+
'drivers/SocketDataDriver'
|
130
|
+
'drivers/Socket_DataDriver'
|
131
|
+
'drivers/socket'
|
132
|
+
'drivers/Socket'
|
133
|
+
'datadriver/socketdatadriver'
|
134
|
+
'datadriver/socket_datadriver'
|
135
|
+
'datadriver/socketDataDriver'
|
136
|
+
'datadriver/socket_DataDriver'
|
137
|
+
'datadriver/SocketDataDriver'
|
138
|
+
'datadriver/Socket_DataDriver'
|
139
|
+
'datadriver/socket'
|
140
|
+
'datadriver/Socket'
|
141
|
+
|
142
|
+
If the plugin is not found, a FactoryError is raised, and the message will list
|
143
|
+
all the permutations that were tried.
|
144
|
+
|
145
|
+
=== Logging
|
146
|
+
|
147
|
+
If you need a little more insight into what's going on, Pluggability uses
|
148
|
+
'Logger' from the standard library. Just set its logger to your own to include
|
149
|
+
log messages about plugins being loaded:
|
150
|
+
|
151
|
+
|
152
|
+
require 'pluggability'
|
153
|
+
require 'logger'
|
154
|
+
|
155
|
+
class DataDriver
|
156
|
+
include Pluggability
|
157
|
+
|
158
|
+
end
|
159
|
+
|
160
|
+
$logger = Logger.new( $stderr )
|
161
|
+
$logger.level = Logger::DEBUG
|
162
|
+
Pluggability.logger = $logger
|
163
|
+
|
164
|
+
DataDriver.create( 'ringbuffer' )
|
165
|
+
|
166
|
+
this might generate a log that looks like:
|
167
|
+
|
168
|
+
D, [...] DEBUG -- : Loading derivative ringbuffer
|
169
|
+
D, [...] DEBUG -- : Subdirs are: [""]
|
170
|
+
D, [...] DEBUG -- : Path is: ["ringbufferdatadriver", "ringbufferDataDriver",
|
171
|
+
"ringbuffer"]...
|
172
|
+
D, [...] DEBUG -- : Trying ringbufferdatadriver...
|
173
|
+
D, [...] DEBUG -- : No module at 'ringbufferdatadriver', trying the next
|
174
|
+
alternative: 'no such file to load -- ringbufferdatadriver'
|
175
|
+
D, [...] DEBUG -- : Trying ringbufferDataDriver...
|
176
|
+
D, [...] DEBUG -- : No module at 'ringbufferDataDriver', trying the next
|
177
|
+
alternative: 'no such file to load -- ringbufferDataDriver'
|
178
|
+
D, [...] DEBUG -- : Trying ringbuffer...
|
179
|
+
D, [...] DEBUG -- : No module at 'ringbuffer', trying the next alternative:
|
180
|
+
'no such file to load -- ringbuffer'
|
181
|
+
D, [...] DEBUG -- : fatals = []
|
182
|
+
E, [...] ERROR -- : Couldn't find a DataDriver named 'ringbuffer':
|
183
|
+
tried ["ringbufferdatadriver", "ringbufferDataDriver", "ringbuffer"]
|
184
|
+
|
185
|
+
|
186
|
+
|
187
|
+
== Installation
|
188
|
+
|
189
|
+
gem install pluggability
|
190
|
+
|
191
|
+
|
192
|
+
== Contributing
|
193
|
+
|
194
|
+
You can check out the current development source with Mercurial via its
|
195
|
+
{Mercurial repo}[http://repo.deveiate.org/Pluggability]. Or if you prefer
|
196
|
+
Git, via {its Github mirror}[https://github.com/ged/pluggability].
|
197
|
+
|
198
|
+
After checking out the source, run:
|
199
|
+
|
200
|
+
$ rake newb
|
201
|
+
|
202
|
+
This task will install any missing dependencies, run the tests/specs,
|
203
|
+
and generate the API documentation.
|
204
|
+
|
205
|
+
|
206
|
+
== License
|
207
|
+
|
208
|
+
Copyright (c) 2008-2012, Michael Granger and Martin Chase
|
209
|
+
All rights reserved.
|
210
|
+
|
211
|
+
Redistribution and use in source and binary forms, with or without
|
212
|
+
modification, are permitted provided that the following conditions are met:
|
213
|
+
|
214
|
+
* Redistributions of source code must retain the above copyright notice,
|
215
|
+
this list of conditions and the following disclaimer.
|
216
|
+
|
217
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
218
|
+
this list of conditions and the following disclaimer in the documentation
|
219
|
+
and/or other materials provided with the distribution.
|
220
|
+
|
221
|
+
* Neither the name of the author/s, nor the names of the project's
|
222
|
+
contributors may be used to endorse or promote products derived from this
|
223
|
+
software without specific prior written permission.
|
224
|
+
|
225
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
226
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
227
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
228
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
229
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
230
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
231
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
232
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
233
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
234
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
235
|
+
|
236
|
+
|
237
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
|
3
|
+
require 'hoe'
|
4
|
+
|
5
|
+
Hoe.plugin :deveiate
|
6
|
+
Hoe.plugin :mercurial
|
7
|
+
Hoe.plugin :signing
|
8
|
+
|
9
|
+
Hoe.plugins.delete :rubyforge
|
10
|
+
|
11
|
+
hoespec = Hoe.spec 'pluggability' do
|
12
|
+
self.readme_file = 'README.rdoc'
|
13
|
+
self.history_file = 'History.rdoc'
|
14
|
+
self.extra_rdoc_files = Rake::FileList[ '*.rdoc' ]
|
15
|
+
self.spec_extras[:rdoc_options] = ['-f', 'fivefish', '-t', 'Pluggability Toolkit']
|
16
|
+
|
17
|
+
self.developer 'Martin Chase', 'stillflame@FaerieMUD.org'
|
18
|
+
self.developer 'Michael Granger', 'ged@FaerieMUD.org'
|
19
|
+
|
20
|
+
self.dependency 'loggability', '~> 0.5'
|
21
|
+
|
22
|
+
self.dependency 'hoe-deveiate', '~> 0.1', :development
|
23
|
+
|
24
|
+
self.spec_extras[:licenses] = ["BSD"]
|
25
|
+
self.rdoc_locations << "deveiate:/usr/local/www/public/code/#{remote_rdoc_dir}"
|
26
|
+
end
|
27
|
+
|
28
|
+
ENV['VERSION'] ||= hoespec.spec.version.to_s
|
29
|
+
|
30
|
+
task 'hg:precheckin' => [ :check_history, :check_manifest, :spec ]
|
31
|
+
|
data/lib/pluggability.rb
ADDED
@@ -0,0 +1,343 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
require 'loggability' unless defined?( Loggability )
|
4
|
+
|
5
|
+
|
6
|
+
# The Pluggability module
|
7
|
+
module Pluggability
|
8
|
+
extend Loggability
|
9
|
+
|
10
|
+
# Loggability API -- Set up a logger.
|
11
|
+
log_as :pluggability
|
12
|
+
|
13
|
+
|
14
|
+
# Library version
|
15
|
+
VERSION = '0.0.1'
|
16
|
+
|
17
|
+
|
18
|
+
### An exception class for Pluggability specific errors.
|
19
|
+
class FactoryError < RuntimeError; end
|
20
|
+
|
21
|
+
|
22
|
+
### Add the @derivatives instance variable to including classes.
|
23
|
+
def self::extend_object( obj )
|
24
|
+
obj.instance_variable_set( :@plugin_prefixes, [] )
|
25
|
+
obj.instance_variable_set( :@derivatives, {} )
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
#############################################################
|
31
|
+
### M I X I N M E T H O D S
|
32
|
+
#############################################################
|
33
|
+
|
34
|
+
### Get/set the prefixes that will be used when searching for particular plugins
|
35
|
+
### for the calling Class.
|
36
|
+
def plugin_prefixes( *args )
|
37
|
+
@plugin_prefixes.replace( args ) if !args.empty?
|
38
|
+
return @plugin_prefixes
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
### Set the prefixes that will be used when searching for particular plugins
|
43
|
+
### for the calling Class.
|
44
|
+
def plugin_prefixes=( args )
|
45
|
+
@plugin_prefixes = Array( args )
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
### Return the Hash of derivative classes, keyed by various versions of
|
50
|
+
### the class name.
|
51
|
+
def derivatives
|
52
|
+
ancestors.each do |klass|
|
53
|
+
if klass.instance_variables.include?( :@derivatives ) ||
|
54
|
+
klass.instance_variables.include?( "@derivatives" )
|
55
|
+
return klass.instance_variable_get( :@derivatives )
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
### Returns the type name used when searching for a derivative.
|
62
|
+
def factory_type
|
63
|
+
base = nil
|
64
|
+
self.ancestors.each do |klass|
|
65
|
+
if klass.instance_variables.include?( :@derivatives ) ||
|
66
|
+
klass.instance_variables.include?( "@derivatives" )
|
67
|
+
base = klass
|
68
|
+
break
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
raise FactoryError, "Couldn't find factory base for #{self.name}" if
|
73
|
+
base.nil?
|
74
|
+
|
75
|
+
if base.name =~ /^.*::(.*)/
|
76
|
+
return $1
|
77
|
+
else
|
78
|
+
return base.name
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
### Inheritance callback -- Register subclasses in the derivatives hash
|
84
|
+
### so that ::create knows about them.
|
85
|
+
def inherited( subclass )
|
86
|
+
keys = [ subclass ]
|
87
|
+
|
88
|
+
# If it's not an anonymous class, make some keys out of variants of its name
|
89
|
+
if subclass.name
|
90
|
+
simple_name = subclass.name.sub( /#<Class:0x[[:xdigit:]]+>::/i, '' )
|
91
|
+
keys << simple_name << simple_name.downcase
|
92
|
+
|
93
|
+
# Handle class names like 'FooBar' for 'Bar' factories.
|
94
|
+
Pluggability.log.debug "Inherited %p for %p-type plugins" % [ subclass, self.factory_type ]
|
95
|
+
if subclass.name.match( /(?:.*::)?(\w+)(?:#{self.factory_type})/i )
|
96
|
+
keys << Regexp.last_match[1].downcase
|
97
|
+
else
|
98
|
+
keys << subclass.name.sub( /.*::/, '' ).downcase
|
99
|
+
end
|
100
|
+
else
|
101
|
+
Pluggability.log.debug " no name-based variants for anonymous subclass %p" % [ subclass ]
|
102
|
+
end
|
103
|
+
|
104
|
+
keys.compact.uniq.each do |key|
|
105
|
+
Pluggability.log.info "Registering %s derivative of %s as %p" %
|
106
|
+
[ subclass.name, self.name, key ]
|
107
|
+
self.derivatives[ key ] = subclass
|
108
|
+
end
|
109
|
+
|
110
|
+
super
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
### Returns an Array of registered derivatives
|
115
|
+
def derivative_classes
|
116
|
+
self.derivatives.values.uniq
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
### Given the <tt>class_name</tt> of the class to instantiate, and other
|
121
|
+
### arguments bound for the constructor of the new object, this method
|
122
|
+
### loads the derivative class if it is not loaded already (raising a
|
123
|
+
### LoadError if an appropriately-named file cannot be found), and
|
124
|
+
### instantiates it with the given <tt>args</tt>. The <tt>class_name</tt>
|
125
|
+
### may be the the fully qualified name of the class, the class object
|
126
|
+
### itself, or the unique part of the class name. The following examples
|
127
|
+
### would all try to load and instantiate a class called "FooListener"
|
128
|
+
### if Listener included Factory
|
129
|
+
### obj = Listener.create( 'FooListener' )
|
130
|
+
### obj = Listener.create( FooListener )
|
131
|
+
### obj = Listener.create( 'Foo' )
|
132
|
+
def create( class_name, *args, &block )
|
133
|
+
subclass = get_subclass( class_name )
|
134
|
+
|
135
|
+
begin
|
136
|
+
return subclass.new( *args, &block )
|
137
|
+
rescue => err
|
138
|
+
nicetrace = err.backtrace.reject {|frame| /#{__FILE__}/ =~ frame}
|
139
|
+
msg = "When creating '#{class_name}': " + err.message
|
140
|
+
Kernel.raise( err, msg, nicetrace )
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
### Given a <tt>class_name</tt> like that of the first argument to
|
146
|
+
### #create, attempt to load the corresponding class if it is not
|
147
|
+
### already loaded and return the class object.
|
148
|
+
def get_subclass( class_name )
|
149
|
+
return self if ( self.name == class_name || class_name == '' )
|
150
|
+
if class_name.is_a?( Class )
|
151
|
+
return class_name if class_name <= self
|
152
|
+
raise ArgumentError, "%s is not a descendent of %s" % [class_name, self]
|
153
|
+
end
|
154
|
+
|
155
|
+
class_name = class_name.to_s
|
156
|
+
|
157
|
+
# If the derivatives hash doesn't already contain the class, try to load it
|
158
|
+
unless self.derivatives.has_key?( class_name.downcase )
|
159
|
+
self.load_derivative( class_name )
|
160
|
+
|
161
|
+
subclass = self.derivatives[ class_name.downcase ]
|
162
|
+
unless subclass.is_a?( Class )
|
163
|
+
raise FactoryError,
|
164
|
+
"load_derivative(%s) added something other than a class "\
|
165
|
+
"to the registry for %s: %p" %
|
166
|
+
[ class_name, self.name, subclass ]
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
return self.derivatives[ class_name.downcase ]
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
### Find and load all derivatives of this class, using plugin_prefixes if any
|
175
|
+
### are defined, or a pattern derived from the #factory_type if not. Returns
|
176
|
+
### an array of all derivative classes. Load failures are logged but otherwise
|
177
|
+
### ignored.
|
178
|
+
def load_all
|
179
|
+
patterns = []
|
180
|
+
prefixes = self.plugin_prefixes
|
181
|
+
|
182
|
+
if prefixes && !prefixes.empty?
|
183
|
+
Pluggability.log.debug "Using plugin prefixes (%p) to build load patterns." % [ prefixes ]
|
184
|
+
prefixes.each do |prefix|
|
185
|
+
patterns << "#{prefix}/*.rb"
|
186
|
+
end
|
187
|
+
else
|
188
|
+
# Use all but the last pattern, which will just be '*.rb'
|
189
|
+
Pluggability.log.debug "Using factory type (%p) to build load patterns." %
|
190
|
+
[ self.factory_type ]
|
191
|
+
patterns += self.make_require_path( '*', '' )[0..-2].
|
192
|
+
map {|f| f + '.rb' }
|
193
|
+
end
|
194
|
+
|
195
|
+
patterns.each do |glob|
|
196
|
+
Pluggability.log.debug " finding derivatives matching pattern %p" % [ glob ]
|
197
|
+
candidates = Gem.find_files( glob )
|
198
|
+
Pluggability.log.debug " found %d matching files" % [ candidates.length ]
|
199
|
+
next if candidates.empty?
|
200
|
+
|
201
|
+
candidates.each {|path| require(path) }
|
202
|
+
end
|
203
|
+
|
204
|
+
return self.derivative_classes
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
### Calculates an appropriate filename for the derived class using the
|
209
|
+
### name of the base class and tries to load it via <tt>require</tt>. If
|
210
|
+
### the including class responds to a method named
|
211
|
+
### <tt>derivativeDirs</tt>, its return value (either a String, or an
|
212
|
+
### array of Strings) is added to the list of prefix directories to try
|
213
|
+
### when attempting to require a modules. Eg., if
|
214
|
+
### <tt>class.derivativeDirs</tt> returns <tt>['foo','bar']</tt> the
|
215
|
+
### require line is tried with both <tt>'foo/'</tt> and <tt>'bar/'</tt>
|
216
|
+
### prepended to it.
|
217
|
+
def load_derivative( class_name )
|
218
|
+
Pluggability.log.debug "Loading derivative #{class_name}"
|
219
|
+
|
220
|
+
# Get the unique part of the derived class name and try to
|
221
|
+
# load it from one of the derivative subdirs, if there are
|
222
|
+
# any.
|
223
|
+
mod_name = self.get_module_name( class_name )
|
224
|
+
result = self.require_derivative( mod_name )
|
225
|
+
|
226
|
+
# Check to see if the specified listener is now loaded. If it
|
227
|
+
# is not, raise an error to that effect.
|
228
|
+
unless self.derivatives[ class_name.downcase ]
|
229
|
+
errmsg = "Require of '%s' succeeded, but didn't load a %s named '%s' for some reason." % [
|
230
|
+
result,
|
231
|
+
self.factory_type,
|
232
|
+
class_name.downcase,
|
233
|
+
]
|
234
|
+
Pluggability.log.error( errmsg )
|
235
|
+
raise FactoryError, errmsg, caller(3)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
|
240
|
+
### Build and return the unique part of the given <tt>class_name</tt>
|
241
|
+
### either by stripping leading namespaces if the name already has the
|
242
|
+
### name of the factory type in it (eg., 'My::FooService' for Service,
|
243
|
+
### or by appending the factory type if it doesn't.
|
244
|
+
def get_module_name( class_name )
|
245
|
+
if class_name =~ /\w+#{self.factory_type}/
|
246
|
+
mod_name = class_name.sub( /(?:.*::)?(\w+)(?:#{self.factory_type})/, "\\1" )
|
247
|
+
else
|
248
|
+
mod_name = class_name
|
249
|
+
end
|
250
|
+
|
251
|
+
return mod_name
|
252
|
+
end
|
253
|
+
|
254
|
+
|
255
|
+
### Search for the module with the specified <tt>mod_name</tt>, using any
|
256
|
+
### #plugin_prefixes that have been set.
|
257
|
+
def require_derivative( mod_name )
|
258
|
+
|
259
|
+
subdirs = self.plugin_prefixes
|
260
|
+
subdirs << '' if subdirs.empty?
|
261
|
+
Pluggability.log.debug "Subdirs are: %p" % [subdirs]
|
262
|
+
fatals = []
|
263
|
+
tries = []
|
264
|
+
|
265
|
+
# Iterate over the subdirs until we successfully require a
|
266
|
+
# module.
|
267
|
+
subdirs.map( &:strip ).each do |subdir|
|
268
|
+
self.make_require_path( mod_name, subdir ).each do |path|
|
269
|
+
Pluggability.logger.debug "Trying #{path}..."
|
270
|
+
tries << path
|
271
|
+
|
272
|
+
# Try to require the module, saving errors and jumping
|
273
|
+
# out of the catch block on success.
|
274
|
+
begin
|
275
|
+
require( path.untaint )
|
276
|
+
rescue LoadError => err
|
277
|
+
Pluggability.log.debug "No module at '%s', trying the next alternative: '%s'" %
|
278
|
+
[ path, err.message ]
|
279
|
+
rescue Exception => err
|
280
|
+
fatals << err
|
281
|
+
Pluggability.log.error "Found '#{path}', but encountered an error: %s\n\t%s" %
|
282
|
+
[ err.message, err.backtrace.join("\n\t") ]
|
283
|
+
else
|
284
|
+
Pluggability.log.info "Loaded '#{path}' without error."
|
285
|
+
return path
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
Pluggability.logger.debug "fatals = %p" % [ fatals ]
|
291
|
+
|
292
|
+
# Re-raise is there was a file found, but it didn't load for
|
293
|
+
# some reason.
|
294
|
+
if fatals.empty?
|
295
|
+
errmsg = "Couldn't find a %s named '%s': tried %p" % [
|
296
|
+
self.factory_type,
|
297
|
+
mod_name,
|
298
|
+
tries
|
299
|
+
]
|
300
|
+
Pluggability.log.error( errmsg )
|
301
|
+
raise FactoryError, errmsg
|
302
|
+
else
|
303
|
+
Pluggability.log.debug "Re-raising first fatal error"
|
304
|
+
Kernel.raise( fatals.first )
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
|
309
|
+
### Make a list of permutations of the given +modname+ for the given
|
310
|
+
### +subdir+. Called on a +DataDriver+ class with the arguments 'Socket' and
|
311
|
+
### 'drivers', returns:
|
312
|
+
### ["drivers/socketdatadriver", "drivers/socketDataDriver",
|
313
|
+
### "drivers/SocketDataDriver", "drivers/socket", "drivers/Socket"]
|
314
|
+
def make_require_path( modname, subdir )
|
315
|
+
path = []
|
316
|
+
myname = self.factory_type
|
317
|
+
|
318
|
+
# Make permutations of the two parts
|
319
|
+
path << modname
|
320
|
+
path << modname.downcase
|
321
|
+
path << modname + myname
|
322
|
+
path << modname.downcase + myname
|
323
|
+
path << modname.downcase + myname.downcase
|
324
|
+
path << modname + '_' + myname
|
325
|
+
path << modname.downcase + '_' + myname
|
326
|
+
path << modname.downcase + '_' + myname.downcase
|
327
|
+
|
328
|
+
# If a non-empty subdir was given, prepend it to all the items in the
|
329
|
+
# path
|
330
|
+
unless subdir.nil? or subdir.empty?
|
331
|
+
path.collect! {|m| File.join(subdir, m)}
|
332
|
+
end
|
333
|
+
|
334
|
+
Pluggability.log.debug "Path is: #{path.uniq.reverse.inspect}..."
|
335
|
+
return path.uniq.reverse
|
336
|
+
end
|
337
|
+
|
338
|
+
end # module Pluggability
|
339
|
+
|
340
|
+
|
341
|
+
# Backward-compatibility alias
|
342
|
+
FactoryError = Pluggability::FactoryError unless defined?( FactoryError )
|
343
|
+
|