classx-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.
Files changed (42) hide show
  1. data/ChangeLog +221 -0
  2. data/README +88 -0
  3. data/Rakefile +52 -0
  4. data/doc/output/coverage/-Library-Ruby-Gems-gems-classx-0_0_5-lib-classx-attribute_rb.html +925 -0
  5. data/doc/output/coverage/-Library-Ruby-Gems-gems-classx-0_0_5-lib-classx-attributes_rb.html +772 -0
  6. data/doc/output/coverage/-Library-Ruby-Gems-gems-classx-0_0_5-lib-classx-bracketable_rb.html +671 -0
  7. data/doc/output/coverage/-Library-Ruby-Gems-gems-classx-0_0_5-lib-classx-role-logger_rb.html +716 -0
  8. data/doc/output/coverage/-Library-Ruby-Gems-gems-classx-0_0_5-lib-classx-validate_rb.html +663 -0
  9. data/doc/output/coverage/-Library-Ruby-Gems-gems-classx-0_0_5-lib-classx_rb.html +820 -0
  10. data/doc/output/coverage/-Library-Ruby-Gems-gems-diff-lcs-1_1_2-lib-diff-lcs-block_rb.html +661 -0
  11. data/doc/output/coverage/-Library-Ruby-Gems-gems-diff-lcs-1_1_2-lib-diff-lcs-callbacks_rb.html +932 -0
  12. data/doc/output/coverage/-Library-Ruby-Gems-gems-diff-lcs-1_1_2-lib-diff-lcs-change_rb.html +779 -0
  13. data/doc/output/coverage/-Library-Ruby-Gems-gems-diff-lcs-1_1_2-lib-diff-lcs-hunk_rb.html +867 -0
  14. data/doc/output/coverage/-Library-Ruby-Gems-gems-diff-lcs-1_1_2-lib-diff-lcs_rb.html +1715 -0
  15. data/doc/output/coverage/-Library-Ruby-Gems-gems-rcov-0_8_1_2_0-lib-rcov_rb.html +1598 -0
  16. data/doc/output/coverage/examples-test_runner-bin-test_runner_rb.html +628 -0
  17. data/doc/output/coverage/examples-test_runner-lib-test_runner-plugin-setup_fixture_rb.html +641 -0
  18. data/doc/output/coverage/examples-test_runner-lib-test_runner-plugin-test_info_rb.html +638 -0
  19. data/doc/output/coverage/examples-test_runner-lib-test_runner-plugin-test_timer_rb.html +666 -0
  20. data/doc/output/coverage/examples-test_runner-lib-test_runner_rb.html +643 -0
  21. data/doc/output/coverage/index.html +711 -0
  22. data/doc/output/coverage/lib-classx-pluggable-plugin_rb.html +676 -0
  23. data/doc/output/coverage/lib-classx-pluggable_rb.html +841 -0
  24. data/examples/test_runner/bin/test_runner.rb +18 -0
  25. data/examples/test_runner/conf/config.yaml +19 -0
  26. data/examples/test_runner/lib/test_runner.rb +33 -0
  27. data/examples/test_runner/lib/test_runner/plugin/setup_fixture.rb +31 -0
  28. data/examples/test_runner/lib/test_runner/plugin/test_info.rb +28 -0
  29. data/examples/test_runner/lib/test_runner/plugin/test_timer.rb +56 -0
  30. data/lib/classx/pluggable.rb +231 -0
  31. data/lib/classx/pluggable/plugin.rb +66 -0
  32. data/spec/classx-pluggable-util/module2path_spec.rb +30 -0
  33. data/spec/classx-pluggable-util/nested_autoload_spec.rb +48 -0
  34. data/spec/classx-pluggable-util/nested_const_get_spec.rb +48 -0
  35. data/spec/classx-pluggable/component_class_get.rb +18 -0
  36. data/spec/classx-pluggable_spec.rb +5 -0
  37. data/spec/example_spec.rb +20 -0
  38. data/spec/spec.opts +1 -0
  39. data/spec/spec_helper.rb +6 -0
  40. data/tasks/basic_config.rake +22 -0
  41. data/tasks/basic_tasks.rake +139 -0
  42. metadata +127 -0
@@ -0,0 +1,18 @@
1
+ $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')))
2
+
3
+ require 'test_runner'
4
+
5
+ require 'yaml'
6
+ conf = ARGV[0] || File.join(File.dirname(__FILE__), '..', 'conf/config.yaml')
7
+
8
+ config = nil
9
+ File.open(conf) do |f|
10
+ config = YAML.load(f.read)
11
+ end
12
+ app = TestRunner.new(config["global"].merge({
13
+ :test_cases => [:foo, :bar, :baz],
14
+ :logger => $logger ? $logger : Logger.new($stderr) # FIXME: It's for spec/example_spec.rb
15
+ }))
16
+ app.load_plugins(config["plugins"])
17
+
18
+ app.run
@@ -0,0 +1,19 @@
1
+ ---
2
+ global:
3
+ log_level: debug
4
+ check_events: true
5
+ events:
6
+ - BEFORE_EACH
7
+ - AROUND_ALL
8
+ - AROUND_EACH
9
+
10
+ plugins:
11
+ - module: TestRunner::Plugin::SetupFixture #=> autoloading TestRunner::Plugin::SetupFixture, "test_runner/plugin/setup_fixture"
12
+ - module: +TestTimer # same means of TestRunner::Plugin::TestTimer #=> autoloading TestRunner::Plugin::TestTimer, "test_runner/plugin/test_timer"
13
+ - module: +TestInfo
14
+ config:
15
+ template: start test %s
16
+ - module: +TestInfo
17
+ config:
18
+ template: you can also ouput other info for %s
19
+
@@ -0,0 +1,33 @@
1
+ require 'classx'
2
+ require 'classx/validate'
3
+ $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '../../../lib')))
4
+ require 'classx/pluggable'
5
+ require 'classx/pluggable/plugin'
6
+
7
+ class TestRunner
8
+ include ClassX
9
+ include ClassX::Role::Logger
10
+ include ClassX::Pluggable
11
+
12
+ module Plugin; end
13
+
14
+ has :test_cases
15
+
16
+ def run
17
+ call_event_around(:ALL, :logger => logger) do
18
+
19
+ test_cases.each do |tc|
20
+ _do_test(tc)
21
+ end
22
+
23
+ end
24
+ end
25
+
26
+ # it's dummy
27
+ def _do_test tc
28
+ call_event_around(:EACH, :logger => logger, :test => tc) do
29
+ sleep(0.1)
30
+ end
31
+ end
32
+ end
33
+
@@ -0,0 +1,31 @@
1
+ require 'classx'
2
+ require 'classx/validate'
3
+ $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '../../../../lib')))
4
+ require 'classx/pluggable/plugin'
5
+
6
+ class TestRunner
7
+ module Plugin
8
+ class SetupFixture
9
+ include ClassX
10
+ include ClassX::Pluggable::Plugin
11
+ include ClassX::Pluggable::Plugin::AutoRegister
12
+
13
+ # without C::P::P::AutoRegister
14
+ # you can do followings:
15
+ #
16
+ # define_events({
17
+ # :AROUND_ALL => :on_around_all,
18
+ # })
19
+
20
+ def on_around_all param
21
+ param = ClassX::Validate.validate param do
22
+ has :logger
23
+ end
24
+
25
+ param.logger.info "#{self.class}: Setup Fixture"
26
+ yield
27
+ param.logger.info "#{self.class}: Clear Fixture"
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,28 @@
1
+ require 'classx'
2
+ require 'classx/validate'
3
+ $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '../../../../lib')))
4
+ require 'classx/pluggable/plugin'
5
+
6
+ class TestRunner
7
+ module Plugin
8
+ class TestInfo
9
+ include ClassX
10
+ include ClassX::Pluggable::Plugin
11
+ include ClassX::Pluggable::Plugin::AutoRegister
12
+
13
+ has :template
14
+
15
+ def on_before_each param
16
+ param = ClassX::Validate.validate param do
17
+ has :logger
18
+ has :test
19
+ end
20
+
21
+ info = self.template % [ param.test ]
22
+ param.logger.info "#{self.class}: #{info}"
23
+
24
+ info
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,56 @@
1
+ require 'classx'
2
+ require 'classx/validate'
3
+ $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '../../../../lib')))
4
+ require 'classx/pluggable/plugin'
5
+
6
+
7
+ class TestRunner
8
+ module Plugin
9
+ class TestTimer
10
+ include ClassX
11
+ include ClassX::Pluggable::Plugin
12
+ include ClassX::Pluggable::Plugin::AutoRegister
13
+
14
+ # without C::P::P::AutoRegister
15
+ # you can do followings:
16
+ #
17
+ # define_events({
18
+ # :AROUND_ALL => :on_around_all,
19
+ # :AROUND_EACH => :on_arond_each,
20
+ # })
21
+
22
+ def on_around_all param
23
+ param = ClassX::Validate.validate param do
24
+ has :logger
25
+ end
26
+
27
+ param.logger.info "#{self.class}: total: start timer"
28
+ test_suite_timer = Time.now
29
+
30
+ yield
31
+
32
+ diff = Time.now - test_suite_timer
33
+ param.logger.info "#{self.class}: total: #{diff.to_f} sec."
34
+
35
+ diff
36
+ end
37
+
38
+ def on_around_each param
39
+ param = ClassX::Validate.validate param do
40
+ has :logger
41
+ has :test
42
+ end
43
+
44
+ param.logger.info "#{self.class}: test #{param.test}: start timer"
45
+ test_timer = Time.now
46
+ yield
47
+
48
+ diff = Time.now - test_timer
49
+
50
+ param.logger.info "#{self.class}: test #{param.test}: #{diff.to_f} sec."
51
+
52
+ diff
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,231 @@
1
+ require 'classx'
2
+ require 'ostruct'
3
+
4
+ module ClassX
5
+ # in your context class.
6
+ #
7
+ # require 'classx'
8
+ # require 'classx/pluggable'
9
+ # class YourApp
10
+ # include ClassX
11
+ # include ClassX::Pluggable
12
+ #
13
+ # def run
14
+ # call_event("SETUP", {})
15
+ # # you app
16
+ # call_event("TEARDOWN", {})
17
+ # end
18
+ #
19
+ # end
20
+ #
21
+ # in your plugin class
22
+ #
23
+ # require 'classx'
24
+ # require 'classx/pluggable'
25
+ # class YourApp
26
+ # class Plugin
27
+ # include ClassX
28
+ # include ClassX::Pluggable::Plugin
29
+ #
30
+ # class SomePlugin < Plugin
31
+ # def register
32
+ # add_event("SETUP", :on_setup)
33
+ # end
34
+ #
35
+ # def on_setup param
36
+ # # param is Hash
37
+ # # hooked setup
38
+ # end
39
+ # end
40
+ # end
41
+ # end
42
+ #
43
+ module Pluggable
44
+ extend ClassX::Attributes
45
+ extend ClassX::Role::Logger
46
+
47
+ class PluginLoadError < ::Exception; end
48
+
49
+ has :__classx_pluggable_events_of,
50
+ :lazy => true,
51
+ :no_cmd_option => true,
52
+ :default => proc {|mine| mine.events.inject({}) {|h,event| h[event] = []; h } }
53
+
54
+ has :plugin_dir,
55
+ :optional => true,
56
+ :kind_of => Array
57
+
58
+ has :check_events,
59
+ :default => false,
60
+ :optional => true,
61
+ :desc => "only add events that define before add_event."
62
+
63
+ has :events,
64
+ :lazy => true,
65
+ :optional => true,
66
+ :kind_of => Array,
67
+ :desc => "hook point for #{self}'s instance",
68
+ :default => proc { [] }
69
+
70
+ # register plugin and method to hook point.
71
+ #
72
+ def add_event name, plugin, meth
73
+ name = name.to_s
74
+ if self.check_events && !self.events.include?(name)
75
+ raise "#{name.inspect} should be declared before call add_event. not in #{self.events.inspect}"
76
+ else
77
+ self.__classx_pluggable_events_of[name] ||= []
78
+ end
79
+ self.__classx_pluggable_events_of[name] << { :plugin => plugin, :method => meth }
80
+ end
81
+
82
+ # load plugins.
83
+ #
84
+ # app.load_plugins([
85
+ # { :module => "YourApp::Plugin::Foo", :confiig => { :some_config => "foo"} },
86
+ # { :module => "+Bar", :confiig => { } }, # It's same meaning of YourApp::Plugin::Bar
87
+ # ])
88
+ #
89
+ def load_plugins plugins
90
+ load_components("plugin", plugins)
91
+ end
92
+
93
+ # if you customize Plugin name space. you can use this instead of +load_plugins+.
94
+ #
95
+ # app.load_components('engine', [
96
+ # { :module => "YourApp::Engine::Foo", :confiig => { :some_config => "foo"} },
97
+ # { :module => "+Bar", :confiig => { } }, # It's same meaning of YourApp::Engine::Bar
98
+ # ])
99
+ #
100
+ def load_components type, components
101
+ components.each do |component|
102
+ load_component type, component
103
+ end
104
+ end
105
+
106
+ def load_component type, hash
107
+ component = OpenStruct.new(hash.dup)
108
+ mod = self.class.component_class_get(type, component.module, { :plugin_dir => self.plugin_dir })
109
+ component.config ||= {}
110
+ component.config[:context] = self
111
+ instance = mod.new(component.config)
112
+ instance.register
113
+ logger.debug("ClassX::Pluggable: loaded #{type} #{component.module}, config=#{instance.inspect}")
114
+ end
115
+
116
+ # invoke registered event of +name+ with +args+. and return array of result each callback.
117
+ def call_event name, *args
118
+ name = name.to_s
119
+ if events = self.__classx_pluggable_events_of[name]
120
+ events.map do |event|
121
+ event[:plugin].__send__(event[:method], *args)
122
+ end
123
+ else
124
+ []
125
+ end
126
+ end
127
+
128
+ # invoke registered event of BEFORE_xxxx and yield block and invoke hook AFTER_xxxx.
129
+ def call_event_around name, *args, &block
130
+ name = name.to_s
131
+ around_name = "AROUND_#{name}"
132
+
133
+ call_event("BEFORE_#{name}", *args)
134
+ if events = self.__classx_pluggable_events_of[around_name]
135
+ procs = []
136
+ procs << block
137
+ index = 0
138
+ nested_proc = events.inject(block) {|bl, event| proc { event[:plugin].__send__(event[:method], *args, &bl ) } }
139
+ nested_proc.call
140
+ end
141
+ call_event("AFTER_#{name}", *args)
142
+ end
143
+
144
+ private
145
+
146
+ module Util
147
+ def module2path mod
148
+ mod.split(/::/).map { |s|
149
+ s.gsub(/([A-Z][a-z]+)(?=[A-Z][a-z]*?)/, '\1_').gsub(/([A-Z])(?=[A-Z][a-z]+)/, '\1_').downcase
150
+ }.join(File::SEPARATOR)
151
+ end
152
+
153
+ def nested_const_get mod
154
+ name_spaces = mod.split(/::/)
155
+ result = ::Object
156
+ name_spaces.each do |const|
157
+ result = result.const_get(const)
158
+ end
159
+ return result
160
+ end
161
+
162
+ def nested_autoload mod, path
163
+ name_spaces = mod.split(/::/)
164
+ target = name_spaces.pop
165
+ tmp = ::Object
166
+ name_spaces.each do |const|
167
+ tmp = tmp.const_get(const)
168
+ end
169
+ tmp.autoload(target, path)
170
+ end
171
+
172
+ module_function :module2path, :nested_const_get, :nested_autoload
173
+ end
174
+
175
+ module ClassMethods
176
+ include ClassX::Pluggable::Util
177
+
178
+ def component_class_get type, name, options={}
179
+ case name
180
+ when ::Class
181
+ return name
182
+ else
183
+ mod_name = nil
184
+ target_name = nil
185
+ if name =~ /\A\+([\w:]+)\z/
186
+ target_name = $1
187
+ mod_name = [ self, type.capitalize, target_name ].join("::")
188
+ else
189
+ mod_name = name
190
+ end
191
+ begin
192
+ return nested_const_get(mod_name)
193
+ rescue NameError => e
194
+ begin
195
+ if options[:plugin_dir]
196
+ options[:plugin_dir].each do |path|
197
+ begin
198
+ begin
199
+ self.const_get(type.capitalize).autoload(name, File.expand_path(File.join(path, module2path(target_name))))
200
+ rescue LoadError => e
201
+ raise ::ClassX::Pluggable::PluginLoadError, "class: #{mod_name} is not found"
202
+ end
203
+ return nested_const_get(mod_name)
204
+ rescue NameError => e
205
+ next
206
+ end
207
+ raise NameError, "must not happened unless your code is something wrong!!"
208
+ end
209
+ else
210
+ nested_autoload(mod_name, module2path(mod_name))
211
+ nested_const_get mod_name
212
+ end
213
+ rescue LoadError => e
214
+ raise ::ClassX::Pluggable::PluginLoadError, "class: #{mod_name} is not found."
215
+ end
216
+ end
217
+ end
218
+ end
219
+ end
220
+
221
+ def self.included klass
222
+ klass.extend ClassMethods
223
+ end
224
+
225
+ # It's useful for testing.
226
+ class MockContext
227
+ include ClassX
228
+ include ClassX::Pluggable
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,66 @@
1
+ require 'classx'
2
+
3
+ module ClassX
4
+ module Pluggable
5
+ module Plugin
6
+ extend ClassX::Attributes
7
+
8
+ has :context, :kind_of => ClassX::Pluggable
9
+
10
+ module ClassMethods
11
+ def define_events hash
12
+ define_method :register do
13
+ add_events hash
14
+ end
15
+ end
16
+ end
17
+
18
+ # Abstract method for calling from context instance automatically that you should implement like followings:
19
+ #
20
+ # def register
21
+ # add_event('SOME_EVENT', 'on_some_event')
22
+ # end
23
+ #
24
+ # def on_some_event
25
+ # # do something.
26
+ # end
27
+ #
28
+ def register
29
+ raise NotImprementedError
30
+ end
31
+
32
+ def inspect
33
+ hash = self.to_hash
34
+ hash.delete("context")
35
+ "#{self.class}: #{hash.inspect}"
36
+ end
37
+
38
+ private
39
+
40
+ def self.included klass
41
+ klass.extend(ClassMethods)
42
+ end
43
+
44
+ def add_event name, meth
45
+ self.context.add_event(name, self, meth)
46
+ end
47
+
48
+ def add_events hash
49
+ hash.each do |event, meth|
50
+ add_event(event, meth)
51
+ end
52
+ end
53
+
54
+ module AutoRegister
55
+ EVENT_REGEX = /\Aon_(.+)\z/
56
+
57
+ def register
58
+ methods.map {|meth| meth.to_s }.grep(EVENT_REGEX).each do |meth|
59
+ meth =~ EVENT_REGEX
60
+ add_event $1.upcase, meth
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end