contextr 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,405 @@
1
+ module ContextR
2
+ # This is the public interface of a Layer within a class definition. Use it
3
+ # to add context-dependent behaviour. It is available after calling
4
+ # +layer <em>layer_name</em>+ within a class body via
5
+ # +<em>layer_name</em>+. Each wrapper block should expect an instance of
6
+ # MethodNature to manipulate arguments or return values.
7
+ #
8
+ # class Foo
9
+ # layer :any
10
+ #
11
+ # def bar
12
+ # "bar"
13
+ # end
14
+ #
15
+ # any.pre :bar do | n |
16
+ # logger.info "Foo#bar called"
17
+ # end
18
+ # end
19
+ class LayerInClass
20
+ attr_accessor :contextualized_class, :layer # :nodoc:
21
+
22
+ def initialize( contextualized_class, layer ) # :nodoc:
23
+ self.contextualized_class = contextualized_class
24
+ self.layer = layer
25
+ end
26
+
27
+ # Adds a pre-wrapper to a single method.
28
+ #
29
+ # :call-seq:
30
+ # pre( method_name ) { | method_nature | ... }
31
+ #
32
+ def pre( method_name, &block )
33
+ layer.methods_of( self.contextualized_class )[method_name].pres <<
34
+ block.to_unbound_method( self.contextualized_class )
35
+ nil
36
+ end
37
+
38
+ # Adds a post-wrapper to a single method
39
+ #
40
+ # :call-seq:
41
+ # post( method_name ) { | method_nature | ... }
42
+ #
43
+ def post( method_name, &block )
44
+ layer.methods_of( self.contextualized_class )[method_name].posts <<
45
+ block.to_unbound_method( self.contextualized_class )
46
+ nil
47
+ end
48
+
49
+ # Adds an around-wrapper to a single method
50
+ #
51
+ # :call-seq:
52
+ # around( method_name ) { | method_nature | ... }
53
+ #
54
+ def around( method_name, &block )
55
+ layer.methods_of( self.contextualized_class )[method_name].arounds <<
56
+ block.to_unbound_method( self.contextualized_class )
57
+ nil
58
+ end
59
+
60
+ alias :wrap :around
61
+ end
62
+
63
+ class Layer # :nodoc:
64
+ # its role as base instance of all layers
65
+ class << self
66
+ attr_accessor_with_default_setter :base_layers, :combined_layers do
67
+ Hash.new
68
+ end
69
+
70
+ def core_methods
71
+ @core_methods ||= Hash.new do | hash, extended_class |
72
+ add_redefine_callback( extended_class )
73
+ hash[extended_class] = Hash.new do | class_hash, method_name |
74
+ um = extended_class.instance_method( method_name )
75
+ replace_core_method( extended_class, method_name )
76
+ class_hash[method_name] = um
77
+ end
78
+ end
79
+ end
80
+
81
+ def inherited( new_base_layer )
82
+ unless new_base_layer.name.empty?
83
+ base_layers[ContextR::symbolize( new_base_layer )] = new_base_layer
84
+ end
85
+ end
86
+
87
+ def compose( layers )
88
+ # TODO: Add better caching
89
+ combined_layers[ layers ] ||=
90
+ layers.reverse.inject( nil ) do | akku, layer |
91
+ layer + akku
92
+ end
93
+ end
94
+
95
+ protected
96
+ def replace_core_method( extended_class, method_name )
97
+ num_of_args = extended_class.instance_method( method_name ).arity
98
+ arg_signature = case num_of_args <=> 0
99
+ when 0
100
+ ""
101
+ when 1
102
+ "%s" % Array.new( num_of_args ) { |i| "arg%d" % i }.join( ", " )
103
+ else
104
+ "*arguments"
105
+ end
106
+ arg_call = arg_signature.empty? ? "" : ", " + arg_signature
107
+
108
+ extended_class.class_eval( %Q{
109
+ remove_method :#{method_name}
110
+ def #{method_name} #{arg_signature}
111
+ ContextR::current_layer.extended( self ).send(
112
+ :#{method_name}#{arg_call} )
113
+ end
114
+ }, __FILE__, __LINE__ )
115
+ end
116
+
117
+ def add_redefine_callback( extended_class )
118
+ (class << extended_class; self; end).instance_eval do
119
+ define_method( :method_added ) do | method_name |
120
+ if ContextR::Layer.core_methods[extended_class].
121
+ include?( method_name )
122
+ warn( caller.first + " : ContextR - Redefining already wrapped methods is not supported yet. Your changes _may_ have no effect." )
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ class Layer # :nodoc:
131
+ # its role as base class for all other layers
132
+ class << self
133
+ attr_accessor_with_default_setter :extended_classes do
134
+ Hash.new do | classes, extended_class |
135
+ classes[extended_class] = Hash.new do | hash, key |
136
+ hash[key.to_sym] = ContextualizedMethod.new(
137
+ ContextR::Layer.core_methods[ extended_class ][ key.to_sym ] )
138
+ end
139
+ end
140
+ end
141
+ attr_accessor :extended_objects
142
+
143
+ def methods_of( extended_class )
144
+ self.extended_classes[extended_class]
145
+ end
146
+
147
+ def extended( object )
148
+ self.extended_objects ||= Hash.new do | cache, object |
149
+ cache[ object ] =
150
+ ExtendedObject.new( object, self.methods_of( object.class ) )
151
+ end
152
+ self.extended_objects[object]
153
+ end
154
+
155
+ def + other_layer
156
+ if other_layer.nil?
157
+ self
158
+ else
159
+ combined_layer = Class.new( Layer )
160
+ combined_layer.extended_classes = self.merge_extended_classes_with(
161
+ other_layer.extended_classes )
162
+ combined_layer
163
+ end
164
+ end
165
+
166
+ protected
167
+
168
+ def merge_extended_classes_with( other_ec )
169
+ extended_classes.merge( other_ec ) do | extended_c, my_ms, other_ms |
170
+ my_ms.merge( other_ms ) do | method_name, my_cm, other_cm |
171
+ my_cm + other_cm
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+
178
+ class ExtendedObject # :nodoc:
179
+ attr_accessor :proxied_object
180
+ attr_accessor :extended_methods
181
+ attr_accessor :behaviours
182
+
183
+ def initialize( proxied_object, extended_methods )
184
+ self.proxied_object = proxied_object
185
+ self.extended_methods = extended_methods
186
+ self.behaviours = {}
187
+ end
188
+
189
+ def send( method_name, *arguments )
190
+ ( self.behaviours[method_name] ||=
191
+ self.extended_methods[method_name].behaviour( self.proxied_object )
192
+ ).call( *arguments )
193
+ end
194
+ end
195
+
196
+ class ContextualizedMethod # :nodoc:
197
+ attr_accessor :core
198
+ attr_accessor :behaviour_cache
199
+
200
+ attr_accessor_with_default_setter :pres, :posts, :arounds do
201
+ Array.new
202
+ end
203
+
204
+ def initialize( unbound_core_method )
205
+ self.core = unbound_core_method
206
+ self.behaviour_cache = {}
207
+ end
208
+
209
+ def + other_cm
210
+ new_cm = ContextualizedMethod.new( self.core )
211
+ new_cm.pres = self.pres + other_cm.pres
212
+ new_cm.posts = self.posts + other_cm.posts
213
+ new_cm.arounds = self.arounds + other_cm.arounds
214
+ new_cm
215
+ end
216
+
217
+ def behaviour( instance )
218
+ self.behaviour_cache[instance] ||=
219
+ self.send( self.behaviour_name, instance )
220
+ end
221
+
222
+ def behaviour_name
223
+ wrappers = []
224
+ wrappers << "pres" unless self.pres.empty?
225
+ wrappers << "arounds" unless self.arounds.empty?
226
+ wrappers << "posts" unless self.posts.empty?
227
+
228
+ "behaviour_" + ( wrappers.empty? ? "without_wrappers" :
229
+ "with_" + wrappers.join( "_and_" ) )
230
+ end
231
+
232
+ def behaviour_without_wrappers( instance )
233
+ self.core.bind( instance )
234
+ end
235
+
236
+ def behaviour_with_pres( instance )
237
+ combined_pres = self.combine_pres( instance )
238
+ bound_core = self.bind_core( instance )
239
+
240
+ lambda do | *arguments |
241
+ nature = MethodNature.new( arguments, nil, false )
242
+
243
+ combined_pres.call( nature )
244
+ unless nature.break
245
+ bound_core.call( *nature.arguments )
246
+ else
247
+ nature.return_value
248
+ end
249
+ end
250
+ end
251
+
252
+ def behaviour_with_posts( instance )
253
+ bound_core = self.bind_core( instance )
254
+ combined_posts = self.combine_posts( instance )
255
+
256
+ lambda do | *arguments |
257
+ nature = MethodNature.new( arguments, nil, false )
258
+
259
+ nature.return_value = bound_core.call( *arguments )
260
+ combined_posts.call( nature )
261
+
262
+ nature.return_value
263
+ end
264
+ end
265
+
266
+ def behaviour_with_pres_and_posts( instance )
267
+ combined_pres = self.combine_pres( instance )
268
+ bound_core = self.bind_core( instance )
269
+ combined_posts = self.combine_posts( instance )
270
+
271
+ lambda do | *arguments |
272
+ nature = MethodNature.new( arguments, nil, false )
273
+
274
+ combined_pres.call( nature )
275
+ unless nature.break
276
+ nature.return_value = bound_core.call( *nature.arguments )
277
+ combined_posts.call( nature )
278
+ end
279
+ nature.return_value
280
+ end
281
+ end
282
+
283
+ def behaviour_with_arounds( instance )
284
+ bound_core = self.bind_core( instance )
285
+ bound_arounds = self.bind_arounds( instance )
286
+
287
+ lambda do | *arguments |
288
+ working_arounds = bound_arounds.clone
289
+ nature = MethodNature.new( arguments, nil, false)
290
+ nature.block = around_block( nature, working_arounds, bound_core )
291
+
292
+ catch( :break_in_around ) do
293
+ working_arounds.shift.call( nature )
294
+ end
295
+ nature.return_value
296
+ end
297
+ end
298
+
299
+ def behaviour_with_pres_and_arounds( instance )
300
+ combined_pres = self.combine_pres( instance )
301
+ bound_core = self.bind_core( instance )
302
+ bound_arounds = self.bind_arounds( instance )
303
+
304
+ lambda do | *arguments |
305
+ nature = MethodNature.new( arguments, nil, false )
306
+
307
+ combined_pres.call( nature )
308
+ unless nature.break
309
+ working_arounds = bound_arounds.clone
310
+ nature.block = around_block( nature, working_arounds, bound_core )
311
+ catch( :break_in_around ) do
312
+ working_arounds.shift.call( nature )
313
+ end
314
+ end
315
+ nature.return_value
316
+ end
317
+ end
318
+
319
+ def behaviour_with_arounds_and_posts( instance )
320
+ bound_core = self.bind_core( instance )
321
+ bound_arounds = self.bind_arounds( instance )
322
+ combined_posts = self.combine_posts( instance )
323
+
324
+ lambda do | *arguments |
325
+ working_arounds = bound_arounds.clone
326
+ nature = MethodNature.new( arguments, nil, false,
327
+ around_block( nature, working_arounds, bound_core ) )
328
+
329
+ catch( :break_in_around ) do
330
+ working_arounds.shift.call( nature )
331
+ end
332
+ combinded_posts.call( nature ) unless nature.break
333
+
334
+ nature.return_value
335
+ end
336
+ end
337
+
338
+ def behaviour_with_pres_and_arounds_and_posts( instance )
339
+ combined_pres = self.combine_pres( instance )
340
+ bound_core = self.bind_core( instance )
341
+ bound_arounds = self.bind_arounds( instance )
342
+ combined_posts = self.combine_posts( instance )
343
+
344
+ lambda do | *arguments |
345
+ nature = MethodNature.new( arguments, nil, false )
346
+
347
+ combined_pres.call( nature )
348
+ unless nature.break
349
+ working_arounds = bound_arounds.clone
350
+ nature.block = around_block( nature, working_arounds, bound_core )
351
+ catch( :break_in_around ) do
352
+ working_arounds.shift.call( nature )
353
+ end
354
+ unless nature.break
355
+ combined_posts.call( nature )
356
+ end
357
+ end
358
+ nature.return_value
359
+ end
360
+ end
361
+
362
+
363
+ # helpers
364
+ def combine_pres( instance )
365
+ bound_pres = self.pres.collect { | p | p.bind( instance ) }
366
+ lambda do | nature |
367
+ bound_pres.each do | bound_pre |
368
+ bound_pre.call( nature )
369
+ break if nature.break
370
+ end
371
+ end
372
+ end
373
+
374
+ def bind_arounds( instance )
375
+ self.arounds.collect { | a | a.bind( instance ) }
376
+ end
377
+
378
+ def around_block( nature, bound_arounds, bound_core )
379
+ lambda do
380
+ unless bound_arounds.empty?
381
+ bound_arounds.shift.call( nature )
382
+ throw( :break_in_around ) if nature.break
383
+ else
384
+ nature.return_value = bound_core.call( *nature.arguments )
385
+ end
386
+ end
387
+ end
388
+
389
+ def bind_core( instance )
390
+ self.core.bind( instance )
391
+ end
392
+
393
+ def combine_posts( instance )
394
+ bound_posts = self.posts.collect { | p | p.bind( instance ) }
395
+ lambda do | nature |
396
+ bound_posts.reverse.each do | bound_post |
397
+ bound_post.call( nature )
398
+ break if nature.break
399
+ end
400
+ nature.return_value
401
+ end
402
+ end
403
+ end
404
+ end
405
+
@@ -2,7 +2,7 @@ module ContextR #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
4
  MINOR = 0
5
- TINY = 1
5
+ TINY = 2
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -1,4 +1,9 @@
1
+ # ContextR extends Class to allow the definition of context-dependent behaviour.
2
+ #
3
+ # After registering a layer within in class, it is accessable via its name,
4
+ # to add behaviour to methods.
1
5
  class Class
6
+ # register a layer to be used within a class body
2
7
  def layer( *layer_keys )
3
8
  layer_keys.each do | layer_key |
4
9
  layer_key = layer_key.to_s.downcase.to_sym
@@ -15,7 +20,7 @@ class Class
15
20
  end
16
21
 
17
22
  protected
18
- def define_private_class_method( symbol, &block )
23
+ def define_private_class_method( symbol, &block ) # :nodoc:
19
24
  (class << self; self; end).instance_eval do
20
25
  define_method( symbol, block )
21
26
  private symbol
@@ -1,10 +1,46 @@
1
1
  class Module
2
+ # returns the name without namespace prefixes
3
+ #
4
+ # module A
5
+ # module B
6
+ # module C
7
+ # end
8
+ # end
9
+ # end
10
+ # module C
11
+ # end
12
+ #
13
+ # A::B::C.name # => "A::B::C"
14
+ # C.name # => "C"
15
+ # A::B::C.namespace_free_name # => "C"
16
+ # C.namespace_free_name # => "C"
17
+ #
2
18
  def namespace_free_name
3
19
  self.name.match( /(\w*?)$/ )[1]
4
20
  end
5
21
 
6
- def attr_accessor_with_default_setter( *syms, &block )
7
- raise 'Default value in block required' unless block
22
+ # allows the definition of an attr_accessor with a setter, that is used
23
+ # to set the instance variable if it is accessed before set.
24
+ #
25
+ # class A
26
+ # attr_accessor_with_default_setter :first_access { Time.now }
27
+ # end
28
+ #
29
+ # A.new.first_access # => Wed May 09 18:23:36 0200 2007
30
+ #
31
+ # a1 = A.new
32
+ # a1.first_access # => Wed May 09 18:23:38 0200 2007
33
+ # a1.first_access # => Wed May 09 18:23:38 0200 2007
34
+ #
35
+ # a2 = A.new
36
+ # a2.first_access = Time.now - 10.days
37
+ # a2.first_access # => Sun Apr 29 18:23:40 0200 2007
38
+ #
39
+ # :call-seq:
40
+ # attr_accessor_with_default_setter(symbol, ...) { ... }
41
+ #
42
+ def attr_accessor_with_default_setter( *syms )
43
+ raise 'Default value in block required' unless block_given?
8
44
  syms.each do | sym |
9
45
  module_eval do
10
46
  attr_writer( sym )
@@ -16,7 +52,7 @@ class Module
16
52
  if instance_variables.include? "@#{sym}"
17
53
  instance_variable_get( "@#{sym}" )
18
54
  else
19
- instance_variable_set( "@#{sym}", block.call )
55
+ instance_variable_set( "@#{sym}", yield )
20
56
  end
21
57
  end
22
58
  end