micon 0.1.6 → 0.1.7
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/Rakefile +5 -5
- data/lib/micon.rb +5 -1
- data/lib/micon/class.rb +2 -2
- data/lib/micon/core.rb +408 -0
- data/lib/micon/helper.rb +26 -0
- data/lib/micon/metadata.rb +120 -124
- data/lib/micon/module.rb +39 -8
- data/lib/micon/rad.rb +8 -0
- data/lib/micon/spec.rb +20 -0
- data/lib/micon/support.rb +23 -9
- data/readme.md +18 -4
- data/spec/callbacks_spec.rb +65 -22
- data/spec/constants_spec.rb +75 -0
- data/spec/constants_spec/get_constant_component/lib/components/TheController.rb +3 -0
- data/spec/custom_scope_spec.rb +33 -34
- data/spec/initialization_spec.rb +71 -0
- data/spec/managed_spec.rb +14 -14
- data/spec/micelaneous_spec.rb +33 -29
- data/spec/micelaneous_spec/autoload/lib/components/TheRad/TheView.rb +3 -0
- data/spec/micelaneous_spec/autoload/lib/components/TheRouter.rb +3 -0
- data/spec/micelaneous_spec/autoload/lib/components/some_value.rb +3 -0
- data/spec/nested_custom_scope_spec.rb +13 -14
- data/spec/overview_spec.rb +7 -3
- data/spec/spec_helper.rb +10 -3
- data/spec/static_scope_spec.rb +47 -22
- metadata +30 -41
- data/lib/micon/micon.rb +0 -250
data/Rakefile
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
require 'rake_ext'
|
2
2
|
|
3
3
|
project(
|
4
|
-
:
|
5
|
-
:
|
6
|
-
:
|
4
|
+
name: "micon",
|
5
|
+
version: "0.1.7",
|
6
|
+
summary: "Assembles and Manages Components of Your Application",
|
7
7
|
|
8
|
-
:
|
9
|
-
:
|
8
|
+
author: "Alexey Petrushin",
|
9
|
+
homepage: "http://github.com/alexeypetrushin/micon"
|
10
10
|
)
|
data/lib/micon.rb
CHANGED
data/lib/micon/class.rb
CHANGED
data/lib/micon/core.rb
ADDED
@@ -0,0 +1,408 @@
|
|
1
|
+
# Predefined scopes are: :application | :session | :instance | :"custom_name"
|
2
|
+
#
|
3
|
+
# Micons :"custom_name" are managed by 'scope_begin' / 'scope_end' methods
|
4
|
+
#
|
5
|
+
# :"custom_name" can't be nested (it will destroy old and start new one) and always should be explicitly started!.
|
6
|
+
class Micon::Core
|
7
|
+
#
|
8
|
+
# Scope Management
|
9
|
+
#
|
10
|
+
attr_accessor :custom_scopes
|
11
|
+
|
12
|
+
def activate sname, container, &block
|
13
|
+
raise_without_self "Only custom scopes can be activated!" if sname == :application or sname == :instance
|
14
|
+
raise "container should have type of Hash but has #{container.class.name}" unless container.is_a? Hash
|
15
|
+
|
16
|
+
raise_without_self "Scope '#{sname}' already active!" if !block and @custom_scopes[sname]
|
17
|
+
|
18
|
+
if block
|
19
|
+
begin
|
20
|
+
outer_container_or_nil = @custom_scopes[sname]
|
21
|
+
@custom_scopes[sname] = container
|
22
|
+
@metadata.with_scope_callbacks sname, container, &block
|
23
|
+
ensure
|
24
|
+
if outer_container_or_nil
|
25
|
+
@custom_scopes[sname] = outer_container_or_nil
|
26
|
+
else
|
27
|
+
@custom_scopes.delete sname
|
28
|
+
end
|
29
|
+
end
|
30
|
+
else
|
31
|
+
# not support nested scopes without block
|
32
|
+
@custom_scopes[sname] = container
|
33
|
+
@metadata.call_before_scope sname, container
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def deactivate sname
|
38
|
+
raise_without_self "Only custom scopes can be deactivated!" if sname == :application or sname == :instance
|
39
|
+
|
40
|
+
raise_without_self "Scope '#{sname}' not active!" unless container = @custom_scopes[sname]
|
41
|
+
|
42
|
+
@metadata.call_after_scope sname, container
|
43
|
+
@custom_scopes.delete sname
|
44
|
+
container
|
45
|
+
end
|
46
|
+
|
47
|
+
def active? sname
|
48
|
+
if sname == :application or sname == :instance
|
49
|
+
true
|
50
|
+
else
|
51
|
+
@custom_scopes.include? sname
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def clear
|
56
|
+
@application.clear
|
57
|
+
@custom_scopes.clear
|
58
|
+
end
|
59
|
+
|
60
|
+
def empty?
|
61
|
+
@application.empty? and @custom_scopes.empty?
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
#
|
66
|
+
# Object Management
|
67
|
+
#
|
68
|
+
def include? key
|
69
|
+
sname = @registry[key]
|
70
|
+
|
71
|
+
case sname
|
72
|
+
when nil
|
73
|
+
false
|
74
|
+
when :instance
|
75
|
+
true
|
76
|
+
when :application
|
77
|
+
@application.include? key
|
78
|
+
else # custom
|
79
|
+
container = @custom_scopes[sname]
|
80
|
+
return false unless container
|
81
|
+
container.include? key
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def [] key
|
86
|
+
sname = @registry[key] || autoload_component_definition(key)
|
87
|
+
|
88
|
+
case sname
|
89
|
+
when :instance
|
90
|
+
return create_object(key)
|
91
|
+
when :application
|
92
|
+
o = @application[key]
|
93
|
+
unless o
|
94
|
+
return create_object(key, @application)
|
95
|
+
else
|
96
|
+
return o
|
97
|
+
end
|
98
|
+
else # custom
|
99
|
+
container = @custom_scopes[sname]
|
100
|
+
raise_without_self "Scope '#{sname}' not started!" unless container
|
101
|
+
o = container[key]
|
102
|
+
unless o
|
103
|
+
return create_object(key, container)
|
104
|
+
else
|
105
|
+
return o
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# def get_constant_component key
|
111
|
+
# sname = @registry[key] || autoload_component_definition(key, false)
|
112
|
+
#
|
113
|
+
# case sname
|
114
|
+
# when nil
|
115
|
+
# nil
|
116
|
+
# when :instance
|
117
|
+
# must_be.never_called
|
118
|
+
# when :application
|
119
|
+
# return nil unless @constants.include? key
|
120
|
+
#
|
121
|
+
# o = @application[key]
|
122
|
+
# unless o
|
123
|
+
# return create_object(key, @application)
|
124
|
+
# else
|
125
|
+
# return o
|
126
|
+
# end
|
127
|
+
# else # custom
|
128
|
+
# must_be.never_called
|
129
|
+
# end
|
130
|
+
# end
|
131
|
+
#
|
132
|
+
# def get_constant namespace, const
|
133
|
+
# original_namespace = namespace
|
134
|
+
# namespace = nil if namespace == Object or namespace == Module
|
135
|
+
# target_namespace = namespace
|
136
|
+
#
|
137
|
+
# # Name hack (for anonymous classes)
|
138
|
+
# namespace = eval "#{name_hack(namespace)}" if namespace
|
139
|
+
#
|
140
|
+
# class_name = namespace ? "#{namespace.name}::#{const}" : const
|
141
|
+
#
|
142
|
+
# simple_also_tried = false
|
143
|
+
# begin
|
144
|
+
# simple_also_tried = (namespace == nil)
|
145
|
+
#
|
146
|
+
# if result = get_constant_component(class_name.to_sym)
|
147
|
+
# if @loaded_classes.include?(class_name)
|
148
|
+
# raise_without_self "something wrong is goin on, constant '#{const}' in '#{original_namespace}' namespace already has been defined!"
|
149
|
+
# end
|
150
|
+
#
|
151
|
+
# real_namespace = namespace ? namespace : Object
|
152
|
+
# if real_namespace.const_defined?(const)
|
153
|
+
# raise_without_self "component trying to redefine constant '#{const}' that already defined in '#{real_namespace}'!"
|
154
|
+
# end
|
155
|
+
#
|
156
|
+
# real_namespace.const_set const, result
|
157
|
+
#
|
158
|
+
# @loaded_classes[class_name] = [real_namespace, const]
|
159
|
+
#
|
160
|
+
# return result
|
161
|
+
# elsif namespace
|
162
|
+
# namespace = Module.namespace_for(namespace.name)
|
163
|
+
# class_name = namespace ? "#{namespace.name}::#{const}" : const
|
164
|
+
# end
|
165
|
+
# end until simple_also_tried
|
166
|
+
#
|
167
|
+
# return nil
|
168
|
+
# end
|
169
|
+
|
170
|
+
def []= key, value
|
171
|
+
raise "can't assign nill as :#{key} component!" unless value
|
172
|
+
|
173
|
+
sname = @registry[key] || autoload_component_definition(key)
|
174
|
+
|
175
|
+
value = case sname
|
176
|
+
when :instance
|
177
|
+
raise_without_self "You can't outject variable with the 'instance' sname!"
|
178
|
+
when :application
|
179
|
+
@application[key] = value
|
180
|
+
else # custom
|
181
|
+
container = @custom_scopes[sname]
|
182
|
+
raise_without_self "Scope '#{sname}' not started!" unless container
|
183
|
+
container[key] = value
|
184
|
+
end
|
185
|
+
|
186
|
+
@metadata.call_after key, value
|
187
|
+
|
188
|
+
value
|
189
|
+
end
|
190
|
+
|
191
|
+
def delete key
|
192
|
+
sname = @registry[key] # || autoload_component_definition(key)
|
193
|
+
|
194
|
+
case sname
|
195
|
+
when nil
|
196
|
+
when :instance
|
197
|
+
raise_without_self "You can't outject variable with the 'instance' scope!"
|
198
|
+
when :application
|
199
|
+
@application.delete key
|
200
|
+
else # Custom
|
201
|
+
container = @custom_scopes[sname]
|
202
|
+
# raise_without_self "Scope '#{sname}' not started!" unless container
|
203
|
+
container.delete key if container
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def delete_all key
|
208
|
+
metadata.delete key
|
209
|
+
delete key
|
210
|
+
end
|
211
|
+
|
212
|
+
def reset key
|
213
|
+
delete key
|
214
|
+
self[key]
|
215
|
+
end
|
216
|
+
|
217
|
+
#
|
218
|
+
# Metadata
|
219
|
+
#
|
220
|
+
attr_accessor :metadata
|
221
|
+
|
222
|
+
def register key, options = {}, &initializer
|
223
|
+
raise "key should not be nil or false value!" unless key
|
224
|
+
options = options.symbolize_keys
|
225
|
+
|
226
|
+
sname = options.delete(:scope) || :application
|
227
|
+
dependencies = Array(options.delete(:require) || options.delete(:depends_on))
|
228
|
+
# constant = options.delete(:constant) || false
|
229
|
+
|
230
|
+
raise "unknown options :#{options.keys.join(', :')}!" unless options.empty?
|
231
|
+
|
232
|
+
unless @registry.object_id == @metadata.registry.object_id
|
233
|
+
raise "internal error, reference to registry aren't equal to actual registry!"
|
234
|
+
end
|
235
|
+
@metadata.registry[key] = sname
|
236
|
+
@metadata.initializers[key] = [initializer, dependencies] #, constant]
|
237
|
+
# if constant
|
238
|
+
# raise "component '#{key}' defined as constant must be a symbol!" unless key.is_a? Symbol
|
239
|
+
# raise "component '#{key}' defined as constant can have only :application scope!" unless sname == :application
|
240
|
+
# @constants[key] = true
|
241
|
+
# end
|
242
|
+
end
|
243
|
+
|
244
|
+
def unregister key
|
245
|
+
@metadata.delete key
|
246
|
+
# @constants.delete key
|
247
|
+
end
|
248
|
+
|
249
|
+
def before component, options = {}, &block
|
250
|
+
options[:bang] = true unless options.include? :bang
|
251
|
+
raise_without_self "component :#{component} already created!" if options[:bang] and include?(component)
|
252
|
+
@metadata.register_before component, &block
|
253
|
+
end
|
254
|
+
|
255
|
+
def after component, options = {}, &block
|
256
|
+
options[:bang] = true unless options.include? :bang
|
257
|
+
if include? component
|
258
|
+
if options[:bang]
|
259
|
+
raise_without_self "component :#{component} already created!"
|
260
|
+
else
|
261
|
+
block.call self[component]
|
262
|
+
end
|
263
|
+
end
|
264
|
+
@metadata.register_after component, &block
|
265
|
+
end
|
266
|
+
|
267
|
+
def before_scope sname, options = {}, &block
|
268
|
+
options[:bang] = true unless options.include? :bang
|
269
|
+
raise_without_self "scope :#{sname} already started!" if options[:bang] and active?(sname)
|
270
|
+
@metadata.register_before_scope sname, &block
|
271
|
+
end
|
272
|
+
|
273
|
+
def after_scope sname, options = {}, &block
|
274
|
+
options[:bang] = true unless options.include? :bang
|
275
|
+
raise_without_self "scope :#{sname} already started!" if options[:bang] and active?(sname)
|
276
|
+
@metadata.register_after_scope sname, &block
|
277
|
+
end
|
278
|
+
|
279
|
+
|
280
|
+
|
281
|
+
def clone
|
282
|
+
another = super
|
283
|
+
%w(@metadata @application @custom_scopes).each do |name| # @loaded_classes, @constants
|
284
|
+
value = instance_variable_get name
|
285
|
+
another.instance_variable_set name, value.clone
|
286
|
+
end
|
287
|
+
another.instance_variable_set '@registry', another.metadata.registry
|
288
|
+
another.instance_variable_set '@initialized', another.instance_variable_get('@initialized')
|
289
|
+
another
|
290
|
+
end
|
291
|
+
alias_method :deep_clone, :clone
|
292
|
+
|
293
|
+
def initialize!
|
294
|
+
unless @initialized
|
295
|
+
# quick access to Metadata inner variable.
|
296
|
+
# I intentially broke the Metadata incapsulation to provide better performance, don't refactor it.
|
297
|
+
@registry = {} # @loaded_classes, @constants = {}, {}
|
298
|
+
@metadata = Micon::Metadata.new(@registry)
|
299
|
+
|
300
|
+
@application, @custom_scopes = {}, {}
|
301
|
+
|
302
|
+
@initialized = true
|
303
|
+
end
|
304
|
+
|
305
|
+
# Micon::Core is independent itself and there can be multiple Cores simultaneously.
|
306
|
+
# But some of it's extensions can work only with one global instance, and them need to know how to get it,
|
307
|
+
# the MICON constant references this global instance.
|
308
|
+
Object.send(:remove_const, :MICON) if Object.const_defined?(:MICON)
|
309
|
+
Object.const_set :MICON, self
|
310
|
+
end
|
311
|
+
|
312
|
+
def deinitialize!
|
313
|
+
Object.send(:remove_const, :MICON) if Object.const_defined?(:MICON)
|
314
|
+
|
315
|
+
# @loaded_classes.each do |class_name, tuple|
|
316
|
+
# namespace, const = tuple
|
317
|
+
# namespace.send(:remove_const, const)
|
318
|
+
# end
|
319
|
+
# @loaded_classes.clear
|
320
|
+
end
|
321
|
+
|
322
|
+
protected
|
323
|
+
def autoload_component_definition key, bang = true
|
324
|
+
begin
|
325
|
+
load "components/#{key.to_s.gsub(/::/, '/')}.rb"
|
326
|
+
rescue LoadError
|
327
|
+
end
|
328
|
+
sname = @registry[key]
|
329
|
+
raise_without_self "'#{key}' component not managed!" if bang and !sname
|
330
|
+
sname
|
331
|
+
end
|
332
|
+
|
333
|
+
def create_object key, container = nil
|
334
|
+
initializer, dependencies = @metadata.initializers[key]
|
335
|
+
raise "no initializer for :#{key} component!" unless initializer
|
336
|
+
|
337
|
+
dependencies.each{|d| self[d]}
|
338
|
+
@metadata.call_before key
|
339
|
+
|
340
|
+
if container
|
341
|
+
unless o = container[key]
|
342
|
+
o = initializer.call
|
343
|
+
container[key] = o
|
344
|
+
else
|
345
|
+
# complex case, there's an circular dependency, and the 'o' already has been
|
346
|
+
# initialized in dependecies or callbacks
|
347
|
+
# here's the sample case:
|
348
|
+
#
|
349
|
+
# app.register :environment, :application do
|
350
|
+
# p :environment
|
351
|
+
# 'environment'
|
352
|
+
# end
|
353
|
+
#
|
354
|
+
# app.register :conveyors, :application, depends_on: :environment do
|
355
|
+
# p :conveyors
|
356
|
+
# 'conveyors'
|
357
|
+
# end
|
358
|
+
#
|
359
|
+
# app.after :environment do
|
360
|
+
# app[:conveyors]
|
361
|
+
# end
|
362
|
+
#
|
363
|
+
# app[:conveyors]
|
364
|
+
|
365
|
+
o = container[key]
|
366
|
+
end
|
367
|
+
else
|
368
|
+
o = initializer.call
|
369
|
+
end
|
370
|
+
raise "initializer for component :#{key} returns nill!" unless o
|
371
|
+
|
372
|
+
@metadata.call_after key, o
|
373
|
+
o
|
374
|
+
end
|
375
|
+
|
376
|
+
def name_hack namespace
|
377
|
+
if namespace
|
378
|
+
namespace.to_s.gsub("#<Class:", "").gsub(">", "")
|
379
|
+
else
|
380
|
+
""
|
381
|
+
end
|
382
|
+
# Namespace Hack description
|
383
|
+
# Module.name doesn't works correctly for Anonymous classes.
|
384
|
+
# try to execute this code:
|
385
|
+
#
|
386
|
+
#class Module
|
387
|
+
# def const_missing const
|
388
|
+
# p self.to_s
|
389
|
+
# end
|
390
|
+
#end
|
391
|
+
#
|
392
|
+
#class A
|
393
|
+
# class << self
|
394
|
+
# def a
|
395
|
+
# p self
|
396
|
+
# MissingConst
|
397
|
+
# end
|
398
|
+
# end
|
399
|
+
#end
|
400
|
+
#
|
401
|
+
#A.a
|
402
|
+
#
|
403
|
+
# the output will be:
|
404
|
+
# A
|
405
|
+
# "#<Class:A>"
|
406
|
+
#
|
407
|
+
end
|
408
|
+
end
|
data/lib/micon/helper.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
#
|
2
|
+
# Generates helper methods for Micon,
|
3
|
+
# so you can use micon.config instead of micon[:config]
|
4
|
+
#
|
5
|
+
module Micon::Helper
|
6
|
+
def method_missing m, *args, &block
|
7
|
+
super if args.size > 1 or block
|
8
|
+
|
9
|
+
key = m.to_s.sub(/[?=]$/, '').to_sym
|
10
|
+
self.class.class_eval do
|
11
|
+
define_method key do
|
12
|
+
self[key]
|
13
|
+
end
|
14
|
+
|
15
|
+
define_method "#{key}=" do |value|
|
16
|
+
self[key] = value
|
17
|
+
end
|
18
|
+
|
19
|
+
define_method "#{key}?" do
|
20
|
+
include? key
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
send m, *args
|
25
|
+
end
|
26
|
+
end
|