reduxco 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/LICENSE.txt +24 -0
- data/README.rdoc +126 -0
- data/Rakefile +19 -0
- data/lib/reduxco.rb +10 -0
- data/lib/reduxco/callable_ref.rb +142 -0
- data/lib/reduxco/context.rb +243 -0
- data/lib/reduxco/context/callable_table.rb +81 -0
- data/lib/reduxco/context/callstack.rb +74 -0
- data/lib/reduxco/reduxer.rb +41 -0
- data/lib/reduxco/version.rb +4 -0
- data/spec/callable_ref_spec.rb +273 -0
- data/spec/callable_table_spec.rb +174 -0
- data/spec/callstack_spec.rb +128 -0
- data/spec/context_spec.rb +619 -0
- data/spec/rdoc_examples_spec.rb +46 -0
- data/spec/reduxer_spec.rb +88 -0
- data/spec/spec_helper.rb +17 -0
- metadata +69 -0
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Reduxco::Context::CallableTable do
|
4
|
+
|
5
|
+
let(:identity_dynref) { Reduxco::CallableRef.new(:identity) }
|
6
|
+
let(:identity_callable) { ->(c){c} }
|
7
|
+
let(:app_callable) { ->(c){c[:identity]} }
|
8
|
+
|
9
|
+
it 'should instantiate with an array of maps' do
|
10
|
+
table = Reduxco::Context::CallableTable.new( [{:identity => identity_callable}, {:app => app_callable}] )
|
11
|
+
table.should include(identity_dynref)
|
12
|
+
table.resolve(identity_dynref).should_not be_nil
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should instantiate with an array of arrays of arrays' do
|
16
|
+
table = Reduxco::Context::CallableTable.new( [[[:identity, identity_callable]], [[:app, app_callable]]] )
|
17
|
+
table.should include(identity_dynref)
|
18
|
+
table.resolve(identity_dynref).should_not be_nil
|
19
|
+
end
|
20
|
+
|
21
|
+
describe 'introspection' do
|
22
|
+
|
23
|
+
let(:example_callable) { ->(c){c} }
|
24
|
+
|
25
|
+
before(:each) do
|
26
|
+
map1 = {:moho => example_callable, :eve => example_callable}
|
27
|
+
map2 = {:eve => example_callable, :kerbin => example_callable}
|
28
|
+
@table = Reduxco::Context::CallableTable.new([map1,map2])
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should introspect existence of static refs' do
|
32
|
+
@table.should include(Reduxco::CallableRef.new(:moho,1))
|
33
|
+
@table.should include(Reduxco::CallableRef.new(:eve,1))
|
34
|
+
@table.should include(Reduxco::CallableRef.new(:eve,2))
|
35
|
+
@table.should include(Reduxco::CallableRef.new(:kerbin,2))
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should introspect existence of dynamic refs' do
|
39
|
+
@table.should include(Reduxco::CallableRef.new(:moho))
|
40
|
+
@table.should include(Reduxco::CallableRef.new(:eve))
|
41
|
+
@table.should include(Reduxco::CallableRef.new(:kerbin))
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should introspect the non-existance of static refs' do
|
45
|
+
@table.should_not include(Reduxco::CallableRef.new(:duna,1))
|
46
|
+
@table.should_not include(Reduxco::CallableRef.new(:moho,2))
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should introspect the non-existance of dynamic refs' do
|
50
|
+
@table.should_not include(Reduxco::CallableRef.new(:duna))
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
describe 'resolution' do
|
56
|
+
|
57
|
+
before(:each) do
|
58
|
+
@map1 = {moho: ->(c){1}, eve: ->(c){2}, jool: ->(c){5}}
|
59
|
+
@map2 = {eve: ->(c){'second'}, kerbin: ->(c){3}}
|
60
|
+
@map3 = {jool: ->(c){'5th'}}
|
61
|
+
@table = Reduxco::Context::CallableTable.new([@map1,@map2,@map3])
|
62
|
+
end
|
63
|
+
|
64
|
+
let(:context) {double('context')}
|
65
|
+
|
66
|
+
it 'should resolve top level static ref' do
|
67
|
+
@table.resolve(Reduxco::CallableRef.new(:moho,1)).tap do |callref, callable|
|
68
|
+
callref.should == Reduxco::CallableRef.new(:moho,1)
|
69
|
+
callable.call(context).should == 1
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should resolve a deeper level static ref' do
|
74
|
+
@table.resolve(Reduxco::CallableRef.new(:kerbin,2)).tap do |callref, callable|
|
75
|
+
callref.should == Reduxco::CallableRef.new(:kerbin,2)
|
76
|
+
callable.call(context).should == 3
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should resolve a static ref that shadows higher level one' do
|
81
|
+
@table.resolve(Reduxco::CallableRef.new(:eve,2)).tap do |callref, callable|
|
82
|
+
callref.should == Reduxco::CallableRef.new(:eve,2)
|
83
|
+
callable.call(context).should == 'second'
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should resolve a shadowed static ref' do
|
88
|
+
@table.resolve(Reduxco::CallableRef.new(:eve,1)).tap do |callref, callable|
|
89
|
+
callref.should == Reduxco::CallableRef.new(:eve,1)
|
90
|
+
callable.call(context).should == 2
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'should resolve simple (non-shadowed) dynamic refs to correct level' do
|
95
|
+
@table.resolve(Reduxco::CallableRef.new(:moho)).tap do |callref, callable|
|
96
|
+
callref.should == Reduxco::CallableRef.new(:moho,1)
|
97
|
+
callable.call(context).should == 1
|
98
|
+
end
|
99
|
+
|
100
|
+
@table.resolve(Reduxco::CallableRef.new(:kerbin)).tap do |callref, callable|
|
101
|
+
callref.should == Reduxco::CallableRef.new(:kerbin,2)
|
102
|
+
callable.call(context).should == 3
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should resolve a shadowed dynamic ref to the lowest level' do
|
107
|
+
@table.resolve(Reduxco::CallableRef.new(:eve)).tap do |callref, callable|
|
108
|
+
callref.should == Reduxco::CallableRef.new(:eve,2)
|
109
|
+
callable.call(context).should == 'second'
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
it 'should return a callable value of nil if a static ref cannot be resolved' do
|
115
|
+
@table.resolve(Reduxco::CallableRef.new(:eeloo, 1)).tap do |callref, callable|
|
116
|
+
callref.should be_nil
|
117
|
+
callable.should be_nil
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'should return a callable value of nil if a dynamic ref cannot be resolved' do
|
122
|
+
@table.resolve(Reduxco::CallableRef.new(:eeloo)).tap do |callref, callable|
|
123
|
+
callref.should be_nil
|
124
|
+
callable.should be_nil
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'should return RESOLUTION_FAILURE on resolution failure' do
|
129
|
+
@table.resolve(Reduxco::CallableRef.new(:eeloo, 1)).should == Reduxco::Context::CallableTable::RESOLUTION_FAILURE
|
130
|
+
@table.resolve(Reduxco::CallableRef.new(:eeloo)).should == Reduxco::Context::CallableTable::RESOLUTION_FAILURE
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'should give nil values for multiple assignment of RESOLUTION_FAILURE' do
|
134
|
+
callref, callable = Reduxco::Context::CallableTable::RESOLUTION_FAILURE
|
135
|
+
callref.should be_nil
|
136
|
+
callable.should be_nil
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'should use :[] as an alias to :resolve' do
|
140
|
+
@table.method(:[]).should == @table.method(:resolve)
|
141
|
+
end
|
142
|
+
|
143
|
+
describe 'super' do
|
144
|
+
|
145
|
+
it 'should resolve super for a static ref' do
|
146
|
+
@table.resolve_super(Reduxco::CallableRef.new(:eve,2)).tap do |callref, callable|
|
147
|
+
callref.should == Reduxco::CallableRef.new(:eve,1)
|
148
|
+
callable.call(context).should == 2
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'should resolve super across depth gaps' do
|
153
|
+
@table.resolve_super(Reduxco::CallableRef.new(:jool,3)).tap do |callref, callable|
|
154
|
+
callref.should == Reduxco::CallableRef.new(:jool, 1)
|
155
|
+
callable.call(context).should == 5
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'should return a callable value of nil if a super ref cannot be resolved' do
|
160
|
+
@table.resolve_super(Reduxco::CallableRef.new(:kerbin,2)).tap do |callref, callable|
|
161
|
+
callref.should be_nil
|
162
|
+
callable.should be_nil
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'should raise an exception with a dynamic ref' do
|
167
|
+
->{ @table.resolve_super(Reduxco::CallableRef.new(:moho)) }.should raise_error(ArgumentError)
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Reduxco::Context::Callstack do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@stack = Reduxco::Context::Callstack.new([:top, :middle, :bottom])
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should return the depth' do
|
10
|
+
@stack.depth.should == 3
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should get the top frame' do
|
14
|
+
@stack.top.should == :top
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should initialize as empty' do
|
18
|
+
stack = Reduxco::Context::Callstack.new
|
19
|
+
stack.depth.should == 0
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should be able to initialize with an array, top elem first' do
|
23
|
+
@stack.depth.should == 3
|
24
|
+
@stack.top.should == :top
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should pop' do
|
28
|
+
frame = @stack.pop
|
29
|
+
|
30
|
+
@stack.depth.should == 2
|
31
|
+
frame.should == :top
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should push' do
|
35
|
+
s = @stack.push(:new_top)
|
36
|
+
|
37
|
+
@stack.depth.should == 4
|
38
|
+
@stack.top.should == :new_top
|
39
|
+
|
40
|
+
s.object_id.should == @stack.object_id
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should check for frame inclusion' do
|
44
|
+
@stack.should include(:middle)
|
45
|
+
|
46
|
+
@stack.should_not include(:eeloo)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should return the rest of the stack' do
|
50
|
+
rest = @stack.rest
|
51
|
+
|
52
|
+
rest.depth.should == 2
|
53
|
+
rest.top.should == :middle
|
54
|
+
|
55
|
+
rest.should be_kind_of(@stack.class)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should return a properly structured copy of the stack' do
|
59
|
+
dup = @stack.dup
|
60
|
+
|
61
|
+
dup.should be_kind_of(@stack.class)
|
62
|
+
dup.object_id.should_not == @stack.object_id
|
63
|
+
|
64
|
+
dup.top.should == @stack.top
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should not mutate the copy of the stack out from under it' do
|
68
|
+
dup = @stack.dup
|
69
|
+
|
70
|
+
@stack.push(:moho)
|
71
|
+
dup.should_not include(:moho)
|
72
|
+
|
73
|
+
dup.push(:eve)
|
74
|
+
@stack.should_not include(:eve)
|
75
|
+
end
|
76
|
+
|
77
|
+
describe 'conversion and formatting' do
|
78
|
+
|
79
|
+
let(:frame) do
|
80
|
+
double('frame').tap do |f|
|
81
|
+
f.stub(:to_s) {'frame-string'}
|
82
|
+
f.stub(:inspect) {'frame-inspect'}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'should output to_s with the top element first' do
|
87
|
+
str = @stack.to_s
|
88
|
+
|
89
|
+
str.index('top').should < str.index('bottom')
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'should inspect with the top element first' do
|
93
|
+
str = @stack.inspect
|
94
|
+
str.index('top').should < str.index('bottom')
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'should invoke inspect of frames when inspecting' do
|
98
|
+
@stack.push(frame)
|
99
|
+
|
100
|
+
frame.should_receive(:inspect)
|
101
|
+
str = @stack.inspect
|
102
|
+
|
103
|
+
str.should include('frame-inspect')
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should convert to a caller-style callstack' do
|
107
|
+
cs = @stack.to_caller
|
108
|
+
|
109
|
+
cs.each {|tr| tr.should be_kind_of(String)}
|
110
|
+
cs.first.should include('top')
|
111
|
+
cs.last.should include('bottom')
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'should prepend a callstack line into the caller-style callstack, conterting it to string' do
|
115
|
+
top = Object.new
|
116
|
+
cs = @stack.to_caller(top)
|
117
|
+
|
118
|
+
cs.each {|tr| tr.should be_kind_of(String)}
|
119
|
+
|
120
|
+
head, *tail = cs
|
121
|
+
|
122
|
+
head.should == top.to_s
|
123
|
+
tail.should == @stack.to_caller
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
@@ -0,0 +1,619 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Reduxco::Context do
|
4
|
+
|
5
|
+
let(:add_map) do
|
6
|
+
{
|
7
|
+
sum: ->(c){ c[:a]+c[:b] },
|
8
|
+
a: ->(c){3},
|
9
|
+
b: ->(c){5}
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:cyclical_map) do
|
14
|
+
{
|
15
|
+
a: ->(c){c[:b]},
|
16
|
+
b: ->(c){c[:c]},
|
17
|
+
c: ->(c){c[:a]}
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:non_callable_map){ {moho:'foo'} }
|
22
|
+
|
23
|
+
let(:handler) do
|
24
|
+
{
|
25
|
+
handler: Proc.new do |c|
|
26
|
+
begin
|
27
|
+
c[:a]
|
28
|
+
rescue => e
|
29
|
+
[e, c.callstack]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
describe 'call' do
|
36
|
+
|
37
|
+
it 'should alias :[] to call' do
|
38
|
+
context = Reduxco::Context.new
|
39
|
+
context.method(:[]).should == context.method(:call)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should invoke the call method of the object it resolves to' do
|
43
|
+
callable = double('callable')
|
44
|
+
context = Reduxco::Context.new(:moho => callable)
|
45
|
+
|
46
|
+
callable.should_receive(:call).with(context)
|
47
|
+
|
48
|
+
context.call(:moho)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should resolve a leaf refname to a value' do
|
52
|
+
context = Reduxco::Context.new(add_map)
|
53
|
+
context.call(:a).should == 3
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should resolve a refname with dependencies to a value via cascading calculation' do
|
57
|
+
context = Reduxco::Context.new(add_map)
|
58
|
+
context.call(:sum).should == 8
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should resolve a callref' do
|
62
|
+
context = Reduxco::Context.new(add_map)
|
63
|
+
context.call( Reduxco::CallableRef.new(:sum) ).should == 8
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should resolve a callref to a shadowed value' do
|
67
|
+
context = Reduxco::Context.new(add_map, {sum: ->(c){-101}})
|
68
|
+
context.call( Reduxco::CallableRef.new(:sum, 1) ).should == 8
|
69
|
+
context.call( Reduxco::CallableRef.new(:sum, 2) ).should == -101
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should cache the results' do
|
73
|
+
context = Reduxco::Context.new(app: ->(c){ Object.new })
|
74
|
+
|
75
|
+
generated_object = context.call(:app)
|
76
|
+
context.call(:app).should == generated_object
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should allow yielding to a block.' do
|
80
|
+
context = Reduxco::Context.new(
|
81
|
+
app: ->(c){ c.call(:outter){|x| x*x} },
|
82
|
+
outter: ->(c){ c.yield(3) + c.yield(4) }
|
83
|
+
)
|
84
|
+
|
85
|
+
context.call(:app).should == 25
|
86
|
+
end
|
87
|
+
|
88
|
+
describe 'errors' do
|
89
|
+
|
90
|
+
it 'should error if refname resolves to a non-callable' do
|
91
|
+
context = Reduxco::Context.new(non_callable_map)
|
92
|
+
|
93
|
+
->{ context.call(:moho) }.should raise_error(Reduxco::Context::NotCallableError)
|
94
|
+
|
95
|
+
begin
|
96
|
+
context.call(:moho)
|
97
|
+
rescue => e
|
98
|
+
e.message.should include('moho')
|
99
|
+
e.backtrace.select {|tr| tr.include?('context.rb')}.should == []
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should error if refname is non-resolvable' do
|
104
|
+
context = Reduxco::Context.new(add_map)
|
105
|
+
|
106
|
+
->{ context.call(:eeloo) }.should raise_error(Reduxco::Context::NameError)
|
107
|
+
|
108
|
+
begin
|
109
|
+
context.call(:eeloo)
|
110
|
+
rescue => e
|
111
|
+
e.message.should include('eeloo')
|
112
|
+
e.backtrace.select {|tr| tr =~ /context.rb.+`call'$/}.should == []
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should error on cyclical dependencies' do
|
118
|
+
context = Reduxco::Context.new(cyclical_map)
|
119
|
+
|
120
|
+
->{ context.call(:a) }.should raise_error(Reduxco::Context::CyclicalError)
|
121
|
+
|
122
|
+
begin
|
123
|
+
context.call(:a)
|
124
|
+
rescue => e
|
125
|
+
e.message.should include('a:1')
|
126
|
+
e.message.should include('c:1')
|
127
|
+
e.backtrace.select {|tr| tr =~ /context.rb.+`call'$/}.should == []
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'should not corrupt the callstack when catching a non-resolvalbe name error' do
|
132
|
+
context = Reduxco::Context.new({a:->(c){c[:eeloo]}}, handler)
|
133
|
+
|
134
|
+
error, stack = context.call(:handler)
|
135
|
+
|
136
|
+
stack.top.should == Reduxco::CallableRef.new(:handler, 2)
|
137
|
+
stack.depth.should == 1
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'should not corrupt the callstack when catching a cyclical dependency' do
|
141
|
+
context = Reduxco::Context.new(cyclical_map, handler)
|
142
|
+
|
143
|
+
error, stack = context.call(:handler)
|
144
|
+
|
145
|
+
stack.top.should == Reduxco::CallableRef.new(:handler, 2)
|
146
|
+
stack.depth.should == 1
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
describe 'super' do
|
154
|
+
|
155
|
+
it 'should resolve to the next previous ref of the same name' do
|
156
|
+
context = Reduxco::Context.new({
|
157
|
+
moho: ->(c){ 'top' }
|
158
|
+
},
|
159
|
+
{
|
160
|
+
moho: ->(c){ c.super }
|
161
|
+
})
|
162
|
+
|
163
|
+
context.call(:moho).should == 'top'
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'should chain calls and their results' do
|
167
|
+
context = Reduxco::Context.new({
|
168
|
+
moho: ->(c){ 1 }
|
169
|
+
},
|
170
|
+
{
|
171
|
+
moho: ->(c){ c.super + 2 }
|
172
|
+
},
|
173
|
+
{
|
174
|
+
eve: ->(c){ 1024 }
|
175
|
+
},
|
176
|
+
{
|
177
|
+
moho: ->(c){ c.super + 4 }
|
178
|
+
})
|
179
|
+
|
180
|
+
context.call(:moho).should == 7
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'should throw a NameError if super cannot be resolved' do
|
184
|
+
context = Reduxco::Context.new({
|
185
|
+
eve: ->(c){ 'eve' }
|
186
|
+
},
|
187
|
+
{
|
188
|
+
moho: ->(c){ c.super }
|
189
|
+
})
|
190
|
+
|
191
|
+
->{ context.call(:moho) }.should raise_error(Reduxco::Context::NameError)
|
192
|
+
end
|
193
|
+
|
194
|
+
it 'should allow super to recieve a block to yield to' do
|
195
|
+
|
196
|
+
context = Reduxco::Context.new({
|
197
|
+
moho: ->(c){ c.yield(3) }
|
198
|
+
},
|
199
|
+
{
|
200
|
+
moho: ->(c){ c.super {|x| x*x} }
|
201
|
+
})
|
202
|
+
|
203
|
+
context.call(:moho).should == 9
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
it 'should forward the yield block to super' do
|
208
|
+
context = Reduxco::Context.new({
|
209
|
+
moho: ->(c){ c.yield(3) }
|
210
|
+
},
|
211
|
+
{
|
212
|
+
moho: ->(c){ c.super },
|
213
|
+
app: ->(c){ c.call(:moho){|x| x*x } }
|
214
|
+
})
|
215
|
+
|
216
|
+
context.call(:app).should == 9
|
217
|
+
end
|
218
|
+
|
219
|
+
end
|
220
|
+
|
221
|
+
describe 'callstack interface' do
|
222
|
+
|
223
|
+
it 'should return a copy of the callstack' do
|
224
|
+
context = Reduxco::Context.new
|
225
|
+
|
226
|
+
context.callstack.should be_kind_of(Reduxco::Context::Callstack)
|
227
|
+
context.callstack.should.object_id.should_not == context.callstack
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'should return a copy of the callstack from inside of a call' do
|
231
|
+
context = Reduxco::Context.new({
|
232
|
+
app: ->(c){ c.callstack }
|
233
|
+
})
|
234
|
+
|
235
|
+
stack = context.call(:app)
|
236
|
+
|
237
|
+
stack.depth.should == 1
|
238
|
+
stack.top.name.should == :app
|
239
|
+
end
|
240
|
+
|
241
|
+
it 'should return the current frame' do
|
242
|
+
context = Reduxco::Context.new({
|
243
|
+
app: ->(c){ c.current_frame.should == c.callstac.top }
|
244
|
+
})
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
248
|
+
|
249
|
+
describe 'introspection interface' do
|
250
|
+
|
251
|
+
let(:map1) do
|
252
|
+
{moho: ->(c){'moho-value'}, eve: ->(c){'eve-value'}}
|
253
|
+
end
|
254
|
+
|
255
|
+
let(:map2) do
|
256
|
+
{kerbin: ->(c){'kerbin-value'}, duna: ->(c){'duna-value'}, app: ->(c){c[:kerbin] + c[:moho]}}
|
257
|
+
end
|
258
|
+
|
259
|
+
let(:context) do
|
260
|
+
Reduxco::Context.new(map1, map2)
|
261
|
+
end
|
262
|
+
|
263
|
+
describe 'include' do
|
264
|
+
|
265
|
+
it 'should introspect which refnames are valid.' do
|
266
|
+
context.include?(:moho).should be_true
|
267
|
+
context.include?(:kerbin).should be_true
|
268
|
+
|
269
|
+
context.include?(:eeloo).should be_false
|
270
|
+
end
|
271
|
+
|
272
|
+
it 'should introspect which dynamic callrefs are valid.' do
|
273
|
+
context.include?( Reduxco::CallableRef.new(:moho) ).should be_true
|
274
|
+
context.include?( Reduxco::CallableRef.new(:kerbin) ).should be_true
|
275
|
+
context.include?( Reduxco::CallableRef.new(:eeloo) ).should be_false
|
276
|
+
end
|
277
|
+
|
278
|
+
it 'should introspect which static callrefs are valid.' do
|
279
|
+
context.include?( Reduxco::CallableRef.new(:moho,1) ).should be_true
|
280
|
+
context.include?( Reduxco::CallableRef.new(:kerbin,2) ).should be_true
|
281
|
+
context.include?( Reduxco::CallableRef.new(:moho,2) ).should be_false
|
282
|
+
context.include?( Reduxco::CallableRef.new(:kerbin,1) ).should be_false
|
283
|
+
end
|
284
|
+
|
285
|
+
end
|
286
|
+
|
287
|
+
describe 'completed' do
|
288
|
+
|
289
|
+
it 'should introspect which refnames have been computed.' do
|
290
|
+
[:moho, :eve, :kerbin, :duna, :app].each do |refname|
|
291
|
+
context.completed?(refname).should be_false
|
292
|
+
end
|
293
|
+
|
294
|
+
context.call(:app)
|
295
|
+
|
296
|
+
[:moho, :kerbin, :app].each do |refname|
|
297
|
+
context.completed?(refname).should be_true
|
298
|
+
end
|
299
|
+
|
300
|
+
[:eve, :duna].each do |refname|
|
301
|
+
context.completed?(refname).should be_false
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
it 'should introspect which callrefs have been computed.' do
|
306
|
+
all_ref_args = [[:kerbin,1], [:kerbin,2], [:moho,1], [:moho,2], [:duna,1], [:duna,2]]
|
307
|
+
|
308
|
+
all_ref_args.each do |name, depth|
|
309
|
+
callref = Reduxco::CallableRef.new(name, depth)
|
310
|
+
context.completed?(callref).should be_false
|
311
|
+
end
|
312
|
+
|
313
|
+
context.call(:app)
|
314
|
+
|
315
|
+
completed_ref_args = [[:kerbin,2], [:moho,1]]
|
316
|
+
incomplete_ref_args = all_ref_args - completed_ref_args
|
317
|
+
|
318
|
+
completed_ref_args.each do |name, depth|
|
319
|
+
callref = Reduxco::CallableRef.new(name, depth)
|
320
|
+
context.completed?(callref).should be_true
|
321
|
+
end
|
322
|
+
|
323
|
+
incomplete_ref_args.each do |name, depth|
|
324
|
+
callref = Reduxco::CallableRef.new(name, depth)
|
325
|
+
context.completed?(callref).should be_false
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
it 'should report not-found refs as uncomputed.' do
|
330
|
+
context.completed?(:eeloo).should be_false
|
331
|
+
context.completed?( Reduxco::CallableRef.new(:eloo,1) ).should be_false
|
332
|
+
end
|
333
|
+
|
334
|
+
end
|
335
|
+
|
336
|
+
describe 'assert_completed' do
|
337
|
+
|
338
|
+
it 'should raise an exception if failed' do
|
339
|
+
->{ context.assert_completed(:app) }.should raise_error(Reduxco::Context::AssertError)
|
340
|
+
end
|
341
|
+
|
342
|
+
it 'should return nil if success' do
|
343
|
+
context[:app]
|
344
|
+
|
345
|
+
context.assert_completed(:app).should be_nil
|
346
|
+
end
|
347
|
+
|
348
|
+
end
|
349
|
+
|
350
|
+
end
|
351
|
+
|
352
|
+
describe 'initialization' do
|
353
|
+
|
354
|
+
let(:map1) { {moho: ->(c){'moho1'}} }
|
355
|
+
let(:map2) { {eve: ->(c){'eve2'}} }
|
356
|
+
|
357
|
+
it 'should initialize with a single callable map' do
|
358
|
+
context = Reduxco::Context.new(map1)
|
359
|
+
context.should include(:moho)
|
360
|
+
end
|
361
|
+
|
362
|
+
it 'should initialize with multiple callable maps' do
|
363
|
+
context = Reduxco::Context.new(map1, map2)
|
364
|
+
context.should include(:moho)
|
365
|
+
context.should include(:eve)
|
366
|
+
end
|
367
|
+
|
368
|
+
it 'should only invoke :each on the map (returning name/callable pairs)' do
|
369
|
+
map = double('map')
|
370
|
+
map.should_receive(:each)
|
371
|
+
|
372
|
+
Reduxco::Context.new(map)
|
373
|
+
end
|
374
|
+
|
375
|
+
end
|
376
|
+
|
377
|
+
describe 'duplication' do
|
378
|
+
|
379
|
+
it 'should instantiate a new Context with the same callables on dup' do
|
380
|
+
map1 = double('map1')
|
381
|
+
map1.stub(:each)
|
382
|
+
map2 = double('map2')
|
383
|
+
map2.stub(:each)
|
384
|
+
context = Reduxco::Context.new(map1, map2)
|
385
|
+
|
386
|
+
dup = context.dup
|
387
|
+
|
388
|
+
dup.instance_variables.each do |ivar|
|
389
|
+
dup.instance_variable_get(ivar).object_id.should_not == context.instance_variable_get(ivar)
|
390
|
+
end
|
391
|
+
|
392
|
+
dup.instance_variable_get(:@callable_maps).tap do |maps|
|
393
|
+
maps.should == [map1, map2]
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
end
|
398
|
+
|
399
|
+
describe 'reduce' do
|
400
|
+
|
401
|
+
it 'should reduce with a refname and return the result' do
|
402
|
+
context = Reduxco::Context.new(app: ->(c){'app-result'})
|
403
|
+
|
404
|
+
context.reduce(:app).should == 'app-result'
|
405
|
+
end
|
406
|
+
|
407
|
+
it 'should reduce with :app by default' do
|
408
|
+
context = Reduxco::Context.new(app: ->(c){'app-result'})
|
409
|
+
|
410
|
+
context.reduce(:app).should == 'app-result'
|
411
|
+
end
|
412
|
+
|
413
|
+
end
|
414
|
+
|
415
|
+
describe 'flow helpers' do
|
416
|
+
|
417
|
+
describe 'before' do
|
418
|
+
|
419
|
+
it 'should invoke the before helper block before calling the passed refname' do
|
420
|
+
call_order = []
|
421
|
+
context = Reduxco::Context.new({
|
422
|
+
app: ->(c){ c.before(:a) {c[:b]} },
|
423
|
+
a: ->(c){ call_order << :a },
|
424
|
+
b: ->(c){ call_order << :b }
|
425
|
+
})
|
426
|
+
|
427
|
+
context.call(:app)
|
428
|
+
|
429
|
+
call_order.should == [:b, :a]
|
430
|
+
end
|
431
|
+
|
432
|
+
it 'should return the value of the passed refname' do
|
433
|
+
context = Reduxco::Context.new({
|
434
|
+
app: ->(c){ c.before(:a) {c[:b]} },
|
435
|
+
a: ->(c){ 'a-result' },
|
436
|
+
b: ->(c){ 'b-result' }
|
437
|
+
})
|
438
|
+
|
439
|
+
context.call(:app).should == 'a-result'
|
440
|
+
end
|
441
|
+
|
442
|
+
it 'should not yield anything to the block' do
|
443
|
+
block_args = nil
|
444
|
+
context = Reduxco::Context.new({
|
445
|
+
app: Proc.new do |c|
|
446
|
+
c.before(:a) do |*args|
|
447
|
+
block_args = args
|
448
|
+
c[:b]
|
449
|
+
end
|
450
|
+
end,
|
451
|
+
a: ->(c){ 'a-result' },
|
452
|
+
b: ->(c){ 'b-result' }
|
453
|
+
})
|
454
|
+
|
455
|
+
context.call(:app)
|
456
|
+
|
457
|
+
block_args.should == []
|
458
|
+
end
|
459
|
+
|
460
|
+
end
|
461
|
+
|
462
|
+
describe 'after' do
|
463
|
+
|
464
|
+
it 'should invoke the after helper block after calling the passed refname' do
|
465
|
+
call_order = []
|
466
|
+
context = Reduxco::Context.new({
|
467
|
+
app: ->(c){ c.after(:a) {c[:b]} },
|
468
|
+
a: ->(c){ call_order << :a },
|
469
|
+
b: ->(c){ call_order << :b }
|
470
|
+
})
|
471
|
+
|
472
|
+
context.call(:app)
|
473
|
+
|
474
|
+
call_order.should == [:a, :b]
|
475
|
+
end
|
476
|
+
|
477
|
+
it 'should return the value of the passed refname' do
|
478
|
+
context = Reduxco::Context.new({
|
479
|
+
app: ->(c){ c.after(:a) {c[:b]} },
|
480
|
+
a: ->(c){ 'a-result' },
|
481
|
+
b: ->(c){ 'b-result' }
|
482
|
+
})
|
483
|
+
|
484
|
+
context.call(:app).should == 'a-result'
|
485
|
+
end
|
486
|
+
|
487
|
+
it 'should not yield anything' do
|
488
|
+
block_args = nil
|
489
|
+
context = Reduxco::Context.new({
|
490
|
+
app: Proc.new do |c|
|
491
|
+
c.after(:a) do |*args|
|
492
|
+
block_args = args
|
493
|
+
c[:b]
|
494
|
+
end
|
495
|
+
end,
|
496
|
+
a: ->(c){ 'a-result' },
|
497
|
+
b: ->(c){ 'b-result' }
|
498
|
+
})
|
499
|
+
|
500
|
+
context.call(:app)
|
501
|
+
|
502
|
+
block_args.should == []
|
503
|
+
end
|
504
|
+
|
505
|
+
end
|
506
|
+
|
507
|
+
describe 'inside' do
|
508
|
+
|
509
|
+
describe 'client behavior' do
|
510
|
+
|
511
|
+
let(:table){ {} }
|
512
|
+
|
513
|
+
let(:outter) do
|
514
|
+
Proc.new do |c|
|
515
|
+
table[:yeild_result] = c.yield('arg1', 'arg2')
|
516
|
+
'outter_result'
|
517
|
+
end
|
518
|
+
end
|
519
|
+
|
520
|
+
let(:app) do
|
521
|
+
Proc.new do |c|
|
522
|
+
table[:inside_method_result] = c.inside(:outter) do |*args|
|
523
|
+
table[:args] = args
|
524
|
+
'yield_result'
|
525
|
+
end
|
526
|
+
'app_result'
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
let(:map) do
|
531
|
+
{app:app, outter:outter}
|
532
|
+
end
|
533
|
+
|
534
|
+
let(:context) do
|
535
|
+
context = Reduxco::Context.new(map)
|
536
|
+
end
|
537
|
+
|
538
|
+
it 'should be an alias to call, as it is just call with a block provided.' do
|
539
|
+
context.method(:inside).should == context.method(:call)
|
540
|
+
end
|
541
|
+
|
542
|
+
it 'should return the block\'s value from inside the passed refname' do
|
543
|
+
context.call(:app)
|
544
|
+
|
545
|
+
table[:yeild_result].should == 'yield_result'
|
546
|
+
end
|
547
|
+
|
548
|
+
it 'should return the value of the passed refname' do
|
549
|
+
context.call(:app)
|
550
|
+
|
551
|
+
|
552
|
+
table[:inside_method_result].should == 'outter_result'
|
553
|
+
end
|
554
|
+
|
555
|
+
it 'should allow yielding of args to the block' do
|
556
|
+
context.call(:app)
|
557
|
+
|
558
|
+
table[:args].should == ['arg1', 'arg2']
|
559
|
+
end
|
560
|
+
|
561
|
+
it 'should allow taking no block' do
|
562
|
+
context = Reduxco::Context.new(app: ->(c){ c.inside(:moho) }, moho: ->(c){'moho-result'})
|
563
|
+
|
564
|
+
context.call(:app).should == 'moho-result'
|
565
|
+
end
|
566
|
+
|
567
|
+
end
|
568
|
+
|
569
|
+
describe 'yield failure modes' do
|
570
|
+
|
571
|
+
it 'should give nested insides their correct block handles' do
|
572
|
+
context = Reduxco::Context.new(
|
573
|
+
app: ->(c){ c.inside(:outter){ c.inside(:middle){ c[:inner] } } },
|
574
|
+
outter: ->(c){ c.yield << 'outter' },
|
575
|
+
middle: ->(c){ c.yield << 'middle' },
|
576
|
+
inner: ->(c){ ['inner'] }
|
577
|
+
)
|
578
|
+
|
579
|
+
context.call(:app).should == ['inner', 'middle', 'outter']
|
580
|
+
end
|
581
|
+
|
582
|
+
it 'should not corrupt the handle stack when an exception is thrown and then caught inside of nested insides' do
|
583
|
+
context = Reduxco::Context.new(
|
584
|
+
app: ->(c){ c.inside(:outter){ c.inside(:middle){ c.inside(:inner) {c[:leaf]} } } },
|
585
|
+
outter: ->(c){ c.yield << 'outter' },
|
586
|
+
middle: ->(c){ begin c.yield << 'middle' rescue ['middle-caught'] end },
|
587
|
+
inner: ->(c){ c.yield << 'inner' },
|
588
|
+
leaf: ->(c){ raise RuntimeError }
|
589
|
+
)
|
590
|
+
|
591
|
+
context.call(:app).should == ['middle-caught', 'outter']
|
592
|
+
end
|
593
|
+
|
594
|
+
it 'should not allow yielding from a nested call.' do
|
595
|
+
context = Reduxco::Context.new(
|
596
|
+
app: ->(c){ c.inside(:outter){ 2 } },
|
597
|
+
outter: ->(c){ c[:middle] },
|
598
|
+
middle: ->(c){ c.yield }
|
599
|
+
)
|
600
|
+
|
601
|
+
->{ context.call(:app) }.should raise_error(Reduxco::Context::LocalJumpError)
|
602
|
+
end
|
603
|
+
|
604
|
+
it 'should allow a double yield.' do
|
605
|
+
context = Reduxco::Context.new(
|
606
|
+
app: ->(c){ c.inside(:outter){|x| x*x} },
|
607
|
+
outter: ->(c){ c.yield(3) + c.yield(4) }
|
608
|
+
)
|
609
|
+
|
610
|
+
context.call(:app).should == 25
|
611
|
+
end
|
612
|
+
|
613
|
+
end
|
614
|
+
|
615
|
+
end
|
616
|
+
|
617
|
+
end
|
618
|
+
|
619
|
+
end
|