contextr 0.0.1 → 0.0.2

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,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