gem_plugin 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.
@@ -0,0 +1,29 @@
1
+
2
+ <?xml version="1.0" encoding="iso-8859-1"?>
3
+ <!DOCTYPE html
4
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
5
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
6
+
7
+ <!--
8
+
9
+ Classes
10
+
11
+ -->
12
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
13
+ <head>
14
+ <title>Classes</title>
15
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
16
+ <link rel="stylesheet" href="rdoc-style.css" type="text/css" />
17
+ <base target="docwin" />
18
+ </head>
19
+ <body>
20
+ <div id="index">
21
+ <h1 class="section-bar">Classes</h1>
22
+ <div id="index-entries">
23
+ <a href="classes/GemPlugin.html">GemPlugin</a><br />
24
+ <a href="classes/GemPlugin/Base.html">GemPlugin::Base</a><br />
25
+ <a href="classes/GemPlugin/Manager.html">GemPlugin::Manager</a><br />
26
+ </div>
27
+ </div>
28
+ </body>
29
+ </html>
@@ -0,0 +1,30 @@
1
+
2
+ <?xml version="1.0" encoding="iso-8859-1"?>
3
+ <!DOCTYPE html
4
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
5
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
6
+
7
+ <!--
8
+
9
+ Files
10
+
11
+ -->
12
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
13
+ <head>
14
+ <title>Files</title>
15
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
16
+ <link rel="stylesheet" href="rdoc-style.css" type="text/css" />
17
+ <base target="docwin" />
18
+ </head>
19
+ <body>
20
+ <div id="index">
21
+ <h1 class="section-bar">Files</h1>
22
+ <div id="index-entries">
23
+ <a href="files/COPYING.html">COPYING</a><br />
24
+ <a href="files/LICENSE.html">LICENSE</a><br />
25
+ <a href="files/README.html">README</a><br />
26
+ <a href="files/lib/gem_plugin_rb.html">lib/gem_plugin.rb</a><br />
27
+ </div>
28
+ </div>
29
+ </body>
30
+ </html>
@@ -0,0 +1,35 @@
1
+
2
+ <?xml version="1.0" encoding="iso-8859-1"?>
3
+ <!DOCTYPE html
4
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
5
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
6
+
7
+ <!--
8
+
9
+ Methods
10
+
11
+ -->
12
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
13
+ <head>
14
+ <title>Methods</title>
15
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
16
+ <link rel="stylesheet" href="rdoc-style.css" type="text/css" />
17
+ <base target="docwin" />
18
+ </head>
19
+ <body>
20
+ <div id="index">
21
+ <h1 class="section-bar">Methods</h1>
22
+ <div id="index-entries">
23
+ <a href="classes/GemPlugin.html#M000001">Plugin (GemPlugin)</a><br />
24
+ <a href="classes/GemPlugin/Manager.html#M000009">available (GemPlugin::Manager)</a><br />
25
+ <a href="classes/GemPlugin/Base.html#M000003">category= (GemPlugin::Base)</a><br />
26
+ <a href="classes/GemPlugin/Manager.html#M000008">create (GemPlugin::Manager)</a><br />
27
+ <a href="classes/GemPlugin/Base.html#M000002">inherited (GemPlugin::Base)</a><br />
28
+ <a href="classes/GemPlugin/Manager.html#M000006">load (GemPlugin::Manager)</a><br />
29
+ <a href="classes/GemPlugin/Manager.html#M000005">new (GemPlugin::Manager)</a><br />
30
+ <a href="classes/GemPlugin/Base.html#M000004">new (GemPlugin::Base)</a><br />
31
+ <a href="classes/GemPlugin/Manager.html#M000007">register (GemPlugin::Manager)</a><br />
32
+ </div>
33
+ </div>
34
+ </body>
35
+ </html>
@@ -0,0 +1,24 @@
1
+ <?xml version="1.0" encoding="iso-8859-1"?>
2
+ <!DOCTYPE html
3
+ PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
4
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
5
+
6
+ <!--
7
+
8
+ RDoc Documentation
9
+
10
+ -->
11
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
12
+ <head>
13
+ <title>RDoc Documentation</title>
14
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
15
+ </head>
16
+ <frameset rows="20%, 80%">
17
+ <frameset cols="25%,35%,45%">
18
+ <frame src="fr_file_index.html" title="Files" name="Files" />
19
+ <frame src="fr_class_index.html" name="Classes" />
20
+ <frame src="fr_method_index.html" name="Methods" />
21
+ </frameset>
22
+ <frame src="files/README.html" name="docwin" />
23
+ </frameset>
24
+ </html>
@@ -0,0 +1,208 @@
1
+
2
+ body {
3
+ font-family: Verdana,Arial,Helvetica,sans-serif;
4
+ font-size: 90%;
5
+ margin: 0;
6
+ margin-left: 40px;
7
+ padding: 0;
8
+ background: white;
9
+ }
10
+
11
+ h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; }
12
+ h1 { font-size: 150%; }
13
+ h2,h3,h4 { margin-top: 1em; }
14
+
15
+ a { background: #eef; color: #039; text-decoration: none; }
16
+ a:hover { background: #039; color: #eef; }
17
+
18
+ /* Override the base stylesheet's Anchor inside a table cell */
19
+ td > a {
20
+ background: transparent;
21
+ color: #039;
22
+ text-decoration: none;
23
+ }
24
+
25
+ /* and inside a section title */
26
+ .section-title > a {
27
+ background: transparent;
28
+ color: #eee;
29
+ text-decoration: none;
30
+ }
31
+
32
+ /* === Structural elements =================================== */
33
+
34
+ div#index {
35
+ margin: 0;
36
+ margin-left: -40px;
37
+ padding: 0;
38
+ font-size: 90%;
39
+ }
40
+
41
+
42
+ div#index a {
43
+ margin-left: 0.7em;
44
+ }
45
+
46
+ div#index .section-bar {
47
+ margin-left: 0px;
48
+ padding-left: 0.7em;
49
+ background: #ccc;
50
+ font-size: small;
51
+ }
52
+
53
+
54
+ div#classHeader, div#fileHeader {
55
+ width: auto;
56
+ color: white;
57
+ padding: 0.5em 1.5em 0.5em 1.5em;
58
+ margin: 0;
59
+ margin-left: -40px;
60
+ border-bottom: 3px solid #006;
61
+ }
62
+
63
+ div#classHeader a, div#fileHeader a {
64
+ background: inherit;
65
+ color: white;
66
+ }
67
+
68
+ div#classHeader td, div#fileHeader td {
69
+ background: inherit;
70
+ color: white;
71
+ }
72
+
73
+
74
+ div#fileHeader {
75
+ background: #057;
76
+ }
77
+
78
+ div#classHeader {
79
+ background: #048;
80
+ }
81
+
82
+
83
+ .class-name-in-header {
84
+ font-size: 180%;
85
+ font-weight: bold;
86
+ }
87
+
88
+
89
+ div#bodyContent {
90
+ padding: 0 1.5em 0 1.5em;
91
+ }
92
+
93
+ div#description {
94
+ padding: 0.5em 1.5em;
95
+ background: #efefef;
96
+ border: 1px dotted #999;
97
+ }
98
+
99
+ div#description h1,h2,h3,h4,h5,h6 {
100
+ color: #125;;
101
+ background: transparent;
102
+ }
103
+
104
+ div#validator-badges {
105
+ text-align: center;
106
+ }
107
+ div#validator-badges img { border: 0; }
108
+
109
+ div#copyright {
110
+ color: #333;
111
+ background: #efefef;
112
+ font: 0.75em sans-serif;
113
+ margin-top: 5em;
114
+ margin-bottom: 0;
115
+ padding: 0.5em 2em;
116
+ }
117
+
118
+
119
+ /* === Classes =================================== */
120
+
121
+ table.header-table {
122
+ color: white;
123
+ font-size: small;
124
+ }
125
+
126
+ .type-note {
127
+ font-size: small;
128
+ color: #DEDEDE;
129
+ }
130
+
131
+ .xxsection-bar {
132
+ background: #eee;
133
+ color: #333;
134
+ padding: 3px;
135
+ }
136
+
137
+ .section-bar {
138
+ color: #333;
139
+ border-bottom: 1px solid #999;
140
+ margin-left: -20px;
141
+ }
142
+
143
+
144
+ .section-title {
145
+ background: #79a;
146
+ color: #eee;
147
+ padding: 3px;
148
+ margin-top: 2em;
149
+ margin-left: -30px;
150
+ border: 1px solid #999;
151
+ }
152
+
153
+ .top-aligned-row { vertical-align: top }
154
+ .bottom-aligned-row { vertical-align: bottom }
155
+
156
+ /* --- Context section classes ----------------------- */
157
+
158
+ .context-row { }
159
+ .context-item-name { font-family: monospace; font-weight: bold; color: black; }
160
+ .context-item-value { font-size: small; color: #448; }
161
+ .context-item-desc { color: #333; padding-left: 2em; }
162
+
163
+ /* --- Method classes -------------------------- */
164
+ .method-detail {
165
+ background: #efefef;
166
+ padding: 0;
167
+ margin-top: 0.5em;
168
+ margin-bottom: 1em;
169
+ border: 1px dotted #ccc;
170
+ }
171
+ .method-heading {
172
+ color: black;
173
+ background: #ccc;
174
+ border-bottom: 1px solid #666;
175
+ padding: 0.2em 0.5em 0 0.5em;
176
+ }
177
+ .method-signature { color: black; background: inherit; }
178
+ .method-name { font-weight: bold; }
179
+ .method-args { font-style: italic; }
180
+ .method-description { padding: 0 0.5em 0 0.5em; }
181
+
182
+ /* --- Source code sections -------------------- */
183
+
184
+ a.source-toggle { font-size: 90%; }
185
+ div.method-source-code {
186
+ background: #262626;
187
+ color: #ffdead;
188
+ margin: 1em;
189
+ padding: 0.5em;
190
+ border: 1px dashed #999;
191
+ overflow: hidden;
192
+ }
193
+
194
+ div.method-source-code pre { color: #ffdead; overflow: hidden; }
195
+
196
+ /* --- Ruby keyword styles --------------------- */
197
+
198
+ .standalone-code { background: #221111; color: #ffdead; overflow: hidden; }
199
+
200
+ .ruby-constant { color: #7fffd4; background: transparent; }
201
+ .ruby-keyword { color: #00ffff; background: transparent; }
202
+ .ruby-ivar { color: #eedd82; background: transparent; }
203
+ .ruby-operator { color: #00ffee; background: transparent; }
204
+ .ruby-identifier { color: #ffdead; background: transparent; }
205
+ .ruby-node { color: #ffa07a; background: transparent; }
206
+ .ruby-comment { color: #b22222; font-weight: bold; background: transparent; }
207
+ .ruby-regexp { color: #ffa07a; background: transparent; }
208
+ .ruby-value { color: #7fffd4; background: transparent; }
@@ -0,0 +1,204 @@
1
+ require 'singleton'
2
+ require 'rubygems'
3
+
4
+ # Implements a dynamic plugin loading, configuration, and discovery system
5
+ # based on RubyGems and a simple additional name space that looks like a URI.
6
+ #
7
+ # A plugin is created and put into a category with the following code:
8
+ #
9
+ # class MyThing < GemPlugin::Plugin "/things"
10
+ # ...
11
+ # end
12
+ #
13
+ # What this does is sets up your MyThing in the plugin registry via GemPlugin::Manager.
14
+ # You can then later get this plugin with GemPlugin::Manager.create("/things/mything")
15
+ # and can also pass in options as a second parameter.
16
+ #
17
+ # This isn't such a big deal, but the power is really from the GemPlugin::Manager.load
18
+ # method. This method will go through the installed gems and require_gem any
19
+ # that depend on the gem_plugin RubyGem. You can arbitrarily include or exclude
20
+ # gems based on what they also depend on, thus letting you load these gems when appropriate.
21
+ #
22
+ # Since this system was written originally for the Mongrel project that'll be the
23
+ # best examle of using it.
24
+ #
25
+ # Imagine you have a neat plugin for Mongrel called snazzy_command that give the
26
+ # mongrel_rails a new command snazzy (like: mongrel_rails snazzy). You'd like
27
+ # people to be able to grab this plugin if they want and use it, because it's snazzy.
28
+ #
29
+ # First thing you do is create a gem of your project and make sure that it depends
30
+ # on "mongrel" AND "gem_plugin". This signals to the GemPlugin system that this is
31
+ # a plugin for mongrel.
32
+ #
33
+ # Next you put this code into a file like lib/init.rb (can be anything really):
34
+ #
35
+ # class Snazzy < GemPlugin::Plugin "/commands"
36
+ # ...
37
+ # end
38
+ #
39
+ # Then when you create your gem you have the following bits in your Rakefile:
40
+ #
41
+ # spec.add_dependency('mongrel', '>= 0.3.9')
42
+ # spec.add_dependency('gem_plugin', '>= 0.1')
43
+ # spec.autorequire = 'init.rb'
44
+ #
45
+ # Finally, you just have to now publish this gem for people to install and Mongrel
46
+ # will "magically" be able to install it.
47
+ #
48
+ # The "magic" part though is pretty simple and done via the GemPlugin::Manager.load
49
+ # method. Read that to see how it is really done.
50
+ module GemPlugin
51
+
52
+ EXCLUDE = true
53
+ INCLUDE = false
54
+
55
+ # This class is used by people who use gem plugins (but don't necessarily make them)
56
+ # to add plugins to their own systems. It provides a way to load plugins, list them,
57
+ # and create them as needed.
58
+ #
59
+ # It is a singleton so you use like this: GemPlugins::Manager.instance.load
60
+ class Manager
61
+ include Singleton
62
+
63
+ def initialize
64
+ @plugins = {}
65
+ @loaded_gems = []
66
+ end
67
+
68
+
69
+ # Responsible for going through the list of available gems and loading
70
+ # any plugins requested. It keeps track of what it's loaded already
71
+ # and won't load them again.
72
+ #
73
+ # It accepts one parameter which is a hash of gem depends that should include
74
+ # or exclude a gem from being loaded. A gem must depend on gem_plugin to be
75
+ # considered, but then each system has to add it's own INCLUDE to make sure
76
+ # that only plugins related to it are loaded.
77
+ #
78
+ # An example again comes from Mongrel. In order to load all Mongrel plugins:
79
+ #
80
+ # GemPlugin::Manager.instance.load "mongrel" => GemPlugin::INCLUDE
81
+ #
82
+ # Which will load all plugins that depend on mongrel AND gem_plugin. Now, one
83
+ # extra thing we do is we delay loading Rails Mongrel plugins until after rails
84
+ # is configured. Do do this the mongrel_rails script has:
85
+ #
86
+ # GemPlugin::Manager.instance.load "mongrel" => GemPlugin::INCLUDE, "rails" => GemPlugin::EXCLUDE
87
+ # The only thing to remember is that this is saying "include a plugin if it
88
+ # depends on gem_plugin, mongrel, but NOT rails". If a plugin also depends on other
89
+ # stuff then it's loaded just fine. Only gem_plugin, mongrel, and rails are
90
+ # ever used to determine if it should be included.
91
+ def load(needs = {})
92
+ sdir = File.join(Gem.dir, "specifications")
93
+ gems = Gem::SourceIndex.from_installed_gems(sdir)
94
+ needs = needs.merge({"gem_plugin" => INCLUDE})
95
+
96
+ gems.each do |path, gem|
97
+ # don't load gems more than once
98
+ next if @loaded_gems.include? gem.name
99
+ check = needs.dup
100
+
101
+ # rolls through the depends and inverts anything it finds
102
+ gem.dependencies.each do |dep|
103
+ # this will fail if a gem is depended more than once
104
+ if check.has_key? dep.name
105
+ check[dep.name] = !check[dep.name]
106
+ end
107
+ end
108
+
109
+ # now since excluded gems start as true, inverting them
110
+ # makes them false so we'll skip this gem if any excludes are found
111
+ if (check.select {|name,test| !test}).length == 0
112
+ # looks like no needs were set to false, so it's good
113
+ require_gem gem.name
114
+ @loaded_gems << gem.name
115
+ end
116
+
117
+ end
118
+ end
119
+
120
+
121
+ # Not necessary for you to call directly, but this is
122
+ # how GemPlugin::Base.inherited actually adds a
123
+ # plugin to a category.
124
+ def register(category, name, klass)
125
+ @plugins[category] ||= {}
126
+ @plugins[category][name.downcase] = klass
127
+ end
128
+
129
+ # Resolves the given name (should include /category/name) to
130
+ # find the plugin class and create an instance. You can
131
+ # pass a second hash option that is then given to the Plugin
132
+ # to configure it.
133
+ def create(name, options = {})
134
+ last_slash = name.rindex("/")
135
+ category = name[0 ... last_slash]
136
+ plugin = name[last_slash .. -1]
137
+
138
+ map = @plugins[category]
139
+ if not map
140
+ raise "Plugin category #{category} does not exist"
141
+ elsif not map.has_key? plugin
142
+ raise "Plugin #{plugin} does not exist in category #{category}"
143
+ else
144
+ map[plugin].new(options)
145
+ end
146
+ end
147
+
148
+
149
+ # Returns a map of URIs->{"name" => Plugin} that you can
150
+ # use to investigate available handlers.
151
+ def available
152
+ return @plugins
153
+ end
154
+
155
+ end
156
+
157
+ # This base class for plugins reallys does nothing
158
+ # more than wire up the new class into the right category.
159
+ # It is not thread-safe yet but will be soon.
160
+ class Base
161
+
162
+ attr_reader :options
163
+
164
+
165
+ # See Mongrel::Plugin for an explanation.
166
+ def Base.inherited(klass)
167
+ name = "/" + klass.to_s.downcase
168
+ Manager.instance.register(@@category, name, klass)
169
+ @@category = nil
170
+ end
171
+
172
+ # See Mongrel::Plugin for an explanation.
173
+ def Base.category=(category)
174
+ @@category = category
175
+ end
176
+
177
+ def initialize(options = {})
178
+ @options = options
179
+ end
180
+
181
+ end
182
+
183
+ # This nifty function works with the GemPlugin::Base to give you
184
+ # the syntax:
185
+ #
186
+ # class MyThing < GemPlugin::Plugin "/things"
187
+ # ...
188
+ # end
189
+ #
190
+ # What it does is temporarily sets the GemPlugin::Base.category, and then
191
+ # returns GemPlugin::Base. Since the next immediate thing Ruby does is
192
+ # use this returned class to create the new class, GemPlugin::Base.inherited
193
+ # gets called. GemPlugin::Base.inherited then uses the set category, class name,
194
+ # and class to register the plugin in the right way.
195
+ def GemPlugin::Plugin(c)
196
+ Base.category = c
197
+ Base
198
+ end
199
+
200
+ end
201
+
202
+
203
+
204
+