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
@@ -0,0 +1,192 @@
|
|
1
|
+
require 'moosex/types'
|
2
|
+
require 'moosex/attribute/modifiers'
|
3
|
+
|
4
|
+
module MooseX
|
5
|
+
class Attribute
|
6
|
+
include MooseX::Types
|
7
|
+
|
8
|
+
attr_reader :attr_symbol, :is, :isa, :default, :required, :predicate,
|
9
|
+
:clearer, :handles, :lazy, :reader, :writter, :builder, :init_arg, :trigger,
|
10
|
+
:coerce, :weak, :doc, :methods, :override
|
11
|
+
|
12
|
+
def initialize(attr_symbol, options ,klass)
|
13
|
+
@attr_symbol = attr_symbol
|
14
|
+
|
15
|
+
init_internal_modifiers(options.clone, klass)
|
16
|
+
|
17
|
+
generate_all_methods
|
18
|
+
end
|
19
|
+
|
20
|
+
def init_internal_modifiers(options, klass)
|
21
|
+
|
22
|
+
init_internal_modifiers_1(options)
|
23
|
+
|
24
|
+
init_internal_modifiers_2(options)
|
25
|
+
|
26
|
+
MooseX.warn "Unused attributes #{options} for attribute #{@attr_symbol} @ #{klass} #{klass.class}",caller() if ! options.empty?
|
27
|
+
end
|
28
|
+
|
29
|
+
def init_internal_modifiers_1(options)
|
30
|
+
@is = Is.new.process(options, @attr_symbol)
|
31
|
+
@isa = Isa.new.process(options, @attr_symbol)
|
32
|
+
@default = Default.new.process(options, @attr_symbol)
|
33
|
+
@required = Required.new.process(options, @attr_symbol)
|
34
|
+
@predicate = Predicate.new.process(options, @attr_symbol)
|
35
|
+
@clearer = Clearer.new.process(options, @attr_symbol)
|
36
|
+
@handles = Handles.new.process(options, @attr_symbol)
|
37
|
+
@lazy = Lazy.new.process(options, @attr_symbol)
|
38
|
+
@reader = Reader.new.process(options, @attr_symbol)
|
39
|
+
end
|
40
|
+
|
41
|
+
def init_internal_modifiers_2(options)
|
42
|
+
@writter = Writter.new.process(options, @attr_symbol)
|
43
|
+
@builder = Builder.new.process(options, @attr_symbol) # TODO: warn if has builder and it is not lazy
|
44
|
+
@init_arg = InitArg.new.process(options, @attr_symbol)
|
45
|
+
@trigger = Trigger.new.process(options, @attr_symbol)
|
46
|
+
@coerce = Coerce.new.process(options, @attr_symbol)
|
47
|
+
@weak = Weak.new.process(options, @attr_symbol)
|
48
|
+
@doc = Doc.new.process(options, @attr_symbol)
|
49
|
+
@override = Override.new.process(options, @attr_symbol)
|
50
|
+
end
|
51
|
+
|
52
|
+
def generate_all_methods
|
53
|
+
@methods = {}
|
54
|
+
|
55
|
+
if @reader
|
56
|
+
@methods[@reader] = generate_reader
|
57
|
+
end
|
58
|
+
|
59
|
+
if @writter
|
60
|
+
@methods[@writter] = generate_writter
|
61
|
+
end
|
62
|
+
|
63
|
+
inst_variable_name = "@#{@attr_symbol}".to_sym
|
64
|
+
if @predicate
|
65
|
+
@methods[@predicate] = Proc.new do
|
66
|
+
instance_variable_defined? inst_variable_name
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
inst_variable_name = "@#{@attr_symbol}".to_sym
|
71
|
+
if @clearer
|
72
|
+
@methods[@clearer] = Proc.new do
|
73
|
+
if instance_variable_defined? inst_variable_name
|
74
|
+
remove_instance_variable inst_variable_name
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
generate_handles @attr_symbol
|
80
|
+
end
|
81
|
+
|
82
|
+
def generate_handles(attr_symbol)
|
83
|
+
@handles.each_pair do | method, target_method |
|
84
|
+
if target_method.is_a? Array
|
85
|
+
original, currying = target_method
|
86
|
+
|
87
|
+
@methods[method] = generate_handles_with_currying(attr_symbol,original, currying)
|
88
|
+
else
|
89
|
+
@methods[method] = Proc.new do |*args, &proc|
|
90
|
+
self.send(attr_symbol).send(target_method, *args, &proc)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def generate_handles_with_currying(attr_symbol,original, currying)
|
97
|
+
Proc.new do |*args, &proc|
|
98
|
+
|
99
|
+
a1 = [ currying ]
|
100
|
+
|
101
|
+
if currying.is_a?Proc
|
102
|
+
a1 = currying.call()
|
103
|
+
elsif currying.is_a? Array
|
104
|
+
a1 = currying.map{|c| (c.is_a?(Proc)) ? c.call : c }
|
105
|
+
end
|
106
|
+
|
107
|
+
self.send(attr_symbol).send(original, *a1, *args, &proc)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def init(object, args)
|
112
|
+
value = nil
|
113
|
+
value_from_default = false
|
114
|
+
|
115
|
+
if args.has_key? @init_arg
|
116
|
+
value = args.delete(@init_arg)
|
117
|
+
elsif @default
|
118
|
+
value = @default.call
|
119
|
+
value_from_default = true
|
120
|
+
elsif @required
|
121
|
+
raise InvalidAttributeError, "attr \"#{@attr_symbol}\" is required"
|
122
|
+
else
|
123
|
+
return
|
124
|
+
end
|
125
|
+
|
126
|
+
value = @coerce.call(value)
|
127
|
+
begin
|
128
|
+
@isa.call( value )
|
129
|
+
rescue MooseX::Types::TypeCheckError => e
|
130
|
+
raise MooseX::Types::TypeCheckError, "isa check for field #{attr_symbol}: #{e}"
|
131
|
+
end
|
132
|
+
unless value_from_default
|
133
|
+
@trigger.call(object, value)
|
134
|
+
end
|
135
|
+
inst_variable_name = "@#{@attr_symbol}".to_sym
|
136
|
+
object.instance_variable_set inst_variable_name, value
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
def generate_reader
|
141
|
+
inst_variable_name = "@#{@attr_symbol}".to_sym
|
142
|
+
|
143
|
+
builder = @builder
|
144
|
+
before_get = lambda {|object| }
|
145
|
+
|
146
|
+
if @lazy
|
147
|
+
type_check = protect_isa(@isa, "isa check for #{inst_variable_name} from builder")
|
148
|
+
coerce = @coerce
|
149
|
+
trigger = @trigger
|
150
|
+
before_get = lambda do |object|
|
151
|
+
return if object.instance_variable_defined? inst_variable_name
|
152
|
+
|
153
|
+
value = builder.call(object)
|
154
|
+
value = coerce.call(value)
|
155
|
+
type_check.call( value )
|
156
|
+
|
157
|
+
trigger.call(object, value)
|
158
|
+
object.instance_variable_set(inst_variable_name, value)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
Proc.new do
|
163
|
+
before_get.call(self)
|
164
|
+
instance_variable_get inst_variable_name
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def protect_isa(type_check, message)
|
169
|
+
lambda do |value|
|
170
|
+
begin
|
171
|
+
type_check.call( value )
|
172
|
+
rescue MooseX::Types::TypeCheckError => e
|
173
|
+
raise MooseX::Types::TypeCheckError, "#{message}: #{e}"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def generate_writter
|
179
|
+
writter_name = @writter
|
180
|
+
inst_variable_name = "@#{@attr_symbol}".to_sym
|
181
|
+
coerce = @coerce
|
182
|
+
type_check = protect_isa(@isa, "isa check for #{writter_name}")
|
183
|
+
trigger = @trigger
|
184
|
+
Proc.new do |value|
|
185
|
+
value = coerce.call(value)
|
186
|
+
type_check.call( value )
|
187
|
+
trigger.call(self,value)
|
188
|
+
instance_variable_set inst_variable_name, value
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
@@ -0,0 +1,303 @@
|
|
1
|
+
module MooseX
|
2
|
+
class Attribute
|
3
|
+
module AttrBaseModifier
|
4
|
+
def process(options, attr_symbol)
|
5
|
+
@attr_symbol = attr_symbol
|
6
|
+
|
7
|
+
local_options = { name => default }.merge(options)
|
8
|
+
options.delete(name)
|
9
|
+
|
10
|
+
return nil unless local_options.has_key?(name)
|
11
|
+
|
12
|
+
attr = local_options[name]
|
13
|
+
attr = coerce(attr,attr_symbol)
|
14
|
+
validate(attr, attr_symbol)
|
15
|
+
|
16
|
+
attr = update_options(options, name, attr)
|
17
|
+
|
18
|
+
attr
|
19
|
+
end
|
20
|
+
|
21
|
+
def default; nil; end
|
22
|
+
def coerce(x,f); x ; end
|
23
|
+
def validate(x,f); end
|
24
|
+
def update_options(options, name, attr); attr; end
|
25
|
+
end
|
26
|
+
|
27
|
+
module AttrCoerceToBoolean
|
28
|
+
def coerce(x, f)
|
29
|
+
!! x
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module AttrCoerceToSymbol
|
34
|
+
def coerce(x, f)
|
35
|
+
x.to_sym
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module AttrCoerceMethodToLambda
|
40
|
+
def coerce(x, field_name)
|
41
|
+
unless x.is_a? Proc
|
42
|
+
x_name = x.to_sym
|
43
|
+
x = lambda do |object, *value|
|
44
|
+
object.send(x_name,*value)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
x
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
module AttrCoerceToString
|
53
|
+
def coerce(x, f)
|
54
|
+
x.to_s
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class Is
|
59
|
+
include AttrBaseModifier
|
60
|
+
include AttrCoerceToSymbol
|
61
|
+
def name; :is ; end
|
62
|
+
def default; :rw ; end
|
63
|
+
def validate(is, field_name)
|
64
|
+
unless [:rw, :rwp, :ro, :lazy, :private].include?(is)
|
65
|
+
raise InvalidAttributeError, "invalid value for field '#{field_name}' is '#{is}', must be one of :private, :rw, :rwp, :ro or :lazy"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
def update_options(options, name, attr)
|
69
|
+
if attr == :lazy
|
70
|
+
attr = :ro
|
71
|
+
options[:lazy] = true
|
72
|
+
end
|
73
|
+
|
74
|
+
if attr == :ro
|
75
|
+
options[:writter] = nil
|
76
|
+
end
|
77
|
+
|
78
|
+
attr
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class Isa
|
83
|
+
include AttrBaseModifier
|
84
|
+
def name; :isa ; end
|
85
|
+
def default; MooseX::Attribute.isAny ; end
|
86
|
+
def coerce(isa, field_name); MooseX::Attribute.isType(isa); end
|
87
|
+
end
|
88
|
+
|
89
|
+
class Default
|
90
|
+
include AttrBaseModifier
|
91
|
+
def name; :default ; end
|
92
|
+
def coerce(default, field_name)
|
93
|
+
if default.is_a?(Proc) || default.nil?
|
94
|
+
return default
|
95
|
+
end
|
96
|
+
return lambda { default }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class Required
|
101
|
+
include AttrBaseModifier
|
102
|
+
include AttrCoerceToBoolean
|
103
|
+
def name; :required ; end
|
104
|
+
end
|
105
|
+
|
106
|
+
module AttrCoerceToMethodBasedOnFieldName
|
107
|
+
def coerce(x, field_name)
|
108
|
+
if ! x
|
109
|
+
return x
|
110
|
+
elsif x.is_a? TrueClass
|
111
|
+
return method_name(field_name)
|
112
|
+
end
|
113
|
+
|
114
|
+
begin
|
115
|
+
x.to_sym
|
116
|
+
rescue => e
|
117
|
+
# create a nested exception here
|
118
|
+
raise InvalidAttributeError, "cannot coerce field #{name} to a symbol for #{field_name}: #{e}"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class Predicate
|
124
|
+
include AttrBaseModifier
|
125
|
+
include AttrCoerceToMethodBasedOnFieldName
|
126
|
+
|
127
|
+
def name; :predicate ; end
|
128
|
+
def method_name(field_name); "has_#{field_name}?".to_sym ; end
|
129
|
+
end
|
130
|
+
|
131
|
+
class Clearer
|
132
|
+
include AttrBaseModifier
|
133
|
+
include AttrCoerceToMethodBasedOnFieldName
|
134
|
+
|
135
|
+
def name; :clearer ; end
|
136
|
+
def method_name(field_name); "clear_#{field_name}!".to_sym ; end
|
137
|
+
end
|
138
|
+
|
139
|
+
class Handles
|
140
|
+
include AttrBaseModifier
|
141
|
+
def name; :handles ; end
|
142
|
+
def default; {} ; end
|
143
|
+
|
144
|
+
def populate_handles(handles, field_name)
|
145
|
+
array_of_handles = handles
|
146
|
+
|
147
|
+
unless array_of_handles.is_a? Array
|
148
|
+
array_of_handles = [ array_of_handles ]
|
149
|
+
end
|
150
|
+
|
151
|
+
handles = array_of_handles.map do |handle|
|
152
|
+
|
153
|
+
if handle == BasicObject
|
154
|
+
|
155
|
+
raise InvalidAttributeError, "ops, should not use BasicObject for handles in #{field_name}"
|
156
|
+
|
157
|
+
elsif handle.is_a? Class
|
158
|
+
|
159
|
+
handle = handle.public_instance_methods - handle.superclass.public_instance_methods
|
160
|
+
|
161
|
+
elsif handle.is_a? Module
|
162
|
+
|
163
|
+
handle = handle.public_instance_methods
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
handle
|
168
|
+
|
169
|
+
end.flatten.reduce({}) do |hash, method_name|
|
170
|
+
hash.merge({ method_name => method_name })
|
171
|
+
end
|
172
|
+
|
173
|
+
handles
|
174
|
+
end
|
175
|
+
|
176
|
+
def coerce(handles, field_name)
|
177
|
+
|
178
|
+
unless handles.is_a? Hash
|
179
|
+
handles = populate_handles(handles, field_name)
|
180
|
+
end
|
181
|
+
|
182
|
+
handles.map do |key,value|
|
183
|
+
if value.is_a? Hash
|
184
|
+
raise "ops! Handle should accept only one map / currying" unless value.count == 1
|
185
|
+
|
186
|
+
original, currying = value.shift
|
187
|
+
|
188
|
+
{ key.to_sym => [original.to_sym, currying] }
|
189
|
+
else
|
190
|
+
{ key.to_sym => value.to_sym }
|
191
|
+
end
|
192
|
+
end.reduce({}) do |hash,e|
|
193
|
+
hash.merge(e)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
class Lazy
|
199
|
+
include AttrBaseModifier
|
200
|
+
include AttrCoerceToBoolean
|
201
|
+
def name; :lazy ; end
|
202
|
+
end
|
203
|
+
|
204
|
+
class Reader
|
205
|
+
include AttrBaseModifier
|
206
|
+
include AttrCoerceToSymbol
|
207
|
+
def name; :reader ; end
|
208
|
+
def default
|
209
|
+
@attr_symbol
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
class Writter
|
214
|
+
include AttrBaseModifier
|
215
|
+
|
216
|
+
def name; :writter ; end
|
217
|
+
def default
|
218
|
+
@attr_symbol.to_s.concat("=").to_sym
|
219
|
+
end
|
220
|
+
def coerce(writter, field_name)
|
221
|
+
return writter if writter.nil?
|
222
|
+
|
223
|
+
writter.to_sym
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
class Builder
|
228
|
+
include AttrBaseModifier
|
229
|
+
include AttrCoerceMethodToLambda
|
230
|
+
|
231
|
+
def name; :builder ; end
|
232
|
+
def default
|
233
|
+
"build_#{@attr_symbol}".to_sym
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
class InitArg
|
238
|
+
include AttrBaseModifier
|
239
|
+
include AttrCoerceToSymbol
|
240
|
+
|
241
|
+
def name; :init_arg; end
|
242
|
+
|
243
|
+
def default
|
244
|
+
@attr_symbol
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
|
249
|
+
class Trigger
|
250
|
+
include AttrBaseModifier
|
251
|
+
include AttrCoerceMethodToLambda
|
252
|
+
|
253
|
+
def name; :trigger; end
|
254
|
+
|
255
|
+
def default
|
256
|
+
lambda{|object, value| }
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
class Coerce
|
261
|
+
include AttrBaseModifier
|
262
|
+
include AttrCoerceMethodToLambda
|
263
|
+
|
264
|
+
def name; :coerce; end
|
265
|
+
|
266
|
+
def default
|
267
|
+
lambda {|object| object}
|
268
|
+
end
|
269
|
+
|
270
|
+
def update_options(options, name, attr)
|
271
|
+
if options[:weak]
|
272
|
+
old_coerce = attr
|
273
|
+
attr = lambda do |value|
|
274
|
+
WeakRef.new old_coerce.call(value)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
attr
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
class Weak
|
283
|
+
include AttrBaseModifier
|
284
|
+
include AttrCoerceToBoolean
|
285
|
+
|
286
|
+
def name; :weak ; end
|
287
|
+
end
|
288
|
+
|
289
|
+
class Doc
|
290
|
+
include AttrBaseModifier
|
291
|
+
include AttrCoerceToString
|
292
|
+
|
293
|
+
def name; :doc ; end
|
294
|
+
end
|
295
|
+
|
296
|
+
class Override
|
297
|
+
include AttrBaseModifier
|
298
|
+
include AttrCoerceToBoolean
|
299
|
+
|
300
|
+
def name; :override ; end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|