pluggability 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|