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