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 ADDED
@@ -0,0 +1,4 @@
1
+ == 1.0.0 / 2009-07-16
2
+
3
+ * 1 major enhancement
4
+ * Birthday!
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
@@ -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