contextr 0.1.1 → 0.1.9

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.
@@ -2,7 +2,7 @@ module ContextR #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
4
  MINOR = 1
5
- TINY = 1
5
+ TINY = 9
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -65,9 +65,9 @@ module Dynamic
65
65
  save[key] = self[key]
66
66
  self[key] = value
67
67
  }
68
- return_value = block.call
68
+ block.call
69
+ ensure
69
70
  variables.update save
70
- return_value
71
71
  end
72
72
 
73
73
  def method_missing(name, *args)
@@ -2,7 +2,7 @@ require File.dirname(__FILE__) + '/spec_helper.rb'
2
2
 
3
3
  module AddressMethods
4
4
  def to_s
5
- "#{yield(:next)} (#{yield(:receiver).address})"
5
+ "#{super} (#{yield(:receiver).address})"
6
6
  end
7
7
  end
8
8
 
@@ -11,7 +11,9 @@ class University < Struct.new(:name, :address)
11
11
  name
12
12
  end
13
13
 
14
- include AddressMethods => :address
14
+ in_layer :address do
15
+ include AddressMethods
16
+ end
15
17
  end
16
18
 
17
19
  class Student < Struct.new(:name, :address, :education)
@@ -19,35 +21,35 @@ class Student < Struct.new(:name, :address, :education)
19
21
  name
20
22
  end
21
23
 
22
- include AddressMethods => :address
23
-
24
- module EducationMethods
24
+ in_layer :address do
25
+ include AddressMethods
26
+ end
27
+ in_layer :education do
25
28
  def to_s
26
- "#{yield(:next)}, #{yield(:receiver).education}"
29
+ "#{super}, #{yield(:receiver).education}"
27
30
  end
28
31
  end
29
-
30
- include EducationMethods => :education
31
32
  end
32
33
 
33
34
  class Ordering
34
- def test
35
+ def inner_outer
35
36
  "base"
36
37
  end
37
38
 
38
39
  module InnerMethods
39
- def test
40
- "inner #{yield(:next)} inner"
40
+ def inner_outer
41
+ "inner #{super} inner"
41
42
  end
42
43
  end
43
- include InnerMethods => :multiple_modules
44
-
45
44
  module OuterMethods
46
- def test
47
- "outer #{yield(:next)} outer"
45
+ def inner_outer
46
+ "outer #{super} outer"
48
47
  end
49
48
  end
50
- include OuterMethods => :multiple_modules
49
+ in_layer :multiple_modules do
50
+ include InnerMethods
51
+ include OuterMethods
52
+ end
51
53
  end
52
54
 
53
55
 
@@ -104,22 +106,26 @@ describe "A contextified object" do
104
106
 
105
107
  it "should also activate multiple modules per layer" do
106
108
  ContextR::with_layers :multiple_modules do
107
- Ordering.new.test.should == "outer inner base inner outer"
109
+ Ordering.new.inner_outer.should == "outer inner base inner outer"
108
110
  end
109
111
  end
110
112
 
111
113
  it "should show new specific behaviour after changing module definitions" do
112
- module Student::EducationMethods
113
- def to_s
114
- "#{yield(:next)} @ #{yield(:receiver).education}"
114
+ class Student
115
+ in_layer :education do
116
+ def to_s
117
+ "#{super} @ #{yield(:receiver).education}"
118
+ end
115
119
  end
116
120
  end
117
121
  ContextR::with_layer :education do
118
122
  @student.to_s.should == "Gregor Schmidt @ HPI"
119
123
  end
120
- module Student::EducationMethods
121
- def to_s
122
- "#{yield(:next)}, #{yield(:receiver).education}"
124
+ class Student
125
+ in_layer :education do
126
+ def to_s
127
+ "#{super}, #{yield(:receiver).education}"
128
+ end
123
129
  end
124
130
  end
125
131
  end
@@ -149,14 +155,13 @@ describe "A method modules defining context dependent behaviour" do
149
155
 
150
156
  it "should have inner state" do
151
157
  class Student
152
- module LogMethods
158
+ in_layer :log do
153
159
  def to_s
154
160
  @i ||= 0
155
161
  @i += 1
156
- "#{@i}: #{yield(:next)}"
162
+ "#{@i}: #{super}"
157
163
  end
158
164
  end
159
- include LogMethods => :log
160
165
  end
161
166
  ContextR::with_layer :log do
162
167
  @student.to_s.should == "1: Gregor Schmidt"
@@ -172,11 +177,11 @@ describe "A method modules defining context dependent behaviour" do
172
177
 
173
178
  it "should not lose its state after redefinition of the module" do
174
179
  class Student
175
- module LogMethods
180
+ in_layer :log do
176
181
  def to_s
177
182
  @i ||= 0
178
183
  @i += 1
179
- "(#{@i}) #{yield(:next)}"
184
+ "(#{@i}) #{super}"
180
185
  end
181
186
  end
182
187
  end
@@ -210,7 +215,8 @@ describe "ContextR" do
210
215
  end
211
216
 
212
217
  it "should provide a method to query for all layers ever defined" do
213
- ContextR::layers.sort_by{ |s| s.to_s }.should ==
214
- [:address, :education, :log, :multiple_modules]
218
+ [:address, :education, :log, :multiple_modules].each do |layer|
219
+ ContextR::layers.sort_by{ |s| s.to_s }.should include(layer)
220
+ end
215
221
  end
216
222
  end
@@ -0,0 +1,59 @@
1
+ module ExampleTest
2
+ module ClassMethods
3
+ attr_accessor :latest_test_class
4
+ attr_accessor :latest_test_case
5
+ end
6
+ extend ClassMethods
7
+
8
+ module ObjectExtension
9
+ def test_class(name)
10
+ ExampleTest::latest_test_class = Class.new(Test::Unit::TestCase)
11
+ ExampleTest::latest_test_case = 0
12
+ Object.const_set(name, ExampleTest::latest_test_class)
13
+ end
14
+
15
+ def example(&block)
16
+ ExampleTest::latest_test_class.class_eval do
17
+ define_method("test_%03d" % (ExampleTest::latest_test_case += 1),
18
+ &block)
19
+ end
20
+ end
21
+ end
22
+
23
+ module TestExtension
24
+ def assert_to_s(expected, actual)
25
+ assert_equal(expected, actual.to_s)
26
+ end
27
+
28
+ def result_of(object)
29
+ Result.new(object, self)
30
+ end
31
+
32
+ def output_of(object)
33
+ Output.new(object, self)
34
+ end
35
+
36
+ class Result
37
+ attr_accessor :object, :test_class
38
+ def initialize(object, test_class)
39
+ self.object = object
40
+ self.test_class = test_class
41
+ end
42
+ def ==(string)
43
+ test_class.assert_equal(string, object)
44
+ end
45
+ end
46
+ class Output < Result
47
+ def ==(string)
48
+ test_class.assert_equal(string, object.to_s)
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ class Test::Unit::TestCase
55
+ include ExampleTest::TestExtension
56
+ end
57
+ class Object
58
+ include ExampleTest::ObjectExtension
59
+ end
@@ -0,0 +1,97 @@
1
+ require 'markaby'
2
+ if PLATFORM == "java"
3
+ class Markaby::Builder
4
+ def pre_block(block)
5
+ end
6
+ end
7
+ else
8
+ require 'ruby2ruby'
9
+ end
10
+
11
+ module LiterateMarkabyTest
12
+ TARGET_DIR = File.dirname(__FILE__) + "/../../website/test/"
13
+ module ObjectExtension
14
+ def test(name, &block)
15
+ mab = Markaby::Builder.new
16
+
17
+ mab.test_class = Class.new(Test::Unit::TestCase)
18
+ mab.latest_test_case = 0
19
+
20
+ Object.const_set(name, mab.test_class)
21
+ mab.xhtml_strict do
22
+ head do
23
+ title { name }
24
+ end
25
+ body do
26
+ h1 { name }
27
+ div(&block)
28
+ end
29
+ end
30
+
31
+ Dir.mkdir(TARGET_DIR) unless File.directory?(TARGET_DIR)
32
+ File.open(TARGET_DIR + name.to_s.underscore + ".html", "w") do |f|
33
+ f.puts mab.to_s
34
+ end
35
+ end
36
+ end
37
+
38
+ module MarkabyBuilderExtension
39
+ attr_accessor :test_class, :latest_test_case
40
+ def pre_block(block)
41
+ self.pre(block.to_ruby.gsub(/^proc \{\n(.*)\n\}$/m, '\1'))
42
+ end
43
+
44
+ def output(&block)
45
+ block.call
46
+ pre_block(block)
47
+ end
48
+ def example(&block)
49
+ name = "test_%03d" % (self.latest_test_case += 1)
50
+ test_class.class_eval do
51
+ define_method(name, &block)
52
+ end
53
+ pre_block(block)
54
+ end
55
+ end
56
+
57
+ module TestExtension
58
+ def assert_to_s(expected, actual)
59
+ assert_equal(expected, actual.to_s)
60
+ end
61
+
62
+ def result_of(object)
63
+ Result.new(object, self)
64
+ end
65
+
66
+ def output_of(object)
67
+ Output.new(object, self)
68
+ end
69
+
70
+ class Result
71
+ attr_accessor :object, :test_class
72
+ def initialize(object, test_class)
73
+ self.object = object
74
+ self.test_class = test_class
75
+ end
76
+ def ==(string)
77
+ test_class.assert_equal(string, object)
78
+ end
79
+ end
80
+ class Output < Result
81
+ def ==(string)
82
+ test_class.assert_equal(string, object.to_s)
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ #class Test::Unit::TestCase
89
+ # include LiterateMarkabyTest::TestExtension
90
+ #end
91
+ class Object
92
+ include LiterateMarkabyTest::ObjectExtension
93
+ end
94
+ class Markaby::Builder
95
+ include LiterateMarkabyTest::MarkabyBuilderExtension
96
+ end
97
+
@@ -0,0 +1,108 @@
1
+ gem "literate_maruku"
2
+ require "literate_maruku"
3
+ require 'markaby'
4
+ require 'active_support'
5
+
6
+ class Fixnum
7
+ def ordinal
8
+ # teens
9
+ return 'th' if (10..19).include?(self % 100)
10
+ # others
11
+ case self % 10
12
+ when 1: return 'st'
13
+ when 2: return 'nd'
14
+ when 3: return 'rd'
15
+ else return 'th'
16
+ end
17
+ end
18
+ end
19
+
20
+ class Time
21
+ def pretty
22
+ return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}"
23
+ end
24
+ end
25
+
26
+ module LiterateMarukuTest
27
+ BASE_DIR = File.dirname(__FILE__) + "/../"
28
+ TARGET_DIR = File.dirname(__FILE__) + "/../../website/test/"
29
+
30
+ def self.load(file)
31
+ content = LiterateMaruku.require(
32
+ BASE_DIR + "#{File.basename(file, '.rb')}.mkd",
33
+ :inline => true,
34
+ :attributes => {:execute => true})
35
+
36
+ download = "http://rubyforge.org/projects/contextr"
37
+ version = ContextR::VERSION::STRING
38
+ modified = Time.now
39
+ sub_title = File.basename(file, '.rb').gsub("test_", "").titleize
40
+
41
+ doc = Markaby::Builder.new.xhtml_strict do
42
+ head do
43
+ title "ContextR - #{sub_title} - Documentation"
44
+ link :href => "../stylesheets/screen.css", :rel=>'stylesheet',
45
+ :type=>'text/css', :media => "screen"
46
+ script :src => "../javascripts/rounded_corners_lite.inc.js",
47
+ :type =>"text/javascript"
48
+ script %Q{
49
+ window.onload = function() {
50
+ settings = {
51
+ tl: { radius: 10 },
52
+ tr: { radius: 10 },
53
+ bl: { radius: 10 },
54
+ br: { radius: 10 },
55
+ antiAlias: true,
56
+ autoPad: true,
57
+ validTags: ["div"]
58
+ }
59
+ var versionBox = new curvyCorners(settings,
60
+ document.getElementById("version"));
61
+ versionBox.applyCornersToAll();
62
+ }
63
+ }, :type => "text/javascript"
64
+ end
65
+ body do
66
+ div.main! do
67
+ h1 sub_title
68
+ div.version! :class => "clickable",
69
+ :onclick => "document.location='#{download}'; return false" do
70
+ p "Get Version"
71
+ a version, :href => download, :class => "numbers"
72
+ end
73
+ h1 do
74
+ self << "&#x2192; &#8216;"
75
+ a "contextr", :href => "http://contextr.rubyforge.org/"
76
+ self << "&#8217;"
77
+ end
78
+
79
+ ul.navi! do
80
+ Dir[File.dirname(file) + "/test_*.mkd"].each do |mkd_file_name|
81
+ li do
82
+ name = File.basename(mkd_file_name, ".mkd").gsub("test_", "")
83
+ a name.titleize, :href => name + ".html"
84
+ end
85
+ end
86
+ end
87
+
88
+ self << content
89
+ p.coda do
90
+ text modified.pretty
91
+ br
92
+ text "Theme extended from "
93
+ a "Paul Battley", :href => "http://rb2js.rubyforge.org/"
94
+ end
95
+ end
96
+ end
97
+ end
98
+ File.open(TARGET_DIR +
99
+ "#{File.basename(file, '.rb').gsub("test_", "")}.html", "w") do |f|
100
+ f.puts(%q{<!DOCTYPE html
101
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
102
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">})
103
+ doc.to_s.each do |chunk|
104
+ f.puts(chunk)
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,234 @@
1
+ **TODO: Update to use new in\_layer API**
2
+
3
+ In Ruby there are multiple ways of defining behaviour on the class side. That
4
+ are messages that are send to the class, not to the instance. Class side
5
+ behaviour is often useful for functional methods, i.e. methods that do not
6
+ rely on inner state and have no side effects. Mathematical functions have
7
+ these characteristics - that is where the name probably comes from.
8
+
9
+ **Note**: Java programmers may know these methods as static. It is similar but
10
+ not exactly the same.
11
+
12
+
13
+ Using `def self.method_name`
14
+ -----------------------------
15
+
16
+ The simpliest way of defining class side behaviour is prepending self. to the
17
+ method definition. This way, the method is attached to the surrounding class
18
+ and not instance. A simple example:
19
+
20
+ class SimpleMath
21
+ def self.pi
22
+ 3.14159265
23
+ end
24
+ end
25
+
26
+ example do
27
+ result_of(SimpleMath.pi) == 3.14159265
28
+ end
29
+
30
+
31
+ Using `class << self`
32
+ ---------------------
33
+
34
+ When you are having lots of class side methods as well as instance side ones,
35
+ it can be difficult to spot the little self. in front of the method name.
36
+ Probably you like to group them more explicitly. You could use Ruby's
37
+ eigenclass principle for that. It will look like the following:
38
+
39
+ class SimpleMath
40
+ class << self
41
+ def e
42
+ 2.71828183
43
+ end
44
+ end
45
+ end
46
+
47
+ example do
48
+ result_of(SimpleMath.e) == 2.71828183
49
+ end
50
+
51
+ **Note**: Eigenclasses are also known as *singleton class* or *meta class*.
52
+ I prefer eigenclass, because it not used with different meanings in other
53
+ contexts, which eases talking about it with experts in different languages.
54
+
55
+ Using a module
56
+ --------------
57
+
58
+ For even more encapsulation you could also use modules and extend the class
59
+ definition with them. I am using extend here on purpose. Module's include
60
+ method adds the behaviour to the instance side, extend to the class side.
61
+ Or to rephrase it: Module's include method adds the behaviour to instances,
62
+ extend to the class itself.
63
+
64
+ class SimpleMath
65
+ module ClassMethods
66
+ def golden_ratio
67
+ 1.6180339887
68
+ end
69
+ end
70
+
71
+ extend ClassMethods
72
+ end
73
+
74
+ example do
75
+ result_of(SimpleMath.golden_ratio) == 1.6180339887
76
+ end
77
+
78
+ The last method is e.g. used in the web framework Ruby on Rails. Often
79
+ a variation of it is used to define class and instance side behaviour for
80
+ mixin modules
81
+
82
+ module MathMixin
83
+ def counter
84
+ @counter ||= 0
85
+ @counter += 1
86
+ end
87
+
88
+ module ClassMethods
89
+ def sqrt(x)
90
+ x ** 0.5
91
+ end
92
+ end
93
+
94
+ def self.included(base)
95
+ base.extend ClassMethods
96
+ end
97
+ end
98
+
99
+ class SimpleMath
100
+ include MathMixin
101
+ end
102
+
103
+ example do
104
+ result_of(SimpleMath.sqrt(4)) == 2
105
+
106
+ my_simple_math = SimpleMath.new
107
+ result_of(my_simple_math.counter) == 1
108
+ result_of(my_simple_math.counter) == 2
109
+ end
110
+
111
+ This is regarded as the most elegant way of defining class and instance
112
+ methods for a mixin module. And the basic functionality is the same as in the
113
+ previous example.
114
+
115
+ After we now know how to define class side behaviour, everybody is curious
116
+ to know how to extend this behaviour using context-oriented programming and
117
+ ContextR.
118
+
119
+ Additionial, context-dependent behaviour is defined in `in_layer` blocks.
120
+ These are then attached to the layer, in which the behaviour should reside.
121
+ For examples on the instance side have a look at the bottom of
122
+ `test_introduction`.
123
+
124
+ Let's look how we can achieve the same on the class side for each of the
125
+ different methods of defining class side behaviour.
126
+
127
+
128
+ Using `def self.method_name`
129
+ ----------------------------
130
+
131
+ **TODO: Allow def self.method\_name to add class side behaviour**
132
+
133
+ Okay, we won't get rid of the modules, used to encapsulate the
134
+ context-dependent behaviour, so the extension is a bit noisier, than the
135
+ basic notation.
136
+
137
+ class SimpleMath
138
+ class << self
139
+ in_layer :access_control do
140
+ def pi
141
+ "You are not allowed to access this method"
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ But we can use the same principles, like we did for the instance side. Simply
148
+ use a hash to tell extend, that the module should only be used in a certain
149
+ layer.
150
+
151
+ example do
152
+ result_of(SimpleMath.pi) == 3.14159265
153
+
154
+ ContextR::with_layer :access_control do
155
+ result_of(SimpleMath.pi) == "You are not allowed to access this method"
156
+ end
157
+ end
158
+
159
+
160
+ Using `class << self`
161
+ ---------------------
162
+
163
+ When your using the eigenclass, you are able to use to good old in\_layer to
164
+ manage the extension.
165
+
166
+ class SimpleMath
167
+ class << self
168
+ in_layer :english do
169
+ def e
170
+ "Euler's constant"
171
+ end
172
+ end
173
+ end
174
+
175
+ class << self
176
+ in_layer :german do
177
+ def e
178
+ "Eulersche Zahl"
179
+ end
180
+ end
181
+ end
182
+ end
183
+
184
+ example do
185
+ result_of(SimpleMath.e) == 2.71828183
186
+
187
+ ContextR::with_layer :german do
188
+ result_of(SimpleMath.e) == "Eulersche Zahl"
189
+ end
190
+ ContextR::with_layer :english do
191
+ result_of(SimpleMath.e) == "Euler's constant"
192
+ end
193
+ end
194
+
195
+
196
+ Using a module
197
+ --------------
198
+
199
+ Hey, this is what we did all the time, so it is only natural to have the same
200
+ syntax to extend a class using in module for context-dependent behaviour. But
201
+ for the sake of completeness, I will attach another example.
202
+
203
+ class SimpleMath
204
+ class << self
205
+ in_layer :exact_computation do
206
+ def golden_ratio
207
+ sleep(0.01) # In real life this would take a bit longer,
208
+ # but I don't have the time.
209
+ 1.6180339887_4989484820_4586834365_6381177203_0917980576
210
+ end
211
+ end
212
+ end
213
+ end
214
+
215
+ example do
216
+ result_of(SimpleMath.golden_ratio) == 1.6180339887
217
+
218
+ ContextR::with_layer :exact_computation do
219
+ result_of(SimpleMath.golden_ratio) ==
220
+ 1.6180339887_4989484820_4586834365_6381177203_0917980576
221
+ end
222
+ end
223
+
224
+
225
+ Conclusion
226
+ ----------
227
+
228
+ In general, there are two options to define context-dependent class side
229
+ behaviour. Use in\_layer in the eigenclass or use extend anywhere else. Both
230
+ options result in the same behaviour, just like the different options in
231
+ plain ruby look different, but have the same effect.
232
+
233
+ The programmer is free to use, whatever suites best. This is still Ruby.
234
+