absinthe 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,25 @@
1
+ # Changelog
2
+
3
+ Breaking changes, bugfixes, and new features for each release are summarized here.
4
+ For full details, please consult the SCM changelog.
5
+
6
+ ## v0.0.3
7
+ * Breaking change: Moved require_dir from :namespace to :source_loader.
8
+ * Breaking change: Plugins are now loaded into the injected module.
9
+ This will break any load path manipulation or lazy require usage.
10
+ * Breaking change: The block passed to boot is now evaluated directly
11
+ against the context, rather then the provided object or main.
12
+ * Add support for registering run blocks which will run against the
13
+ provided object or main.
14
+ * Add the beginnings of a test suite.
15
+ * Add supporting documents to gem build.
16
+ * Add the plugin_injector plugin, which creates proxy classes
17
+ for all registered plugins on the root namespace. For example,
18
+ the context plugin instance will be available through the Context
19
+ class in the namespace.
20
+
21
+ ## v0.0.2
22
+ * Fixed gemspec.
23
+
24
+ ## v0.0.1
25
+ * First release, yanked due to invalid gemspec.
data/LICENCE ADDED
@@ -0,0 +1,9 @@
1
+ Copyright (c) 2013, Lance Cooper <lance.m.cooper@gmail.com>
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
+
6
+ Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
+ Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+ Neither the name of the Absinthe Project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
9
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,15 @@
1
+ # Absinthe
2
+
3
+ Absinthe is a lightweight IoC container for Ruby applications.
4
+
5
+ ## Philosophy
6
+
7
+ Absinthe is designed around the premise that all metaprogramming in a Ruby application must be done via IoC if it is to be reliable, performant, or testable.
8
+
9
+ ## Status
10
+
11
+ The project is currently experimental and should not be used for production applications.
12
+
13
+ ## Licence
14
+
15
+ The 'new' or '3 clause' BSD licence, see LICENCE for details.
@@ -1,7 +1,18 @@
1
1
  module Absinthe
2
2
  $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), 'absinthe')))
3
- require 'dsl'
4
3
  require 'distillery'
5
4
 
6
- extend Dsl
5
+ def boot! boot_file, calling_context = nil, &block
6
+ injector = Distillery.injector
7
+ injector.register :app_root, File.expand_path(File.dirname(boot_file))
8
+ injector.register :calling_context, calling_context
9
+ injector.register :boot_proc, block
10
+ injector.inject(:plugin).tap do |plugin|
11
+ plugin.load :context
12
+ plugin.boot!
13
+ end
14
+ injector.inject(:context)
15
+ end
16
+
17
+ extend self
7
18
  end
@@ -1,28 +1,23 @@
1
1
  module Absinthe
2
- module Plugins; end
3
-
4
2
  class Exception < ::Exception; end
5
3
 
6
4
  module Distillery
7
- require 'distillery/context'
5
+ require 'distillery/injector'
6
+ require 'distillery/root_namespace'
8
7
  require 'distillery/source_loader'
8
+ require 'distillery/plugin'
9
9
 
10
- class Warehouse
11
- attr_reader :root_context
12
- def initialize main_object
13
- raise "Warehouse init failure!" if main_object.nil?
14
- @root_context = Context.new
15
- root_context.register :namespace, RootNamespace.new(main_object)
16
- root_context.register :source_loader, SourceLoader
10
+ def self.injector
11
+ Injector.new.tap do |injector|
12
+ injector.register :main_object, @main_object
13
+ injector.register :namespace, RootNamespace, :main_object
14
+ injector.register :source_loader, SourceLoader, :app_root, :namespace
15
+ injector.register :plugin, Plugin, :injector, :namespace, :source_loader
17
16
  end
18
17
  end
19
18
 
20
- def self.root_context
21
- @warehouse.root_context
22
- end
23
-
24
19
  def self.configure main_object
25
- @warehouse = Warehouse.new(main_object)
20
+ @main_object = main_object
26
21
  end
27
22
  end
28
23
  end
@@ -0,0 +1,30 @@
1
+ module Absinthe
2
+ module Distillery
3
+ class Injector
4
+ attr_reader :service_names
5
+
6
+ def initialize
7
+ @parameters = { }
8
+ @args = { }
9
+ @service_names = []
10
+ register :injector, self
11
+ end
12
+
13
+ def register name, clazz, *args
14
+ @parameters[name] = clazz
15
+ if @parameters[name].is_a?(Class)
16
+ @args[name] = args
17
+ @service_names << name # we lack ordered hash traversal on 1.8.
18
+ end
19
+ end
20
+
21
+ def inject name
22
+ return @parameters[name] unless @parameters[name].is_a?(Class)
23
+ injections = @args[name].map do |injection|
24
+ injection.is_a?(Symbol) ? inject(injection) : injection
25
+ end
26
+ @parameters[name] = @parameters[name].new(*injections)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,35 @@
1
+ module Absinthe
2
+ module Plugins; end
3
+
4
+ module Distillery
5
+ class Plugin
6
+ class NotLoaded < Exception; end
7
+
8
+ attr_reader :injector, :namespace, :names
9
+ def initialize injector, namespace, source_loader
10
+ @injector, @namespace, @source_loader = injector, namespace, source_loader
11
+ @names = []
12
+ end
13
+
14
+ def load name
15
+ @source_loader.require_dir :plugins, name
16
+
17
+ # HACK not like this!
18
+ mod = @namespace.root::Absinthe::Plugins
19
+ plugin_name = mod.constants.grep(/#{name.to_s.gsub('_', '')}/i).first
20
+ raise NotLoaded, "#{name} was not found in Absinthe::Plugins" unless plugin_name
21
+ plugin = mod.const_get plugin_name
22
+ plugin.register @injector if plugin.respond_to? :register
23
+ @names << name
24
+ end
25
+
26
+ def boot!
27
+ @names.map do |name|
28
+ @injector.inject(name)
29
+ end.reverse.each do |plugin|
30
+ plugin.boot! if plugin.respond_to?(:boot!)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,27 @@
1
+ module Absinthe
2
+ module Distillery
3
+ class RootNamespace
4
+ class LoadFailed < Exception; end
5
+
6
+ attr_reader :root, :main
7
+ def initialize main
8
+ @main = main
9
+ @root = Module.new { extend self }
10
+ main.include root
11
+ end
12
+
13
+ def register name, value
14
+ @root.send(:define_method, name) { value }
15
+ end
16
+
17
+ def load_file file
18
+ begin
19
+ @root.send(:module_eval, file.read, file.path, 1)
20
+ rescue Exception => ex
21
+ puts "Failed to load #{file.path}: #{ex.message}"
22
+ raise LoadFailed, ex
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -2,9 +2,10 @@ module Absinthe::Distillery
2
2
  class PluginNotFound < Exception; end
3
3
 
4
4
  class SourceLoader
5
- def boot! ctx
6
- if ctx[:app_root]
7
- $LOAD_PATH.unshift(File.expand_path(File.join(ctx[:app_root], 'lib')))
5
+ def initialize app_root, namespace
6
+ @namespace = namespace
7
+ if app_root
8
+ $LOAD_PATH.unshift(File.expand_path(File.join(app_root, 'lib')))
8
9
  end
9
10
 
10
11
  @plugin_paths = []
@@ -16,32 +17,45 @@ module Absinthe::Distillery
16
17
  end
17
18
  end
18
19
 
19
- def source_files root, paths
20
+ def require_dir root, *paths
20
21
  # TODO validate the root param
21
- sources = []
22
-
23
22
  # TODO this is a mess
24
23
  paths.each do |path|
25
24
  plugin_sources = []
26
25
  @plugin_paths.each do |plugin_path|
27
26
  target = File.join plugin_path, "#{path}.rb"
28
- if File.file? target
29
- file = File.new target
30
- plugin_sources << file
27
+ next unless File.file? target
31
28
 
32
- support_dir = File.join plugin_path, path.to_s
33
- if File.directory? support_dir
34
- $LOAD_PATH.unshift support_dir
35
- end
29
+ support_dir = File.join plugin_path, path.to_s
36
30
 
37
- yield file
38
- $LOAD_PATH.delete support_dir
31
+ with_hooked_require(support_dir) do
32
+ plugin_sources << target
33
+ @namespace.load_file File.new(target)
39
34
  end
40
35
  end
36
+
41
37
  if plugin_sources.empty?
42
38
  raise PluginNotFound, "No plugin named #{path} was found in #{@plugin_paths.join ', '}"
43
39
  end
44
40
  end
45
41
  end
42
+
43
+ private
44
+ def with_hooked_require support_dir
45
+ original_require = ::Kernel.method(:require)
46
+ namespace = @namespace
47
+ ::Kernel.send(:define_method, :require) do |filename|
48
+ target = File.join support_dir, "#{filename}.rb"
49
+ if File.file? target
50
+ namespace.load_file File.new(target)
51
+ else
52
+ original_require.call filename
53
+ end
54
+ end
55
+
56
+ yield
57
+ ensure
58
+ ::Kernel.send(:define_method, :require, &original_require)
59
+ end
46
60
  end
47
61
  end
@@ -0,0 +1,55 @@
1
+ module Absinthe
2
+ module Plugins
3
+ class Context
4
+ def self.register injector
5
+ injector.register :context, Context, :injector
6
+ end
7
+
8
+ def initialize injector
9
+ @injector = injector
10
+ @run_blocks = []
11
+ self[:namespace].register :context, self
12
+ instance_exec(&self[:boot_proc]) if self[:boot_proc]
13
+ end
14
+
15
+ def configure &block
16
+ self.instance_eval &block
17
+ end
18
+
19
+ def const name, value
20
+ @injector.register name, value
21
+ end
22
+
23
+ def register name, clazz, *args
24
+ @injector.register name, clazz, *args
25
+ end
26
+
27
+ def inject name
28
+ @injector.inject name
29
+ end
30
+
31
+ def [] name
32
+ inject name
33
+ end
34
+
35
+ def plugin! name
36
+ self[:plugin].load name
37
+ end
38
+
39
+ def run *args, &block
40
+ @run_blocks << {
41
+ :args => args,
42
+ :block => block
43
+ }
44
+ end
45
+
46
+ def boot!
47
+ boot_scope = (self[:calling_context] || self[:main_object])
48
+ @run_blocks.each do |run|
49
+ injections = run[:args].map(&method(:inject))
50
+ boot_scope.instance_exec(*injections, &run[:block])
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,25 @@
1
+ module Absinthe::Plugins
2
+ class PluginInjector
3
+ def self.register injector
4
+ injector.register :plugin_injector, PluginInjector, :plugin
5
+ end
6
+
7
+ def initialize plugin
8
+ @plugin = plugin
9
+ end
10
+
11
+ def boot!
12
+ @plugin.names.each do |service_name|
13
+ service = @plugin.injector.inject(service_name)
14
+ proxy = Module.new { extend self }
15
+ proxy.send(:define_method, :method_missing) do |meth, *args, &block|
16
+ service.send meth, *args, &block
17
+ end
18
+
19
+ # TODO should be elsewhere.
20
+ clazz_name = service_name.to_s.split('_').map(&:capitalize!).join ''
21
+ @plugin.namespace.root.send :const_set, clazz_name, proxy
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module Absinthe
2
+ VERSION = '0.0.3'
3
+ end
@@ -0,0 +1,20 @@
1
+ require 'test_helper'
2
+
3
+ class TestAbsintheBoot < Test::Unit::TestCase
4
+ def test_registers_constants_on_the_returned_context
5
+ context = Absinthe.boot!(__FILE__) {
6
+ const :foo, :bar
7
+ }
8
+ assert_equal :bar, context[:foo]
9
+ end
10
+
11
+ def test_calls_run_blocks_in_the_given_scope
12
+ calling_context = Object.new
13
+ Absinthe.boot!(__FILE__, calling_context) do
14
+ run do
15
+ @scope_var = 'set_on_scope'
16
+ end
17
+ end
18
+ assert_equal 'set_on_scope', calling_context.instance_variable_get(:@scope_var)
19
+ end
20
+ end
@@ -0,0 +1,30 @@
1
+ require 'test_helper'
2
+
3
+ class TestPluginInjector < Test::Unit::TestCase
4
+ class Foo
5
+ attr_reader :msg
6
+
7
+ def initialize msg
8
+ @msg = msg
9
+ end
10
+ end
11
+
12
+ def test_injector_is_available_in_run_blocks
13
+ Absinthe.boot!(__FILE__, self) do
14
+ plugin! :plugin_injector
15
+ run do
16
+ assert PluginInjector.class
17
+ end
18
+ end
19
+ end
20
+
21
+ def test_injected_plugins_are_proxied_on_the_namespace
22
+ context = Absinthe.boot!(__FILE__) do
23
+ register :baxter, Foo, 'hi!'
24
+ plugin! :plugin_injector
25
+ end
26
+
27
+ msg = context[:namespace].root::Context[:baxter].msg
28
+ assert_equal 'hi!', msg
29
+ end
30
+ end
@@ -0,0 +1,21 @@
1
+ require 'test_helper'
2
+
3
+ TOPLEVEL = self
4
+ class TestRootContext < Test::Unit::TestCase
5
+ def test_contexts_namespace_has_main
6
+ context = Absinthe.boot! __FILE__
7
+ assert_equal TOPLEVEL, context[:namespace].main
8
+ end
9
+
10
+ def test_context_injects_params_for_run_blocks
11
+ params = nil
12
+ Absinthe.boot!(__FILE__) do
13
+ const :a, 1
14
+ const :b, 2
15
+ run :a, :b, do |a, b|
16
+ params = [a, b]
17
+ end
18
+ end
19
+ assert_equal [1, 2], params
20
+ end
21
+ end
@@ -0,0 +1,2 @@
1
+ require 'test/unit'
2
+ require 'absinthe'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: absinthe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-06 00:00:00.000000000 Z
12
+ date: 2013-06-09 00:00:00.000000000Z
13
13
  dependencies: []
14
14
  description: Not yet suitable for production use!
15
15
  email: lance.m.cooper@gmail.com
@@ -17,11 +17,22 @@ executables: []
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
- - lib/absinthe/distillery/context.rb
20
+ - README.md
21
+ - CHANGELOG.md
22
+ - LICENCE
23
+ - lib/absinthe/distillery/injector.rb
24
+ - lib/absinthe/distillery/root_namespace.rb
21
25
  - lib/absinthe/distillery/source_loader.rb
22
- - lib/absinthe/dsl.rb
26
+ - lib/absinthe/distillery/plugin.rb
23
27
  - lib/absinthe/distillery.rb
28
+ - lib/absinthe/plugins/context.rb
29
+ - lib/absinthe/plugins/plugin_injector.rb
30
+ - lib/absinthe/version.rb
24
31
  - lib/absinthe.rb
32
+ - test/integration/test_absinthe_boot.rb
33
+ - test/integration/test_plugin_injector.rb
34
+ - test/integration/test_root_context.rb
35
+ - test/test_helper.rb
25
36
  homepage: https://github.com/deathwish/absinthe
26
37
  licenses:
27
38
  - bsd
@@ -43,7 +54,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
43
54
  version: '0'
44
55
  requirements: []
45
56
  rubyforge_project:
46
- rubygems_version: 1.8.25
57
+ rubygems_version: 1.8.10
47
58
  signing_key:
48
59
  specification_version: 3
49
60
  summary: Absinthe is an opinioned IoC container for Ruby.
@@ -1,112 +0,0 @@
1
- module Absinthe
2
- module Distillery
3
- class RootNamespace
4
- class LoadFailed < Exception; end
5
-
6
- attr_reader :root, :main
7
- def initialize main_object
8
- @main = main_object
9
- @plugins = []
10
- end
11
-
12
- def boot! ctx
13
- @context = ctx
14
- @root = Module.new { extend self }
15
-
16
- # Import ourselves into the new namespace, and remove ourselves from main.
17
- @root.const_set :Absinthe, ::Absinthe
18
- Object.send :remove_const, :Absinthe
19
-
20
- root.send(:define_method, :context) { ctx }
21
- main.include root
22
- (ctx[:calling_context] || main).instance_eval {
23
- ctx[:boot_proc].call ctx if ctx[:boot_proc]
24
- }
25
- end
26
-
27
- def load_plugin name
28
- require_dir :plugins, name
29
-
30
- # HACK not like this!
31
- plugin_name = Absinthe::Plugins.constants.grep(/#{name.to_s.gsub('_', '')}/i).first
32
- plugin = Absinthe::Plugins.const_get plugin_name
33
- plugin
34
- end
35
-
36
- def require_dir root, *paths
37
- @context[:source_loader].source_files(root, paths) do |file|
38
- eval_in_scope file
39
- end
40
- end
41
-
42
- private
43
- def eval_in_scope file
44
- begin
45
- @root.send(:module_eval, file.read, file.path, 1)
46
- rescue Exception => ex
47
- puts "Failed to load #{file.path}: #{ex.message}"
48
- raise LoadFailed, ex
49
- end
50
- end
51
- end
52
-
53
- class Context
54
- module Dsl
55
- def configure &block
56
- self.instance_eval &block
57
- end
58
-
59
- def const name, value
60
- register name, value
61
- end
62
-
63
- def [] name, *args
64
- inject name, *args
65
- end
66
- end
67
- include Dsl
68
-
69
- attr_reader :parameters, :args
70
- def initialize
71
- @parameters = { }
72
- @args = { }
73
- end
74
-
75
- def boot!
76
- inject(:namespace).boot! self
77
- end
78
-
79
- def register name, clazz, *args
80
- @parameters[name] = clazz
81
- @args[name] = args if @parameters[name].is_a?(Class)
82
- end
83
-
84
- def inject name, *args
85
- return @parameters[name] unless @parameters[name].is_a?(Class)
86
- injections = (@args[name] + args).map do |injection|
87
- injection.is_a?(Symbol) ? inject(injection) : injection
88
- end
89
- @parameters[name].new(*injections).tap do |obj|
90
- if obj.respond_to?(:boot!)
91
- # anything responding to boot is treated as a singleton service.
92
- @parameters[name] = obj
93
- case obj.method(:boot!).arity
94
- when 0
95
- obj.boot!
96
- when 1
97
- obj.boot! self
98
- else
99
- raise "Invalid boot method for #{name}"
100
- end
101
- end
102
- end
103
- end
104
-
105
- def plugin! name
106
- plugin = inject(:namespace).load_plugin name
107
- plugin.register self if plugin.respond_to? :register
108
- inject name
109
- end
110
- end
111
- end
112
- end
@@ -1,12 +0,0 @@
1
- module Absinthe
2
- module Dsl
3
- def boot! boot_file, calling_context = nil, &block
4
- Distillery.root_context.configure do
5
- const :app_root, File.expand_path(File.dirname(boot_file))
6
- const :calling_context, calling_context
7
- const :boot_proc, block
8
- boot!
9
- end
10
- end
11
- end
12
- end