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.
- data/COPYRIGHT +131 -0
- data/ChangeLog +102 -0
- data/README +39 -0
- data/install.rb +172 -0
- data/lib/pluginfactory.rb +389 -0
- data/test.rb +72 -0
- data/tests/dir/deepsubofmybase.rb +3 -0
- data/tests/mybase.rb +11 -0
- data/tests/othersub.rb +5 -0
- data/tests/subofmybase.rb +5 -0
- data/utils.rb +672 -0
- metadata +57 -0
data/COPYRIGHT
ADDED
|
@@ -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
|
data/ChangeLog
ADDED
|
@@ -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 $
|
data/install.rb
ADDED
|
@@ -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
|