moosex 0.0.17 → 0.0.18
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.
- checksums.yaml +4 -4
- data/Changelog +38 -33
- data/Gemfile.lock +1 -1
- data/README.md +43 -1
- data/lib/moosex.rb +55 -622
- data/lib/moosex/attribute.rb +192 -0
- data/lib/moosex/attribute/modifiers.rb +303 -0
- data/lib/moosex/core.rb +114 -0
- data/lib/moosex/exceptions.rb +11 -0
- data/lib/moosex/meta.rb +139 -0
- data/lib/moosex/types.rb +35 -23
- data/lib/moosex/version.rb +1 -1
- data/spec/lol_spec.rb +1 -0
- data/spec/meta_spec.rb +90 -0
- data/spec/modifiers_spec.rb +28 -0
- data/spec/types_spec.rb +12 -14
- metadata +11 -2
data/lib/moosex/core.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
module MooseX
|
2
|
+
module Core
|
3
|
+
def on_init(&block)
|
4
|
+
__moosex__meta.add_role(block)
|
5
|
+
end
|
6
|
+
|
7
|
+
def after(*methods_name, &block)
|
8
|
+
__moosex__add_hooks(:after, methods_name, block, caller())
|
9
|
+
end
|
10
|
+
|
11
|
+
def before(*methods_name, &block)
|
12
|
+
__moosex__add_hooks(:before, methods_name, block, caller())
|
13
|
+
end
|
14
|
+
|
15
|
+
def around(*methods_name, &block)
|
16
|
+
__moosex__add_hooks(:around, methods_name, block, caller())
|
17
|
+
end
|
18
|
+
|
19
|
+
def requires(*methods)
|
20
|
+
methods.each do |method_name|
|
21
|
+
__moosex__meta.add_requires(method_name)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def has(attr_name, attr_options = {})
|
26
|
+
if attr_name.is_a? Array
|
27
|
+
attr_name.each do |attr|
|
28
|
+
__moosex__has(attr, attr_options)
|
29
|
+
end
|
30
|
+
elsif attr_name.is_a? Hash
|
31
|
+
attr_name.each_pair do |attr, options |
|
32
|
+
has(attr, options)
|
33
|
+
end
|
34
|
+
else
|
35
|
+
__moosex__has(attr_name, attr_options)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def __moosex__add_hooks(hook, methods_name, block, c)
|
41
|
+
methods_name.each do |method_name|
|
42
|
+
begin
|
43
|
+
__moosex__try_to_add_hook_now(hook, method_name, block)
|
44
|
+
rescue => e
|
45
|
+
MooseX.warn "unable to apply hook #{hook} in #{method_name} @ #{self}: #{e}", c if self.is_a?(Class)
|
46
|
+
__moosex__meta.add_hook(hook, method_name, block)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def __moosex__try_to_add_hook_now(hook, method_name, block)
|
52
|
+
case hook
|
53
|
+
when :before
|
54
|
+
__moosex__try_to_add_before_now(method_name, block)
|
55
|
+
when :after
|
56
|
+
__moosex__try_to_add_after_now(method_name, block)
|
57
|
+
when :around
|
58
|
+
__moosex__try_to_add_around_now(method_name, block)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def __moosex__try_to_add_before_now(method_name, block)
|
63
|
+
method = instance_method method_name
|
64
|
+
|
65
|
+
define_method method_name do |*args, &proc|
|
66
|
+
block.call(self,*args, &proc)
|
67
|
+
method.bind(self).call(*args, &proc)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def __moosex__try_to_add_after_now(method_name, block)
|
72
|
+
method = instance_method method_name
|
73
|
+
|
74
|
+
define_method method_name do |*args, &proc|
|
75
|
+
result = method.bind(self).call(*args, &proc)
|
76
|
+
block.call(self,*args,&proc)
|
77
|
+
result
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def __moosex__try_to_add_around_now(method_name, block)
|
82
|
+
method = instance_method method_name
|
83
|
+
|
84
|
+
code = Proc.new do | o, *a, &proc|
|
85
|
+
method.bind(o).call(*a,&proc)
|
86
|
+
end
|
87
|
+
|
88
|
+
define_method method_name do |*args, &proc|
|
89
|
+
block.call(code, self,*args, &proc)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def __moosex__has(attr_name, attr_options)
|
94
|
+
attr = MooseX::Attribute.new(attr_name, attr_options, self)
|
95
|
+
|
96
|
+
__moosex__meta.add(attr)
|
97
|
+
|
98
|
+
__moosex__create_methods(attr)
|
99
|
+
end
|
100
|
+
|
101
|
+
def __moosex__create_methods(attr)
|
102
|
+
attr.methods.each_pair do |method, proc|
|
103
|
+
define_method method, &proc
|
104
|
+
end
|
105
|
+
|
106
|
+
if attr.is.eql?(:rwp)
|
107
|
+
private attr.writter
|
108
|
+
elsif attr.is.eql?(:private)
|
109
|
+
private attr.writter
|
110
|
+
private attr.reader
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/lib/moosex/meta.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
module MooseX
|
2
|
+
|
3
|
+
class Meta
|
4
|
+
attr_reader :attrs, :requires, :hooks
|
5
|
+
|
6
|
+
def initialize(old_meta=nil)
|
7
|
+
@initialized = false
|
8
|
+
@attrs = {}
|
9
|
+
@requires = []
|
10
|
+
@roles = []
|
11
|
+
|
12
|
+
@hooks = {
|
13
|
+
before: Hash.new { |hash, key| hash[key] = [] },
|
14
|
+
after: Hash.new { |hash, key| hash[key] = [] },
|
15
|
+
around: Hash.new { |hash, key| hash[key] = [] },
|
16
|
+
}
|
17
|
+
|
18
|
+
if old_meta
|
19
|
+
old_meta.attrs.each_pair do |key, value|
|
20
|
+
@attrs[key] = value.clone
|
21
|
+
end
|
22
|
+
@requires = old_meta.requires.clone
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def info
|
27
|
+
@attrs.map{|attr_symbol, attr| {attr_symbol => attr.doc }}.reduce(:merge)
|
28
|
+
end
|
29
|
+
|
30
|
+
def init_roles(*args)
|
31
|
+
@roles.each do|role|
|
32
|
+
role.call(*args)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def load_from(module_or_class)
|
37
|
+
other_meta = module_or_class.__moosex__meta
|
38
|
+
other_meta.attrs.each_pair do |key, value|
|
39
|
+
@attrs[key] = value.clone
|
40
|
+
end
|
41
|
+
@requires += other_meta.requires
|
42
|
+
end
|
43
|
+
|
44
|
+
def load_from_klass(klass)
|
45
|
+
other_meta = klass.__moosex__meta
|
46
|
+
|
47
|
+
other_meta.hooks.each_pair do |hook, data|
|
48
|
+
data.each_pair do |m, b|
|
49
|
+
@hooks[hook][m] += b.clone
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def add(attr)
|
55
|
+
if @attrs.has_key?(attr.attr_symbol) && ! attr.override
|
56
|
+
raise FatalError, "#{attr.attr_symbol} already exists, you should specify override: true"
|
57
|
+
end
|
58
|
+
@attrs[attr.attr_symbol] = attr
|
59
|
+
end
|
60
|
+
|
61
|
+
def add_requires(method)
|
62
|
+
@requires << method
|
63
|
+
end
|
64
|
+
|
65
|
+
def add_hook(hook, method, block)
|
66
|
+
@hooks[hook][method] << block.clone
|
67
|
+
end
|
68
|
+
|
69
|
+
def add_role(block)
|
70
|
+
@roles << block
|
71
|
+
end
|
72
|
+
|
73
|
+
def init(object, args)
|
74
|
+
@attrs.each_pair do |symbol, attr|
|
75
|
+
attr.init(object, args)
|
76
|
+
end
|
77
|
+
|
78
|
+
MooseX.warn "unused attributes #{args} for #{object.class}", caller unless args.empty?
|
79
|
+
|
80
|
+
@requires.each do |method|
|
81
|
+
unless object.respond_to? method
|
82
|
+
raise RequiredMethodNotFoundError,
|
83
|
+
"you must implement method '#{method}' in #{object.class}: required"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def init_klass(klass)
|
89
|
+
@hooks.values.map {|h| h.keys }.flatten.uniq.map do |method_name|
|
90
|
+
begin
|
91
|
+
[ klass.instance_method(method_name), method_name ]
|
92
|
+
rescue => e
|
93
|
+
MooseX.warn "Unable to apply hooks (after/before/around) in #{klass}::#{method_name} : #{e}" # if $MOOSEX_DEBUG
|
94
|
+
nil
|
95
|
+
end
|
96
|
+
end.select do |value|
|
97
|
+
!value.nil?
|
98
|
+
end.reduce({}) do |hash, tuple|
|
99
|
+
method, method_name = tuple
|
100
|
+
|
101
|
+
hash[method_name] = __moosex__init_hooks(method_name, method)
|
102
|
+
|
103
|
+
hash
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def verify_requires_for(x)
|
108
|
+
@requires.each do |method|
|
109
|
+
unless x.public_instance_methods.include? method
|
110
|
+
MooseX.warn "you must implement method '#{method}' in #{x} #{x.class}: required"# if $MOOSEX_DEBUG
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
private
|
115
|
+
def __moosex__init_hooks(method_name, method)
|
116
|
+
|
117
|
+
before = @hooks[:before][method_name]
|
118
|
+
after = @hooks[:after][method_name]
|
119
|
+
around = @hooks[:around][method_name]
|
120
|
+
|
121
|
+
original = lambda do |object, *args, &proc|
|
122
|
+
method.bind(object).call(*args, &proc)
|
123
|
+
end
|
124
|
+
|
125
|
+
lambda do |*args, &proc|
|
126
|
+
before.each{|b| b.call(self,*args, &proc)}
|
127
|
+
|
128
|
+
result = around.inject(original) do |lambda1, lambda2|
|
129
|
+
lambda2.curry[lambda1]
|
130
|
+
end.call(self, *args, &proc)
|
131
|
+
|
132
|
+
after.each{|b| b.call(self,*args, &proc)}
|
133
|
+
|
134
|
+
result
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
data/lib/moosex/types.rb
CHANGED
@@ -6,6 +6,7 @@ module MooseX
|
|
6
6
|
end
|
7
7
|
|
8
8
|
class TypeCheckError < TypeError
|
9
|
+
|
9
10
|
end
|
10
11
|
|
11
12
|
module Core
|
@@ -181,45 +182,56 @@ module MooseX
|
|
181
182
|
|
182
183
|
def isTuple(*types)
|
183
184
|
|
184
|
-
|
185
|
-
isType(Array).call(tuple)
|
186
|
-
|
185
|
+
size_validation = lambda do |tuple|
|
187
186
|
unless tuple.size == types.size
|
188
187
|
raise TypeCheckError, "Tuple violation: size should be #{types.size} instead #{tuple.size}"
|
189
|
-
end
|
190
|
-
|
191
|
-
types.each_index do |index|
|
192
|
-
begin
|
193
|
-
isType(types[index]).call(tuple[index])
|
194
|
-
rescue TypeCheckError => e
|
195
|
-
raise TypeCheckError, "Tuple violation: on position #{index} caused by #{e}"
|
196
|
-
end
|
197
|
-
end
|
188
|
+
end
|
198
189
|
end
|
199
190
|
|
191
|
+
individual_validations = create_individual_validations_for_tuples(types)
|
192
|
+
|
193
|
+
proc = isAllOf(
|
194
|
+
isType(Array),
|
195
|
+
size_validation,
|
196
|
+
*individual_validations,
|
197
|
+
)
|
198
|
+
|
199
|
+
createValidator("[Tuple [#{types.map{|t| t.to_s}.join ', '}]]", &proc)
|
200
200
|
end
|
201
201
|
|
202
202
|
def isSet(type=nil)
|
203
203
|
type = isAny if type.nil?
|
204
204
|
|
205
|
-
|
206
|
-
|
205
|
+
proc = isAllOf(
|
206
|
+
isArray(type),
|
207
|
+
lambda do |set|
|
208
|
+
if set.uniq.size != set.size
|
209
|
+
raise TypeCheckError, "Set violation: has one or more non unique elements"
|
210
|
+
end
|
211
|
+
end,
|
212
|
+
)
|
207
213
|
|
208
|
-
|
209
|
-
|
210
|
-
raise TypeCheckError, "Set violation: has one or more non unique elements: #{duplicated} (value => count)"
|
211
|
-
end
|
214
|
+
createValidator("[Set #{type.to_s}]", &proc)
|
215
|
+
end
|
212
216
|
|
213
|
-
|
217
|
+
private
|
218
|
+
def create_individual_validations_for_tuples(types)
|
219
|
+
individual_validations = []
|
220
|
+
types.each_index do |index|
|
221
|
+
individual_validations << lambda do |tuple|
|
214
222
|
begin
|
215
|
-
isType(
|
223
|
+
isType(types[index]).call(tuple[index])
|
216
224
|
rescue TypeCheckError => e
|
217
|
-
raise TypeCheckError, "
|
225
|
+
raise TypeCheckError, "Tuple violation: on position #{index} caused by #{e}"
|
218
226
|
end
|
219
227
|
end
|
220
228
|
end
|
221
|
-
|
229
|
+
|
230
|
+
individual_validations
|
231
|
+
end
|
222
232
|
|
223
233
|
end
|
234
|
+
|
224
235
|
end
|
225
|
-
|
236
|
+
|
237
|
+
end
|
data/lib/moosex/version.rb
CHANGED
data/spec/lol_spec.rb
CHANGED
data/spec/meta_spec.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'moosex'
|
2
|
+
|
3
|
+
class TestMeta
|
4
|
+
include MooseX.init(meta: true)
|
5
|
+
|
6
|
+
has :foo
|
7
|
+
has :bar, {
|
8
|
+
is: :ro,
|
9
|
+
default: 1,
|
10
|
+
doc: "etc",
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
class TestMeta2
|
15
|
+
include MooseX.init(meta: :mymeta)
|
16
|
+
|
17
|
+
has :foo
|
18
|
+
has :bar, {
|
19
|
+
is: :ro,
|
20
|
+
default: 1,
|
21
|
+
doc: "etc",
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
describe TestMeta do
|
26
|
+
it "should has 'meta'" do
|
27
|
+
TestMeta.respond_to?(:meta).should be_true
|
28
|
+
end
|
29
|
+
|
30
|
+
it "meta should return list of attributes" do
|
31
|
+
attrs = TestMeta.meta.attrs
|
32
|
+
|
33
|
+
attributes = attrs.keys
|
34
|
+
attributes[0].should == :foo
|
35
|
+
attributes[1].should == :bar
|
36
|
+
|
37
|
+
attrs[:foo].is.should == :rw
|
38
|
+
|
39
|
+
attrs[:bar].is.should == :ro
|
40
|
+
attrs[:bar].default.call.should == 1
|
41
|
+
attrs[:bar].doc.should == "etc"
|
42
|
+
end
|
43
|
+
|
44
|
+
it "meta should return list of documentations" do
|
45
|
+
docs = TestMeta.meta.info
|
46
|
+
|
47
|
+
docs[:foo].should == ""
|
48
|
+
docs[:bar].should == "etc"
|
49
|
+
end
|
50
|
+
|
51
|
+
it "TestMeta shuold be possible add an attribute on the fly" do
|
52
|
+
TestMeta.has :baz, { required: true}
|
53
|
+
tm = TestMeta.new(baz: 1)
|
54
|
+
tm.baz.should == 1
|
55
|
+
|
56
|
+
expect{
|
57
|
+
TestMeta.new
|
58
|
+
}.to raise_error(MooseX::InvalidAttributeError,
|
59
|
+
'attr "baz" is required')
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe TestMeta2 do
|
64
|
+
it "should has 'mymeta'" do
|
65
|
+
TestMeta2.respond_to?(:meta).should be_false
|
66
|
+
TestMeta2.respond_to?(:mymeta).should be_true
|
67
|
+
end
|
68
|
+
|
69
|
+
it "meta should return list of attributes" do
|
70
|
+
attrs = TestMeta2.mymeta.attrs
|
71
|
+
|
72
|
+
attributes = attrs.keys
|
73
|
+
attributes[0].should == :foo
|
74
|
+
attributes[1].should == :bar
|
75
|
+
|
76
|
+
attrs[:foo].is.should == :rw
|
77
|
+
|
78
|
+
attrs[:bar].is.should == :ro
|
79
|
+
attrs[:bar].default.call.should == 1
|
80
|
+
attrs[:bar].doc.should == "etc"
|
81
|
+
end
|
82
|
+
|
83
|
+
it "meta should return list of documentations" do
|
84
|
+
docs = TestMeta2.mymeta.info
|
85
|
+
|
86
|
+
docs[:foo].should == ""
|
87
|
+
docs[:bar].should == "etc"
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|