module-pluggable 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Changelog ADDED
@@ -0,0 +1,8 @@
1
+ 2007-10-06 SATOH Hiroh <cho45@lowreal.net>
2
+
3
+ * 0.0.1 release
4
+
5
+ 2007-10-04 SATOH Hiroh <cho45@lowreal.net>
6
+
7
+ * Initial Coding.
8
+ This library is derivation of chokan.
data/README ADDED
@@ -0,0 +1,41 @@
1
+ = README for module-pluggable
2
+
3
+ module-pluggable provides plugin system for classes.
4
+
5
+ == Installation
6
+
7
+ === Archive Installation
8
+
9
+ rake install
10
+
11
+ === Gem Installation
12
+
13
+ gem install module-pluggable
14
+
15
+ === Subversion Repository
16
+
17
+ Hosted by CodeRepos[http://coderepos.org/share/browser/lang/ruby/module-pluggable]
18
+
19
+ svn co http://svn.coderepos.org/share/lang/ruby/module-pluggable/
20
+
21
+ == Examples
22
+
23
+ class APluggableClass
24
+ pluggable :plugins
25
+
26
+ def initialize
27
+ plugins.init(self)
28
+ end
29
+
30
+ def say
31
+ plugins.say("hello")
32
+ end
33
+ end
34
+
35
+ see examples/simple.rb.
36
+
37
+ == Copyright
38
+
39
+ Author:: cho45 <cho45@lowreal.net>
40
+ Copyright:: Copyright (c) 2007 cho45 www.lowreal.net
41
+ License:: Ruby's
data/Rakefile ADDED
@@ -0,0 +1,144 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/testtask'
5
+ require 'rake/packagetask'
6
+ require 'rake/gempackagetask'
7
+ require 'rake/rdoctask'
8
+ require 'rake/contrib/rubyforgepublisher'
9
+ require 'rubyforge'
10
+ require 'fileutils'
11
+ include FileUtils
12
+
13
+ AUTHOR = "cho45"
14
+ EMAIL = "cho45@lowreal.net"
15
+ DESCRIPTION = ""
16
+ RUBYFORGE_PROJECT = "modulepluggable"
17
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
18
+ BIN_FILES = %w( )
19
+ VERS = "0.0.1"
20
+
21
+
22
+ NAME = "module-pluggable"
23
+ REV = File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
24
+ CLEAN.include ['**/.*.sw?', '*.gem', '.config']
25
+ RDOC_OPTS = ['--quiet', '--title', "#{NAME} documentation",
26
+ "--charset", "utf-8",
27
+ "--opname", "index.html",
28
+ "--line-numbers",
29
+ "--main", "README",
30
+ "--inline-source"]
31
+
32
+ desc "Packages up #{NAME} gem."
33
+ task :default => [:test]
34
+ task :package => [:clean]
35
+
36
+ Rake::TestTask.new("test") { |t|
37
+ t.libs << "test"
38
+ t.pattern = "test/**/*_test.rb"
39
+ t.verbose = true
40
+ }
41
+
42
+ spec = Gem::Specification.new do |s|
43
+ s.name = NAME
44
+ s.version = VERS
45
+ s.platform = Gem::Platform::RUBY
46
+ s.has_rdoc = true
47
+ s.extra_rdoc_files = ["README", "Changelog"]
48
+ s.rdoc_options += RDOC_OPTS + ['--exclude', '^(extras)/']
49
+ s.summary = DESCRIPTION
50
+ s.description = DESCRIPTION
51
+ s.author = AUTHOR
52
+ s.email = EMAIL
53
+ s.homepage = HOMEPATH
54
+ s.executables = BIN_FILES
55
+ s.rubyforge_project = RUBYFORGE_PROJECT
56
+ s.bindir = "bin"
57
+ s.require_path = "lib"
58
+ #s.autorequire = "safe_eval"
59
+ s.test_files = Dir["test/test_*.rb"]
60
+
61
+ #s.add_dependency('activesupport', '>=1.3.1')
62
+ #s.required_ruby_version = '>= 1.8.2'
63
+
64
+ s.files = %w(README Changelog Rakefile) +
65
+ Dir.glob("{bin,doc,test,lib,templates,generator,extras,website,script}/**/*") +
66
+ Dir.glob("ext/**/*.{h,c,rb}") +
67
+ Dir.glob("examples/**/*.rb") +
68
+ Dir.glob("tools/*.rb")
69
+
70
+ s.extensions = FileList["ext/**/extconf.rb"].to_a
71
+ end
72
+
73
+ Rake::GemPackageTask.new(spec) do |p|
74
+ p.need_tar = true
75
+ p.gem_spec = spec
76
+ end
77
+
78
+ task :install do
79
+ name = "#{NAME}-#{VERS}.gem"
80
+ sh %{rake package}
81
+ sh %{sudo gem install pkg/#{name}}
82
+ end
83
+
84
+ task :uninstall => [:clean] do
85
+ sh %{sudo gem uninstall #{NAME}}
86
+ end
87
+
88
+
89
+ Rake::RDocTask.new do |rdoc|
90
+ rdoc.rdoc_dir = 'html'
91
+ rdoc.options += RDOC_OPTS
92
+ rdoc.template = "#{ENV["HOME"]}/coderepos/lang/ruby/rdoc/generators/template/html/resh/resh.rb"
93
+ #rdoc.template = "#{ENV['template']}.rb" if ENV['template']
94
+ if ENV['DOC_FILES']
95
+ rdoc.rdoc_files.include(ENV['DOC_FILES'].split(/,\s*/))
96
+ else
97
+ rdoc.rdoc_files.include('README', 'Changelog')
98
+ rdoc.rdoc_files.include('examples/simple.rb')
99
+ rdoc.rdoc_files.include('lib/**/*.rb')
100
+ rdoc.rdoc_files.include('ext/**/*.c')
101
+ end
102
+ end
103
+
104
+ desc "Publish to RubyForge"
105
+ task :rubyforge => [:rdoc, :package] do
106
+ Rake::RubyForgePublisher.new(RUBYFORGE_PROJECT, 'cho45').upload
107
+ end
108
+
109
+ desc "Publish to lab"
110
+ task :publab do
111
+ require 'rake/contrib/sshpublisher'
112
+
113
+ path = File.expand_path(File.dirname(__FILE__))
114
+
115
+ Rake::SshDirPublisher.new(
116
+ "cho45@lab.lowreal.net",
117
+ "/srv/www/lab.lowreal.net/public/site-ruby",
118
+ path + "/pkg"
119
+ ).upload
120
+ end
121
+
122
+ desc 'Package and upload the release to rubyforge.'
123
+ task :release => [:clean, :package] do |t|
124
+ v = ENV["VERSION"] or abort "Must supply VERSION=x.y.z"
125
+ abort "Versions don't match #{v} vs #{VERS}" unless v == VERS
126
+ pkg = "pkg/#{NAME}-#{VERS}"
127
+
128
+ rf = RubyForge.new
129
+ puts "Logging in"
130
+ rf.login
131
+
132
+ c = rf.userconfig
133
+ # c["release_notes"] = description if description
134
+ # c["release_changes"] = changes if changes
135
+ c["preformatted"] = true
136
+
137
+ files = [
138
+ "#{pkg}.tgz",
139
+ "#{pkg}.gem"
140
+ ].compact
141
+
142
+ puts "Releasing #{NAME} v. #{VERS}"
143
+ rf.add_release RUBYFORGE_PROJECT, NAME, VERS, *files
144
+ end
@@ -0,0 +1,10 @@
1
+
2
+ class HogeHoge
3
+ def init(parent)
4
+ @parent = parent
5
+ end
6
+
7
+ def say
8
+ "Hehe, I'm #{self.class} plugin of #{@parent}."
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+
2
+ class Test
3
+ def init(parent)
4
+ @parent = parent
5
+ end
6
+
7
+ def say
8
+ "I'm #{self.class} plugin of #{@parent}."
9
+ end
10
+ end
@@ -0,0 +1,36 @@
1
+ #!ruby -I../lib simple.rb
2
+
3
+ require "module/pluggable"
4
+
5
+ module Example
6
+
7
+ class SimplePluggable
8
+ # pluggable [name=:plugins] [opts]
9
+ # pluggable make the name of instance method
10
+ # returning instance of Module::Pluggable::Plugin.
11
+ #
12
+ # All loaded plugins are in anonymous module,
13
+ # so you can't access the classes directly,
14
+ # and you can create some plugin-sets
15
+ # without confusing class variables etc.
16
+ pluggable
17
+
18
+ # plugins.init(self)
19
+ #
20
+ # `init' method is not defined on Module::Pluggable::Plugin.
21
+ # undefined methods are delegated to `call' the plugins.
22
+ # In this case, `plugins.init(self)' is same as `plugins.call(:init, self)'.
23
+ def initialize
24
+ plugins.init(self)
25
+ end
26
+
27
+ def say
28
+ plugins.each do |name, instance|
29
+ puts instance.say
30
+ end
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+ Example::SimplePluggable.new.say
@@ -0,0 +1,182 @@
1
+ # Author:: cho45 <cho45@lowreal.net>
2
+ # Copyright:: copyright (c) 2007 cho45 www.lowreal.net
3
+ # License:: Ruby's
4
+
5
+ require "pathname"
6
+
7
+ module Module::Pluggable
8
+ DEFAULT_OPTS = {
9
+ :search_path => "plugins",
10
+ :except => /_$/,
11
+ :base_class => nil,
12
+ }.freeze
13
+
14
+ # pluggable make the name of instance method
15
+ # returning instance of Module::Pluggable::Plugin.
16
+ #
17
+ # All loaded plugins are in anonymous module,
18
+ # so you can't access the classes directly,
19
+ # and you can create some plugin-sets
20
+ # without confusing class variables etc.
21
+ #
22
+ # opts => {
23
+ # :search_path => name,
24
+ # :base_class => nil,
25
+ # :except => /_$/, # not yet
26
+ # }
27
+ def pluggable(name=:plugins, o={})
28
+ opts = DEFAULT_OPTS.merge(o)
29
+ opts[:search_path] = name ? name.to_s : opts[:name].to_s unless opts[:search_path]
30
+
31
+ class_eval <<-EOS
32
+ def #{name}
33
+ @#{name} ||= Module::Pluggable::Plugins.new(@@pluggable_opts[:#{name}])
34
+ end
35
+
36
+ def self.set_pluggable_opts(name, opts)
37
+ (@@pluggable_opts ||= {})[name] = opts
38
+ end
39
+ EOS
40
+ self.set_pluggable_opts(name, opts)
41
+ (class << self; self; end).instance_eval do
42
+ remove_method(:set_pluggable_opts)
43
+ end
44
+ end
45
+
46
+ class Plugins
47
+ include Enumerable
48
+
49
+ class PluginsError < StandardError; end
50
+ class ClassNotFoundError < PluginsError; end
51
+ class NotInheritAbstractClassError < PluginsError; end
52
+
53
+ def initialize(opts)
54
+ @opts = opts
55
+ @dir = Pathname.new(opts[:search_path])
56
+ @plugins = {}
57
+ reload
58
+ end
59
+
60
+ # Load +klass_name+.
61
+ # The plugin is loaded in anonymous module not order to
62
+ # destroy the environments.
63
+ # And remember loaded time for reloading.
64
+ #
65
+ # plugin filename must be interconversion with its class name.
66
+ # In this class, the conversion is do with +file2klass+/+klass2file+ methods.
67
+ def load(klass_name)
68
+ return if @plugins.include?(klass_name)
69
+
70
+ filename = klass2file(klass_name)
71
+
72
+ mod = Module.new
73
+ mod.module_eval(File.open("#{@opts[:search_path]}/#{filename}.rb") {|f| f.read}, filename)
74
+
75
+ c = nil
76
+ begin
77
+ c = mod.const_get(klass_name)
78
+ rescue NameError
79
+ raise ClassNotFoundError.new("#{@opts[:search_path]}/#{filename} must include #{klass_name} class")
80
+ end
81
+
82
+ if !@opts[:base_class] || c < @opts[:base_class]
83
+ @plugins[klass_name] = {
84
+ :instance => c.new,
85
+ :loaded => Time.now,
86
+ }
87
+ else
88
+ raise NotInheritAbstractClassError.new("The class #{klass_name} must inherit #{@opts[:base_class]}")
89
+ end
90
+
91
+ @plugins[klass_name][:instance].on_load rescue NameError
92
+ @plugins[klass_name][:instance].instance_variable_set(:@plugins, self)
93
+
94
+ klass_name
95
+ end
96
+
97
+ # Get instance of +klass_name+ plugin.
98
+ def [](klass_name)
99
+ @plugins[klass_name][:instance] if @plugins.key?(klass_name)
100
+ end
101
+
102
+ # Unload +klass_name
103
+ def unload(klass_name)
104
+ if @plugins.key?(klass_name)
105
+ @plugins[klass_name][:instance].on_unload rescue NameError
106
+ @plugins.delete(klass_name)
107
+ end
108
+ end
109
+
110
+ # Reload +klass_name+ or
111
+ # load unloaded plugins or
112
+ # reload modified plugins.
113
+ # returns [loaded, unloaded]
114
+ def reload(klass_name=nil)
115
+ if klass_name
116
+ unload(klass_name)
117
+ load(klass_name)
118
+ klass_name
119
+ else
120
+ loaded = []
121
+ unloaded = []
122
+ Dir.glob("#{@opts[:search_path]}/*.rb") do |f|
123
+ klass_name = file2klass(File.basename(f, ".rb").sub(/^\d+/, ""))
124
+ if @plugins.include?(klass_name)
125
+ if File.mtime(f) > @plugins[klass_name][:loaded]
126
+ loaded << reload(klass_name)
127
+ end
128
+ else
129
+ loaded << reload(klass_name)
130
+ end
131
+ end
132
+ [loaded, unloaded]
133
+ end
134
+ end
135
+
136
+ # Unload all plugins and reload it.
137
+ def force_reload
138
+ call(:on_unload)
139
+ @plugins.clear
140
+ reload
141
+ end
142
+
143
+ # Iterates with plugin name and its instance.
144
+ def each(&block)
145
+ @plugins.each do |k,v|
146
+ yield k, v[:instance]
147
+ end
148
+ end
149
+
150
+ # Call +name+ method of each plugins with +args+
151
+ # and returns Hash of the result and its plugin name.
152
+ def call(name, *args)
153
+ ret = {}
154
+ each do |k,v|
155
+ ret[k] = v.send(name, *args) if v.respond_to?(name)
156
+ end
157
+ ret
158
+ end
159
+
160
+ # Undefined methods are delegated to each plugins.
161
+ # This is alias of +call+
162
+ def method_missing(name, *args)
163
+ call(name, *args)
164
+ end
165
+
166
+ private
167
+ # convert foo/foo_bar to Foo::FooBar
168
+ def file2klass(str)
169
+ str.split("/").map {|c|
170
+ c.split(/_/).collect {|i| i.capitalize }.join("")
171
+ }.join("::")
172
+ end
173
+
174
+ # convert Foo::FooBar to foo/foo_bar
175
+ def klass2file(str)
176
+ str.split(/::/).map {|c|
177
+ c.scan(/[A-Z][a-z0-9]*/).join("_").downcase
178
+ }.join("/")
179
+ end
180
+ end
181
+ end
182
+ Class.instance_eval { include Module::Pluggable }
@@ -0,0 +1,88 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+ require "pathname"
3
+ require "tmpdir"
4
+ require "fileutils"
5
+
6
+ class TestModulePluggable < Test::Unit::TestCase
7
+ def setup
8
+ @dir = Pathname.new(Dir.tmpdir) + "#{File.basename($0)}.#$$.#{rand(0xffffff)}"
9
+ @dir.mkpath
10
+ @test_dir = Pathname.new(File.dirname(__FILE__))
11
+ FileUtils.cp_r(@test_dir + "plugins", @dir)
12
+ @plugins_dir = @dir + "plugins"
13
+ end
14
+
15
+ def teardown
16
+ @dir.rmtree
17
+ end
18
+
19
+ def test_classname_conversion
20
+ m = Module::Pluggable::Plugins.new({:search_path => @plugins_dir})
21
+ assert_equal "foo_bar", m.__send__(:klass2file, "FooBar")
22
+ assert_equal "FooBar", m.__send__(:file2klass, "foo_bar")
23
+ assert_equal "foo/foo_bar", m.__send__(:klass2file, "Foo::FooBar")
24
+ assert_equal "Foo::FooBar", m.__send__(:file2klass, "foo/foo_bar")
25
+ end
26
+
27
+ def test_success
28
+ path = @plugins_dir
29
+ test = Class.new {
30
+ pluggable :plugins, :search_path => path
31
+ }.new
32
+ assert test.plugins["Test"]
33
+ assert_equal "This is test plugin.", test.plugins.call(:description)["Test"]
34
+
35
+ obj = Object.new
36
+ test.plugins.call(:instance_variable_set, :@test_obj, obj)
37
+ assert_equal obj, test.plugins["Test"].instance_variable_get(:@test_obj)
38
+ assert_equal test.plugins, test.plugins["Test"].foobar
39
+
40
+ assert_equal "This is test plugin.", test.plugins.description["Test"]
41
+
42
+ # inherit test
43
+ test = Class.new(test.class).new
44
+ assert test.plugins["Test"]
45
+ assert_equal "This is test plugin.", test.plugins.call(:description)["Test"]
46
+
47
+ test = Class.new {
48
+ pluggable :plugins, :search_path => "'"
49
+ }.new
50
+ end
51
+
52
+ def test_multi
53
+ path = @plugins_dir
54
+ test = Class.new {
55
+ pluggable :plugins, :search_path => path
56
+ pluggable :filters, :search_path => path
57
+ }.new
58
+ assert test.plugins["Test"]
59
+ assert_equal "This is test plugin.", test.plugins.call(:description)["Test"]
60
+ assert test.filters["Test"]
61
+ assert_equal "This is test plugin.", test.filters.call(:description)["Test"]
62
+ end
63
+
64
+ def test_inherit
65
+ path = @plugins_dir
66
+ assert_raise(Module::Pluggable::Plugins::NotInheritAbstractClassError) do
67
+ test = Class.new {
68
+ pluggable :plugins, :search_path => path, :base_class => PluginBase
69
+ }.new
70
+ test.plugins
71
+ end
72
+
73
+ testpl = path + "test.rb"
74
+ n = testpl.read.sub(/class Test/, "class Test < TestModulePluggable::PluginBase")
75
+ testpl.open("w") {|f| f.puts n }
76
+
77
+ assert_nothing_raised do
78
+ test = Class.new {
79
+ pluggable :plugins, :search_path => path, :base_class => PluginBase
80
+ }.new
81
+ test.plugins
82
+ end
83
+ end
84
+
85
+ class PluginBase
86
+ # Nice boat.
87
+ end
88
+ end
@@ -0,0 +1,10 @@
1
+
2
+ class Test
3
+ def description
4
+ "This is test plugin."
5
+ end
6
+
7
+ def foobar
8
+ @plugins
9
+ end
10
+ end
@@ -0,0 +1,2 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/../lib/module/pluggable'
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.4
3
+ specification_version: 1
4
+ name: module-pluggable
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.0.1
7
+ date: 2007-10-06 00:00:00 +09:00
8
+ summary: ''
9
+ require_paths:
10
+ - lib
11
+ email: cho45@lowreal.net
12
+ homepage: http://modulepluggable.rubyforge.org
13
+ rubyforge_project: modulepluggable
14
+ description: ''
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ -
22
+ - ">"
23
+ - !ruby/object:Gem::Version
24
+ version: 0.0.0
25
+ version:
26
+ platform: ruby
27
+ signing_key:
28
+ cert_chain:
29
+ post_install_message:
30
+ authors:
31
+ - cho45
32
+ files:
33
+ - README
34
+ - Changelog
35
+ - Rakefile
36
+ - test/module-pluggable_test.rb
37
+ - test/plugins
38
+ - test/test_helper.rb
39
+ - test/plugins/test.rb
40
+ - lib/module
41
+ - lib/module/pluggable.rb
42
+ - examples/simple.rb
43
+ - examples/plugins/hoge_hoge.rb
44
+ - examples/plugins/test.rb
45
+ test_files:
46
+ - test/test_helper.rb
47
+ rdoc_options:
48
+ - "--quiet"
49
+ - "--title"
50
+ - module-pluggable documentation
51
+ - "--charset"
52
+ - utf-8
53
+ - "--opname"
54
+ - index.html
55
+ - "--line-numbers"
56
+ - "--main"
57
+ - README
58
+ - "--inline-source"
59
+ - "--exclude"
60
+ - "^(extras)/"
61
+ extra_rdoc_files:
62
+ - README
63
+ - Changelog
64
+ executables: []
65
+ extensions: []
66
+ requirements: []
67
+ dependencies: []