contextr 0.1.9 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data.tar.gz.sig +0 -0
  2. data/History.txt +8 -0
  3. data/Manifest.txt +12 -17
  4. data/README.txt +0 -1
  5. data/examples/employer.rb +229 -0
  6. data/examples/node.rb +213 -0
  7. data/lib/contextr/class_methods.rb +14 -6
  8. data/lib/contextr/core_ext/module.rb +3 -2
  9. data/lib/contextr/event_machine.rb +12 -12
  10. data/lib/contextr/inner_class.rb +7 -3
  11. data/lib/contextr/layer.rb +17 -7
  12. data/lib/contextr/public_api.rb +44 -3
  13. data/lib/contextr/version.rb +3 -3
  14. data/lib/ext/active_support_subset.rb +0 -45
  15. data/spec/contextr_spec.rb +97 -7
  16. data/test/{test_class_side.mkd → class_side.mkd} +6 -13
  17. data/test/{test_dynamic_scope.mkd → dynamic_scope.mkd} +2 -2
  18. data/test/{test_dynamics.mkd → dynamics.mkd} +3 -3
  19. data/test/{test_hello_world.mkd → hello_world.mkd} +1 -1
  20. data/test/{test_introduction.mkd → introduction.mkd} +4 -4
  21. data/test/{test_layer_state.mkd → layer_state.mkd} +2 -2
  22. data/test/lib/example_test.rb +2 -2
  23. data/test/lib/literate_maruku_test.rb +11 -9
  24. data/test/{test_meta_api.mkd → meta_api.mkd} +1 -1
  25. data/test/method_missing.mkd +40 -0
  26. data/test/{test_ordering.mkd → ordering.mkd} +8 -8
  27. data/test/restrictions.mkd +177 -0
  28. data/test/test_contextr.rb +7 -0
  29. data/test/test_plain.rb +92 -0
  30. metadata +17 -29
  31. metadata.gz.sig +0 -0
  32. data/test/lib/literate_markaby_test.rb +0 -97
  33. data/test/test_class_side.rb +0 -4
  34. data/test/test_dynamic_scope.rb +0 -4
  35. data/test/test_dynamics.rb +0 -4
  36. data/test/test_hello_world.rb +0 -4
  37. data/test/test_introduction.rb +0 -4
  38. data/test/test_layer_state.rb +0 -3
  39. data/test/test_meta_api.rb +0 -4
  40. data/test/test_ordering.rb +0 -3
@@ -29,11 +29,11 @@ the rest is support code.
29
29
  one_block = lambda do
30
30
  mutex.lock
31
31
  ContextR::with_layer :b do
32
- step(3, :a, :b)
32
+ step(3, :b, :a)
33
33
  mutex.unlock
34
34
  sleep(0.1)
35
35
  mutex.lock
36
- step(5, :a, :b)
36
+ step(5, :b, :a)
37
37
  end
38
38
  end
39
39
 
@@ -79,10 +79,10 @@ should both return true, when the :red_and_yellow state is active.
79
79
  end
80
80
 
81
81
  def red
82
- (yield(:receiver).current == :red_and_yellow) or yield(:next)
82
+ (yield(:receiver).current == :red_and_yellow) or super
83
83
  end
84
84
  def yellow
85
- (yield(:receiver).current == :red_and_yellow) or yield(:next)
85
+ (yield(:receiver).current == :red_and_yellow) or super
86
86
  end
87
87
  end
88
88
  end
@@ -151,7 +151,7 @@ traffic light.
151
151
  if yield(:receiver).current == :red_and_yellow
152
152
  "It's red and yellow at once. It will be green soon."
153
153
  else
154
- yield(:next)
154
+ super
155
155
  end
156
156
  end
157
157
  end
@@ -37,7 +37,7 @@ And what if down under changed its habbits? Let's redefine it.
37
37
  class MyApplication
38
38
  in_layer :au do
39
39
  def greet
40
- yield(:next) + " and God Save the Queen"
40
+ super + " and God Save the Queen"
41
41
  end
42
42
  end
43
43
  end
@@ -1,4 +1,4 @@
1
- Let's build a simple student's database. Each University has a name and
1
+ Let's build a simple students database. Each University has a name and
2
2
  address. Each student has a name, address and an associated university.
3
3
 
4
4
  We are using a `Struct` to build our classes in an easy way. This provides
@@ -283,7 +283,7 @@ options. You may activate the two one after the other or all at once. It
283
283
  is just a matter of taste, the result remains the same.
284
284
 
285
285
  example do
286
- ContextR.with_layer :believe, :location do
286
+ ContextR.with_layer :location, :believe do
287
287
  output_of($the_pope) == "Benedikt XVI (Bavaria); Christianity (Israel)"
288
288
  end
289
289
  end
@@ -296,8 +296,8 @@ If you change your mind within your call stack, you may of course
296
296
  deactivate layers again.
297
297
 
298
298
  example do
299
- ContextR.with_layer :believe do
300
- ContextR::with_layer :location do
299
+ ContextR::with_layer :location do
300
+ ContextR.with_layer :believe do
301
301
  output_of($the_pope) ==
302
302
  "Benedikt XVI (Bavaria); Christianity (Israel)"
303
303
 
@@ -87,7 +87,7 @@ our variable.
87
87
  end
88
88
 
89
89
  def compute(fixnum)
90
- cache[fixnum] ||= yield(:next, fixnum)
90
+ cache[fixnum] ||= super
91
91
  end
92
92
  end
93
93
  end
@@ -101,7 +101,7 @@ Now let's compute Fib(100) again. Of course with caching enabled
101
101
  example do
102
102
  timeout_raised = false
103
103
  begin
104
- Timeout::timeout(0.05) do
104
+ Timeout::timeout(0.1) do
105
105
  ContextR::with_layer :cache do
106
106
  result_of(Fibonacci.compute(100)) == 354_224_848_179_261_915_075
107
107
  end
@@ -12,11 +12,11 @@ module ExampleTest
12
12
  Object.const_set(name, ExampleTest::latest_test_class)
13
13
  end
14
14
 
15
- def example(&block)
15
+ def example(version = RUBY_VERSION, &block)
16
16
  ExampleTest::latest_test_class.class_eval do
17
17
  define_method("test_%03d" % (ExampleTest::latest_test_case += 1),
18
18
  &block)
19
- end
19
+ end if RUBY_VERSION =~ Regexp.new(version.to_s)
20
20
  end
21
21
  end
22
22
 
@@ -1,6 +1,7 @@
1
1
  gem "literate_maruku"
2
2
  require "literate_maruku"
3
3
  require 'markaby'
4
+ require 'maruku'
4
5
  require 'active_support'
5
6
 
6
7
  class Fixnum
@@ -9,10 +10,10 @@ class Fixnum
9
10
  return 'th' if (10..19).include?(self % 100)
10
11
  # others
11
12
  case self % 10
12
- when 1: return 'st'
13
- when 2: return 'nd'
14
- when 3: return 'rd'
15
- else return 'th'
13
+ when 1 then return 'st'
14
+ when 2 then return 'nd'
15
+ when 3 then return 'rd'
16
+ else return 'th'
16
17
  end
17
18
  end
18
19
  end
@@ -27,16 +28,16 @@ module LiterateMarukuTest
27
28
  BASE_DIR = File.dirname(__FILE__) + "/../"
28
29
  TARGET_DIR = File.dirname(__FILE__) + "/../../website/test/"
29
30
 
30
- def self.load(file)
31
+ def self.load(test)
31
32
  content = LiterateMaruku.require(
32
- BASE_DIR + "#{File.basename(file, '.rb')}.mkd",
33
+ BASE_DIR + "#{test}.mkd",
33
34
  :inline => true,
34
35
  :attributes => {:execute => true})
35
36
 
36
37
  download = "http://rubyforge.org/projects/contextr"
37
38
  version = ContextR::VERSION::STRING
38
39
  modified = Time.now
39
- sub_title = File.basename(file, '.rb').gsub("test_", "").titleize
40
+ sub_title = test.titleize
40
41
 
41
42
  doc = Markaby::Builder.new.xhtml_strict do
42
43
  head do
@@ -77,7 +78,7 @@ module LiterateMarukuTest
77
78
  end
78
79
 
79
80
  ul.navi! do
80
- Dir[File.dirname(file) + "/test_*.mkd"].each do |mkd_file_name|
81
+ Dir[File.dirname(__FILE__) + "/../*.mkd"].each do |mkd_file_name|
81
82
  li do
82
83
  name = File.basename(mkd_file_name, ".mkd").gsub("test_", "")
83
84
  a name.titleize, :href => name + ".html"
@@ -95,8 +96,9 @@ module LiterateMarukuTest
95
96
  end
96
97
  end
97
98
  end
99
+ Dir.mkdir(TARGET_DIR) unless File.exist?(TARGET_DIR)
98
100
  File.open(TARGET_DIR +
99
- "#{File.basename(file, '.rb').gsub("test_", "")}.html", "w") do |f|
101
+ "#{test}.html", "w") do |f|
100
102
  f.puts(%q{<!DOCTYPE html
101
103
  PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
102
104
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">})
@@ -5,7 +5,7 @@ active layers.
5
5
  ContextR::with_layer :a do
6
6
  assert_equal([:a], ContextR::active_layers)
7
7
  ContextR::with_layer :b do
8
- assert_equal([:a, :b], ContextR::active_layers)
8
+ assert_equal([:b, :a], ContextR::active_layers)
9
9
  end
10
10
  assert_equal([:a], ContextR::active_layers)
11
11
  end
@@ -0,0 +1,40 @@
1
+ It is used in ContextR as well, and its
2
+ redefinition is simple, The functionality of method missing is
3
+ one of the core ingredients of cleanly designed Ruby programs, it is still
4
+ possible to extend it with context-dependent behaviour.
5
+
6
+ The following code will show the right usage.
7
+
8
+ class MethodMissingExample
9
+ def method_missing(*a)
10
+ "base"
11
+ end
12
+
13
+ in_layer :one do
14
+ def method_missing(*a, &b)
15
+ "pre_one " + super
16
+ end
17
+ end
18
+ in_layer :two do
19
+ def method_missing(*a, &b)
20
+ super + " post_two"
21
+ end
22
+ end
23
+ end
24
+
25
+ example do
26
+ instance = MethodMissingExample.new
27
+ result_of(instance.any_method) == "base"
28
+
29
+ ContextR::with_layer :one do
30
+ result_of(instance.any_method) == "pre_one base"
31
+ end
32
+ ContextR::with_layer :two do
33
+ result_of(instance.any_method) == "base post_two"
34
+ end
35
+
36
+ ContextR::with_layer :one, :two do
37
+ result_of(instance.any_method) == "pre_one base post_two"
38
+ end
39
+ end
40
+
@@ -50,25 +50,25 @@ determines the order of execution.
50
50
  result_of(instance.test_method) == "bar_before base_method bar_after"
51
51
  end
52
52
 
53
- ContextR::with_layer :bar, :foo do
53
+ ContextR::with_layer :foo, :bar do
54
54
  result_of(instance.test_method) ==
55
55
  "bar_before foo_before base_method foo_after bar_after"
56
56
  end
57
57
 
58
- ContextR::with_layer :bar do
59
- ContextR::with_layer :foo do
58
+ ContextR::with_layer :foo do
59
+ ContextR::with_layer :bar do
60
60
  result_of(instance.test_method) ==
61
61
  "bar_before foo_before base_method foo_after bar_after"
62
62
  end
63
63
  end
64
64
 
65
- ContextR::with_layer :foo, :bar do
65
+ ContextR::with_layer :bar, :foo do
66
66
  result_of(instance.test_method) ==
67
67
  "foo_before bar_before base_method bar_after foo_after"
68
68
  end
69
69
 
70
- ContextR::with_layer :foo do
71
- ContextR::with_layer :bar do
70
+ ContextR::with_layer :bar do
71
+ ContextR::with_layer :foo do
72
72
  result_of(instance.test_method) ==
73
73
  "foo_before bar_before base_method bar_after foo_after"
74
74
  end
@@ -85,11 +85,11 @@ activation is hidden, but is restored again after leaving the block.
85
85
  example do
86
86
  instance = OrderingTest.new
87
87
 
88
- ContextR::with_layer :bar, :foo do
88
+ ContextR::with_layer :foo, :bar do
89
89
  result_of(instance.test_method) ==
90
90
  "bar_before foo_before base_method foo_after bar_after"
91
91
 
92
- ContextR::with_layer :bar do
92
+ ContextR::with_layer :foo do
93
93
  result_of(instance.test_method) ==
94
94
  "foo_before bar_before base_method bar_after foo_after"
95
95
  end
@@ -0,0 +1,177 @@
1
+ There are certain cases (i.e. Classes and Methods), that may not be extended
2
+ with ContextR. These are e.g. some methods in Enumberable, Array and Hash,
3
+ that are used internally in ContextR. Extending them would simply result in
4
+ endless stacks.
5
+
6
+ In custom classes, i.e. classes defined by your code, every method may be
7
+ extended. There are only 3 exceptions.
8
+
9
+ First the exceptions. You may never extend
10
+
11
+ * `__id__`
12
+ * `__send__`
13
+ * `__clone__`
14
+
15
+ They shall not be redefined, that is why it triggers a warning. ContextR sticks
16
+ to this convention, that's why it does not support `__id__` and `__clone__`. It
17
+ uses it internally, that's why it does not support `__send__`.
18
+
19
+ Accessing `self`
20
+ ----------------
21
+
22
+ Then there are other problems, related to the way ContextR is implemented and
23
+ its archetecture is designed. The additional context-dependent behaviour does
24
+ not reside in the extended object itself but in the corresponding layer, or to
25
+ be exact, in an anonymous module, that is constructed for each
26
+ layer-class-combination. This results in different `self`s for base code and
27
+ layer code. This is in fact not by intention but by accident.
28
+
29
+ In order to access the original `self`, the one defined by the object, the
30
+ message was sent to, there is a workaround. Since we did not want to change
31
+ the method signature, the receiver is avaible in a block, that is passed to
32
+ each layer method. `yield(:receiver)` gives you a pointer to it. Please be
33
+ aware, that you will be able to access public methods only. Everything else
34
+ could be easily implemented using `instance_eval`, although this has some
35
+ performance implications.
36
+
37
+ If you would like to see `yield(:receiver)` in action, have a look at the other
38
+ examples, e.g. the buttom of the Introduction.
39
+
40
+ Passing Blocks
41
+ --------------
42
+
43
+ As already mentioned, ContextR uses a block to pass parameters to each layered
44
+ method. This implies, that methods, expecting a block, cannot easily access it.
45
+
46
+ But this is only partially true. Every method in a layered stack may access it
47
+ simply by calling `yield(:block)` or execute it with `yield(:block!)`. Of
48
+ course, you should test, if a block was given with `yield(:block_given?)`. It
49
+ is also possible to pass a new block to subsequent calls, but that is not to
50
+ easy, since it is not possible to pass a block as argument to a block. But
51
+ again, there is a slightly ugly workaround.
52
+
53
+ All this may be observed in the following artificial example.
54
+
55
+ class TheMagi
56
+ include Enumerable
57
+
58
+ def casper; "Casper"; end
59
+ def melchior; "Melchior"; end
60
+ def balthasar; "Balthasar"; end
61
+
62
+ def name_methods
63
+ %w(casper melchior balthasar)
64
+ end
65
+
66
+ def each
67
+ if block_given?
68
+ name_methods.each do |name|
69
+ yield self.send(name)
70
+ end
71
+ else
72
+ raise ArgumentError, "No block given"
73
+ end
74
+ end
75
+ end
76
+
77
+ example do
78
+ the_magi = TheMagi.new
79
+ names = the_magi.inject do |akku, element|
80
+ akku.to_s + " " + element.to_s
81
+ end
82
+
83
+ result_of(names) == "Casper Melchior Balthasar"
84
+ end
85
+
86
+ Okay, now we got a working `TheMagi` class, providing an Array-like interface.
87
+ Let's assume, we need HTML-output under certain circumstances. I know this
88
+ example is not to useful, but I needed anything to explain some things.
89
+
90
+ class TheMagi
91
+ in_layer :html_output do
92
+ def each
93
+ if yield(:block_given?)
94
+ yield(:receiver).name_methods.each do |name|
95
+ king = yield(:receiver).send(name)
96
+ yield(:block!, "<strong>#{king}</strong>")
97
+ end
98
+ else
99
+ raise ArgumentError, "No block given"
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ example do
106
+ the_magi = TheMagi.new
107
+ ContextR::with_layer :html_output do
108
+ names = the_magi.inject do |akku, element|
109
+ akku.to_s + " " + element.to_s
110
+ end
111
+
112
+ result_of(names) == "<strong>Casper</strong> " +
113
+ "<strong>Melchior</strong> " +
114
+ "<strong>Balthasar</strong>"
115
+ end
116
+ end
117
+
118
+ So this is how you would use a block, that is provided by a user of your method.
119
+ But you may as well change the block, that will be passed to subsequent layers
120
+ including the base method.
121
+
122
+ The `each` in the `:html_output` layer could as well be implemented in another
123
+ way. I will name it `:xml_output` lacking a better name. I'm sorry, that this
124
+ looks so ugly. I would be happy, if I would know a nicer way.
125
+
126
+ class TheMagi
127
+ in_layer :xml_output do
128
+ def each
129
+ old_block = yield(:block)
130
+
131
+ new_block = lambda do |element|
132
+ old_block.call("<name>" + element + "</name>")
133
+ end
134
+
135
+ yield(:block=, new_block)
136
+
137
+ return_value = super
138
+
139
+ yield(:block=, old_block)
140
+
141
+ return_value
142
+ end
143
+ end
144
+ end
145
+
146
+ In the first step, we store the original block in a local variable. Afterwards,
147
+ we create the block, i.e. a lambda, that should replace the old block. We
148
+ may easily access the old one, since we stored it beforehand. This local
149
+ variable will still be available, when the block is executed.
150
+
151
+ In the next step, the `new_block` is registered as parameter for subsequent
152
+ methods. Then we may call super to pass the control flow to the next layer and
153
+ the base method. Finally we should restore the old block, since layers, that
154
+ were execute before, will get control again, after this execution is finished
155
+ and they might be confused, that the block changed by calling super. This would
156
+ break the call stack behaviour.
157
+
158
+ Finally, we need to store the return value and give it back explicitly,
159
+ otherwise the block would be the result.
160
+
161
+ If in future, this functionality is more often used, I may think about a way
162
+ to make this easier. For now, this is the way to go. Sorry.
163
+
164
+ Oh. Let's use the code and test its output:
165
+
166
+ example do
167
+ the_magi = TheMagi.new
168
+ ContextR::with_layer :xml_output do
169
+ names = the_magi.inject do |akku, element|
170
+ akku.to_s + " " + element.to_s
171
+ end
172
+
173
+ result_of(names) == "<name>Casper</name> " +
174
+ "<name>Melchior</name> " +
175
+ "<name>Balthasar</name>"
176
+ end
177
+ end
@@ -5,3 +5,10 @@
5
5
  # converted to tests, to make sure, that all documentation is in sync with
6
6
  # the implementation. You may find these documents in this directory. It is
7
7
  # just, that they do not look like test, but they are. Believe me.
8
+ require File.dirname(__FILE__) + "/test_helper.rb"
9
+
10
+ %w(class_side dynamic_scope dynamics hello_world introduction
11
+ layer_state meta_api method_missing ordering restrictions).each do |test|
12
+ test_class("test_#{test}".camelcase.to_sym)
13
+ LiterateMarukuTest.load(test)
14
+ end