reduxco 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +1 -1
- data/README.rdoc +338 -37
- data/lib/reduxco/context.rb +18 -2
- data/lib/reduxco/context/callstack.rb +7 -0
- data/lib/reduxco/version.rb +1 -1
- data/spec/callstack_spec.rb +7 -0
- data/spec/context_spec.rb +14 -0
- data/spec/rdoc_examples_spec.rb +92 -1
- data/spec/spec_helper.rb +6 -1
- metadata +10 -14
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 66dee8a1deb9af6c20d475fb99e8e7548c5d2188
|
4
|
+
data.tar.gz: c0ddc9771da228833b264c798a3d532491e306fa
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6804b82d9cefbe4d5a6439e1dbaf225230a89732544dbfc68ffcb45501f616c13ad2195fd66d57fd6e23ab2e76f3db08670b13624ef835120bea781578a2875e
|
7
|
+
data.tar.gz: 4298d0af571f7c98ab7018664791792b8da6f778b82169759d1d423361a35f683d71b7a25cd9d5561a1f0897a1bc44e9154aaea7d69b93f82c1f3f7cceb2e9e2
|
data/LICENSE.txt
CHANGED
data/README.rdoc
CHANGED
@@ -1,24 +1,221 @@
|
|
1
|
+
= Introduction to Reduxco
|
2
|
+
|
3
|
+
== What is Reduxco?
|
4
|
+
|
5
|
+
In a sentence, Reduxco is a general purpose graph reduction engine that is
|
6
|
+
perfect for pipelines and calculation engines.
|
7
|
+
|
8
|
+
At its core, Reduxco allows you to define interchangeable graph nodes and easily
|
9
|
+
evalute them, producing one or more results. Key features include:
|
10
|
+
* Interchangeable, modular nodes,
|
11
|
+
* Self-organizing graph,
|
12
|
+
* Lazy evaluation of nodes,
|
13
|
+
* Node value caching,
|
14
|
+
* Node Overriding and inheritance,
|
15
|
+
* Helpers for nesting, and
|
16
|
+
* Graph Introspection and Reflection.
|
17
|
+
|
18
|
+
== Why would I use Reduxco?
|
19
|
+
|
20
|
+
The prototypical example of graph reduction is in the reduction of expression
|
21
|
+
graphs, whereby each node represents one part of a larger calculation, and the
|
22
|
+
final result is the final product of the calculation.
|
23
|
+
|
24
|
+
But what Reduxco is best at are pipelines.
|
25
|
+
|
26
|
+
Consider Rack Middleware: it is a list of interchangeable black-box like
|
27
|
+
"nodes" that form a response generation pipeline.
|
28
|
+
Now consider this: it often becomes necessary to communicate intermediate
|
29
|
+
information between various components. This requires a sideband channel
|
30
|
+
(e.g. the rack env) to store this data. This has a couple of problems, including
|
31
|
+
a lack of uniform access to data (i.e. did it come from the pipeline or the
|
32
|
+
side-band data?). Even worse, a mixup in the order of middleware can cause
|
33
|
+
errors due to missing dependencies in the sideband channel.
|
34
|
+
|
35
|
+
Reduxco solves these problems for you, by self organizing the dependency graph
|
36
|
+
thanks to a lazy evaluation model that gets the value of each node as it is
|
37
|
+
needed.
|
38
|
+
|
39
|
+
== Where is Reduxco Being Used?
|
40
|
+
|
41
|
+
Reduxco is used to build the request/response pipelines for the Whitepages APIs;
|
42
|
+
it handles everything from taking of the initial request parameters, all the
|
43
|
+
way through generating and returning the final formatted output.
|
44
|
+
|
45
|
+
It's modularity and customizability are used to great effect, allowing us to
|
46
|
+
easily refactor or replace nodes to drastically change the result of a an API
|
47
|
+
call, without having to worry about its effect on the rest of the work the
|
48
|
+
request does.
|
49
|
+
|
50
|
+
== How do I use Reduxco?
|
51
|
+
|
52
|
+
At its core, Reduxco allows you to build a named set of "callables" (i.e. any
|
53
|
+
object that responds to the <code>call</code> method, such as a Proc), and
|
54
|
+
evaluate the values from one or more nodes.
|
55
|
+
|
56
|
+
To give the rich set of features Reduxco offers, callables must take a single
|
57
|
+
argument to their <code>call</code> method which contains a Reduxco::Context. The
|
58
|
+
Context is essentially a little runtime, responsible for dynamically routing
|
59
|
+
calls to dependencies, and caching the results.
|
60
|
+
|
61
|
+
Lastly, Reduxco pipelines are created via the Reduxco::Reduxer class.
|
62
|
+
|
63
|
+
Thus, as a contrived simpleexample, let's do a simple expression graph reduction on
|
64
|
+
the equation <code>(x+y) * (x+y)</code>, with the values of x and y generated
|
65
|
+
at random:
|
66
|
+
|
67
|
+
callables = {
|
68
|
+
x: ->(c){ rand(10) },
|
69
|
+
y: ->(c){ rand(10) },
|
70
|
+
sum: ->(c){ c[:x] + c[:y] },
|
71
|
+
result: ->(c){ c[:sum] * c[:sum] },
|
72
|
+
app: ->(c){ "For x=#{c[:x]} and y=#{c[:y]}, the result is #{c[:result]}." }
|
73
|
+
}
|
74
|
+
|
75
|
+
pipeline = Reduxco::Reduxer.new(callables)
|
76
|
+
output = pipeline.reduce
|
77
|
+
output.should == "For x=3 and y=6, the result is 81."
|
78
|
+
|
79
|
+
This example starts by defining named callables as Ruby blocks, and then creating
|
80
|
+
a Reduxco pipeline out of it. The call to <code>reduce</code> invokes calculation
|
81
|
+
of the <code>:app</code> node, which cascades into evaluation of the required
|
82
|
+
portions of the graph. Note that the result is the square of the sum,
|
83
|
+
demonstrating that the <code>:x</code> and <code>:y</code> nodes are only
|
84
|
+
evaluated once and cached.
|
85
|
+
|
86
|
+
= Example
|
87
|
+
|
88
|
+
This example demonstrates some more advanced features of Reduxco, such as
|
89
|
+
instrospection, yielding values, flow helpers, and overriding.
|
90
|
+
|
91
|
+
Consider the basic structure for an application with error
|
92
|
+
handling, and that has the following requirements:
|
93
|
+
* For the default gut implementation, we generate a random number.
|
94
|
+
* If that number is even, then we raise a RuntimeError.
|
95
|
+
* If that number is odd, we return the number.
|
96
|
+
|
97
|
+
Furthermore, the consumer of the pipeline must define its own error handling
|
98
|
+
strategy as so:
|
99
|
+
* The error handling layer should catch the error and substitute the error
|
100
|
+
message string as the result.
|
101
|
+
|
102
|
+
Lastly, to write tests against this, we nee to override the value to be an even
|
103
|
+
for one test, and an odd for the next test.
|
104
|
+
|
105
|
+
The finished code looks like this
|
106
|
+
|
107
|
+
# The base callables, probably served up from a factory.
|
108
|
+
base_callables = {
|
109
|
+
app: ->(c) do
|
110
|
+
c.inside(:error_handler) do
|
111
|
+
c[:value].even? ? raise(RuntimeError, "Even!") : c[:value]
|
112
|
+
end
|
113
|
+
end,
|
114
|
+
|
115
|
+
error_handler: ->(c) do
|
116
|
+
begin
|
117
|
+
c.yield
|
118
|
+
rescue => error
|
119
|
+
c.call(:onfailure){error} if c.include?(:onfailure)
|
120
|
+
end
|
121
|
+
end,
|
122
|
+
|
123
|
+
value: ->(c){ rand(100) },
|
124
|
+
}
|
125
|
+
|
126
|
+
# The contect specific eror handler implementation.
|
127
|
+
handler_callables = {
|
128
|
+
onfailure: ->(c){ c.yield.message }
|
129
|
+
}
|
130
|
+
|
131
|
+
# Test callables; overrieds the value to be an even value.
|
132
|
+
even_test_callables = {
|
133
|
+
value: ->(c){ 8 }
|
134
|
+
}
|
135
|
+
|
136
|
+
# Test callables: overrieds the value to be an odd value.
|
137
|
+
odd_test_callables = {
|
138
|
+
value: ->(c){ 13 }
|
139
|
+
}
|
140
|
+
|
141
|
+
# Test evens
|
142
|
+
pipeline = Reduxco::Reduxer.new(base_callables, handler_callables, even_test_callables)
|
143
|
+
pipeline.reduce.should == 'Even!'
|
144
|
+
|
145
|
+
# Test odds
|
146
|
+
pipeline = Reduxco::Reduxer.new(base_callables, handler_callables, odd_test_callables)
|
147
|
+
pipeline.reduce.should == 13
|
148
|
+
|
149
|
+
# Invoke with random result
|
150
|
+
pipeline = Reduxco::Reduxer.new(base_callables, handler_callables)
|
151
|
+
random_result = pipeline.reduce
|
152
|
+
|
153
|
+
There are a few features to note about this code, with each explained in more
|
154
|
+
detail below:
|
155
|
+
* When multiple callable maps are given during pipeline instantiation, the
|
156
|
+
Reduxco::Context dispatches the the right-most map with the needed callable. Not shown
|
157
|
+
here is the ability to call <code>c.super</code> to get the value for a given
|
158
|
+
callable in the next highest map.
|
159
|
+
* The error handler demonstrates the use of introspection via the Reduxco::Context#include?
|
160
|
+
method, which checks that a given name is available.
|
161
|
+
Not shown are several other inspection methods, including introspection
|
162
|
+
as to which nodes are evaluated.
|
163
|
+
* The error handler utilizes the Reduxco::Context#yield method, which
|
164
|
+
yields the value of the block provided on the associated Reduxco::Context#call
|
165
|
+
method, in this case the error passed to the
|
166
|
+
<code>:onfailure</code> callable when invoked in the error handler.
|
167
|
+
* The use of the convenience method Reduxco::Context#inside, which although is the
|
168
|
+
same as Reduxco::Context#call, expresses the meaning of the code better.
|
169
|
+
|
1
170
|
= Overview
|
2
171
|
|
3
|
-
Reduxco is a
|
4
|
-
|
5
|
-
|
172
|
+
Reduxco is a graph reduction engine on steroids. It allows the creation of
|
173
|
+
maps of callables to create a self-organizing, lazy evaluated pipeline.
|
174
|
+
|
175
|
+
The main two classes are the Reduxco::Reduxer class, which is used to instantiate
|
176
|
+
pipelines, and the Reduxco::Context class, which is coordinates communication
|
177
|
+
between the nodes.
|
6
178
|
|
7
|
-
|
8
|
-
intermediate calculations that have to be reused later, but unlike Rack Middleware,
|
9
|
-
Reduxco is self organizing based on the dependencies used by each piece.
|
179
|
+
== Callable
|
10
180
|
|
11
|
-
|
181
|
+
They key building block of Reduxco pipelines are named "callables", which become
|
182
|
+
the implementation logic for each node in the graph.
|
12
183
|
|
13
|
-
|
184
|
+
There is no specific callable class. To be a callable, an object need comply
|
185
|
+
with the following two rules:
|
186
|
+
1. The object must respond to the <code>call</code> method.
|
187
|
+
2. The <code>call</code> method must take a single argument, which is a
|
188
|
+
Reduxco::Context instance.
|
14
189
|
|
15
|
-
|
190
|
+
Most of the time, the standard Ruby Proc object is all that is necessary, but
|
191
|
+
there are many clever reasons why one may substitute in a specialty object
|
192
|
+
in its place.
|
16
193
|
|
17
|
-
|
18
|
-
custom class instances), and register them with a Reduxco::Context. Callables can then
|
19
|
-
used their Reduco::Context handle to refer to the callables they can depend on.
|
194
|
+
== Reduxco::Reduxer
|
20
195
|
|
21
|
-
|
196
|
+
The Reduxco::Reduxer class is used to instantiate new pipelines, and to get
|
197
|
+
values from the pipeline.
|
198
|
+
|
199
|
+
=== Pipeline Creation
|
200
|
+
|
201
|
+
Reduxco::Reduxer instances are created by passing one or more maps of callables
|
202
|
+
during instantiation.
|
203
|
+
|
204
|
+
Callable maps are usually Hash instance, whose keys are Symbol instances, and
|
205
|
+
values meet the requirements of callables.
|
206
|
+
|
207
|
+
If more than one map is provieded to the initializer, callables are resolved
|
208
|
+
to the right-most argument that defines it. This provides a mechanism for
|
209
|
+
overriding callables created by a factory to customize for your layer. See
|
210
|
+
the section on overriding below.
|
211
|
+
|
212
|
+
=== Pipeline Invocation
|
213
|
+
|
214
|
+
The resulting Pipeline instance is considered an immutable object whose values
|
215
|
+
are lazily evaluated as necessary and then cached. These values are extracted
|
216
|
+
via the Reduxco::Reduxer#reduce method.
|
217
|
+
|
218
|
+
For example, a simple pipeline can be instantiated with the following code:
|
22
219
|
|
23
220
|
map = {
|
24
221
|
sum: ->(c){ c[:x] + c[:y] },
|
@@ -26,24 +223,84 @@ For example, the addition of two numbers could be done as follows:
|
|
26
223
|
y: ->(c){ 5 }
|
27
224
|
}
|
28
225
|
|
29
|
-
|
226
|
+
pipeline = Reduxco::Reduxer.new(map)
|
227
|
+
sum = pipeline.reduce(:sum)
|
30
228
|
sum.should == 8
|
31
229
|
|
32
|
-
Note that
|
33
|
-
|
34
|
-
|
230
|
+
Note that while Reduxco::Reduxer#reduce can take any named callable as an
|
231
|
+
argument, it by default attempts to reduce the value of the <code>:app</code>
|
232
|
+
callable when called with no argument.
|
233
|
+
|
234
|
+
Thus, most practical pipelines define an <code>:app</code> callable and simply
|
235
|
+
call Reduxco::Reduxer#reduce without an explicit argument:
|
236
|
+
|
237
|
+
pipeline = Reduxco::Reduxer.new(app: ->(c){ "Hello World" })
|
238
|
+
result = pipeline.reduce
|
239
|
+
result.should == "Hello World"
|
240
|
+
|
241
|
+
== Reduxco::Context
|
242
|
+
|
243
|
+
The Reduxco::Context object is the workhorse of the pipeline. It is responsible
|
244
|
+
for communication between the node including invoking the correct callables as
|
245
|
+
necessary and caching their results.
|
35
246
|
|
36
|
-
|
247
|
+
The Reduxco::Context instance is passed into each node's callable when it is
|
248
|
+
invoked, allowing for a plethora of communication and helper methods to be
|
249
|
+
used by the callables. An overview of this functionality is presented below.
|
250
|
+
|
251
|
+
== Basic Calling
|
252
|
+
|
253
|
+
The most common use of the Reduxco::Context object is to retrieve values of
|
254
|
+
other callables. This is accomplished via one of two methods:
|
255
|
+
* Reduxco::Context#[], which is the preferred way to call due to readability.
|
256
|
+
* Reduxco::Context#call, which behaves exactly the same, but has the option of
|
257
|
+
taking a block that can be evaluated by the called callable (explained below)
|
258
|
+
|
259
|
+
Don't forget that callables are only evaluated once and then cached, so multiple
|
260
|
+
retrievals of complex computations are as efficient as possible.
|
261
|
+
|
262
|
+
== Yielding Values
|
263
|
+
|
264
|
+
Sometimes one needs to push values into a callable when it is called. A good
|
265
|
+
example of this are error handling hooks, which are invoked when an error is
|
266
|
+
caught, and must be passed the error for processing.
|
267
|
+
|
268
|
+
Reduxco::Context#yield provides functionality to do this, but allowing the
|
269
|
+
callable to execute the block passed into the associated Reduxco::Context#call
|
270
|
+
method, and retrieve its value.
|
271
|
+
|
272
|
+
For example, consider the following pipeline:
|
273
|
+
|
274
|
+
callables = {
|
275
|
+
app: ->(c){ c.call(:foo) {3+20} },
|
276
|
+
foo: ->(c){ c.yield + 100 }
|
277
|
+
}
|
278
|
+
|
279
|
+
pipeline = Reduxco::Reduxer.new(callables)
|
280
|
+
pipeline.reduce.should == 123
|
37
281
|
|
38
|
-
Of course, any object responding to <code>call</code> can be used as the values in
|
39
|
-
the map, so one could just as easily define a class with an instance method of
|
40
|
-
<code>call</code> on it instead of using Proc objects.
|
41
282
|
|
42
283
|
== Overriding and Super
|
43
284
|
|
44
|
-
|
45
|
-
|
46
|
-
|
285
|
+
=== Dynamic Dispatch
|
286
|
+
|
287
|
+
Resolution of callables for a node is done via a dynamic dispatch methodology
|
288
|
+
that is not all that different than dynamic method dispatch in object oriented
|
289
|
+
dynamic languages like Ruby.
|
290
|
+
|
291
|
+
The Reduxco::Context looks at its stack of callable maps, and tests each map
|
292
|
+
until if finds a matching callable. It then selects that callable, and
|
293
|
+
retrieves the associated value from the cache, evaluating the callable itself
|
294
|
+
if necessary.
|
295
|
+
|
296
|
+
=== Override
|
297
|
+
|
298
|
+
This dynamic dispatching can be used to override the callable for a node with
|
299
|
+
a new one at instantiation (thus shadowing the previous definition). This is
|
300
|
+
especially useful when the primary callables may be generated by a factory, but
|
301
|
+
some pipeline customization is needed at the client layer.
|
302
|
+
|
303
|
+
The following code shows a concise example of overriding:
|
47
304
|
|
48
305
|
map1 = {
|
49
306
|
message: ->(c){ 'Hello From Map 1' }
|
@@ -56,8 +313,16 @@ For example:
|
|
56
313
|
msg = Reduxco::Reduxer.new(map1, map2).reduce(:message)
|
57
314
|
msg.should == 'Hello From Map 2'
|
58
315
|
|
59
|
-
|
60
|
-
|
316
|
+
=== Super
|
317
|
+
|
318
|
+
As mentioned earlier, the dynamic dispatch model used by Reduxco acts a bit like
|
319
|
+
a dynamic object oriented language. The logical extension of this is to allow
|
320
|
+
for a shadowing callable to execute the callable it shadows. This is easily
|
321
|
+
done via the Reduxco::Context#super method, which tells the dynamic dispatcher
|
322
|
+
to call the callable for the same node name, starting with the map "above" you
|
323
|
+
in the stack.
|
324
|
+
|
325
|
+
The following example shows a call to super in the override:
|
61
326
|
|
62
327
|
map1 = {
|
63
328
|
message: ->(c){ 'Hello From Map 1' }
|
@@ -67,17 +332,19 @@ Context#super. For example:
|
|
67
332
|
message: ->(c){ c.super + ' and Hello From Map 2' }
|
68
333
|
}
|
69
334
|
|
70
|
-
msg = Reduxco::
|
335
|
+
msg = Reduxco::Reduxer.new(map1, map2).reduce(:message)
|
71
336
|
msg.should == 'Hello From Map 1 and Hello From Map 2'
|
72
337
|
|
73
|
-
==
|
338
|
+
== Instrospection
|
74
339
|
|
75
340
|
There are several introspection methods for making assertions about the
|
76
|
-
Reduxco::Context.
|
77
|
-
environment before proceeding.
|
341
|
+
Reduxco::Context. These are usually used by callables to inspect their
|
342
|
+
environment before proceeding down an execution path.
|
78
343
|
|
344
|
+
|
345
|
+
The primary introspection methods are as follows:
|
79
346
|
[Reduxco::Context#include?] Allows you to inspect if the Reduxco::Context
|
80
|
-
can resolve a given
|
347
|
+
can resolve a given node name if called.
|
81
348
|
[Reduxco::Context#completed?] Allows you to inspect if the callable associated
|
82
349
|
with a given block name has already been called;
|
83
350
|
useful for assertions about weak dependencies.
|
@@ -86,21 +353,55 @@ environment before proceeding.
|
|
86
353
|
|
87
354
|
== Before, After, and Inside
|
88
355
|
|
356
|
+
While not strictly necessary, it is often useful to control call flow with
|
357
|
+
a method call that is more expressive than <code>call</code>. In other words,
|
358
|
+
while these methods are trivially implementable with just <code>call</code>,
|
359
|
+
it is often more desireable for your code to more directly express your intent
|
360
|
+
as an author to help with readability and maintainability of your code.
|
361
|
+
|
362
|
+
Academically, the key characteristic in common to Reduxco::Context#before,
|
363
|
+
Reduxco::Context#after and Reduxco::Context#insid, is that they each allow for
|
364
|
+
easy expression of ordered flow control, but with the return value being that
|
365
|
+
of the callable initially called.
|
366
|
+
|
367
|
+
As a practical example, Reduxco::Context#inside is often used to insert a
|
368
|
+
genericized error handling node into the app node, as in the following code
|
369
|
+
listing:
|
370
|
+
|
371
|
+
callables = {
|
372
|
+
app: ->(c) do
|
373
|
+
c.inside(:error_handler) do
|
374
|
+
c[:result]
|
375
|
+
end
|
376
|
+
end,
|
377
|
+
|
378
|
+
error_handler: ->(c) do
|
379
|
+
begin
|
380
|
+
c.yield
|
381
|
+
rescue => error
|
382
|
+
c.call(:onfailure){error} if c.include?(:onfailure)
|
383
|
+
raise error
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
result: ->(c) do
|
388
|
+
# do something
|
389
|
+
end
|
390
|
+
}
|
391
|
+
|
89
392
|
= Contact
|
90
393
|
|
91
394
|
Jeff Reinecke <jreinecke@whitepages.com>
|
92
395
|
|
93
|
-
= Roadmap
|
94
|
-
|
95
|
-
TBD
|
96
|
-
|
97
396
|
= History
|
98
397
|
|
99
|
-
[1.0.0 - 2013-Apr
|
398
|
+
[1.0.0 - 2013-Apr-18] Initial Release.
|
399
|
+
[1.0.1 - 2013-Apr-18] Fixed a bug where calling c.yield in a block given to a
|
400
|
+
call would give a stack overflow.
|
100
401
|
|
101
402
|
= License
|
102
403
|
|
103
|
-
Copyright (c)
|
404
|
+
Copyright (c) 2013, WhitePages, Inc.
|
104
405
|
All rights reserved.
|
105
406
|
|
106
407
|
Redistribution and use in source and binary forms, with or without
|
data/lib/reduxco/context.rb
CHANGED
@@ -70,6 +70,7 @@ module Reduxco
|
|
70
70
|
@cache = {}
|
71
71
|
|
72
72
|
@block_association_cache = {}
|
73
|
+
@yield_frame_depth = 0
|
73
74
|
end
|
74
75
|
|
75
76
|
# Given a refname, call it for this context and return the result.
|
@@ -129,11 +130,19 @@ module Reduxco
|
|
129
130
|
|
130
131
|
# Yields to the block given to a #Context.call
|
131
132
|
def yield(*args)
|
132
|
-
block = block_for_frame(
|
133
|
+
block = block_for_frame(yield_frame)
|
133
134
|
if( block.nil? )
|
134
135
|
raise LocalJumpError, "No block given to yield to.", caller
|
135
136
|
else
|
136
|
-
|
137
|
+
begin
|
138
|
+
# If the block call has a context yield inside, resolve that one frame up.
|
139
|
+
# This turns out to be what we usually want as we are forwarding a
|
140
|
+
# yielded value in the call.
|
141
|
+
@yield_frame_depth += 1
|
142
|
+
block.call(*args)
|
143
|
+
ensure
|
144
|
+
@yield_frame_depth -= 1
|
145
|
+
end
|
137
146
|
end
|
138
147
|
end
|
139
148
|
|
@@ -147,6 +156,10 @@ module Reduxco
|
|
147
156
|
@callstack.top
|
148
157
|
end
|
149
158
|
|
159
|
+
def yield_frame
|
160
|
+
@callstack.peek(@yield_frame_depth)
|
161
|
+
end
|
162
|
+
|
150
163
|
# Returns a true value if the given refname is defined in this context.
|
151
164
|
#
|
152
165
|
# If given a CallableRef, it returns a true value if the reference is
|
@@ -221,6 +234,9 @@ module Reduxco
|
|
221
234
|
raise CyclicalError, "Cyclical dependency on #{frame.inspect} in #{@callstack.rest.top.inspect}", callstack.to_caller(caller[1])
|
222
235
|
end
|
223
236
|
|
237
|
+
# On a new call we start resolving yield blocks at the current frame, so reset to zero.
|
238
|
+
@yield_frame_depth = 0
|
239
|
+
|
224
240
|
# Recall from cache, or build if necessary.
|
225
241
|
unless( @cache.include?(frame) )
|
226
242
|
@block_association_cache[frame] = block
|
@@ -28,6 +28,13 @@ module Reduxco
|
|
28
28
|
@stack.last
|
29
29
|
end
|
30
30
|
|
31
|
+
# Returns the element at a given depth from the top of the stack.
|
32
|
+
#
|
33
|
+
# A depth of zero corresponds to the top of the stack.
|
34
|
+
def peek(depth)
|
35
|
+
@stack[-depth - 1]
|
36
|
+
end
|
37
|
+
|
31
38
|
# Returns true if the callstack contains the given frame
|
32
39
|
def include?(frame)
|
33
40
|
@stack.include?(frame)
|
data/lib/reduxco/version.rb
CHANGED
data/spec/callstack_spec.rb
CHANGED
@@ -24,6 +24,13 @@ describe Reduxco::Context::Callstack do
|
|
24
24
|
@stack.top.should == :top
|
25
25
|
end
|
26
26
|
|
27
|
+
it 'should allow peeking into the stack' do
|
28
|
+
@stack.peek(0).should == @stack.top
|
29
|
+
@stack.peek(@stack.depth).should be_nil
|
30
|
+
@stack.peek(1).should == :middle
|
31
|
+
@stack.peek(2).should == :bottom
|
32
|
+
end
|
33
|
+
|
27
34
|
it 'should pop' do
|
28
35
|
frame = @stack.pop
|
29
36
|
|
data/spec/context_spec.rb
CHANGED
@@ -610,6 +610,20 @@ describe Reduxco::Context do
|
|
610
610
|
context.call(:app).should == 25
|
611
611
|
end
|
612
612
|
|
613
|
+
it 'should allow forwarded yielded values to be handed down' do
|
614
|
+
$debug = true
|
615
|
+
value = []
|
616
|
+
|
617
|
+
context = Reduxco::Context.new(
|
618
|
+
app: ->(c){ c.call(:outter){ value } },
|
619
|
+
outter: ->(c){ c.call(:middle){ c.yield } },
|
620
|
+
middle: ->(c){ c.call(:inner){ c.yield } },
|
621
|
+
inner: ->(c){ c.yield + [:inner] }
|
622
|
+
)
|
623
|
+
|
624
|
+
context.call(:app).should == [:inner]
|
625
|
+
end
|
626
|
+
|
613
627
|
end
|
614
628
|
|
615
629
|
end
|
data/spec/rdoc_examples_spec.rb
CHANGED
@@ -4,6 +4,80 @@ describe 'RDoc Examples' do
|
|
4
4
|
|
5
5
|
describe 'README.rdoc' do
|
6
6
|
|
7
|
+
it 'should meet the math example' do
|
8
|
+
srand(5) # For test predictability
|
9
|
+
|
10
|
+
###
|
11
|
+
|
12
|
+
callables = {
|
13
|
+
x: ->(c){ rand(10) },
|
14
|
+
y: ->(c){ rand(10) },
|
15
|
+
sum: ->(c){ c[:x] + c[:y] },
|
16
|
+
result: ->(c){ c[:sum] * c[:sum] },
|
17
|
+
app: ->(c){ "For x=#{c[:x]} and y=#{c[:y]}, the result is #{c[:result]}." }
|
18
|
+
}
|
19
|
+
|
20
|
+
pipeline = Reduxco::Reduxer.new(callables)
|
21
|
+
output = pipeline.reduce
|
22
|
+
output.should == "For x=3 and y=6, the result is 81."
|
23
|
+
|
24
|
+
###
|
25
|
+
|
26
|
+
pipeline.reduce(:sum).should == 9
|
27
|
+
|
28
|
+
###
|
29
|
+
|
30
|
+
srand(Time.now.to_i) # And now randomize better again.
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should meet the error handling exampmle' do
|
34
|
+
# The base callables, probably served up from a factory.
|
35
|
+
base_callables = {
|
36
|
+
app: ->(c) do
|
37
|
+
c.inside(:error_handler) do
|
38
|
+
c[:value].even? ? raise(RuntimeError, "Even!") : c[:value]
|
39
|
+
end
|
40
|
+
end,
|
41
|
+
|
42
|
+
error_handler: ->(c) do
|
43
|
+
begin
|
44
|
+
c.yield
|
45
|
+
rescue => error
|
46
|
+
c.call(:onfailure){error} if c.include?(:onfailure)
|
47
|
+
end
|
48
|
+
end,
|
49
|
+
|
50
|
+
value: ->(c){ rand(100) },
|
51
|
+
}
|
52
|
+
|
53
|
+
# The contect specific eror handler implementation.
|
54
|
+
handler_callables = {
|
55
|
+
onfailure: ->(c){ c.yield.message }
|
56
|
+
}
|
57
|
+
|
58
|
+
# Test callables; overrieds the value to be an even value.
|
59
|
+
even_test_callables = {
|
60
|
+
value: ->(c){ 8 }
|
61
|
+
}
|
62
|
+
|
63
|
+
# Test callables: overrieds the value to be an odd value.
|
64
|
+
odd_test_callables = {
|
65
|
+
value: ->(c){ 13 }
|
66
|
+
}
|
67
|
+
|
68
|
+
# Test evens
|
69
|
+
pipeline = Reduxco::Reduxer.new(base_callables, handler_callables, even_test_callables)
|
70
|
+
pipeline.reduce.should == 'Even!'
|
71
|
+
|
72
|
+
# Test odds
|
73
|
+
pipeline = Reduxco::Reduxer.new(base_callables, handler_callables, odd_test_callables)
|
74
|
+
pipeline.reduce.should == 13
|
75
|
+
|
76
|
+
# Invoke with random result
|
77
|
+
pipeline = Reduxco::Reduxer.new(base_callables, handler_callables)
|
78
|
+
random_result = pipeline.reduce
|
79
|
+
end
|
80
|
+
|
7
81
|
it 'should obey basic context use' do
|
8
82
|
map = {
|
9
83
|
sum: ->(c){ c[:x] + c[:y] },
|
@@ -11,10 +85,27 @@ describe 'RDoc Examples' do
|
|
11
85
|
y: ->(c){ 5 }
|
12
86
|
}
|
13
87
|
|
14
|
-
|
88
|
+
pipeline = Reduxco::Reduxer.new(map)
|
89
|
+
sum = pipeline.reduce(:sum)
|
15
90
|
sum.should == 8
|
16
91
|
end
|
17
92
|
|
93
|
+
it 'should have a basic app pipeline example' do
|
94
|
+
pipeline = Reduxco::Reduxer.new(app: ->(c){ "Hello World" })
|
95
|
+
result = pipeline.reduce
|
96
|
+
result.should == "Hello World"
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'should have a simple yield example' do
|
100
|
+
callables = {
|
101
|
+
app: ->(c){ c.call(:foo) {3+20} },
|
102
|
+
foo: ->(c){ c.yield + 100 }
|
103
|
+
}
|
104
|
+
|
105
|
+
pipeline = Reduxco::Reduxer.new(callables)
|
106
|
+
pipeline.reduce.should == 123
|
107
|
+
end
|
108
|
+
|
18
109
|
it 'should override/shadow' do
|
19
110
|
map1 = {
|
20
111
|
message: ->(c){ 'Hello From Map 1' }
|
data/spec/spec_helper.rb
CHANGED
@@ -7,7 +7,11 @@ require 'reduxco'
|
|
7
7
|
|
8
8
|
# Require the debugger, if present.
|
9
9
|
begin
|
10
|
-
|
10
|
+
if( RUBY_VERSION < '2.0.0' )
|
11
|
+
require 'debugger'
|
12
|
+
else
|
13
|
+
require 'byebug'
|
14
|
+
end
|
11
15
|
rescue LoadError
|
12
16
|
module Kernel
|
13
17
|
def debugger(*args, &block)
|
@@ -15,3 +19,4 @@ rescue LoadError
|
|
15
19
|
end
|
16
20
|
end
|
17
21
|
end
|
22
|
+
|
metadata
CHANGED
@@ -1,22 +1,19 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: reduxco
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
5
|
-
prerelease:
|
4
|
+
version: 1.0.1
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Jeff Reinecke
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2013-04
|
11
|
+
date: 2013-12-04 00:00:00.000000000 Z
|
13
12
|
dependencies: []
|
14
|
-
description:
|
15
|
-
those
|
16
|
-
|
13
|
+
description: |-
|
14
|
+
Reduxco is a general purpose graph reduction calculation engine for those
|
17
15
|
non-linear dependency flows that normal pipelines and Rack Middleware-like
|
18
|
-
|
19
|
-
architectures can''t do cleanly.'
|
16
|
+
architectures can't do cleanly.
|
20
17
|
email:
|
21
18
|
- jreinecke@whitepages.com
|
22
19
|
executables: []
|
@@ -44,26 +41,25 @@ files:
|
|
44
41
|
homepage: https://github.com/whitepages/reduxco
|
45
42
|
licenses:
|
46
43
|
- BSD
|
44
|
+
metadata: {}
|
47
45
|
post_install_message:
|
48
46
|
rdoc_options: []
|
49
47
|
require_paths:
|
50
48
|
- lib
|
51
49
|
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
-
none: false
|
53
50
|
requirements:
|
54
|
-
- -
|
51
|
+
- - '>='
|
55
52
|
- !ruby/object:Gem::Version
|
56
53
|
version: '0'
|
57
54
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
-
none: false
|
59
55
|
requirements:
|
60
|
-
- -
|
56
|
+
- - '>='
|
61
57
|
- !ruby/object:Gem::Version
|
62
58
|
version: '0'
|
63
59
|
requirements: []
|
64
60
|
rubyforge_project:
|
65
|
-
rubygems_version: 1.
|
61
|
+
rubygems_version: 2.1.11
|
66
62
|
signing_key:
|
67
|
-
specification_version:
|
63
|
+
specification_version: 4
|
68
64
|
summary: A graph reduction calculation engine.
|
69
65
|
test_files: []
|