contextr 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,11 @@
1
+ +++ 0.0.2 2007-05-08
2
+
3
+ + 1 major change:
4
+ + Changed the wrapper execution order to a hopefulle more natural ordering
5
+ + Added documentation
6
+ + 1 minor change:
7
+ + finished transition from unit tests to rspec based specification
8
+
1
9
  +++ 0.0.1 2007-05-08
2
10
 
3
11
  + 1 major enhancement:
@@ -41,8 +41,8 @@ You can redistribute it and/or modify it under either the terms of the GPL
41
41
  software (possibly commercial). But some files in the distribution
42
42
  are not written by the author, so that they are not under this terms.
43
43
 
44
- They are ./tasks/annotations.rb and all files under the ./ext directory.
45
- See each file for the copying condition.
44
+ They are ./tasks/annotations.rb and all files under the lib/ext
45
+ directory. See each file for the copying condition.
46
46
 
47
47
  5. The scripts and library files supplied as input to or produced as
48
48
  output from the software do not automatically fall under the
@@ -6,19 +6,21 @@ README.txt
6
6
  Rakefile
7
7
  examples/education.rb
8
8
  examples/fibonacci.rb
9
- ext/dynamic.rb
10
- ext/method_nature.rb
11
9
  lib/contextr.rb
12
10
  lib/contextr/contextr.rb
11
+ lib/contextr/layer.rb
13
12
  lib/contextr/version.rb
14
13
  lib/core_ext/class.rb
15
14
  lib/core_ext/module.rb
16
15
  lib/core_ext/proc.rb
16
+ lib/ext/dynamic.rb
17
+ lib/ext/method_nature.rb
17
18
  rake/group_spec_task.rb
18
19
  rake/specific_group_spec_task.rb
19
20
  scripts/txt2html
20
21
  setup.rb
21
22
  spec/contextr/contextr_api_spec.rb
23
+ spec/contextr/contextr_functional_spec.rb
22
24
  spec/core_ext/module_spec.rb
23
25
  spec/core_ext/proc_spec.rb
24
26
  spec/spec_helper.rb
data/README.txt CHANGED
@@ -1,5 +1,4 @@
1
- ContextR
2
- ========
1
+ = ContextR
3
2
 
4
3
  A context-oriented programming library for Ruby.
5
4
 
@@ -7,11 +6,45 @@ Inspired by ContextL (Pascal Costanza) and ContextS (Robert Hirschfeld) with
7
6
  thanks to Christian Neukirchen for giving the name and lots of ideas.
8
7
 
9
8
  For more information see
9
+ - http://contextr.rubyforge.org/
10
10
  - http://www.contextr.org/ or
11
11
  - http://www.swa.hpi.uni-potsdam.de/cop/
12
12
 
13
- This code is published under the same license as Ruby. See LICENSE for more
13
+ This code is published under the same license as Ruby. See LICENSE.txt for more
14
14
  information.
15
15
 
16
16
  (c) 2007 - Gregor Schmidt - Berlin, Germany
17
17
 
18
+ = Usage
19
+
20
+ require 'rubygems'
21
+ require 'contextr'
22
+
23
+ class A
24
+ def a
25
+ puts "a"
26
+ end
27
+
28
+ layer :foo
29
+
30
+ foo.post :a do | n |
31
+ n.return_value += "_with_context"
32
+ end
33
+ end
34
+
35
+ A.new.a # => "a"
36
+
37
+ ContextR::with_layers( :foo ) do
38
+ A.new.a # => "a_with_context"
39
+ end
40
+
41
+ = Starting Points
42
+
43
+ For a more detailed description
44
+ - visit the project homepage at RubyForge[http://contextr.rubyforge.org/]
45
+ - have a look at the +examples+ folder in the ContextR distribution
46
+
47
+ For detailed API descriptions have a look at the following classes and modules
48
+ - Class
49
+ - ContextR::LayerInClass
50
+ - ContextR::ClassMethods
@@ -2,8 +2,6 @@ require "rubygems"
2
2
  require "contextr"
3
3
 
4
4
  class Person
5
- layer :address, :education
6
-
7
5
  attr_accessor :name, :address, :university
8
6
 
9
7
  def initialize name, address, university
@@ -15,19 +13,9 @@ class Person
15
13
  def to_s
16
14
  "Name: #{name}"
17
15
  end
18
-
19
- address.post :to_s do | n |
20
- n.return_value += "; Address: #{address}"
21
- end
22
-
23
- education.post :to_s do | n |
24
- n.return_value += ";\n[Education] #{university}"
25
- end
26
16
  end
27
17
 
28
18
  class University
29
- layer :address
30
-
31
19
  attr_accessor :name, :address
32
20
 
33
21
  def initialize name, address
@@ -38,6 +26,22 @@ class University
38
26
  def to_s
39
27
  "Name: #{name}"
40
28
  end
29
+ end
30
+
31
+ class Person
32
+ layer :address, :education
33
+
34
+ address.post :to_s do | n |
35
+ n.return_value += "; Address: #{address}"
36
+ end
37
+
38
+ education.post :to_s do | n |
39
+ n.return_value += ";\n[Education] #{university}"
40
+ end
41
+ end
42
+
43
+ class University
44
+ layer :address
41
45
 
42
46
  address.post :to_s do | n |
43
47
  n.return_value += "; Address: #{address}"
@@ -50,15 +54,15 @@ somePerson = Person.new( "Gregor Schmidt", "Berlin", hpi )
50
54
 
51
55
  puts
52
56
  puts somePerson
53
- ContextR::with_layers :address do
57
+ ContextR::with_layers :education do
54
58
  puts
55
59
  puts somePerson
56
60
 
57
- ContextR::with_layers :education do
61
+ ContextR::with_layers :address do
58
62
  puts
59
63
  puts somePerson
60
64
 
61
- ContextR::without_layers :address do
65
+ ContextR::without_layers :education do
62
66
  puts
63
67
  puts somePerson
64
68
  end
@@ -1,13 +1,15 @@
1
+ #--
1
2
  # TODO: get rid of this workaround to avoid double loading on `rake test`
3
+ #++
2
4
  unless Object.const_defined? "ContextR"
3
5
  require 'rubygems'
4
6
  require 'active_support'
5
7
 
6
- Dir[File.join(File.dirname(__FILE__), '../ext/**/*.rb')].sort.each { |lib| require lib }
8
+ Dir[File.join(File.dirname(__FILE__), 'ext/**/*.rb')].sort.each { |lib| require lib }
7
9
  Dir[File.join(File.dirname(__FILE__), 'core_ext/**/*.rb')].sort.each { |lib| require lib }
8
10
  Dir[File.join(File.dirname(__FILE__), 'contextr/**/*.rb')].sort.each { |lib| require lib }
9
11
 
10
12
  end
11
13
  unless Dynamic.variables.include?( :layers )
12
- Dynamic.variable( :layers => [ ContextR::DefaultLayer ] )
14
+ Dynamic.variable( :layers => [ ContextR::layer_by_symbol( :default ) ] )
13
15
  end
@@ -1,19 +1,25 @@
1
1
  module ContextR
2
- @@genid = 0
3
- class << self
4
- def gensym( name, kind = "", postfix = "_%05d_" )
5
- @@genid += 1
6
- ( "_#{name}_#{kind}#{postfix}" % @@genid ).intern
7
- end
8
-
9
- def symbolize( layer_klass )
10
- layer_klass.namespace_free_name.gsub( "Layer", "" ).downcase.to_sym
11
- end
12
-
13
- def layerize( layer_symbol )
14
- "#{layer_symbol}_layer".camelize
15
- end
16
2
 
3
+ # This module is mixed into ContextR module, so that all public methods
4
+ # are available via
5
+ # ContextR::current_layers
6
+ # ContextR::with_layers( layer_name, ... ) { ... }
7
+ # ContextR::without_layers( layer_name, ... ) { ... }
8
+ module ClassMethods
9
+ # allows the explicit activation of layers within a block context
10
+ #
11
+ # ContextR::with_layers( :foo, :bar ) do
12
+ # ContextR::current_layers # => [:default, :foo, :bar]
13
+ #
14
+ # ContextR::with_layers( :baz ) do
15
+ # ContextR::current_layers # => [:default, :foo, :bar, :baz]
16
+ # end
17
+ #
18
+ # end
19
+ #
20
+ # :call-seq:
21
+ # with_layers( layer_name, ... ) { ... }
22
+ #
17
23
  def with_layers( *layer_symbols, &block )
18
24
  layers = layer_symbols.collect do | layer_symbol |
19
25
  ContextR.layer_by_name( ContextR.layerize( layer_symbol ) )
@@ -21,6 +27,20 @@ module ContextR
21
27
  Dynamic.let( { :layers => Dynamic[:layers] | layers }, &block )
22
28
  end
23
29
 
30
+ # allows the explicit deactivation of layers within a block context
31
+ #
32
+ # ContextR::with_layers( :foo, :bar ) do
33
+ # ContextR::current_layers # => [:default, :foo, :bar]
34
+ #
35
+ # ContextR::without_layers( :foo ) do
36
+ # ContextR::current_layers # => [:default, :bar]
37
+ # end
38
+ #
39
+ # end
40
+ #
41
+ # :call-seq:
42
+ # without_layers( layer_name, ... ) { ... }
43
+ #
24
44
  def without_layers( *layer_symbols, &block )
25
45
  layers = layer_symbols.collect do | layer_symbol |
26
46
  ContextR.layer_by_name( ContextR.layerize( layer_symbol ) )
@@ -28,11 +48,30 @@ module ContextR
28
48
  Dynamic.let( { :layers => Dynamic[:layers] - layers }, &block )
29
49
  end
30
50
 
31
- def layer_by_symbol( layer_symbol )
51
+ # returns the names of the currently activated layers
52
+ #
53
+ # ContextR::current_layers # => [:default]
54
+ #
55
+ # ContextR::with_layers :foo do
56
+ # ContextR::current_layers # => [:default, :foo]
57
+ # end
58
+ def current_layers
59
+ Dynamic[:layers].collect{ | layer_class | self.symbolize( layer_class ) }
60
+ end
61
+
62
+ def symbolize( layer_klass ) # :nodoc:
63
+ layer_klass.namespace_free_name.gsub( "Layer", "" ).downcase.to_sym
64
+ end
65
+
66
+ def layerize( layer_symbol ) # :nodoc:
67
+ "#{layer_symbol}_layer".camelize
68
+ end
69
+
70
+ def layer_by_symbol( layer_symbol ) # :nodoc:
32
71
  layer_by_name( layerize( layer_symbol ) )
33
72
  end
34
73
 
35
- def layer_by_name( layer_name )
74
+ def layer_by_name( layer_name ) # :nodoc:
36
75
  unless ContextR.const_defined?( layer_name )
37
76
  ContextR::module_eval(
38
77
  "class #{layer_name} < Layer; end", __FILE__, __LINE__ )
@@ -41,377 +80,11 @@ module ContextR
41
80
  ContextR.const_get( layer_name )
42
81
  end
43
82
 
44
- def current_layer
83
+ def current_layer # :nodoc:
45
84
  Layer.compose( Dynamic[:layers] )
46
85
  end
47
- end
48
-
49
-
50
- class Layer # its role as base instance of all layers
51
- class << self
52
- attr_accessor_with_default_setter :base_layers, :combined_layers do
53
- Hash.new
54
- end
55
-
56
- def core_methods
57
- @core_methods ||= Hash.new do | hash, extended_class |
58
- add_redefine_callback( extended_class )
59
- hash[extended_class] = Hash.new do | class_hash, method_name |
60
- um = extended_class.instance_method( method_name )
61
- replace_core_method( extended_class, method_name )
62
- class_hash[method_name] = um
63
- end
64
- end
65
- end
66
-
67
- def inherited( new_base_layer )
68
- unless new_base_layer.name.empty?
69
- base_layers[ContextR::symbolize( new_base_layer )] = new_base_layer
70
- end
71
- end
72
-
73
- def compose( layers )
74
- # TODO: Add better caching
75
- combined_layers[ layers ] ||=
76
- layers.reverse.inject( nil ) do | akku, layer |
77
- layer + akku
78
- end
79
- end
80
-
81
- protected
82
- def replace_core_method( extended_class, method_name )
83
- num_of_args = extended_class.instance_method( method_name ).arity
84
- arg_signature = case num_of_args <=> 0
85
- when 0
86
- ""
87
- when 1
88
- "%s" % Array.new( num_of_args ) { |i| "arg%d" % i }.join( ", " )
89
- else
90
- "*arguments"
91
- end
92
- arg_call = arg_signature.empty? ? "" : ", " + arg_signature
93
-
94
- extended_class.class_eval( %Q{
95
- remove_method :#{method_name}
96
- def #{method_name} #{arg_signature}
97
- ContextR::current_layer.extended( self ).send(
98
- :#{method_name}#{arg_call} )
99
- end
100
- }, __FILE__, __LINE__ )
101
- end
102
-
103
- def add_redefine_callback( extended_class )
104
- (class << extended_class; self; end).instance_eval do
105
- define_method( :method_added ) do | method_name |
106
- if ContextR::Layer.core_methods[extended_class].
107
- include?( method_name )
108
- warn( caller.first + " : ContextR - Redefining already wrapped methods is not supported yet. Your changes _may_ have no effect." )
109
- end
110
- end
111
- end
112
- end
113
- end
114
- end
115
-
116
- class Layer # its role as base class for all other layers
117
- class << self
118
- attr_accessor_with_default_setter :extended_classes do
119
- Hash.new do | classes, extended_class |
120
- classes[extended_class] = Hash.new do | hash, key |
121
- hash[key] = ContextualizedMethod.new(
122
- ContextR::Layer.core_methods[ extended_class ][ key ] )
123
- end
124
- end
125
- end
126
- attr_accessor :extended_objects
127
-
128
- def methods_of( extended_class )
129
- self.extended_classes[extended_class]
130
- end
131
-
132
- def extended( object )
133
- self.extended_objects ||= Hash.new do | cache, object |
134
- cache[ object ] =
135
- ExtendedObject.new( object, self.methods_of( object.class ) )
136
- end
137
- self.extended_objects[object]
138
- end
139
-
140
- def + other_layer
141
- if other_layer.nil?
142
- self
143
- else
144
- combined_layer = Class.new( Layer )
145
- combined_layer.extended_classes = self.merge_extended_classes_with(
146
- other_layer.extended_classes )
147
- combined_layer
148
- end
149
- end
150
-
151
- protected
152
-
153
- def merge_extended_classes_with( other_ec )
154
- extended_classes.merge( other_ec ) do | extended_c, my_ms, other_ms |
155
- my_ms.merge( other_ms ) do | method_name, my_cm, other_cm |
156
- my_cm + other_cm
157
- end
158
- end
159
- end
160
- end
161
- end
162
86
 
163
- class LayerInClass # its public interface
164
- attr_accessor :contextualized_class
165
- attr_accessor :layer
166
-
167
- def initialize( contextualized_class, layer )
168
- self.contextualized_class = contextualized_class
169
- self.layer = layer
170
- end
171
-
172
- def pre( method_name, &block )
173
- layer.methods_of( self.contextualized_class )[method_name].pres <<
174
- block.to_unbound_method( self.contextualized_class )
175
- nil
176
- end
177
- def post( method_name, &block )
178
- layer.methods_of( self.contextualized_class )[method_name].posts <<
179
- block.to_unbound_method( self.contextualized_class )
180
- nil
181
- end
182
- def around( method_name, &block )
183
- layer.methods_of( self.contextualized_class )[method_name].arounds <<
184
- block.to_unbound_method( self.contextualized_class )
185
- nil
186
- end
187
- alias :wrap :around
188
- end
189
-
190
- class ExtendedObject
191
- attr_accessor :proxied_object
192
- attr_accessor :extended_methods
193
- attr_accessor :behaviours
194
-
195
- def initialize( proxied_object, extended_methods )
196
- self.proxied_object = proxied_object
197
- self.extended_methods = extended_methods
198
- self.behaviours = {}
199
- end
200
-
201
- def send( method_name, *arguments )
202
- ( self.behaviours[method_name] ||=
203
- self.extended_methods[method_name].behaviour( self.proxied_object )
204
- ).call( *arguments )
205
- end
206
- end
207
-
208
- class ContextualizedMethod
209
- attr_accessor :core
210
- attr_accessor :behaviour_cache
211
-
212
- attr_accessor_with_default_setter :pres, :posts, :arounds do
213
- Array.new
214
- end
215
-
216
- def initialize( unbound_core_method )
217
- self.core = unbound_core_method
218
- self.behaviour_cache = {}
219
- end
220
-
221
- def + other_cm
222
- new_cm = ContextualizedMethod.new( self.core )
223
- new_cm.pres = self.pres + other_cm.pres
224
- new_cm.posts = self.posts + other_cm.posts
225
- new_cm.arounds = self.arounds + other_cm.arounds
226
- new_cm
227
- end
228
-
229
- def behaviour( instance )
230
- self.behaviour_cache[instance] ||=
231
- self.send( self.behaviour_name, instance )
232
- end
233
-
234
- def behaviour_name
235
- wrappers = []
236
- wrappers << "pres" unless self.pres.empty?
237
- wrappers << "arounds" unless self.arounds.empty?
238
- wrappers << "posts" unless self.posts.empty?
239
-
240
- "behaviour_" + ( wrappers.empty? ? "without_wrappers" :
241
- "with_" + wrappers.join( "_and_" ) )
242
- end
243
-
244
- def behaviour_without_wrappers( instance )
245
- self.core.bind( instance )
246
- end
247
-
248
- def behaviour_with_pres( instance )
249
- combined_pres = self.combine_pres( instance )
250
- bound_core = self.bind_core( instance )
251
-
252
- lambda do | *arguments |
253
- nature = MethodNature.new( arguments, nil, false )
254
-
255
- combined_pres.call( nature )
256
- unless nature.break
257
- bound_core.call( *nature.arguments )
258
- else
259
- nature.return_value
260
- end
261
- end
262
- end
263
-
264
- def behaviour_with_posts( instance )
265
- bound_core = self.bind_core( instance )
266
- combined_posts = self.combine_posts( instance )
267
-
268
- lambda do | *arguments |
269
- nature = MethodNature.new( arguments, nil, false )
270
-
271
- nature.return_value = bound_core.call( *arguments )
272
- combined_posts.call( nature )
273
-
274
- nature.return_value
275
- end
276
- end
277
-
278
- def behaviour_with_pres_and_posts( instance )
279
- combined_pres = self.combine_pres( instance )
280
- bound_core = self.bind_core( instance )
281
- combined_posts = self.combine_posts( instance )
282
-
283
- lambda do | *arguments |
284
- nature = MethodNature.new( arguments, nil, false )
285
-
286
- combined_pres.call( nature )
287
- unless nature.break
288
- nature.return_value = bound_core.call( *nature.arguments )
289
- combined_posts.call( nature )
290
- end
291
- nature.return_value
292
- end
293
- end
294
-
295
- def behaviour_with_arounds( instance )
296
- bound_core = self.bind_core( instance )
297
- bound_arounds = self.bind_arounds( instance )
298
-
299
- lambda do | *arguments |
300
- working_arounds = bound_arounds.clone
301
- nature = MethodNature.new( arguments, nil, false)
302
- nature.block = around_block( nature, working_arounds, bound_core )
303
-
304
- catch( :break_in_around ) do
305
- working_arounds.pop.call( nature )
306
- end
307
- nature.return_value
308
- end
309
- end
310
-
311
- def behaviour_with_pres_and_arounds( instance )
312
- combined_pres = self.combine_pres( instance )
313
- bound_core = self.bind_core( instance )
314
- bound_arounds = self.bind_arounds( instance )
315
-
316
- lambda do | *arguments |
317
- nature = MethodNature.new( arguments, nil, false )
318
-
319
- combined_pres.call( nature )
320
- unless nature.break
321
- working_arounds = bound_arounds.clone
322
- nature.block = around_block( nature, working_arounds, bound_core )
323
- catch( :break_in_around ) do
324
- working_arounds.pop.call( nature )
325
- end
326
- end
327
- nature.return_value
328
- end
329
- end
330
-
331
- def behaviour_with_arounds_and_posts( instance )
332
- bound_core = self.bind_core( instance )
333
- bound_arounds = self.bind_arounds( instance )
334
- combined_posts = self.combine_posts( instance )
335
-
336
- lambda do | *arguments |
337
- working_arounds = bound_arounds.clone
338
- nature = MethodNature.new( arguments, nil, false,
339
- around_block( nature, working_arounds, bound_core ) )
340
-
341
- catch( :break_in_around ) do
342
- working_arounds.pop.call( nature )
343
- end
344
- combinded_posts.call( nature ) unless nature.break
345
-
346
- nature.return_value
347
- end
348
- end
349
-
350
- def behaviour_with_pres_and_arounds_and_posts( instance )
351
- combined_pres = self.combine_pres( instance )
352
- bound_core = self.bind_core( instance )
353
- bound_arounds = self.bind_arounds( instance )
354
- combined_posts = self.combine_posts( instance )
355
-
356
- lambda do | *arguments |
357
- nature = MethodNature.new( arguments, nil, false )
358
-
359
- combined_pres.call( nature )
360
- unless nature.break
361
- working_arounds = bound_arounds.clone
362
- nature.block = around_block( nature, working_arounds, bound_core )
363
- catch( :break_in_around ) do
364
- working_arounds.pop.call( nature )
365
- end
366
- unless nature.break
367
- combined_posts.call( nature )
368
- end
369
- end
370
- nature.return_value
371
- end
372
- end
373
-
374
-
375
- # helpers
376
- def combine_pres( instance )
377
- bound_pres = self.pres.collect { | p | p.bind( instance ) }
378
- lambda do | nature |
379
- bound_pres.reverse.each do | bound_pre |
380
- bound_pre.call( nature )
381
- break if nature.break
382
- end
383
- end
384
- end
385
-
386
- def bind_arounds( instance )
387
- self.arounds.collect { | a | a.bind( instance ) }
388
- end
389
-
390
- def around_block( nature, bound_arounds, bound_core )
391
- lambda do
392
- unless bound_arounds.empty?
393
- bound_arounds.pop.call( nature )
394
- throw( :break_in_around ) if nature.break
395
- else
396
- nature.return_value = bound_core.call( *nature.arguments )
397
- end
398
- end
399
- end
400
-
401
- def bind_core( instance )
402
- self.core.bind( instance )
403
- end
404
-
405
- def combine_posts( instance )
406
- bound_posts = self.posts.collect { | p | p.bind( instance ) }
407
- lambda do | nature |
408
- bound_posts.each do | bound_post |
409
- bound_post.call( nature )
410
- break if nature.break
411
- end
412
- end
413
- end
414
87
  end
415
88
 
416
- class DefaultLayer < Layer; end
89
+ extend ClassMethods
417
90
  end