little-plugger 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/README.rdoc +53 -0
- data/Rakefile +40 -0
- data/lib/little-plugger.rb +305 -0
- data/spec/little-plugger_spec.rb +26 -0
- data/spec/spec_helper.rb +16 -0
- data/tasks/ann.rake +80 -0
- data/tasks/bones.rake +20 -0
- data/tasks/gem.rake +201 -0
- data/tasks/git.rake +40 -0
- data/tasks/notes.rake +27 -0
- data/tasks/post_load.rake +34 -0
- data/tasks/rdoc.rake +51 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/setup.rb +292 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- data/tasks/test.rake +40 -0
- data/tasks/zentest.rake +36 -0
- metadata +97 -0
data/History.txt
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
= Little Plugger
|
2
|
+
* by Tim Pease
|
3
|
+
* http://github.com/TwP/little-plugger/tree/master
|
4
|
+
|
5
|
+
=== DESCRIPTION:
|
6
|
+
|
7
|
+
LittlePlugger is a module that provides Gem based plugin management.
|
8
|
+
By extending your own class or module with LittlePlugger you can easily
|
9
|
+
manage the loading and initializing of plugins provided by other gems.
|
10
|
+
|
11
|
+
=== FEATURES:
|
12
|
+
|
13
|
+
* List of plugins so that some plugins can be excluded while others are
|
14
|
+
loaded by default.
|
15
|
+
* Loading and initializing of plugins.
|
16
|
+
* Access to the plugin classes and modules.
|
17
|
+
|
18
|
+
LittlePlugger is a distallation of the plugin system from Hoe. It has been
|
19
|
+
"genericized" and encapsulated into its own easy to use module.
|
20
|
+
|
21
|
+
=== REQUIREMENTS:
|
22
|
+
|
23
|
+
Since Little Plugger is a Gem based plugin system, Ruby Gems must be
|
24
|
+
installed on your system.
|
25
|
+
|
26
|
+
=== INSTALL:
|
27
|
+
|
28
|
+
gem install little-plugger
|
29
|
+
|
30
|
+
=== LICENSE:
|
31
|
+
|
32
|
+
(The MIT License)
|
33
|
+
|
34
|
+
Copyright (c) 2009
|
35
|
+
|
36
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
37
|
+
a copy of this software and associated documentation files (the
|
38
|
+
'Software'), to deal in the Software without restriction, including
|
39
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
40
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
41
|
+
permit persons to whom the Software is furnished to do so, subject to
|
42
|
+
the following conditions:
|
43
|
+
|
44
|
+
The above copyright notice and this permission notice shall be
|
45
|
+
included in all copies or substantial portions of the Software.
|
46
|
+
|
47
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
48
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
49
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
50
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
51
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
52
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
53
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# Look in the tasks/setup.rb file for the various options that can be
|
2
|
+
# configured in this Rakefile. The .rake files in the tasks directory
|
3
|
+
# are where the options are used.
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'bones'
|
7
|
+
Bones.setup
|
8
|
+
rescue LoadError
|
9
|
+
begin
|
10
|
+
load 'tasks/setup.rb'
|
11
|
+
rescue LoadError
|
12
|
+
raise RuntimeError, '### please install the "bones" gem ###'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
ensure_in_path 'lib'
|
17
|
+
require 'little-plugger'
|
18
|
+
|
19
|
+
task :default => 'spec:specdoc'
|
20
|
+
|
21
|
+
PROJ.name = 'little-plugger'
|
22
|
+
PROJ.authors = 'Tim Pease'
|
23
|
+
PROJ.email = 'tim.pease@gmail.com'
|
24
|
+
PROJ.url = 'http://codeforpeople.rubyforge.org/little-plugger'
|
25
|
+
PROJ.version = LittlePlugger::VERSION
|
26
|
+
PROJ.rubyforge.name = 'codeforpeople'
|
27
|
+
PROJ.exclude << 'little-plugger.gemspec'
|
28
|
+
PROJ.readme_file = 'README.rdoc'
|
29
|
+
PROJ.ignore_file = '.gitignore'
|
30
|
+
PROJ.rdoc.remote_dir = 'little-plugger'
|
31
|
+
|
32
|
+
PROJ.spec.opts << '--color'
|
33
|
+
|
34
|
+
PROJ.ann.email[:server] = 'smtp.gmail.com'
|
35
|
+
PROJ.ann.email[:port] = 587
|
36
|
+
PROJ.ann.email[:from] = 'Tim Pease'
|
37
|
+
|
38
|
+
depend_on 'rspec'
|
39
|
+
|
40
|
+
# EOF
|
@@ -0,0 +1,305 @@
|
|
1
|
+
|
2
|
+
# == Synopsis
|
3
|
+
# LittlePlugger is a module that provides Gem based plugin management.
|
4
|
+
# By extending your own class or module with LittlePlugger you can easily
|
5
|
+
# manage the loading and initializing of plugins provided by other gems.
|
6
|
+
#
|
7
|
+
# == Details
|
8
|
+
# Plugins are great! They allow other developers to add functionality to
|
9
|
+
# an application but relieve the application developer of the responsibility
|
10
|
+
# for mainting some other developer's plugin code. LittlePlugger aims to
|
11
|
+
# make it dead simple to manage external plugins as gems.
|
12
|
+
#
|
13
|
+
# === Naming
|
14
|
+
# Every plugin managed by LittlePlugger will have a name represented as a
|
15
|
+
# Symbol. This name is used to register the plugin, load the plugin file,
|
16
|
+
# and manage the plugin class/module. Here are the three rules for plugin
|
17
|
+
# names:
|
18
|
+
#
|
19
|
+
# 1) all lowercase with underscores
|
20
|
+
# 2) maps to a file of the same name with an '.rb' extension
|
21
|
+
# 3) converting the name to camel case yields the plugin class / module
|
22
|
+
#
|
23
|
+
# These rules are essentially the standard ruby practice of naming files
|
24
|
+
# after the class / module the file defines.
|
25
|
+
#
|
26
|
+
# === Finding & Loading
|
27
|
+
# Plugins are found by searching through the lib folders of all installed
|
28
|
+
# gems; these gems are not necessarily loaded - just searched. If the lib
|
29
|
+
# folder has a subdirectory that matches the +plugin_path+, then all ruby
|
30
|
+
# files in the gem's +plugin_path+ are noted for later loading.
|
31
|
+
#
|
32
|
+
# A file is only loaded if the basename of the file matches one of the
|
33
|
+
# registered plugin names. If no plugins are registered, then every file in
|
34
|
+
# the +plugin_path+ is loaded.
|
35
|
+
#
|
36
|
+
# The plugin classes / modules are all expected to live in the same
|
37
|
+
# namespace for a particular application. For example, all plugins for the
|
38
|
+
# "Foo" application should reside in a "Foo::Plugins" namespace. This allows
|
39
|
+
# the plugins to be automatically initialized by LittlePlugger.
|
40
|
+
#
|
41
|
+
# === Initializing
|
42
|
+
# Optionally, plugins can provide an initialization method for running any
|
43
|
+
# setup code needed by the plugin. This initialize method should be named as
|
44
|
+
# follows: "initializer_#{plugin_name}" where the name of the plugin is
|
45
|
+
# appended to the end of the initializer method name.
|
46
|
+
#
|
47
|
+
# If this method exists, it will be called automatically when plugins are
|
48
|
+
# loaded. The order of loading of initialization is not strictly defined, so
|
49
|
+
# do not rely on another plugin being initialized for your own plugin
|
50
|
+
# successfully initialize.
|
51
|
+
#
|
52
|
+
# == Usage
|
53
|
+
# LittlePlugger is used by extending your own class or module with the
|
54
|
+
# LittlePlugger module.
|
55
|
+
#
|
56
|
+
# module Logging
|
57
|
+
# extend LittlePlugger
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# This defines a +plugin_path+ and a +plugin_module+ for our Logging module.
|
61
|
+
# The +plugin_path+ is set to "logging/plugins", and therefore, the
|
62
|
+
# +plugin_modlue+ is defined as Logging::Plugins. All plugins for the
|
63
|
+
# Logging module should be found underneath this plugin module.
|
64
|
+
#
|
65
|
+
# The plugins for the Logging module are loaded and initialized by calling
|
66
|
+
# the +initialize_plugins+ method.
|
67
|
+
#
|
68
|
+
# Logging.initialize_plugins
|
69
|
+
#
|
70
|
+
# If you only want to load the plugin files but not initialize the plugin
|
71
|
+
# classes / modules then you can call the +load_plugins+ method.
|
72
|
+
#
|
73
|
+
# Logging.load_plugins
|
74
|
+
#
|
75
|
+
# Finally, you can get a hash of all the loaded plugins.
|
76
|
+
#
|
77
|
+
# Logging.plugins
|
78
|
+
#
|
79
|
+
# This returns a hash keyed by the plugin names with the plugin class /
|
80
|
+
# module as the value.
|
81
|
+
#
|
82
|
+
# If you only want a certain set of plugins to be loaded, then pass the
|
83
|
+
# names to the +plugin+ method.
|
84
|
+
#
|
85
|
+
# Logging.plugin :foo, :bar, :baz
|
86
|
+
#
|
87
|
+
# Now only three plugins for the Logging module will be loaded.
|
88
|
+
#
|
89
|
+
# === Customizing
|
90
|
+
# LittlePlugger allows the use of a custom plugin path and module. These are
|
91
|
+
# specified when extending with LilttlePlugger by passing the specific path
|
92
|
+
# and module to LittlePlugger.
|
93
|
+
#
|
94
|
+
# class Hoe
|
95
|
+
# extend LittlePlugger( :path => 'hoe', :module => Hoe )
|
96
|
+
#
|
97
|
+
# plugin(
|
98
|
+
# :clean, :debug, :deps, :flay, :flog, :package,
|
99
|
+
# :publish, :rcov, :signing, :test
|
100
|
+
# )
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# All ruby files found under the "hoe" directory will be treated as
|
104
|
+
# plugins, and the plugin classes / modules should reside directly under the
|
105
|
+
# Hoe namespace.
|
106
|
+
#
|
107
|
+
# We also specify a list of plugins to be loaded. Only these plugins will be
|
108
|
+
# loaded and initialized by the LittlePlugger module. The +plugin+ method
|
109
|
+
# can be called multiple times to add more plugins.
|
110
|
+
#
|
111
|
+
module LittlePlugger
|
112
|
+
|
113
|
+
VERSION = '1.0.0' # :nodoc:
|
114
|
+
|
115
|
+
# Returns the version string for the library.
|
116
|
+
#
|
117
|
+
def self.version
|
118
|
+
VERSION
|
119
|
+
end
|
120
|
+
|
121
|
+
module ClassMethods
|
122
|
+
|
123
|
+
# Add the _names_ to the list of plugins that will be loaded.
|
124
|
+
#
|
125
|
+
def plugin( *names )
|
126
|
+
plugin_names.concat(names.map! {|n| n.to_sym})
|
127
|
+
end
|
128
|
+
|
129
|
+
# Returns the array of plugin names that will be loaded. If the array is
|
130
|
+
# empty, then any plugin found in the +plugin_path+ will be loaded.
|
131
|
+
#
|
132
|
+
def plugin_names
|
133
|
+
@plugin_names ||= []
|
134
|
+
end
|
135
|
+
|
136
|
+
# Loads the desired plugins and returns a hash. The hash contains all
|
137
|
+
# the plugin classes and modules keyed by the plugin name.
|
138
|
+
#
|
139
|
+
def plugins
|
140
|
+
load_plugins
|
141
|
+
pm = plugin_module
|
142
|
+
names = pm.constants.map { |s| s.to_s }
|
143
|
+
names.reject! { |n| n =~ %r/^[A-Z_]+$/ }
|
144
|
+
|
145
|
+
h = {}
|
146
|
+
names.each do |name|
|
147
|
+
sym = ::LittlePlugger.underscore(name).to_sym
|
148
|
+
next unless plugin_names.empty? or plugin_names.include? sym
|
149
|
+
h[sym] = pm.const_get name
|
150
|
+
end
|
151
|
+
h
|
152
|
+
end
|
153
|
+
|
154
|
+
# Iterate over the loaded plugin classes and modules and call the
|
155
|
+
# initialize method for each plugin. The plugin's initialize method is
|
156
|
+
# defeind as +initialize_plugin_name+, where the plugin name is unique
|
157
|
+
# to each plugin.
|
158
|
+
#
|
159
|
+
def initialize_plugins
|
160
|
+
plugins.each do |name, klass|
|
161
|
+
msg = "initialize_#{name}"
|
162
|
+
klass.send msg if klass.respond_to? msg
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Iterate through all installed gems looking for those that have the
|
167
|
+
# +plugin_path+ in their "lib" folder, and load all .rb files found in
|
168
|
+
# the gem's plugin path. Each .rb file should define one class or module
|
169
|
+
# that will be used as a plugin.
|
170
|
+
#
|
171
|
+
def load_plugins
|
172
|
+
@loaded ||= {}
|
173
|
+
found = {}
|
174
|
+
|
175
|
+
Gem.find_files(File.join(plugin_path, '*.rb')).each do |path|
|
176
|
+
found[File.basename(path, '*.rb').to_sym] = path
|
177
|
+
end
|
178
|
+
|
179
|
+
:keep_on_truckin while found.map { |name, path|
|
180
|
+
next unless plugin_names.empty? or plugin_names.include? name
|
181
|
+
next if @loaded[name]
|
182
|
+
begin
|
183
|
+
@loaded[name] = load path
|
184
|
+
rescue LoadError => err
|
185
|
+
warn "Error loading #{path.inspect}: #{err.message}. skipping..."
|
186
|
+
end
|
187
|
+
}.any?
|
188
|
+
end
|
189
|
+
|
190
|
+
# The path to search in a gem's 'lib' folder for plugins.
|
191
|
+
#
|
192
|
+
def plugin_path
|
193
|
+
::LittlePlugger.default_plugin_path(self)
|
194
|
+
end
|
195
|
+
|
196
|
+
# This module or class where plugins are located.
|
197
|
+
#
|
198
|
+
def plugin_module
|
199
|
+
::LittlePlugger.default_plugin_module(plugin_path)
|
200
|
+
end
|
201
|
+
|
202
|
+
end # module ClassMethods
|
203
|
+
|
204
|
+
# :stopdoc:
|
205
|
+
|
206
|
+
# Called when another object extends itself with LittlePlugger.
|
207
|
+
#
|
208
|
+
def self.extended( other )
|
209
|
+
other.extend ClassMethods
|
210
|
+
end
|
211
|
+
|
212
|
+
# Convert the given string from camel case to snake case. Method liberally
|
213
|
+
# stolen from ActiveSupport.
|
214
|
+
#
|
215
|
+
# underscore( "FooBar" ) #=> "foo_bar"
|
216
|
+
#
|
217
|
+
def self.underscore( string )
|
218
|
+
string.to_s.
|
219
|
+
gsub(%r/::/, '/').
|
220
|
+
gsub(%r/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
221
|
+
gsub(%r/([a-z\d])([A-Z])/,'\1_\2').
|
222
|
+
tr('-', '_').
|
223
|
+
downcase
|
224
|
+
end
|
225
|
+
|
226
|
+
# For a given object returns a default plugin path. The path is
|
227
|
+
# created by splitting the object's class name on the namespace separator
|
228
|
+
# "::" and converting each part of the namespace into an underscored
|
229
|
+
# string (see the +underscore+ method). The strings are then joined using
|
230
|
+
# the File#join method to give a filesystem path. Appended to this path is
|
231
|
+
# the 'plugins' directory.
|
232
|
+
#
|
233
|
+
# default_plugin_path( FooBar::Baz ) #=> "foo_bar/baz/plugins"
|
234
|
+
#
|
235
|
+
def self.default_plugin_path( obj )
|
236
|
+
obj = obj.class unless obj.is_a? Module
|
237
|
+
File.join(underscore(obj.name), 'plugins')
|
238
|
+
end
|
239
|
+
|
240
|
+
# For a given path returns the class or module corresponding to the
|
241
|
+
# path. This method assumes a correspondence between directory names and
|
242
|
+
# Ruby namespaces.
|
243
|
+
#
|
244
|
+
# default_plugin_module( "foo_bar/baz/plugins" ) #=> FooBar::Baz::Plugins
|
245
|
+
#
|
246
|
+
# This method will fail if any of the namespaces have not yet been
|
247
|
+
# defined.
|
248
|
+
#
|
249
|
+
def self.default_plugin_module( path )
|
250
|
+
path.split(File::SEPARATOR).inject(Object) do |mod, const|
|
251
|
+
const = const.split('_').map { |s| s.capitalize }.join
|
252
|
+
mod.const_get const
|
253
|
+
end
|
254
|
+
end
|
255
|
+
# :startdoc:
|
256
|
+
|
257
|
+
end # module LittlePlugger
|
258
|
+
|
259
|
+
|
260
|
+
module Kernel
|
261
|
+
|
262
|
+
# call-seq:
|
263
|
+
# LittlePlugger( opts = {} )
|
264
|
+
#
|
265
|
+
# This method allows the user to override some of LittlePlugger's default
|
266
|
+
# settings when mixed into a module or class.
|
267
|
+
#
|
268
|
+
# See the "Customizing" section of the LittlePlugger documentation for an
|
269
|
+
# example of how this method is used.
|
270
|
+
#
|
271
|
+
# ==== Options
|
272
|
+
#
|
273
|
+
# * :path <String>
|
274
|
+
# The default plugin path. Defaults to "module_name/plugins".
|
275
|
+
#
|
276
|
+
# * :module <Module>
|
277
|
+
# The module where plugins will be loaded. Defaults to
|
278
|
+
# ModuleName::Plugins.
|
279
|
+
#
|
280
|
+
# * :plugins <Array>
|
281
|
+
# The array of default plugins to load. Only the plugins listed in this
|
282
|
+
# array will be loaded by LittlePlugger.
|
283
|
+
#
|
284
|
+
def LittlePlugger( opts = {} )
|
285
|
+
return ::LittlePlugger::ClassMethods if opts.empty?
|
286
|
+
Module.new {
|
287
|
+
include ::LittlePlugger::ClassMethods
|
288
|
+
|
289
|
+
if opts.key?(:path)
|
290
|
+
eval %Q{def plugin_path() #{opts[:path].to_s.inspect} end}
|
291
|
+
end
|
292
|
+
|
293
|
+
if opts.key?(:module)
|
294
|
+
eval %Q{def plugin_module() #{opts[:module].name} end}
|
295
|
+
end
|
296
|
+
|
297
|
+
if opts.key?(:plugins)
|
298
|
+
plugins = Array(opts[:plugins]).map {|val| val.to_sym.inspect}.join(',')
|
299
|
+
eval %Q{def plugin_names() @plugin_names ||= [#{plugins}] end}
|
300
|
+
end
|
301
|
+
}
|
302
|
+
end
|
303
|
+
end # module Kernel
|
304
|
+
|
305
|
+
# EOF
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
require File.join(File.dirname(__FILE__), %w[spec_helper])
|
3
|
+
|
4
|
+
describe LittlePlugger do
|
5
|
+
|
6
|
+
it "converts a string from camel-case to underscore" do
|
7
|
+
LittlePlugger.underscore('FooBarBaz').should == 'foo_bar_baz'
|
8
|
+
LittlePlugger.underscore('CouchDB').should == 'couch_db'
|
9
|
+
LittlePlugger.underscore('FOOBar').should == 'foo_bar'
|
10
|
+
LittlePlugger.underscore('Foo::Bar::BazBuz').should == 'foo/bar/baz_buz'
|
11
|
+
end
|
12
|
+
|
13
|
+
it "generates a default plugin path" do
|
14
|
+
LittlePlugger.default_plugin_path(LittlePlugger).should == 'little_plugger/plugins'
|
15
|
+
LittlePlugger.default_plugin_path(Spec::Runner).should == 'spec/runner/plugins'
|
16
|
+
end
|
17
|
+
|
18
|
+
it "generates a default plugin module" do
|
19
|
+
LittlePlugger.default_plugin_module('little_plugger').should == LittlePlugger
|
20
|
+
lambda {LittlePlugger.default_plugin_module('little_plugger/plugins')}.
|
21
|
+
should raise_error(NameError, 'uninitialized constant LittlePlugger::Plugins')
|
22
|
+
LittlePlugger.default_plugin_module('spec/runner').should == Spec::Runner
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# EOF
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
|
2
|
+
require File.expand_path(
|
3
|
+
File.join(File.dirname(__FILE__), %w[.. lib little-plugger]))
|
4
|
+
|
5
|
+
Spec::Runner.configure do |config|
|
6
|
+
# == Mock Framework
|
7
|
+
#
|
8
|
+
# RSpec uses it's own mocking framework by default. If you prefer to
|
9
|
+
# use mocha, flexmock or RR, uncomment the appropriate line:
|
10
|
+
#
|
11
|
+
# config.mock_with :mocha
|
12
|
+
# config.mock_with :flexmock
|
13
|
+
# config.mock_with :rr
|
14
|
+
end
|
15
|
+
|
16
|
+
# EOF
|