pluggable 0.3.0

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,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Andrew C. Greenberg
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,102 @@
1
+ = pluggable
2
+
3
+ Pluggable is a mixin for classes requiring plugins. Including *Pluggable* adds several methods:
4
+
5
+ [*install_plugins*] Should generally be called when initializing an instance of the Pluggable class.
6
+ [*plugins*] After initialization, returns a list of plugin instances
7
+
8
+ For example:
9
+
10
+ require 'rubygems'
11
+ require 'pluggable'
12
+ class Test
13
+ include Pluggable
14
+ def initialize; install_plugins; end
15
+ def process; plugins.map {|plugin| plugin.process}; end
16
+ end
17
+
18
+ class Plugin1 < Test::Plugin
19
+ def process; "foo"; end
20
+ end
21
+
22
+ class Plugin2 < Test::Plugin
23
+ def process; "bar"; end
24
+ end
25
+
26
+ Test.new.process # => ["foo", "bar"]
27
+
28
+ It may be convenient to have public methods of plugins delegated to from the +plugins+ object, which may in turn be delgated to by the +Pluggable+ class in various ways. This is accomplished with the class method *delegate_plugin_public_methods_except*, which should be called after loading all of the plugins. For example:
29
+
30
+ require 'rubygems'
31
+ require 'pluggable'
32
+ class Test
33
+ include Pluggable
34
+ def initialize; install_plugins; end
35
+ def process; plugins.map {|plugin| plugin.process}; end
36
+ end
37
+
38
+ class Plugin1 < Test::Plugin
39
+ def foo; "foo"; end
40
+ def process; foo; end
41
+ end
42
+
43
+ class Plugin2 < Test::Plugin
44
+ def bar; "bar"; end
45
+ def process; bar; end
46
+ end
47
+
48
+ Test.delegate_plugin_public_methods_except :process
49
+
50
+ Test.new.process # => ["foo", "bar"]
51
+ Test.new.plugins.foo # => "foo"
52
+ Test.new.plugins.bar # => "bar"
53
+
54
+ Finally, it may be useful to establish an API for plugins in the form of a traditional Ruby module. The module may be included into the plugins using the class method *plugin_install_methods, which may be called in the Pluggable class definition. For example:
55
+
56
+ require 'rubygems'
57
+ require 'pluggable'
58
+ module PluginAPI
59
+ def initialize(one, two, three); end
60
+ def first; "first"; end
61
+ def second; private_second; end
62
+ module ClassMethods
63
+ def class_first; "class_first"; end
64
+ end
65
+ private
66
+ def private_second; "second"; end
67
+ def self.included(klass)
68
+ klass.extend ClassMethods
69
+ end
70
+ end
71
+
72
+ class Test
73
+ include Pluggable
74
+ def initialize; install_plugins(:one, :two, :three); end # arity must match initialize methods for all plugins
75
+ def process; plugins.map {|plugin| plugin.process}; end
76
+ private
77
+ def method_missing symbol, *args
78
+ plugins.send(symbol, *args)
79
+ end
80
+ end
81
+
82
+ require 'plugin_definitions'
83
+
84
+ Test.new.plugins.first.first # => "first"
85
+ Test.new.plugins.first.second # => "second"
86
+ Test.new.plugins.first.class.class_first # => "class_first"
87
+
88
+ And of course, the plugins may override the API definitions.
89
+
90
+ == Note on Patches/Pull Requests
91
+
92
+ * Fork the project.
93
+ * Make your feature addition or bug fix.
94
+ * Add tests for it. This is important so I don't break it in a
95
+ future version unintentionally.
96
+ * Commit, do not mess with rakefile, version, or history.
97
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
98
+ * Send me a pull request. Bonus points for topic branches.
99
+
100
+ == Copyright
101
+
102
+ Copyright (c) 2009 Andrew C. Greenberg. See LICENSE for details.
@@ -0,0 +1,48 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "pluggable"
8
+ gem.summary = %Q{Pluggable is a mixin for classes requiring plugins.}
9
+ gem.description = %Q{Pluggable classes are automatically registered from classes that subclass from <classname>::Plugin, and can use tools for managing, installing and delegating methods therefrom.}
10
+ gem.email = "wizardwerdna@gmail.com"
11
+ gem.homepage = "http://github.com/wizardwerdna/pluggable"
12
+ gem.authors = ["Andrew C. Greenberg"]
13
+ gem.add_development_dependency "rspec"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ rescue LoadError
17
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
18
+ end
19
+
20
+ require 'spec/rake/spectask'
21
+ Spec::Rake::SpecTask.new(:spec) do |spec|
22
+ spec.libs << 'lib' << 'spec'
23
+ spec.spec_files = FileList['spec/**/*_spec.rb']
24
+ end
25
+
26
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
27
+ spec.libs << 'lib' << 'spec'
28
+ spec.pattern = 'spec/**/*_spec.rb'
29
+ spec.rcov = true
30
+ end
31
+
32
+ task :spec => :check_dependencies
33
+
34
+ task :default => :spec
35
+
36
+ require 'rake/rdoctask'
37
+ Rake::RDocTask.new do |rdoc|
38
+ if File.exist?('VERSION')
39
+ version = File.read('VERSION')
40
+ else
41
+ version = ""
42
+ end
43
+
44
+ rdoc.rdoc_dir = 'rdoc'
45
+ rdoc.title = "pluggable #{version}"
46
+ rdoc.rdoc_files.include('README*')
47
+ rdoc.rdoc_files.include('lib/**/*.rb')
48
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.0
@@ -0,0 +1,75 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'singleton'
5
+ require 'forwardable'
6
+ module Pluggable
7
+
8
+ class Plugins < Array
9
+ extend Forwardable
10
+ def self.from_array_of_instance_and_name_pairs array
11
+ result = new
12
+ array.each do |each_pair|
13
+ result << each_pair[:instance]
14
+ result.instance_variable_set each_pair[:name], each_pair[:instance]
15
+ end
16
+ result
17
+ end
18
+ end
19
+
20
+ class PluginFactory < Array
21
+ include Singleton
22
+ def build_plugins(*args)
23
+ array_of_instance_and_name_pairs = map do |each|
24
+ instance = each.new(*args)
25
+ {:name => variable_name_for_plugin_instance(instance), :instance => instance}
26
+ end
27
+ Plugins.from_array_of_instance_and_name_pairs(array_of_instance_and_name_pairs)
28
+ end
29
+ def delegate_plugin_public_methods_to_plugins_class_except *excluded_methods
30
+ excluded_methods = excluded_methods.map{|each| each.to_s}
31
+ each do |klass|
32
+ delegated_methods = klass.public_instance_methods-Plugin.public_instance_methods-excluded_methods
33
+ variable_name = variable_name_for_plugin_class klass
34
+ Plugins.def_delegators variable_name, *delegated_methods
35
+ end
36
+ end
37
+ private
38
+ def variable_name_for_plugin_instance instance
39
+ variable_name_for_plugin_class instance.class
40
+ end
41
+ def variable_name_for_plugin_class klass
42
+ "@ivar_for_#{klass.to_s}".gsub(/::/) {'_colons_'}.to_sym
43
+ end
44
+ end
45
+
46
+ class Plugin
47
+ def self.inherited(klass)
48
+ PluginFactory.instance << klass
49
+ end
50
+ end
51
+
52
+ def install_plugins(*args)
53
+ instance_variable_set :@pluggable_module_plugins, PluginFactory.instance.build_plugins(*args)
54
+ end
55
+
56
+ def plugins
57
+ instance_variable_get :@pluggable_module_plugins
58
+ end
59
+
60
+ def self.included(klass)
61
+ klass.extend ClassMethods
62
+ end
63
+
64
+ module ClassMethods
65
+ def plugin_factory
66
+ PluginFactory.instance
67
+ end
68
+ def delegate_plugin_public_methods_except *excluded_methods
69
+ PluginFactory.instance.delegate_plugin_public_methods_to_plugins_class_except *excluded_methods
70
+ end
71
+ def plugin_include_module mod
72
+ Plugin.class_eval "include #{mod.to_s}"
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,53 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{pluggable}
8
+ s.version = "0.3.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Andrew C. Greenberg"]
12
+ s.date = %q{2009-08-21}
13
+ s.description = %q{Pluggable classes are automatically registered from classes that subclass from <classname>::Plugin, and can use tools for managing, installing and delegating methods therefrom.}
14
+ s.email = %q{wizardwerdna@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "lib/pluggable.rb",
27
+ "pluggable.gemspec",
28
+ "spec/pluggable_spec.rb",
29
+ "spec/spec_helper.rb"
30
+ ]
31
+ s.homepage = %q{http://github.com/wizardwerdna/pluggable}
32
+ s.rdoc_options = ["--charset=UTF-8"]
33
+ s.require_paths = ["lib"]
34
+ s.rubygems_version = %q{1.3.5}
35
+ s.summary = %q{Pluggable is a mixin for classes requiring plugins.}
36
+ s.test_files = [
37
+ "spec/pluggable_spec.rb",
38
+ "spec/spec_helper.rb"
39
+ ]
40
+
41
+ if s.respond_to? :specification_version then
42
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
43
+ s.specification_version = 3
44
+
45
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
46
+ s.add_development_dependency(%q<rspec>, [">= 0"])
47
+ else
48
+ s.add_dependency(%q<rspec>, [">= 0"])
49
+ end
50
+ else
51
+ s.add_dependency(%q<rspec>, [">= 0"])
52
+ end
53
+ end
@@ -0,0 +1,189 @@
1
+ require 'forwardable'
2
+ require File.dirname(__FILE__) + '/spec_helper.rb'
3
+
4
+ module PluginAPI
5
+ def initialize(one, two, three); end
6
+ def first; "first"; end
7
+ def second; private_second; end
8
+ module ClassMethods
9
+ def class_first; "class_first"; end
10
+ end
11
+ private
12
+ def private_second; "second"; end
13
+ def self.included(klass)
14
+ klass.extend ClassMethods
15
+ end
16
+ end
17
+
18
+ class Test
19
+ include Pluggable
20
+ plugin_include_module PluginAPI
21
+ def process
22
+ plugins.map{|each| each.process}
23
+ end
24
+ private
25
+ def method_missing symbol, *args
26
+ plugins.send(symbol, *args)
27
+ end
28
+ end
29
+
30
+ class Plugin1 < Test::Plugin
31
+ def foo; "foo"; end
32
+ def process; foo; end
33
+ private
34
+ def exception_private; end
35
+ end
36
+
37
+ class Plugin2 < Test::Plugin
38
+ def bar; "bar"; end
39
+ def process; bar; end
40
+ private
41
+ def exception_private; end
42
+ end
43
+
44
+ class Plugin3 < Test::Plugin
45
+ def baz; "baz"; end
46
+ def process; baz; end
47
+ private
48
+ def exception_private; end
49
+ end
50
+
51
+ if Test.respond_to? :delegate_plugin_public_methods_except
52
+ Test.delegate_plugin_public_methods_except :exception_public, :one, :two, :three, :initialize
53
+ end
54
+
55
+
56
+ describe Pluggable, "when included in a class" do
57
+
58
+ before(:each) do
59
+ @test_instance = Test.new
60
+ end
61
+
62
+ it "should install a class method #plugin_factory, an array of all the plugin classes" do
63
+ Test.should respond_to(:plugin_factory)
64
+ Test.plugin_factory.should have(3).items
65
+ Test.plugin_factory.should include(Plugin1, Plugin2, Plugin3)
66
+ end
67
+
68
+ it "should install a class method #delegate_plugin_public_methods_except" do
69
+ Test.should respond_to(:delegate_plugin_public_methods_except)
70
+ end
71
+
72
+ it "should install a class method #plugin_include_module" do
73
+ Test.should respond_to(:plugin_include_module)
74
+ end
75
+
76
+ it "should add a method #plugins that should be nil upon creation" do
77
+ @test_instance.should respond_to(:plugins)
78
+ @test_instance.plugins.should be_nil
79
+ end
80
+
81
+ it "should add a method #install_plugins" do
82
+ Test.new.should respond_to(:install_plugins)
83
+ end
84
+
85
+ it "should properly instantiate plugins upon a call to #install_plugins" do
86
+ [Plugin1, Plugin2, Plugin3].each{|each| each.should_receive(:new).with(:one, :two, :three)}
87
+ Test.new.install_plugins(:one, :two, :three)
88
+ end
89
+ end
90
+
91
+ describe Pluggable, "after installing plugins" do
92
+ before(:each) do
93
+ @test_instance = Test.new
94
+ @test_instance.install_plugins(:one, :two, :three)
95
+ end
96
+
97
+ it "should have #plugins answer an object containing a collection of new plugin instances after #install_plugins" do
98
+ @test_instance.should_not be_nil
99
+ @test_instance.plugins.should_not be_nil
100
+ @test_instance.plugins.should be_an_instance_of(Test::Plugins)
101
+ @test_instance.plugins.should have(3).items
102
+ @test_instance.plugins.map{|each| each.class}.should include(Plugin1, Plugin2, Plugin3)
103
+ end
104
+
105
+ it "should have #plugins answer an object containing a collection of new plugin instances after #install_plugins a second time" do
106
+ @test_instance.should_not be_nil
107
+ @test_instance.plugins.should_not be_nil
108
+ @test_instance.plugins.should be_an_instance_of(Test::Plugins)
109
+ @test_instance.plugins.should have(3).items
110
+ @test_instance.plugins.map{|each| each.class}.should include(Plugin1, Plugin2, Plugin3)
111
+ end
112
+ end
113
+
114
+ describe Pluggable, "after Test has delegated all but excepted plugin public methods" do
115
+ before(:each) do
116
+ @test_instance = Test.new
117
+ @test_instance.install_plugins(:one, :two, :three)
118
+ end
119
+
120
+ it "should delegate public methods of plugins" do
121
+ @test_instance.plugins.should respond_to(:foo)
122
+ @test_instance.plugins.should respond_to(:bar)
123
+ @test_instance.plugins.should respond_to(:baz)
124
+ @test_instance.plugins.foo.should == "foo"
125
+ @test_instance.plugins.bar.should == "bar"
126
+ @test_instance.plugins.baz.should == "baz"
127
+ end
128
+
129
+ it "should not delegate excepted public methods of plugins" do
130
+ @test_instance.plugins.should_not respond_to(:exception_public)
131
+ @test_instance.should_not respond_to(:exception_public)
132
+ end
133
+
134
+ it "should not delegate private methods of plugins" do
135
+ @test_instance.plugins.should_not respond_to(:exception_private)
136
+ @test_instance.should_not respond_to(:exception_private)
137
+ end
138
+ end
139
+
140
+ describe Test::Plugin, "after Test has included a module" do
141
+ it "should have the instance methods of the module" do
142
+ Test::Plugin.instance_methods.should include(*PluginAPI.instance_methods)
143
+ end
144
+
145
+ it "should have the class methods of the module" do
146
+ Test::Plugin.methods.should include(*PluginAPI::ClassMethods.instance_methods)
147
+ end
148
+ end
149
+
150
+ describe Test::PluginFactory, "instances" do
151
+ before(:each) do
152
+ @instance = Test::PluginFactory.instance
153
+ end
154
+
155
+ it "should be an array of all the plugins" do
156
+ @instance.should have(3).items
157
+ @instance.should include(Plugin1, Plugin2, Plugin3)
158
+ end
159
+
160
+ it "should build a Plugins object with fresh instances of each plugin" do
161
+ @installed_plugins = @instance.build_plugins(:one, :two, :three)
162
+ @installed_plugins.should be_a_kind_of(Test::Plugins)
163
+ @installed_plugins.should have(3).items
164
+ @installed_plugins.map{|each| each.class}.should include(Plugin1, Plugin2, Plugin3)
165
+ end
166
+ end
167
+
168
+ describe Test, "when using message_missing to simulate delegation from the parent" do
169
+ before(:each) do
170
+ @test_instance = Test.new
171
+ @test_instance.install_plugins(:one, :two, :three)
172
+ end
173
+
174
+ it "should properly process using all the plugins" do
175
+ @test_instance.process.should == ["foo", "bar", "baz"]
176
+ end
177
+
178
+ it "should delegate public methods of plugins" do
179
+ @test_instance.foo.should == "foo"
180
+ @test_instance.bar.should == "bar"
181
+ @test_instance.baz.should == "baz"
182
+ end
183
+
184
+ it "should work with included module procedures" do
185
+ @test_instance.plugins.first.first.should == "first"
186
+ @test_instance.plugins.first.second.should == "second"
187
+ @test_instance.plugins.first.class.class_first.should == "class_first"
188
+ end
189
+ end
@@ -0,0 +1,9 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'pluggable'
4
+ require 'spec'
5
+ require 'spec/autorun'
6
+
7
+ Spec::Runner.configure do |config|
8
+
9
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pluggable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrew C. Greenberg
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-08-21 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ description: Pluggable classes are automatically registered from classes that subclass from <classname>::Plugin, and can use tools for managing, installing and delegating methods therefrom.
26
+ email: wizardwerdna@gmail.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - LICENSE
33
+ - README.rdoc
34
+ files:
35
+ - .document
36
+ - .gitignore
37
+ - LICENSE
38
+ - README.rdoc
39
+ - Rakefile
40
+ - VERSION
41
+ - lib/pluggable.rb
42
+ - pluggable.gemspec
43
+ - spec/pluggable_spec.rb
44
+ - spec/spec_helper.rb
45
+ has_rdoc: true
46
+ homepage: http://github.com/wizardwerdna/pluggable
47
+ licenses: []
48
+
49
+ post_install_message:
50
+ rdoc_options:
51
+ - --charset=UTF-8
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
66
+ requirements: []
67
+
68
+ rubyforge_project:
69
+ rubygems_version: 1.3.5
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: Pluggable is a mixin for classes requiring plugins.
73
+ test_files:
74
+ - spec/pluggable_spec.rb
75
+ - spec/spec_helper.rb