contextr 0.1.9 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/History.txt +8 -0
- data/Manifest.txt +12 -17
- data/README.txt +0 -1
- data/examples/employer.rb +229 -0
- data/examples/node.rb +213 -0
- data/lib/contextr/class_methods.rb +14 -6
- data/lib/contextr/core_ext/module.rb +3 -2
- data/lib/contextr/event_machine.rb +12 -12
- data/lib/contextr/inner_class.rb +7 -3
- data/lib/contextr/layer.rb +17 -7
- data/lib/contextr/public_api.rb +44 -3
- data/lib/contextr/version.rb +3 -3
- data/lib/ext/active_support_subset.rb +0 -45
- data/spec/contextr_spec.rb +97 -7
- data/test/{test_class_side.mkd → class_side.mkd} +6 -13
- data/test/{test_dynamic_scope.mkd → dynamic_scope.mkd} +2 -2
- data/test/{test_dynamics.mkd → dynamics.mkd} +3 -3
- data/test/{test_hello_world.mkd → hello_world.mkd} +1 -1
- data/test/{test_introduction.mkd → introduction.mkd} +4 -4
- data/test/{test_layer_state.mkd → layer_state.mkd} +2 -2
- data/test/lib/example_test.rb +2 -2
- data/test/lib/literate_maruku_test.rb +11 -9
- data/test/{test_meta_api.mkd → meta_api.mkd} +1 -1
- data/test/method_missing.mkd +40 -0
- data/test/{test_ordering.mkd → ordering.mkd} +8 -8
- data/test/restrictions.mkd +177 -0
- data/test/test_contextr.rb +7 -0
- data/test/test_plain.rb +92 -0
- metadata +17 -29
- metadata.gz.sig +0 -0
- data/test/lib/literate_markaby_test.rb +0 -97
- data/test/test_class_side.rb +0 -4
- data/test/test_dynamic_scope.rb +0 -4
- data/test/test_dynamics.rb +0 -4
- data/test/test_hello_world.rb +0 -4
- data/test/test_introduction.rb +0 -4
- data/test/test_layer_state.rb +0 -3
- data/test/test_meta_api.rb +0 -4
- data/test/test_ordering.rb +0 -3
@@ -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
|
82
|
+
(yield(:receiver).current == :red_and_yellow) or super
|
83
83
|
end
|
84
84
|
def yellow
|
85
|
-
(yield(:receiver).current == :red_and_yellow) or
|
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
|
-
|
154
|
+
super
|
155
155
|
end
|
156
156
|
end
|
157
157
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
Let's build a simple
|
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 :
|
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
|
300
|
-
ContextR
|
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] ||=
|
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.
|
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
|
data/test/lib/example_test.rb
CHANGED
@@ -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
|
13
|
-
when 2
|
14
|
-
when 3
|
15
|
-
else
|
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(
|
31
|
+
def self.load(test)
|
31
32
|
content = LiterateMaruku.require(
|
32
|
-
BASE_DIR + "#{
|
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 =
|
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(
|
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
|
-
"#{
|
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([:
|
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 :
|
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 :
|
59
|
-
ContextR::with_layer :
|
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 :
|
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 :
|
71
|
-
ContextR::with_layer :
|
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 :
|
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 :
|
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
|
data/test/test_contextr.rb
CHANGED
@@ -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
|