module-pluggable 0.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.
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: []