moosex 0.0.17 → 0.0.18

Sign up to get free protection for your applications and to get access to all the features.
@@ -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