pluginfactory 1.0.2

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.
@@ -0,0 +1,131 @@
1
+
2
+
3
+
4
+
5
+ The "Artistic License"
6
+
7
+ Preamble
8
+
9
+ The intent of this document is to state the conditions under which a
10
+ Package may be copied, such that the Copyright Holder maintains some
11
+ semblance of artistic control over the development of the package,
12
+ while giving the users of the package the right to use and distribute
13
+ the Package in a more-or-less customary fashion, plus the right to make
14
+ reasonable modifications.
15
+
16
+ Definitions:
17
+
18
+ "Package" refers to the collection of files distributed by the
19
+ Copyright Holder, and derivatives of that collection of files
20
+ created through textual modification.
21
+
22
+ "Standard Version" refers to such a Package if it has not been
23
+ modified, or has been modified in accordance with the wishes
24
+ of the Copyright Holder as specified below.
25
+
26
+ "Copyright Holder" is whoever is named in the copyright or
27
+ copyrights for the package.
28
+
29
+ "You" is you, if you're thinking about copying or distributing
30
+ this Package.
31
+
32
+ "Reasonable copying fee" is whatever you can justify on the
33
+ basis of media cost, duplication charges, time of people involved,
34
+ and so on. (You will not be required to justify it to the
35
+ Copyright Holder, but only to the computing community at large
36
+ as a market that must bear the fee.)
37
+
38
+ "Freely Available" means that no fee is charged for the item
39
+ itself, though there may be fees involved in handling the item.
40
+ It also means that recipients of the item may redistribute it
41
+ under the same conditions they received it.
42
+
43
+ 1. You may make and give away verbatim copies of the source form of the
44
+ Standard Version of this Package without restriction, provided that you
45
+ duplicate all of the original copyright notices and associated disclaimers.
46
+
47
+ 2. You may apply bug fixes, portability fixes and other modifications
48
+ derived from the Public Domain or from the Copyright Holder. A Package
49
+ modified in such a way shall still be considered the Standard Version.
50
+
51
+ 3. You may otherwise modify your copy of this Package in any way, provided
52
+ that you insert a prominent notice in each changed file stating how and
53
+ when you changed that file, and provided that you do at least ONE of the
54
+ following:
55
+
56
+ a) place your modifications in the Public Domain or otherwise make them
57
+ Freely Available, such as by posting said modifications to Usenet or
58
+ an equivalent medium, or placing the modifications on a major archive
59
+ site such as uunet.uu.net, or by allowing the Copyright Holder to include
60
+ your modifications in the Standard Version of the Package.
61
+
62
+ b) use the modified Package only within your corporation or organization.
63
+
64
+ c) rename any non-standard executables so the names do not conflict
65
+ with standard executables, which must also be provided, and provide
66
+ a separate manual page for each non-standard executable that clearly
67
+ documents how it differs from the Standard Version.
68
+
69
+ d) make other distribution arrangements with the Copyright Holder.
70
+
71
+ 4. You may distribute the programs of this Package in object code or
72
+ executable form, provided that you do at least ONE of the following:
73
+
74
+ a) distribute a Standard Version of the executables and library files,
75
+ together with instructions (in the manual page or equivalent) on where
76
+ to get the Standard Version.
77
+
78
+ b) accompany the distribution with the machine-readable source of
79
+ the Package with your modifications.
80
+
81
+ c) give non-standard executables non-standard names, and clearly
82
+ document the differences in manual pages (or equivalent), together
83
+ with instructions on where to get the Standard Version.
84
+
85
+ d) make other distribution arrangements with the Copyright Holder.
86
+
87
+ 5. You may charge a reasonable copying fee for any distribution of this
88
+ Package. You may charge any fee you choose for support of this
89
+ Package. You may not charge a fee for this Package itself. However,
90
+ you may distribute this Package in aggregate with other (possibly
91
+ commercial) programs as part of a larger (possibly commercial) software
92
+ distribution provided that you do not advertise this Package as a
93
+ product of your own. You may embed this Package's interpreter within
94
+ an executable of yours (by linking); this shall be construed as a mere
95
+ form of aggregation, provided that the complete Standard Version of the
96
+ interpreter is so embedded.
97
+
98
+ 6. The scripts and library files supplied as input to or produced as
99
+ output from the programs of this Package do not automatically fall
100
+ under the copyright of this Package, but belong to whoever generated
101
+ them, and may be sold commercially, and may be aggregated with this
102
+ Package. If such scripts or library files are aggregated with this
103
+ Package via the so-called "undump" or "unexec" methods of producing a
104
+ binary executable image, then distribution of such an image shall
105
+ neither be construed as a distribution of this Package nor shall it
106
+ fall under the restrictions of Paragraphs 3 and 4, provided that you do
107
+ not represent such an executable image as a Standard Version of this
108
+ Package.
109
+
110
+ 7. C subroutines (or comparably compiled subroutines in other
111
+ languages) supplied by you and linked into this Package in order to
112
+ emulate subroutines and variables of the language defined by this
113
+ Package shall not be considered part of this Package, but are the
114
+ equivalent of input as in Paragraph 6, provided these subroutines do
115
+ not change the language in any way that would cause it to fail the
116
+ regression tests for the language.
117
+
118
+ 8. Aggregation of this Package with a commercial distribution is always
119
+ permitted provided that the use of this Package is embedded; that is,
120
+ when no overt attempt is made to make this Package's interfaces visible
121
+ to the end user of the commercial distribution. Such use shall not be
122
+ construed as a distribution of this Package.
123
+
124
+ 9. The name of the Copyright Holder may not be used to endorse or promote
125
+ products derived from this software without specific prior written permission.
126
+
127
+ 10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
128
+ IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
129
+ WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
130
+
131
+ The End
@@ -0,0 +1,102 @@
1
+ 2004-03-09 22:32 Michael Granger <ged@FaerieMUD.org>
2
+
3
+ * makedist.rb (1.1), utils.rb (1.1), docs/.cvsignore (1.1),
4
+ docs/CATALOG (1.1), docs/makedocs.rb (1.1):
5
+
6
+ Initial checkin.
7
+
8
+ 2004-03-08 00:07 Michael Granger <ged@FaerieMUD.org>
9
+
10
+ * tests/: othersub.rb (1.2), dir/deepsubofmybase.rb (1.2):
11
+
12
+ - Fixed class/file agreement.
13
+
14
+ 2004-03-08 00:06 Michael Granger <ged@FaerieMUD.org>
15
+
16
+ * tests/mybase.rb (1.2):
17
+
18
+ - Fixed derivativeDirs method.
19
+
20
+ 2004-03-08 00:05 Michael Granger <ged@FaerieMUD.org>
21
+
22
+ * lib/pluginfactory.rb (1.4):
23
+
24
+ - Documentation additions
25
+
26
+ - Refactored path-search code
27
+
28
+ 2004-03-07 21:30 Michael Granger <ged@FaerieMUD.org>
29
+
30
+ * test.rb (1.3):
31
+
32
+ - Added a hook into -d that turns on logging.
33
+
34
+ 2004-03-07 19:13 Martin Chase <stillflame@FaerieMUD.org>
35
+
36
+ * README (1.4):
37
+
38
+ no, this is not using the default install.rb installation process
39
+
40
+ 2004-03-07 19:07 Martin Chase <stillflame@FaerieMUD.org>
41
+
42
+ * lib/pluginfactory.rb (1.3):
43
+
44
+ FactoryError now inherits from RuntimeError
45
+
46
+ 2004-03-07 19:06 Martin Chase <stillflame@FaerieMUD.org>
47
+
48
+ * test.rb (1.2):
49
+
50
+ filling in
51
+
52
+ 2004-03-07 19:05 Martin Chase <stillflame@FaerieMUD.org>
53
+
54
+ * tests/: mybase.rb (1.1), othersub.rb (1.1), subofmybase.rb (1.1),
55
+ dir/deepsubofmybase.rb (1.1):
56
+
57
+ initial import
58
+
59
+ 2004-03-07 15:42 Martin Chase <stillflame@FaerieMUD.org>
60
+
61
+ * README (1.3):
62
+
63
+ reformatted
64
+
65
+ 2004-03-07 15:41 Martin Chase <stillflame@FaerieMUD.org>
66
+
67
+ * install.rb (1.2):
68
+
69
+ filled in
70
+
71
+ 2004-03-07 14:36 Martin Chase <stillflame@FaerieMUD.org>
72
+
73
+ * COPYRIGHT (1.2), README (1.2):
74
+
75
+ filled in
76
+
77
+ 2004-03-07 10:49 Martin Chase <stillflame@FaerieMUD.org>
78
+
79
+ * lib/pluginfactory.rb (1.2):
80
+
81
+ cleaned out ties to other modules
82
+
83
+ 2004-03-07 09:33 Martin Chase <stillflame@FaerieMUD.org>
84
+
85
+ * lib/: factory.rb (1.2), pluginfactory.rb (1.1):
86
+
87
+ renaming
88
+
89
+ 2004-03-07 09:31 Martin Chase <stillflame@FaerieMUD.org>
90
+
91
+ * COPYRIGHT (1.1), README (1.1), install.rb (1.1), test.rb (1.1),
92
+ lib/factory.rb (1.1):
93
+
94
+ Initial revision
95
+
96
+ 2004-03-07 09:31 Martin Chase <stillflame@FaerieMUD.org>
97
+
98
+ * COPYRIGHT (1.1.1.1), README (1.1.1.1), install.rb (1.1.1.1),
99
+ test.rb (1.1.1.1), lib/factory.rb (1.1.1.1) (utags: release):
100
+
101
+ initial stripping out of Arrow/MUES
102
+
data/README ADDED
@@ -0,0 +1,39 @@
1
+ = The PluginFactory mixin
2
+
3
+ A mixin implementing a factory pattern, modified to load in subclasses on
4
+ request.
5
+
6
+ == Install
7
+
8
+ $ sudo ./install.rb
9
+
10
+ == Tests
11
+
12
+ $ ./test.rb
13
+
14
+ == Authors
15
+
16
+ Michael Granger <ged@FaerieMUD.org>
17
+
18
+ == Contributors
19
+
20
+ Martin Chase <stillflame@FaerieMUD.org>
21
+
22
+ == License/Legal
23
+
24
+ All assets of the project are copyright (c) 2003-2004, The FaerieMUD
25
+ Consortium. All rights reserved.
26
+
27
+ You may use, modify, and/or redistribute this software under the terms of the
28
+ Perl Artistic License, a copy of which should have been included in this
29
+ distribution (See the file COPYRIGHT). If it was not, a copy of it may be
30
+ obtained from http://language.perl.com/misc/Artistic.html or
31
+ http://www.faeriemud.org/artistic.html).
32
+
33
+ THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES,
34
+ INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND
35
+ FITNESS FOR A PARTICULAR PURPOSE.
36
+
37
+ == Rcsid
38
+
39
+ $Id: README 14 2004-03-08 03:13:38Z stillflame $
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # Module Install Script
4
+ # $Id: install.rb 31 2004-11-21 17:14:13Z ged $
5
+ #
6
+ # Thanks to Masatoshi SEKI for ideas found in his install.rb.
7
+ #
8
+ # Copyright (c) 2001-2004 The FaerieMUD Consortium.
9
+ #
10
+ # This is free software. You may use, modify, and/or redistribute this
11
+ # software under the terms of the Perl Artistic License. (See
12
+ # http://language.perl.com/misc/Artistic.html)
13
+ #
14
+
15
+ require './utils.rb'
16
+ include UtilityFunctions
17
+
18
+ require 'rbconfig'
19
+ include Config
20
+
21
+ require 'find'
22
+ require 'ftools'
23
+ require 'optparse'
24
+
25
+ $version = %q$Revision: 31 $
26
+ $rcsId = %q$Id: install.rb 31 2004-11-21 17:14:13Z ged $
27
+
28
+ # Define required libraries
29
+ RequiredLibraries = [
30
+ # libraryname, nice name, RAA URL, Download URL, e.g.,
31
+ #[ 'strscan', "Strscan",
32
+ # 'http://www.ruby-lang.org/en/raa-list.rhtml?name=strscan',
33
+ # 'http://i.loveruby.net/archive/strscan/strscan-0.6.7.tar.gz',
34
+ #],
35
+ ]
36
+
37
+ class Installer
38
+
39
+ @@PrunePatterns = [
40
+ /CVS/,
41
+ /~$/,
42
+ %r:(^|/)\.:,
43
+ /\.tpl$/,
44
+ ]
45
+
46
+ def initialize( testing=false )
47
+ @ftools = (testing) ? self : File
48
+ end
49
+
50
+ ### Make the specified dirs (which can be a String or an Array of Strings)
51
+ ### with the specified mode.
52
+ def makedirs( dirs, mode=0755, verbose=false )
53
+ dirs = [ dirs ] unless dirs.is_a? Array
54
+
55
+ oldumask = File::umask
56
+ File::umask( 0777 - mode )
57
+
58
+ for dir in dirs
59
+ if @ftools == File
60
+ File::mkpath( dir, $verbose )
61
+ else
62
+ $stderr.puts "Make path %s with mode %o" % [ dir, mode ]
63
+ end
64
+ end
65
+
66
+ File::umask( oldumask )
67
+ end
68
+
69
+ def install( srcfile, dstfile, mode=nil, verbose=false )
70
+ dstfile = File.catname(srcfile, dstfile)
71
+ unless FileTest.exist? dstfile and File.cmp srcfile, dstfile
72
+ $stderr.puts " install #{srcfile} -> #{dstfile}"
73
+ else
74
+ $stderr.puts " skipping #{dstfile}: unchanged"
75
+ end
76
+ end
77
+
78
+ public
79
+
80
+ def installFiles( src, dstDir, mode=0444, verbose=false )
81
+ directories = []
82
+ files = []
83
+
84
+ if File.directory?( src )
85
+ Find.find( src ) {|f|
86
+ Find.prune if @@PrunePatterns.find {|pat| f =~ pat}
87
+ next if f == src
88
+
89
+ if FileTest.directory?( f )
90
+ directories << f.gsub( /^#{src}#{File::Separator}/, '' )
91
+ next
92
+
93
+ elsif FileTest.file?( f )
94
+ files << f.gsub( /^#{src}#{File::Separator}/, '' )
95
+
96
+ else
97
+ Find.prune
98
+ end
99
+ }
100
+ else
101
+ files << File.basename( src )
102
+ src = File.dirname( src )
103
+ end
104
+
105
+ dirs = [ dstDir ]
106
+ dirs |= directories.collect {|d| File.join(dstDir,d)}
107
+ makedirs( dirs, 0755, verbose )
108
+ files.each {|f|
109
+ srcfile = File.join(src,f)
110
+ dstfile = File.dirname(File.join( dstDir,f ))
111
+
112
+ if verbose
113
+ if mode
114
+ $stderr.puts "Install #{srcfile} -> #{dstfile} (mode %o)" % mode
115
+ else
116
+ $stderr.puts "Install #{srcfile} -> #{dstfile}"
117
+ end
118
+ end
119
+
120
+ @ftools.install( srcfile, dstfile, mode, verbose )
121
+ }
122
+ end
123
+
124
+ end
125
+
126
+
127
+ if $0 == __FILE__
128
+ for lib in RequiredLibraries
129
+ testForRequiredLibrary( *lib )
130
+ end
131
+
132
+ dryrun = false
133
+
134
+ # Parse command-line switches
135
+ ARGV.options {|oparser|
136
+ oparser.banner = "Usage: #$0 [options]\n"
137
+
138
+ oparser.on( "--verbose", "-v", TrueClass, "Make progress verbose" ) {
139
+ $VERBOSE = true
140
+ debugMsg "Turned verbose on."
141
+ }
142
+
143
+ oparser.on( "--dry-run", "-n", TrueClass, "Don't really install anything" ) {
144
+ debugMsg "Turned dry-run on."
145
+ dryrun = true
146
+ }
147
+
148
+ # Handle the 'help' option
149
+ oparser.on( "--help", "-h", "Display this text." ) {
150
+ $stderr.puts oparser
151
+ exit!(0)
152
+ }
153
+
154
+ oparser.parse!
155
+ }
156
+
157
+ debugMsg "Sitelibdir = '#{CONFIG['sitelibdir']}'"
158
+ sitelibdir = CONFIG['sitelibdir']
159
+ debugMsg "Sitearchdir = '#{CONFIG['sitearchdir']}'"
160
+ sitearchdir = CONFIG['sitearchdir']
161
+
162
+ message "Installing..."
163
+ i = Installer.new( dryrun )
164
+ #i.installFiles( "redist", sitelibdir, 0444, verbose )
165
+ i.installFiles( "lib", sitelibdir, 0444, $VERBOSE )
166
+
167
+ message "done.\n"
168
+ end
169
+
170
+
171
+
172
+
@@ -0,0 +1,389 @@
1
+ #!/usr/bin/env ruby -w
2
+ #
3
+ # This module contains the PluginFactory mixin. Including PluginFactory in your
4
+ # class turns it into a factory for its derivatives, capable of searching for
5
+ # and loading them by name. This is useful when you have an abstract base class
6
+ # which defines an interface and basic functionality for a part of a larger
7
+ # system, and a collection of subclasses which implement the interface for
8
+ # different underlying functionality.
9
+ #
10
+ # An example of where this might be useful is in a program which talks to a
11
+ # database. To avoid coupling it to a specific database, you use a Driver class
12
+ # which encapsulates your program's interaction with the database behind a
13
+ # useful interface. Now you can create a concrete implementation of the Driver
14
+ # class for each kind of database you wish to talk to. If you make the base
15
+ # Driver class a PluginFactory, too, you can add new drivers simply by dropping
16
+ # them in a directory and using the Driver's <tt>create</tt> method to
17
+ # instantiate them:
18
+ #
19
+ # == Creation Argument Variants
20
+ #
21
+ # The +create+ class method added to your class by PluginFactory searches for your module using
22
+ #
23
+ # == Synopsis
24
+ #
25
+ # in driver.rb:
26
+ #
27
+ # require "PluginFactory"
28
+ #
29
+ # class Driver
30
+ # include PluginFactory
31
+ # def self::derivative_dirs
32
+ # ["drivers"]
33
+ # end
34
+ # end
35
+ #
36
+ # in drivers/mysql.rb:
37
+ #
38
+ # require 'driver'
39
+ #
40
+ # class MysqlDriver < Driver
41
+ # ...implementation...
42
+ # end
43
+ #
44
+ # in /usr/lib/ruby/1.8/PostgresDriver.rb:
45
+ #
46
+ # require 'driver'
47
+ #
48
+ # class PostgresDriver < Driver
49
+ # ...implementation...
50
+ # end
51
+ #
52
+ # elsewhere
53
+ #
54
+ # require 'driver'
55
+ #
56
+ # config[:driver_type] #=> "mysql"
57
+ # driver = Driver.create( config[:driver_type] )
58
+ # driver.class #=> MysqlDriver
59
+ # pgdriver = Driver.create( "PostGresDriver" )
60
+ #
61
+ # == Subversion ID
62
+ #
63
+ # $Id: pluginfactory.rb 38 2007-03-13 18:20:58Z deveiant $
64
+ #
65
+ # == Authors
66
+ #
67
+ # * Martin Chase <stillflame@FaerieMUD.org>
68
+ # * Michael Granger <ged@FaerieMUD.org>
69
+ #
70
+ #:include: COPYRIGHT
71
+ #
72
+ #---
73
+ #
74
+ # Please see the file docs/COPYRIGHT for licensing details.
75
+ #
76
+
77
+
78
+ ### An exception class for PluginFactory specific errors.
79
+ class FactoryError < RuntimeError
80
+ def initialize( *args )
81
+ if ! args.empty?
82
+ msg = args.collect {|a| a.to_s}.join
83
+ super( msg )
84
+ else
85
+ super( message )
86
+ end
87
+ end
88
+ end # class FactoryError
89
+
90
+
91
+ ### A mixin that adds PluginFactory class methods to a base class, so that
92
+ ### subclasses may be instantiated by name.
93
+ module PluginFactory
94
+
95
+ ### A callback for logging the various debug and information this module
96
+ ### has to log. Should take two arguments, the log level, possibly as a
97
+ ### symbol, and the log message itself.
98
+ @logger_callback = nil
99
+ class << self
100
+ attr_accessor :logger_callback
101
+ end
102
+
103
+ ### If the logger callback is set, use it to pass on a log entry. First argument is
104
+ def self::log(level, *msg)
105
+ @logger_callback.call(level, msg.join) if @logger_callback
106
+ end
107
+
108
+
109
+ ### Inclusion callback -- extends the including class.
110
+ def self::included( klass )
111
+ klass.extend( self )
112
+ end
113
+
114
+
115
+ ### Raise an exception if the object being extended is anything but a
116
+ ### class.
117
+ def self::extend_object( obj )
118
+ unless obj.is_a?( Class )
119
+ raise TypeError, "Cannot extend a #{obj.class.name}", caller(1)
120
+ end
121
+ obj.instance_variable_set( :@derivatives, {} )
122
+ super
123
+ end
124
+
125
+
126
+ #############################################################
127
+ ### M I X I N M E T H O D S
128
+ #############################################################
129
+
130
+ ### Return the Hash of derivative classes, keyed by various versions of
131
+ ### the class name.
132
+ def derivatives
133
+ ancestors.each {|klass|
134
+ if klass.instance_variables.include?( "@derivatives" )
135
+ break klass.instance_variable_get( :@derivatives )
136
+ end
137
+ }
138
+ end
139
+
140
+
141
+ ### Returns the type name used when searching for a derivative.
142
+ def factory_type
143
+ base = nil
144
+ self.ancestors.each {|klass|
145
+ if klass.instance_variables.include?( "@derivatives" )
146
+ base = klass
147
+ break
148
+ end
149
+ }
150
+
151
+ raise FactoryError, "Couldn't find factory base for #{self.name}" if
152
+ base.nil?
153
+
154
+ if base.name =~ /^.*::(.*)/
155
+ return $1
156
+ else
157
+ return base.name
158
+ end
159
+ end
160
+ alias_method :factoryType, :factory_type
161
+
162
+
163
+ ### Inheritance callback -- Register subclasses in the derivatives hash
164
+ ### so that ::create knows about them.
165
+ def inherited( subclass )
166
+ keys = [ subclass.name, subclass.name.downcase, subclass ]
167
+
168
+ # Handle class names like 'FooBar' for 'Bar' factories.
169
+ if subclass.name.match( /(?:.*::)?(\w+)(?:#{self.factory_type})/i )
170
+ keys << Regexp.last_match[1].downcase
171
+ else
172
+ keys << subclass.name.sub( /.*::/, '' ).downcase
173
+ end
174
+
175
+ keys.uniq.each {|key|
176
+ #PluginFactory::log :info, "Registering %s derivative of %s as %p" %
177
+ # [ subclass.name, self.name, key ]
178
+ self.derivatives[ key ] = subclass
179
+ }
180
+ super
181
+ end
182
+
183
+
184
+ ### Returns an Array of registered derivatives
185
+ def derivative_classes
186
+ self.derivatives.values.uniq
187
+ end
188
+ alias_method :derivativeClasses, :derivative_classes
189
+
190
+
191
+ ### Given the <tt>className</tt> of the class to instantiate, and other
192
+ ### arguments bound for the constructor of the new object, this method
193
+ ### loads the derivative class if it is not loaded already (raising a
194
+ ### LoadError if an appropriately-named file cannot be found), and
195
+ ### instantiates it with the given <tt>args</tt>. The <tt>className</tt>
196
+ ### may be the the fully qualified name of the class, the class object
197
+ ### itself, or the unique part of the class name. The following examples
198
+ ### would all try to load and instantiate a class called "FooListener"
199
+ ### if Listener included Factory
200
+ ### obj = Listener::create( 'FooListener' )
201
+ ### obj = Listener::create( FooListener )
202
+ ### obj = Listener::create( 'Foo' )
203
+ def create( subType, *args, &block )
204
+ subclass = get_subclass( subType )
205
+
206
+ return subclass.new( *args, &block )
207
+ rescue => err
208
+ nicetrace = err.backtrace.reject {|frame| /#{__FILE__}/ =~ frame}
209
+ msg = "When creating '#{subType}': " + err.message
210
+ Kernel::raise( err.class, msg, nicetrace )
211
+ end
212
+
213
+
214
+ ### Given a <tt>className</tt> like that of the first argument to
215
+ ### #create, attempt to load the corresponding class if it is not
216
+ ### already loaded and return the class object.
217
+ def get_subclass( className )
218
+ return self if ( self.name == className || className == '' )
219
+ return className if className.is_a?( Class ) && className >= self
220
+
221
+ unless self.derivatives.has_key?( className.downcase )
222
+ self.load_derivative( className )
223
+
224
+ unless self.derivatives.has_key?( className.downcase )
225
+ raise FactoryError,
226
+ "load_derivative(%s) didn't add a '%s' key to the "\
227
+ "registry for %s" %
228
+ [ className, className.downcase, self.name ]
229
+ end
230
+
231
+ subclass = self.derivatives[ className.downcase ]
232
+ unless subclass.is_a?( Class )
233
+ raise FactoryError,
234
+ "load_derivative(%s) added something other than a class "\
235
+ "to the registry for %s: %p" %
236
+ [ className, self.name, subclass ]
237
+ end
238
+ end
239
+
240
+ return self.derivatives[ className.downcase ]
241
+ end
242
+ alias_method :getSubclass, :get_subclass
243
+
244
+
245
+ ### Calculates an appropriate filename for the derived class using the
246
+ ### name of the base class and tries to load it via <tt>require</tt>. If
247
+ ### the including class responds to a method named
248
+ ### <tt>derivativeDirs</tt>, its return value (either a String, or an
249
+ ### array of Strings) is added to the list of prefix directories to try
250
+ ### when attempting to require a modules. Eg., if
251
+ ### <tt>class.derivativeDirs</tt> returns <tt>['foo','bar']</tt> the
252
+ ### require line is tried with both <tt>'foo/'</tt> and <tt>'bar/'</tt>
253
+ ### prepended to it.
254
+ def load_derivative( className )
255
+ className = className.to_s
256
+
257
+ #PluginFactory::log :debug, "Loading derivative #{className}"
258
+
259
+ # Get the unique part of the derived class name and try to
260
+ # load it from one of the derivative subdirs, if there are
261
+ # any.
262
+ mod_name = self.get_module_name( className )
263
+ self.require_derivative( mod_name )
264
+
265
+ # Check to see if the specified listener is now loaded. If it
266
+ # is not, raise an error to that effect.
267
+ unless self.derivatives[ className.downcase ]
268
+ raise FactoryError,
269
+ "Couldn't find a %s named '%s'. Loaded derivatives are: %p" % [
270
+ self.factory_type,
271
+ className.downcase,
272
+ self.derivatives.keys,
273
+ ], caller(3)
274
+ end
275
+
276
+ return true
277
+ end
278
+ alias_method :loadDerivative, :load_derivative
279
+
280
+
281
+ ### Build and return the unique part of the given <tt>className</tt>
282
+ ### either by stripping leading namespaces if the name already has the
283
+ ### name of the factory type in it (eg., 'My::FooService' for Service,
284
+ ### or by appending the factory type if it doesn't.
285
+ def get_module_name( className )
286
+ if className =~ /\w+#{self.factory_type}/
287
+ mod_name = className.sub( /(?:.*::)?(\w+)(?:#{self.factory_type})/, "\\1" )
288
+ else
289
+ mod_name = className
290
+ end
291
+
292
+ return mod_name
293
+ end
294
+ alias_method :getModuleName, :get_module_name
295
+
296
+
297
+ ### If the factory responds to the #derivativeDirs method, call
298
+ ### it and use the returned array as a list of directories to
299
+ ### search for the module with the specified <tt>mod_name</tt>.
300
+ def require_derivative( mod_name )
301
+
302
+ # See if we have a list of special subdirs that derivatives
303
+ # live in
304
+ if ( self.respond_to?(:derivative_dirs) )
305
+ subdirs = self.derivative_dirs
306
+
307
+ elsif ( self.respond_to?(:derivativeDirs) )
308
+ subdirs = self.derivativeDirs
309
+
310
+ # If not, just try requiring it from $LOAD_PATH
311
+ else
312
+ subdirs = ['']
313
+ end
314
+
315
+ subdirs = [ subdirs ] unless subdirs.is_a?( Array )
316
+ PluginFactory::log :debug, "Subdirs are: %p" % [subdirs]
317
+ fatals = []
318
+
319
+ # Iterate over the subdirs until we successfully require a
320
+ # module.
321
+ catch( :found ) {
322
+ subdirs.collect {|dir| dir.strip}.each do |subdir|
323
+ self.make_require_path( mod_name, subdir ).each {|path|
324
+ PluginFactory::log :debug, "Trying #{path}..."
325
+
326
+ # Try to require the module, saving errors and jumping
327
+ # out of the catch block on success.
328
+ begin
329
+ require( path.untaint )
330
+ rescue LoadError => err
331
+ PluginFactory::log :debug,
332
+ "No module at '%s', trying the next alternative: '%s'" %
333
+ [ path, err.message ]
334
+ rescue ScriptError,StandardError => err
335
+ fatals << err
336
+ PluginFactory::log :error,
337
+ "Found '#{path}', but encountered an error: %s\n\t%s" %
338
+ [ err.message, err.backtrace.join("\n\t") ]
339
+ else
340
+ #PluginFactory::log :debug,
341
+ # "Found '#{path}'. Throwing :found"
342
+ throw :found
343
+ end
344
+ }
345
+ end
346
+
347
+ #PluginFactory::log :debug, "fatals = %p" % [ fatals ]
348
+
349
+ # Re-raise is there was a file found, but it didn't load for
350
+ # some reason.
351
+ if ! fatals.empty?
352
+ #PluginFactory::log :debug, "Re-raising first fatal error"
353
+ Kernel::raise( fatals.first )
354
+ end
355
+
356
+ nil
357
+ }
358
+ end
359
+ alias_method :requireDerivative, :require_derivative
360
+
361
+
362
+ ### Make a list of permutations of the given +modname+ for the given
363
+ ### +subdir+. Called on a +DataDriver+ class with the arguments 'Socket' and
364
+ ### 'drivers', returns:
365
+ ### ["drivers/socketdatadriver", "drivers/socketDataDriver",
366
+ ### "drivers/SocketDataDriver", "drivers/socket", "drivers/Socket"]
367
+ def make_require_path( modname, subdir )
368
+ path = []
369
+ myname = self.factory_type
370
+
371
+ # Make permutations of the two parts
372
+ path << modname
373
+ path << modname.downcase
374
+ path << modname + myname
375
+ path << modname.downcase + myname
376
+ path << modname.downcase + myname.downcase
377
+
378
+ # If a non-empty subdir was given, prepend it to all the items in the
379
+ # path
380
+ unless subdir.nil? or subdir.empty?
381
+ path.collect! {|m| File::join(subdir, m)}
382
+ end
383
+
384
+ PluginFactory::log :debug, "Path is: #{path.uniq.reverse.inspect}..."
385
+ return path.uniq.reverse
386
+ end
387
+ alias_method :makeRequirePath, :make_require_path
388
+
389
+ end # module Factory