contextr 0.1.9 → 1.0.0
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.
- 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
|