tame_the_beast 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ notification :growl
2
+
3
+ cli_options = `cat $HOME/.rspec .rspec 2>/dev/null`.gsub("\n", ' ')
4
+ guard 'rspec', :version => 2, :cli => cli_options do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ end
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Thomas Stratmann
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,124 @@
1
+ # TameTheBeast
2
+
3
+ ## What?
4
+
5
+ TameTheBeast lets you define the creation of components of your application in a central container.
6
+ It is inspired by [this blog post](http://onestepback.org/index.cgi/Tech/Ruby/DependencyInjectionInRuby.rdoc) by
7
+ Jim Weirich.
8
+
9
+ ## Why?
10
+
11
+ A central singleton is eventually inevitable for any application, but if you are like me, it tends to
12
+ suck up and swallow functionality that really should be separate from each other. You end up with one big blob
13
+ of unmanageable spaghetti since everything is tightly coupled.
14
+
15
+ Dependency injection is a way out that has proven to be effective in OOP. TameTheBeast aims at giving some nice lightweight sugar above it. (What it actually does is it patronizes you. Have been warned.)
16
+
17
+ ## Show me code!
18
+
19
+ container = TameTheBease.new
20
+
21
+ container.register(:app_state) do
22
+ { :running => false, :initializing => true }
23
+ end
24
+
25
+ container.register(:splash_window, :using => :app_state) do |h|
26
+ SplashWindow.new(h.app_state)
27
+ end
28
+
29
+ # ...many more components registered here...
30
+
31
+ resolution = container.resolve(:for => [:splash_window, ...])
32
+ # => { :splash_window => <SplashWindow...> }
33
+
34
+ So this is the pattern:
35
+
36
+ * register your components with a slot (which must be a Symbol) and a constructing block
37
+ * the block argument gives you access to your dependencies. I call it the **inject hash**.
38
+ * declare your dependencies with the :using option, they will appear in the inject hash
39
+ * resolve for the components you need to kick your application up
40
+
41
+ Explicitly declaring your dependencies in this way _really_ helps in refactoring later!
42
+
43
+ ## Features
44
+
45
+ ### Sugar
46
+
47
+ #### Hash access
48
+
49
+ Access the resolution and the inject hashes by `[]` or by name.
50
+ That means you can say `resolution[:app_state]` as well as `resolution.app_state`. Same for the inject hashes.
51
+
52
+ #### Control what's getting resolved for
53
+
54
+ There are three ways to accomplish that:
55
+
56
+ container.resolve_for :configuration, :app_state
57
+ # or, at register time
58
+ container.register(:printer_dialog, :resolve => true)
59
+ # or, at resolution time
60
+ container.resolve(:for => [:configuration, :app_state])
61
+
62
+ All components mentioned in any of the three ways will get resolved for. Call `resolve_for` as often as you like.
63
+
64
+ #### Slim dependency notation
65
+
66
+ Slots have to be symbols, but when declaring dependencies or resolving,
67
+ you can reference them as strings like this:
68
+
69
+ container.register(:foo, :using => %w{bar baz}) { ... }
70
+
71
+ container.resolve_for %w{foo bar baz}
72
+
73
+ #### Stubbing
74
+
75
+ Is this really sugar? Not sure. Anyway, you can leave off the block on `register`,
76
+ and the component will be initialized as a stub. This way you can play around with the effects of
77
+ refactorings on dependencies to some extent without having to go too deep.
78
+
79
+ The stub will yell at you when you try to use it.
80
+
81
+ ### Is my container complete? Do I have a dependency loop?
82
+
83
+ Just ask.
84
+
85
+ container.complete?
86
+ container.free_of_loops?
87
+
88
+ If there are dependency loops, `resolve` will raise an exception.
89
+
90
+ ### Give me a dependency graph, please.
91
+
92
+ Nothing fancy yet, but you can do
93
+
94
+ container.render_dependencies(:format => :hash)
95
+ # => { :splash_window => [:app_state], ... }
96
+
97
+ Let me know if you know of a way to visualize this easily!
98
+
99
+ ### Post-injection as a last resort
100
+
101
+ I have not yet completely made up my mind about this yet, but it seems like it is not always possible to avoid circular dependencies. You can break them up and post-inject like this:
102
+
103
+ container.register(:component) do |h|
104
+ ...
105
+ end.post_inject_into { |h| h.parent = h.root_something } # or similar foo
106
+
107
+ The post injection block will be called right before the resolution is returned. It is passed
108
+ the resolution.
109
+
110
+ If you think you need this feature, _really_ think hard if you cannot find a way around (I believe there usually is). Using this feature should actually give you some pain, but I could not find a reliable way to implement this.
111
+
112
+ ### Multi-phase initialization / Injection of pre-existing objects
113
+
114
+ There is no such thing as incremental resolution, you cannot use a component directly while still registering construction of others.
115
+
116
+ However, you can break up the registration into multiple phases and simply inject the result of prior `resolve` runs:
117
+
118
+ container.inject(resolution_from_the_past)
119
+
120
+ If you have existing objects, you can actually keep them in a hash and inject them in this way. There is nothing special about using a `resolve` result here!
121
+
122
+ ## License
123
+
124
+ Released under the MIT License. See the [LICENSE](https://github.com/schnittchen/tame_the_beast/blob/master/LICENSE.md) file for further details.
@@ -0,0 +1,12 @@
1
+ require 'tame_the_beast/armory'
2
+
3
+ module TameTheBeast
4
+ class Incomplete < RuntimeError; end
5
+ class CircularDependency < RuntimeError; end
6
+ class BadComponent < RuntimeError; end
7
+ class StubUsedError < RuntimeError; end
8
+
9
+ def self.new(*args)
10
+ Armory.new *args
11
+ end
12
+ end
@@ -0,0 +1,142 @@
1
+ require 'tame_the_beast/reg_entry'
2
+ require 'tame_the_beast/stub'
3
+
4
+ module TameTheBeast
5
+ class Armory
6
+ def initialize
7
+ @registry = {}
8
+ @resolve_for = []
9
+ end
10
+
11
+ class ChainedDSLObject
12
+ def initialize(reg_entry)
13
+ @reg_entry = reg_entry
14
+ end
15
+
16
+ def post_inject_into(&block)
17
+ @reg_entry.post_inject_block = block
18
+ end
19
+ end
20
+
21
+ def register(slot, options = {}, &block)
22
+ using = options.delete(:using) || []
23
+ @resolve_for << slot if options[:resolve]
24
+ reg_entry = _register(slot, using, block)
25
+ return ChainedDSLObject.new reg_entry
26
+ end
27
+
28
+ def inject(hash_like)
29
+ hash_like.each do |slot, object|
30
+ block = lambda { object }
31
+ _register(slot, [], block)
32
+ end
33
+ return self
34
+ end
35
+
36
+ def complete?
37
+ @registry.each do |slot, entry|
38
+ entry.dependent_slots.each do |dependent_slot|
39
+ unless @registry.key? dependent_slot
40
+ yield dependent_slot, slot if block_given?
41
+ return false
42
+ end
43
+ end
44
+ end
45
+ return true
46
+ end
47
+
48
+ def free_of_loops?
49
+ raise Incomplete, "Armory is incomplete!" unless complete?
50
+
51
+ dependency_chains = @registry.keys.map &method(:Array)
52
+
53
+ until dependency_chains.empty?
54
+ dependency_chains = dependency_chains.map do |chain|
55
+ chain_begin, chain_end = chain.first, chain.last
56
+
57
+ @registry[chain.last].dependent_slots.map do |dependent_slot|
58
+ if dependent_slot == chain_begin
59
+ yield chain if block_given?
60
+ return false
61
+ end
62
+
63
+ chain + [dependent_slot]
64
+ end
65
+
66
+ end.reduce([], :+)
67
+ end
68
+ return true
69
+ end
70
+
71
+ def render_dependencies(options = {})
72
+ format = options[:format] || :hash
73
+ raise "Unknown format #{format}" unless %w{hash}.include? format.to_s
74
+
75
+ hash = Hash[@registry.map do |slot, entry|
76
+ [slot, entry.dependent_slots]
77
+ end]
78
+
79
+ return hash
80
+ end
81
+
82
+ def resolve_for(*args)
83
+ args = args.first if args.first.kind_of? Array
84
+ @resolve_for += args.map &:to_sym
85
+ return self
86
+ end
87
+
88
+ def resolve(options = {})
89
+ inject_dependent_reg_entries
90
+
91
+ assert_complete_and_free_of_loops
92
+
93
+ #do the actual resolution
94
+ @resolve_for += Array(options[:for]).map &:to_sym
95
+ #return magic hash here
96
+ resolution = Hash[@resolve_for.map { |key| [key, @registry[key].value] }]
97
+
98
+ #remove unused entries
99
+ @registry.values.reject(&:constructed?).each { |reg_entry| @registry.delete reg_entry.key }
100
+
101
+ #call post_inject_blocks
102
+ component_hash = Container.from_reg_entries @registry.values
103
+ @registry.values.map(&:post_inject_block).compact.each do |post_inject_block|
104
+ post_inject_block.call(component_hash)
105
+ end
106
+
107
+ @registry.clear
108
+
109
+ return Container[resolution]
110
+ end
111
+
112
+ private
113
+
114
+ def _register(slot, using, block)
115
+ slot = slot.to_sym
116
+ using = Array(using).map &:to_sym
117
+ block = block_or_default_for_slot slot, block
118
+
119
+ reg_entry = RegEntry.new :slot => slot, :constructor => block, :dependent_slots => using
120
+ @registry[slot] = reg_entry
121
+ return reg_entry
122
+ end
123
+
124
+ def block_or_default_for_slot(key, block)
125
+ return block || lambda do
126
+ Stub.new key.inspect
127
+ end
128
+ end
129
+
130
+ def inject_dependent_reg_entries
131
+ @registry.values.each do |reg_entry|
132
+ reg_entry.dependent_reg_entries = reg_entry.dependent_slots.map { |slot| @registry[slot] }
133
+ end
134
+ end
135
+
136
+ def assert_complete_and_free_of_loops
137
+ complete? { |dependent_slot, slot| raise Incomplete, "No component #{dependent_slot} defined (needed by #{slot}" }
138
+ free_of_loops? { |bad_chain| raise CircularDependency, "Circular dependency: #{bad_chain.join ' '}" }
139
+ end
140
+
141
+ end
142
+ end
@@ -0,0 +1,23 @@
1
+ module TameTheBeast
2
+ class Container < Hash
3
+ def self.[](*args)
4
+ new.replace Hash[*args]
5
+ end
6
+
7
+ def self.from_reg_entries(entries)
8
+ self[entries.map { |re| [re.key, re.value] }]
9
+ end
10
+
11
+ def method_missing(sym)
12
+ self[sym]
13
+ end
14
+
15
+ def [](key)
16
+ fetch key, &method(:_bad_access)
17
+ end
18
+
19
+ def _bad_access(sym)
20
+ raise BadComponent, "component #{sym} unknown or unavailable"
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,26 @@
1
+ require 'tame_the_beast/container'
2
+
3
+ module TameTheBeast
4
+ class RegEntry
5
+ attr_reader :key, :dependent_slots, :constructed
6
+ attr_writer :dependent_reg_entries
7
+ attr_accessor :post_inject_block
8
+ alias_method :constructed?, :constructed
9
+
10
+ def initialize(data)
11
+ @key, @constructor, @dependent_slots = data.values_at :slot, :constructor, :dependent_slots
12
+ @post_inject_block, @constructed = nil, false
13
+ end
14
+
15
+ def value
16
+ return @value if @constructed
17
+ @value = @constructor.call(constructor_argument).tap { @constructed = true }
18
+ end
19
+
20
+ private
21
+
22
+ def constructor_argument
23
+ Container.from_reg_entries @dependent_reg_entries
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ module TameTheBeast
2
+ class Stub
3
+ def initialize(name)
4
+ @name = name
5
+ end
6
+
7
+ def method_missing(sym, *args)
8
+ raise StubUsedError, "#{sym.inspect} invoked on component #{@name}, which is only a stub!"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module TameTheBeast
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,268 @@
1
+ require 'tame_the_beast'
2
+
3
+ module TameTheBeast
4
+ describe TameTheBeast do
5
+ subject { TameTheBeast.new }
6
+
7
+ describe ".resolve" do
8
+ context "with registered dependencies a -> b -> c" do
9
+ before(:each) do
10
+ @component_hashes = {}
11
+ subject.register(:a, :using => :b) do |c|
12
+ @component_hashes[:a] = c
13
+ { :item => :a }
14
+ end
15
+ subject.register(:b, :using => :c) do |c|
16
+ @component_hashes[:b] = c
17
+ { :item => :b }
18
+ end
19
+ end
20
+
21
+ it "should detect that c has not been registered" do
22
+ lambda { subject.resolve(:for => %w{a b}) }.should raise_error(described_class::Incomplete)
23
+ end
24
+
25
+ context "with registered dependency c -> a" do
26
+ before(:each) { subject.register(:c, :using => :a) { 2 } }
27
+
28
+ it "should detect circular dependency" do
29
+ lambda { subject.resolve(:for => %w{a b c}) }.should raise_error(described_class::CircularDependency)
30
+ end
31
+ end
32
+
33
+ context "with simple leaf node c" do
34
+ before(:each) do
35
+ subject.register(:c) do |c|
36
+ @component_hashes[:c] = c
37
+ { :item => :c }
38
+ end
39
+ end
40
+
41
+ it "should call constructor blocks with an argument" do
42
+ subject.resolve(:for => %w{a b c})
43
+
44
+ @component_hashes.should have_key(:a)
45
+ @component_hashes.should have_key(:b)
46
+ @component_hashes.should have_key(:c)
47
+ end
48
+
49
+ it "should pass sparse component hash to constructor blocks" do
50
+ subject.resolve(:for => %w{a b c})
51
+
52
+ @component_hashes[:a].should have(1).item
53
+ @component_hashes[:b].should have(1).item
54
+ @component_hashes[:c].should be_empty
55
+ end
56
+
57
+ it "should raise BadComponent when trying to access an empty slot in a component hash" do
58
+ subject.resolve(:for => %w{a b c})
59
+
60
+ chash = @component_hashes[:a]
61
+ lambda { chash.a }.should raise_error(described_class::BadComponent)
62
+ lambda { chash.c }.should raise_error(described_class::BadComponent)
63
+ lambda { chash.x }.should raise_error(described_class::BadComponent)
64
+
65
+ chash = @component_hashes[:b]
66
+ lambda { chash.a }.should raise_error(described_class::BadComponent)
67
+ lambda { chash.b }.should raise_error(described_class::BadComponent)
68
+
69
+ chash = @component_hashes[:c]
70
+ lambda { chash.a }.should raise_error(described_class::BadComponent)
71
+ lambda { chash.b }.should raise_error(described_class::BadComponent)
72
+ lambda { chash.c }.should raise_error(described_class::BadComponent)
73
+ end
74
+
75
+ it "should pass component hash to constructor blocks containing the right components" do
76
+ subject.resolve(:for => %w{a b c})
77
+
78
+ @component_hashes[:a].b[:item].should == :b
79
+ @component_hashes[:b].c[:item].should == :c
80
+ end
81
+
82
+ describe "result" do
83
+ let(:resolution) { subject.resolve(:for => %w{a b}) }
84
+
85
+ it "contains requested components" do
86
+ # resolution = subject.resolve(:for => %w{a b})
87
+ resolution.should have(2).items
88
+ resolution[:a][:item].should == :a
89
+ resolution[:b][:item].should == :b
90
+ end
91
+
92
+ it "exposes components as methods as well" do
93
+ resolution.a.should be_equal(resolution[:a])
94
+ resolution.b.should be_equal(resolution[:b])
95
+ end
96
+ end
97
+
98
+ describe ".inject" do
99
+ it "is chainable" do
100
+ subject.inject({}).should be_equal(subject)
101
+ end
102
+ end
103
+
104
+ describe "effect of .inject" do
105
+ before(:each) do
106
+ subject.inject :injected => 'injected'
107
+ end
108
+
109
+ it "allows me to resolve for injected objects" do
110
+ resolution = subject.resolve(:for => %w{injected})
111
+
112
+ resolution.should have_key(:injected)
113
+ resolution[:injected].should == 'injected'
114
+ end
115
+
116
+ it "allows me to use injected object for injecting into another slot object" do
117
+ subject.register(:x, :using => :injected) do |c|
118
+ @component_hashes[:x] = c
119
+ "x"
120
+ end
121
+ subject.resolve :for => :x
122
+
123
+ chash = @component_hashes[:x]
124
+ chash.should have_key(:injected)
125
+ chash[:injected].should == "injected"
126
+ end
127
+ end
128
+ end
129
+
130
+ context "with leaf node c subject to post injection" do
131
+ before(:each) do
132
+ subject.register(:c) do |c|
133
+ @component_hashes[:c] = c
134
+ { :item => :c }
135
+ end.post_inject_into { |c| @component_hashes[:c_post] = c }
136
+ end
137
+
138
+ it "should invoke post_inject_into block with an argument" do
139
+ subject.resolve(:for => %w{a})
140
+
141
+ @component_hashes.should have_key(:c_post)
142
+ end
143
+
144
+ it "should invoke post_inject_into block with sparse component hash" do
145
+ subject.resolve(:for => %w{a})
146
+
147
+ @component_hashes[:c_post].should have(3).items
148
+ end
149
+
150
+ it "should invoke post_inject_into block with component hash containing needed components" do
151
+ subject.resolve(:for => %w{a})
152
+
153
+ chash = @component_hashes[:c_post]
154
+ chash[:a][:item].should == :a
155
+ chash[:b][:item].should == :b
156
+ chash[:c][:item].should == :c
157
+ end
158
+
159
+ context "with detached node d" do
160
+ before(:each) do
161
+ subject.register(:d, :using => :a) do |c|
162
+ @component_hashes[:d_post] = c
163
+ { :item => :d }
164
+ end
165
+ end
166
+
167
+ it "should not invoke constructor block for d" do
168
+ subject.resolve(:for => :a)
169
+
170
+ @component_hashes.should_not have_key(:d_post)
171
+ @component_hashes[:b].c[:item].should == :c
172
+ end
173
+
174
+ it "should not keep d in component hash passed to c's post inject block" do
175
+ subject.resolve(:for => :a)
176
+
177
+ chash = @component_hashes[:c_post]
178
+ chash.should have(3).items
179
+ lambda { chash.d }.should raise_error(described_class::BadComponent)
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
185
+
186
+ describe "stubbing" do
187
+ before(:each) do
188
+ subject.register(:a)
189
+ end
190
+
191
+ it "should pass component hash to constructor blocks containing the right components" do
192
+ resolution = subject.resolve(:for => :a)
193
+ resolution[:a].class.should == Stub
194
+ lambda { resolution[:a].some_method }.should raise_error(described_class::StubUsedError)
195
+ end
196
+ end
197
+
198
+ describe "render_dependencies" do
199
+ context "with dependencies a -> b -> c, a -> d" do
200
+ before(:each) do
201
+ subject.register(:a, :using => %w{b d})
202
+ subject.register(:b, :using => :c)
203
+ subject.register(:c)
204
+ subject.register(:d)
205
+ end
206
+
207
+ let(:dependencies) { subject.render_dependencies(:format => :hash) }
208
+
209
+ it "gives me a nice hash with dependencies" do
210
+ keys = dependencies.keys
211
+ keys.sort_by(&:to_s).should == [:a, :b, :c, :d]
212
+
213
+ dependencies.each_value.sort_by(&:to_s)
214
+
215
+ dependencies[:a].should == [:b, :d]
216
+ dependencies[:b].should == [:c]
217
+ dependencies[:c].should be_empty
218
+ dependencies[:d].should be_empty
219
+ end
220
+ end
221
+ end
222
+
223
+ describe "marking for resolution" do
224
+ describe ".resolve_for" do
225
+ before(:each) do
226
+ subject.register(:a)
227
+ subject.register(:b)
228
+ subject.register(:c)
229
+ end
230
+
231
+ it "is chainable" do
232
+ subject.resolve_for.should be_equal(subject)
233
+ end
234
+
235
+ it "has the effekt that .resolve will deliver the argument(s). pt. 1" do
236
+ subject.resolve_for :a
237
+ subject.resolve.keys.should == [:a]
238
+ end
239
+
240
+ it "has the effekt that .resolve will deliver the argument(s). pt. 2" do
241
+ subject.resolve_for :a
242
+ subject.resolve(:for => :b).keys.sort_by(&:to_s).should == [:a, :b]
243
+ end
244
+
245
+ it "has the effekt that .resolve will deliver the argument(s). pt. 3" do
246
+ subject.resolve_for *%w{a b}
247
+ subject.resolve.keys.sort_by(&:to_s).should == [:a, :b]
248
+ end
249
+
250
+ it "has the effekt that .resolve will deliver the argument(s). pt. 4" do
251
+ subject.resolve_for %w{a b}
252
+ subject.resolve.keys.sort_by(&:to_s).should == [:a, :b]
253
+ end
254
+ end
255
+
256
+ describe ".register(:resolve => true)" do
257
+ before(:each) do
258
+ subject.register(:a)
259
+ subject.register(:b, :resolve => true)
260
+ end
261
+
262
+ it "has the effekt that .resolve will deliver the component" do
263
+ subject.resolve.keys.should == [:b]
264
+ end
265
+ end
266
+ end
267
+ end
268
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "tame_the_beast/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "tame_the_beast"
7
+ s.version = TameTheBeast::VERSION
8
+ s.authors = ["Thomas Stratmann"]
9
+ s.email = ["thomas.stratmann@9elements.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Systematic dependency injection: keep your singletons manageable}
12
+ s.description = s.summary
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ s.require_paths = ["lib"]
18
+
19
+ s.add_development_dependency "ruby-debug"
20
+ s.add_development_dependency "rspec", "2.8.0"
21
+ s.add_development_dependency "growl"
22
+ s.add_development_dependency "guard-rspec"
23
+ end
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tame_the_beast
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Thomas Stratmann
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-04-23 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: ruby-debug
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :development
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: rspec
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - "="
41
+ - !ruby/object:Gem::Version
42
+ hash: 47
43
+ segments:
44
+ - 2
45
+ - 8
46
+ - 0
47
+ version: 2.8.0
48
+ type: :development
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: growl
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ hash: 3
59
+ segments:
60
+ - 0
61
+ version: "0"
62
+ type: :development
63
+ version_requirements: *id003
64
+ - !ruby/object:Gem::Dependency
65
+ name: guard-rspec
66
+ prerelease: false
67
+ requirement: &id004 !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ hash: 3
73
+ segments:
74
+ - 0
75
+ version: "0"
76
+ type: :development
77
+ version_requirements: *id004
78
+ description: "Systematic dependency injection: keep your singletons manageable"
79
+ email:
80
+ - thomas.stratmann@9elements.com
81
+ executables: []
82
+
83
+ extensions: []
84
+
85
+ extra_rdoc_files: []
86
+
87
+ files:
88
+ - Guardfile
89
+ - LICENSE.md
90
+ - Readme.md
91
+ - lib/tame_the_beast.rb
92
+ - lib/tame_the_beast/armory.rb
93
+ - lib/tame_the_beast/container.rb
94
+ - lib/tame_the_beast/reg_entry.rb
95
+ - lib/tame_the_beast/stub.rb
96
+ - lib/tame_the_beast/version.rb
97
+ - spec/acceptance/tame_the_beast_spec.rb
98
+ - tame_the_beast.gemspec
99
+ homepage: ""
100
+ licenses: []
101
+
102
+ post_install_message:
103
+ rdoc_options: []
104
+
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ none: false
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ hash: 3
113
+ segments:
114
+ - 0
115
+ version: "0"
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ none: false
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ hash: 3
122
+ segments:
123
+ - 0
124
+ version: "0"
125
+ requirements: []
126
+
127
+ rubyforge_project:
128
+ rubygems_version: 1.8.17
129
+ signing_key:
130
+ specification_version: 3
131
+ summary: "Systematic dependency injection: keep your singletons manageable"
132
+ test_files:
133
+ - spec/acceptance/tame_the_beast_spec.rb