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.
@@ -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