stepladder 0.0.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -3
- data/README.md +227 -75
- data/Rakefile +9 -1
- data/docs/stepladder/worker.md +103 -0
- data/lib/stepladder/dsl.rb +137 -0
- data/lib/stepladder/version.rb +1 -1
- data/lib/stepladder/worker.rb +21 -14
- data/lib/stepladder.rb +3 -0
- data/spec/lib/stepladder/dsl_spec.rb +256 -0
- data/spec/lib/stepladder/worker_spec.rb +62 -24
- data/spec/spec_helper.rb +15 -0
- data/stepladder.gemspec +5 -5
- metadata +41 -50
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7cd6ff328453446dc663837b4e70fd2df7d82de39429409418445e597b393d86
|
4
|
+
data.tar.gz: 9907fe3ed9411bcb7dc50ad62a08cd50faaecfd5bed31d172948e5997c3c87dc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6ad4c68022bf02a60ad99c60fd7ed77b5d67f20e88e1adf344b1957807249065329279cc70afcf6759886f4fdfb0e49152bf406533215693a57e261df66142d1
|
7
|
+
data.tar.gz: 262b43d27e1d36b5da47a648611d75b912545a6487ec1acf493b08c91ce39c3b10ca3e355de5b969e27558aeaaaee0519ba8fd3c5ccc186ce23b8a8155be7cec
|
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,112 +1,255 @@
|
|
1
|
+
[![Build Status](https://travis-ci.org/joelhelbling/stepladder.png)](https://travis-ci.org/joelhelbling/stepladder)
|
2
|
+
[![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/joelhelbling/stepladder)
|
3
|
+
|
1
4
|
# The Stepladder Framework
|
2
5
|
|
3
6
|
_"How many Ruby fibers does it take to screw in a lightbulb?"_
|
4
7
|
|
5
8
|
## Quick Start
|
6
9
|
|
7
|
-
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem 'stepladder'
|
14
|
+
```
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself:
|
8
21
|
|
9
|
-
|
22
|
+
$ gem install steplader
|
23
|
+
|
24
|
+
And then use it:
|
10
25
|
|
11
26
|
```ruby
|
12
|
-
|
27
|
+
require 'stepladder'
|
28
|
+
```
|
29
|
+
|
30
|
+
## New! Stepladders now has a DSL!
|
31
|
+
|
32
|
+
As of version ?? there is now a DSL which provides convenient shorthand
|
33
|
+
for several common types of workers. Let's look at them.
|
13
34
|
|
14
|
-
|
35
|
+
But first, be sure to include the DSL mixin:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
include Stepladder::Dsl
|
15
39
|
```
|
16
|
-
|
17
|
-
|
40
|
+
|
41
|
+
### Source Worker
|
42
|
+
|
43
|
+
At the headwater of every Stepladder pipeline, there is a source worker
|
44
|
+
which is able to generate its own work items.
|
45
|
+
|
46
|
+
Here is possibly the simplest of source workers, which provides the same
|
47
|
+
string each time it is invoked:
|
18
48
|
|
19
49
|
```ruby
|
20
|
-
|
21
|
-
relay_worker.supplier = source_worker
|
50
|
+
worker = source_worker { "Number 9" }
|
22
51
|
|
23
|
-
|
52
|
+
worker.product #=> "Number 9"
|
53
|
+
worker.product #=> "Number 9"
|
54
|
+
worker.product #=> "Number 9"
|
55
|
+
# ...ad nauseum...
|
24
56
|
```
|
25
57
|
|
26
|
-
|
27
|
-
as its task:
|
58
|
+
Sometimes you want a worker which generates until it doesn't:
|
28
59
|
|
29
60
|
```ruby
|
30
|
-
|
31
|
-
|
61
|
+
counter = 0
|
62
|
+
worker = source_worker do
|
63
|
+
if counter < 3
|
64
|
+
counter += 1
|
65
|
+
counter + 1000
|
66
|
+
end
|
67
|
+
end
|
32
68
|
|
33
|
-
|
69
|
+
worker.product #=> 1001
|
70
|
+
worker.product #=> 1002
|
71
|
+
worker.product #=> 1003
|
72
|
+
worker.product #=> nil
|
73
|
+
worker.product #=> nil (and will be nil henceforth)
|
34
74
|
```
|
35
75
|
|
36
|
-
|
76
|
+
If all you want is a source worker which generates a series of numbers
|
77
|
+
the DSL provides an easier way to do it:
|
37
78
|
|
38
79
|
```ruby
|
39
|
-
|
40
|
-
|
80
|
+
w1 = source_worker [0,1,2]
|
81
|
+
w2 = source_worker (0..2)
|
41
82
|
|
42
|
-
|
83
|
+
w1.product == w2.product #=> 0
|
84
|
+
w1.product == w2.product #=> 1
|
85
|
+
w1.product == w2.product #=> 2
|
86
|
+
w1.product == w2.product #=> nil (and henceforth, etc.)
|
43
87
|
```
|
44
88
|
|
45
|
-
|
46
|
-
|
89
|
+
### Relay Worker
|
90
|
+
|
91
|
+
This is perhaps the most "normal" kind of worker in Stepladder. It
|
92
|
+
accepts a value and returns some transformation of that value:
|
47
93
|
|
48
94
|
```ruby
|
49
|
-
|
50
|
-
|
95
|
+
squarer = relay_worker do |number|
|
96
|
+
number ** 2
|
51
97
|
end
|
98
|
+
```
|
99
|
+
|
100
|
+
Of course a relay worker needs a source from which to get values upon
|
101
|
+
which to operate:
|
52
102
|
|
53
|
-
|
103
|
+
```ruby
|
104
|
+
source = source_worker (0..3)
|
105
|
+
|
106
|
+
squarer.supplier = source
|
54
107
|
```
|
55
108
|
|
56
|
-
|
57
|
-
|
109
|
+
Or, if you prefer, the DSL provides a vertical pipe for linking the
|
110
|
+
workers together into a pipeline:
|
58
111
|
|
59
112
|
```ruby
|
60
|
-
|
113
|
+
pipeline = source | squarer
|
61
114
|
|
62
|
-
|
115
|
+
pipeline.product #=> 0
|
116
|
+
pipeline.product #=> 1
|
117
|
+
pipeline.product #=> 4
|
118
|
+
pipeline.product #=> 9
|
119
|
+
pipeline.product #=> nil
|
63
120
|
```
|
64
121
|
|
65
|
-
|
122
|
+
### Side Worker
|
66
123
|
|
67
|
-
|
124
|
+
As we travel down the pipeline, from time to time, we will want to
|
125
|
+
stop and smell the roses, or write to a log file, or drop a beat, or
|
126
|
+
create some other kind of side-effect. That's the purpose of the
|
127
|
+
`side_worker`. A side worker will pass through the same value that
|
128
|
+
it received, but as it does so, it can perform some kind side-effect
|
129
|
+
work.
|
68
130
|
|
69
|
-
|
131
|
+
```
|
132
|
+
source = source_worker (0..3)
|
70
133
|
|
71
|
-
|
72
|
-
|
73
|
-
|
134
|
+
evens = []
|
135
|
+
even_stasher = side_effect do |value|
|
136
|
+
if value % 2 == 0
|
137
|
+
evens << value
|
138
|
+
end
|
74
139
|
end
|
140
|
+
|
141
|
+
# re-using "squarer" from above example...
|
142
|
+
pipeline = source | even_stasher | squarer
|
143
|
+
|
144
|
+
pipeline.product #=> 0
|
145
|
+
pipeline.product #=> 1
|
146
|
+
pipeline.product #=> 4
|
147
|
+
pipeline.product #=> 9
|
148
|
+
pipeline.product #=> nil
|
149
|
+
|
150
|
+
evens #=> [0, 2]
|
75
151
|
```
|
76
152
|
|
77
|
-
|
153
|
+
Notice that the output is the same as the previous example, even though
|
154
|
+
we put this `even_stasher` `side_worker` in the middle of the pipeline.
|
155
|
+
|
156
|
+
However, we can also see that the `evens` array now contains the even numbers from `source` (the side effect).
|
157
|
+
|
158
|
+
_But wait,_ you want to say, _can't we still create side effects with
|
159
|
+
regular ole relay workers?_ Why, yes. Yes you can. Ruby being what
|
160
|
+
it is, there really isn't a way to prevent the implementation of any
|
161
|
+
worker from creating side effects.
|
162
|
+
|
163
|
+
_And still wait,_ you'll also be wanting to say, _isn't it possible
|
164
|
+
that a side worker could mutate the value as it's passed through?_ And
|
165
|
+
again, yes. It would be very difficult\* in the Ruby language (where
|
166
|
+
so many things are passed by reference) to perfectly prevent a side
|
167
|
+
worker from mutating the value. But please don't.
|
168
|
+
|
169
|
+
The side effect worker's purpose is to provide _intentionality_ and
|
170
|
+
clarity. When you're creating a side effect, let it be very clear.
|
171
|
+
And don't do regular, pure functional style work in the same worker.
|
172
|
+
|
173
|
+
This will make side effects easier to troubleshoot; if you pull them
|
174
|
+
out of the pipeline, the pipeline's output shouldn't change. By the
|
175
|
+
same token, if there is a problem with a side effect, troubleshooting
|
176
|
+
it will be much simpler if the side effects are already isolated and
|
177
|
+
named.
|
178
|
+
|
179
|
+
\* _Under consideration: a variant of the side-effect which attempts
|
180
|
+
to prevent side-effects by doing a `Marshal.dump/load`. But the
|
181
|
+
potential overhead in all that marshalling makes me hesitant to make
|
182
|
+
this the default behavior. Making it available as an option, however,
|
183
|
+
opens the possibility to troubleshoot side effects: if the marshalling
|
184
|
+
eliminates an unwanted mutation, then chances are that you have a
|
185
|
+
side effect that is doing mutation._
|
186
|
+
|
187
|
+
### Filter Worker
|
188
|
+
|
189
|
+
The filter worker simply passes through the values which are given
|
190
|
+
to it, but _only_ those values which result in truthiness when your
|
191
|
+
provided callable is run against it. Values which result in
|
192
|
+
falsiness are simply discarded.
|
78
193
|
|
79
194
|
```ruby
|
80
|
-
|
81
|
-
|
195
|
+
source = source_worker (0..5)
|
196
|
+
|
197
|
+
filter = filter_worker do |value|
|
198
|
+
value % 2 == 0
|
199
|
+
end
|
82
200
|
|
83
|
-
|
84
|
-
|
85
|
-
|
201
|
+
pipeline = source | filter
|
202
|
+
|
203
|
+
pipeline.product #=> 0
|
204
|
+
pipeline.product #=> 2
|
205
|
+
pipeline.product #=> 4
|
206
|
+
pipeline.product #=> nil
|
86
207
|
```
|
87
208
|
|
88
|
-
###
|
209
|
+
### Batch Worker
|
89
210
|
|
90
|
-
|
211
|
+
The batch worker gathers outputs into batches and the returns each
|
212
|
+
batch as its output.
|
91
213
|
|
92
214
|
```ruby
|
93
|
-
|
215
|
+
source = source_worker (0..7)
|
216
|
+
|
217
|
+
batch = batch_worker gathering: 3
|
218
|
+
|
219
|
+
pipeline = source | batch
|
220
|
+
|
221
|
+
pipeline.product #=> [0, 1, 2]
|
222
|
+
pipeline.product #=> [3, 4, 5]
|
223
|
+
pipeline.product #=> [6, 7]
|
224
|
+
pipeline.product #=> nil
|
94
225
|
```
|
95
226
|
|
96
|
-
|
97
|
-
|
227
|
+
Notice how the final batch doesn't have full compliment of three.
|
228
|
+
|
229
|
+
Fixed-numbered batch workers are useful for things like pagination,
|
230
|
+
perhaps, but sometimes we don't want a batch to include a specific
|
231
|
+
number of items, but to batch together all items until a certain
|
232
|
+
condition is met. So batch worker can also accept a callable:
|
98
233
|
|
99
234
|
```ruby
|
100
|
-
|
101
|
-
|
102
|
-
|
235
|
+
source = source_worker [
|
236
|
+
"some", "rain", "must\n", "fall", "but", "ok" ]
|
237
|
+
|
238
|
+
line_reader = batch_worker do |value|
|
239
|
+
value.end_with? "\n"
|
103
240
|
end
|
241
|
+
|
242
|
+
pipeline = source | line_reader
|
243
|
+
|
244
|
+
pipeline.product #=> ["some", "rain", "must\n"]
|
245
|
+
pipeline.product #=> ["fall", "but", "ok"]
|
246
|
+
pipeline.product #=> nil
|
104
247
|
```
|
105
248
|
|
106
249
|
## Origins of Stepladder
|
107
250
|
|
108
251
|
Stepladder grew out of experimentation with Ruby fibers, after readings
|
109
|
-
[Dave Thomas' demo of Ruby fibers](http://pragdave.
|
252
|
+
[Dave Thomas' demo of Ruby fibers](http://pragdave.me/blog/2007/12/30/pipelines-using-fibers-in-ruby-19/), wherein he created a
|
110
253
|
pipeline of fiber processes, emulating the style and syntax of the
|
111
254
|
\*nix command line. I noticed that, courtesy of fibers' extremely
|
112
255
|
low surface area, fiber-to-fiber collaborators could operate with
|
@@ -176,50 +319,59 @@ just _common objects_ coupling-- this little remaining coupling is
|
|
176
319
|
mitigated by the possibility of having coupled code _live together_.
|
177
320
|
I call this feature the _folded collaborators_ effect.
|
178
321
|
|
179
|
-
Consider the following
|
322
|
+
Consider the following ~~code~~ vaporware:
|
180
323
|
|
181
324
|
```ruby
|
182
|
-
|
325
|
+
SUBJECT = 'kitteh'
|
183
326
|
|
184
|
-
|
185
|
-
tweet_getter = Worker.new do
|
186
|
-
twitter_api.fetch_my_tweets
|
187
|
-
end
|
327
|
+
include Stepladder::Dsl
|
188
328
|
|
189
|
-
|
190
|
-
|
329
|
+
tweet_getter = source_worker do
|
330
|
+
twitter_api.fetch_my_tweets
|
331
|
+
end
|
191
332
|
|
192
|
-
|
193
|
-
|
194
|
-
|
333
|
+
about_me = filter_worker do |tweet|
|
334
|
+
tweet.referenced.include? SUBJECT
|
335
|
+
end
|
336
|
+
|
337
|
+
tweet_formatter = relay_worker do |tweet|
|
338
|
+
apply_format_to tweet
|
339
|
+
end
|
340
|
+
|
341
|
+
kitteh_tweets = tweet_getter | about_me | tweet_formatter
|
195
342
|
|
196
|
-
|
343
|
+
while tweet = kitteh_tweets.product
|
344
|
+
display(tweet)
|
197
345
|
end
|
198
346
|
```
|
199
347
|
|
200
348
|
None of these tasks have hard coupling with each other. If we were to
|
201
|
-
insert another worker between the filter and the formatter, neither of
|
202
|
-
workers would need changes in their code, assuming the inserted
|
203
|
-
nicely with the objects they're all passing along.
|
349
|
+
insert another worker between the filter and the formatter, neither of
|
350
|
+
those workers would need changes in their code, assuming the inserted
|
351
|
+
worker plays nicely with the objects they're all passing along.
|
204
352
|
|
205
353
|
Which brings me to the point: these workers to have a dependency upon the
|
206
354
|
objects they're handing off and receiving. But we have the capability to
|
207
355
|
coordinate those workers in a centralized location (such as in this code
|
208
356
|
example).
|
209
357
|
|
210
|
-
##
|
211
|
-
|
212
|
-
This framework's name was inspired by a conversation with Tim Wingfield
|
213
|
-
in which we joked about the profusion of new frameworks in the Ruby
|
214
|
-
community. We quickly began riffing on a fictional framework called
|
215
|
-
"Stepladder" which all the cool kids, we asserted, were (or would soon
|
216
|
-
be) using.
|
358
|
+
## Stepladder::Worker
|
217
359
|
|
218
|
-
|
219
|
-
|
220
|
-
([Really?](http://github.com/joelhelbling/really))
|
360
|
+
The Stepladder::Worker documentation has been moved
|
361
|
+
[here](docs/stepladder/worker.md).
|
221
362
|
|
222
363
|
## Roadmap
|
223
364
|
|
224
|
-
-
|
225
|
-
|
365
|
+
- `splitter_worker` -- would accept a value and return an array. The
|
366
|
+
elements of that returned array would then be output as separate values
|
367
|
+
to the next worker in the pipeline.
|
368
|
+
- `batch_worker trailing: n` -- accepts a value, and returns the last n
|
369
|
+
values. This would mean that no values would be returned until n
|
370
|
+
values had been accumulated. This could be useful for things like
|
371
|
+
rolling averages.
|
372
|
+
- `side_worker(:hardened) { |v| do_stuff_with(v) }` -- the `:hardened`
|
373
|
+
flag would attempt to ensure no side effects may occur by using
|
374
|
+
`Marshal` to dump/load the value before handing it to the workers
|
375
|
+
callable. Also might make a runtime-wide toggle which hardens all
|
376
|
+
side_workers, which could be useful in flushing out side workers
|
377
|
+
which are doing inadvertent mutation.
|
data/Rakefile
CHANGED
@@ -0,0 +1,103 @@
|
|
1
|
+
# Stepladder::Worker
|
2
|
+
|
3
|
+
_The workhorse of the Stepladder framework._
|
4
|
+
|
5
|
+
## Workers have tasks...
|
6
|
+
|
7
|
+
Initialize with a block of code:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
source_worker = Stepladder::Worker.new { "hulk" }
|
11
|
+
|
12
|
+
source_worker.product #=> "hulk"
|
13
|
+
```
|
14
|
+
If you supply a worker with another worker as its supplier, then you
|
15
|
+
can give it a task which accepts a value:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
relay_worker = Stepladder::Worker.new { |name| name.upcase }
|
19
|
+
relay_worker.supplier = source_worker
|
20
|
+
|
21
|
+
relay_worker.product #=> "HULK"
|
22
|
+
```
|
23
|
+
|
24
|
+
You can also initialize a worker by passing in a callable object
|
25
|
+
as its task:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
capitalizer = Proc.new { |name| name.capitalize }
|
29
|
+
relay_worker = Stepladder::Worker.new(task: capitalizer, supplier: source_worker)
|
30
|
+
|
31
|
+
relay_worker.product #=> 'Hulk'
|
32
|
+
```
|
33
|
+
|
34
|
+
A worker also has an accessor for its @task:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
doofusizer = Proc.new { |name| name.gsub(/u/, 'oo') }
|
38
|
+
relay_worker.task = doofusizer
|
39
|
+
|
40
|
+
relay_worker.product #=> 'hoolk'
|
41
|
+
```
|
42
|
+
|
43
|
+
And finally, you can provide a task by directly overriding the
|
44
|
+
worker's #task instance method:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
def relay_worker.task(name)
|
48
|
+
name.to_sym
|
49
|
+
end
|
50
|
+
|
51
|
+
relay_worker.product #=> :hulk
|
52
|
+
```
|
53
|
+
|
54
|
+
Even workers without a task have a task; all workers actually come
|
55
|
+
with a default task which simply passes on the received value unchanged:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
useless_worker = Stepladder::Worker.new(supplier: source_worker)
|
59
|
+
|
60
|
+
useless_worker.product #=> 'hulk'
|
61
|
+
```
|
62
|
+
|
63
|
+
This turns out to be helpful in implementing filter workers, which are up next.
|
64
|
+
|
65
|
+
## Workers can have filters...
|
66
|
+
|
67
|
+
Given a source worker which provides integers 1-3:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
source = Stepladder::Worker.new do
|
71
|
+
(1..3).each { |number| handoff number }
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
...we can define a subscribing worker with a filter:
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
odd_number_filter = Proc.new { |number| number % 2 > 0 }
|
79
|
+
filter_worker = Stepladder::Worker.new filter: odd_number_filter
|
80
|
+
|
81
|
+
filter_worker.product #=> 1
|
82
|
+
filter_worker.product #=> 3
|
83
|
+
filter_worker.product #=> nil
|
84
|
+
```
|
85
|
+
|
86
|
+
## The pipeline DSL
|
87
|
+
|
88
|
+
You can stitch your workers together using the vertical pipe ("|") like so:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
pipeline = source_worker | filter_worker | relay_worker | another worker
|
92
|
+
```
|
93
|
+
|
94
|
+
...and then just call on that pipeline (it's actually the last worker in the
|
95
|
+
chain):
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
while next_value = pipeline.product do
|
99
|
+
do_something_with next_value
|
100
|
+
# etc.
|
101
|
+
end
|
102
|
+
```
|
103
|
+
|
@@ -0,0 +1,137 @@
|
|
1
|
+
module Stepladder
|
2
|
+
class WorkerInitializationError < StandardError; end
|
3
|
+
|
4
|
+
module Dsl
|
5
|
+
def source_worker(argument=nil, &block)
|
6
|
+
ensure_correct_arity_for!(argument, block)
|
7
|
+
|
8
|
+
series = series_from(argument)
|
9
|
+
callable = setup_callable_for(block, series)
|
10
|
+
|
11
|
+
return Worker.new(&callable) if series.nil?
|
12
|
+
|
13
|
+
Worker.new do
|
14
|
+
series.each(&callable)
|
15
|
+
|
16
|
+
while true do
|
17
|
+
handoff nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
def relay_worker(&block)
|
24
|
+
ensure_regular_arity(block)
|
25
|
+
|
26
|
+
Worker.new do |value|
|
27
|
+
value && block.call(value)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def side_worker(&block)
|
32
|
+
ensure_regular_arity(block)
|
33
|
+
|
34
|
+
Worker.new do |value|
|
35
|
+
value.tap do |v|
|
36
|
+
v && block.call(v)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def filter_worker(argument=nil, &block)
|
42
|
+
if (block && argument.respond_to?(:call))
|
43
|
+
throw_with 'You cannot supply two callables'
|
44
|
+
end
|
45
|
+
callable = argument.respond_to?(:call) ? argument : block
|
46
|
+
|
47
|
+
ensure_callable(callable)
|
48
|
+
Worker.new filter: block
|
49
|
+
end
|
50
|
+
|
51
|
+
def batch_worker(options = {gathering: 1}, &block)
|
52
|
+
ensure_regular_arity(block) if block
|
53
|
+
|
54
|
+
Worker.new.tap do |worker|
|
55
|
+
worker.instance_variable_set(:@batch_size, options[:gathering])
|
56
|
+
worker.instance_variable_set(:@batch_complete_block, block)
|
57
|
+
|
58
|
+
def worker.task(value)
|
59
|
+
if value
|
60
|
+
@collection = [value]
|
61
|
+
until batch_complete?(@collection.last)
|
62
|
+
@collection << supplier.product
|
63
|
+
end
|
64
|
+
@collection.compact
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def worker.batch_complete?(value)
|
69
|
+
return true if value.nil?
|
70
|
+
if @batch_complete_block
|
71
|
+
!! @batch_complete_block.call(value)
|
72
|
+
else
|
73
|
+
@collection.size >= @batch_size
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def handoff(something)
|
80
|
+
Fiber.yield something
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def throw_with(*msg)
|
86
|
+
raise WorkerInitializationError.new([msg].flatten.join(' '))
|
87
|
+
end
|
88
|
+
|
89
|
+
def ensure_callable(callable)
|
90
|
+
unless callable && callable.respond_to?(:call)
|
91
|
+
throw_with 'You must supply a callable'
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def ensure_regular_arity(block)
|
96
|
+
if block.arity != 1
|
97
|
+
throw_with \
|
98
|
+
"Worker must accept exactly one argument (arity == 1)"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# only valid for #source_worker
|
103
|
+
def ensure_correct_arity_for!(argument, block)
|
104
|
+
return unless block
|
105
|
+
if argument
|
106
|
+
ensure_regular_arity(block)
|
107
|
+
else
|
108
|
+
if block.arity > 0
|
109
|
+
throw_with \
|
110
|
+
'Source worker cannot accept any arguments (arity == 0)'
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def series_from(series)
|
116
|
+
return if series.nil?
|
117
|
+
case
|
118
|
+
when series.respond_to?(:to_a)
|
119
|
+
series.to_a
|
120
|
+
when series.respond_to?(:scan)
|
121
|
+
series.scan(/./)
|
122
|
+
else
|
123
|
+
[series]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def setup_callable_for(block, series)
|
128
|
+
return block unless series
|
129
|
+
if block
|
130
|
+
return Proc.new { |value| handoff block.call(value) }
|
131
|
+
else
|
132
|
+
return Proc.new { |value| handoff value }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
data/lib/stepladder/version.rb
CHANGED
data/lib/stepladder/worker.rb
CHANGED
@@ -6,20 +6,20 @@ module Stepladder
|
|
6
6
|
@supplier = p[:supplier]
|
7
7
|
@filter = p[:filter] || default_filter
|
8
8
|
@task = block || p[:task]
|
9
|
+
# don't define default task here
|
10
|
+
# because we want to allow for
|
11
|
+
# an initialized worker to have
|
12
|
+
# a task injected, including
|
13
|
+
# method-based tasks.
|
9
14
|
end
|
10
15
|
|
11
16
|
def product
|
12
|
-
|
13
|
-
|
14
|
-
end
|
17
|
+
ensure_ready_to_work!
|
18
|
+
workflow.resume
|
15
19
|
end
|
16
20
|
|
17
21
|
def ready_to_work?
|
18
|
-
@task
|
19
|
-
if (task_accepts_a_value? && supplier.nil?)
|
20
|
-
raise "This worker's task expects to receive a value from a supplier, but has no supplier."
|
21
|
-
end
|
22
|
-
true
|
22
|
+
@task && (supplier || !task_accepts_a_value?)
|
23
23
|
end
|
24
24
|
|
25
25
|
def |(subscribing_worker)
|
@@ -29,12 +29,23 @@ module Stepladder
|
|
29
29
|
|
30
30
|
private
|
31
31
|
|
32
|
-
def
|
32
|
+
def ensure_ready_to_work!
|
33
|
+
@task ||= default_task
|
34
|
+
# at this point we will ensure a task exists
|
35
|
+
# because we know that the worker is being
|
36
|
+
# asked for product
|
37
|
+
|
38
|
+
unless ready_to_work?
|
39
|
+
raise "This worker's task expects to receive a value from a supplier, but has no supplier."
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def workflow
|
33
44
|
@my_little_machine ||= Fiber.new do
|
34
45
|
loop do
|
35
46
|
value = supplier && supplier.product
|
36
47
|
if value.nil? || passes_filter?(value)
|
37
|
-
|
48
|
+
Fiber.yield @task.call(value)
|
38
49
|
end
|
39
50
|
end
|
40
51
|
end
|
@@ -76,7 +87,3 @@ module Stepladder
|
|
76
87
|
|
77
88
|
end
|
78
89
|
end
|
79
|
-
|
80
|
-
def handoff(product)
|
81
|
-
Fiber.yield product
|
82
|
-
end
|
data/lib/stepladder.rb
ADDED
@@ -0,0 +1,256 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Stepladder
|
4
|
+
describe Dsl do
|
5
|
+
include Stepladder::Dsl
|
6
|
+
|
7
|
+
describe '#source_worker' do
|
8
|
+
context 'normal usage' do
|
9
|
+
context 'with an array' do
|
10
|
+
Given(:worker) { source_worker [:fee, :fi] }
|
11
|
+
|
12
|
+
Then { worker.product == :fee }
|
13
|
+
And { worker.product == :fi }
|
14
|
+
And { worker.product.nil? }
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'with a range' do
|
18
|
+
Given(:worker) { source_worker (0..2) }
|
19
|
+
|
20
|
+
Then { worker.product == 0 }
|
21
|
+
And { worker.product == 1 }
|
22
|
+
And { worker.product == 2 }
|
23
|
+
And { worker.product.nil? }
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'with a string' do
|
27
|
+
Given(:worker) { source_worker 'abc' }
|
28
|
+
|
29
|
+
Then { worker.product == 'a' }
|
30
|
+
And { worker.product == 'b' }
|
31
|
+
And { worker.product == 'c' }
|
32
|
+
And { worker.product.nil? }
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'with a hash' do
|
36
|
+
Given(:worker) { source_worker({foo: 2, bar: 'yarr'}) }
|
37
|
+
|
38
|
+
Then { worker.product == [:foo, 2] }
|
39
|
+
And { worker.product == [:bar, 'yarr'] }
|
40
|
+
And { worker.product.nil? }
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'with a anything else' do
|
44
|
+
Given(:worker) { source_worker :foo }
|
45
|
+
|
46
|
+
Then { worker.product == :foo }
|
47
|
+
And { worker.product.nil? }
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'with a callable' do
|
51
|
+
Given(:worker) do
|
52
|
+
notes = %i[doh ray me fa so la ti]
|
53
|
+
source_worker do
|
54
|
+
notes.shift if notes.size > 4
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
Then { worker.product == :doh }
|
59
|
+
And { worker.product == :ray }
|
60
|
+
And { worker.product == :me }
|
61
|
+
And { worker.product.nil? }
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'with a callable and an argument' do
|
65
|
+
Given(:notes) { %i[so la ti] }
|
66
|
+
Given(:worker) do
|
67
|
+
source_worker notes do |note|
|
68
|
+
note.to_s.upcase
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
Then { worker.product == 'SO' }
|
73
|
+
And { worker.product == 'LA' }
|
74
|
+
And { worker.product == 'TI' }
|
75
|
+
And { worker.product.nil? }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'illegal usage' do
|
80
|
+
context 'with no argument and arity > 0' do
|
81
|
+
Given(:invocation) do
|
82
|
+
-> { source_worker { |v| v * 2 } }
|
83
|
+
end
|
84
|
+
Then { expect(invocation).to raise_error(/arity == 0/) }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'with argument' do
|
89
|
+
context 'and arity == 0' do
|
90
|
+
Given(:invocation) do
|
91
|
+
-> { source_worker([]) { :boo } }
|
92
|
+
end
|
93
|
+
Then { expect(invocation).to raise_error(/arity == 1/) }
|
94
|
+
end
|
95
|
+
context 'and arity > 1' do
|
96
|
+
Given(:invocation) do
|
97
|
+
-> { source_worker([]) { |p, q| :boo } }
|
98
|
+
end
|
99
|
+
Then { expect(invocation).to raise_error(/arity == 1/) }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe '#relay_worker' do
|
105
|
+
context 'normal usage' do
|
106
|
+
Given(:source) { source_worker %w[better stronger faster] }
|
107
|
+
Given(:relay) do
|
108
|
+
relay_worker { |v| v.gsub(/t/, '+') }
|
109
|
+
end
|
110
|
+
|
111
|
+
When { source | relay }
|
112
|
+
|
113
|
+
Then { relay.product == 'be++er' }
|
114
|
+
And { relay.product == 's+ronger' }
|
115
|
+
And { relay.product == 'fas+er' }
|
116
|
+
And { relay.product.nil? }
|
117
|
+
end
|
118
|
+
|
119
|
+
context 'illegal usage' do
|
120
|
+
context 'arity == 0' do
|
121
|
+
Given(:invocation) do
|
122
|
+
-> { relay_worker() { :foo } }
|
123
|
+
end
|
124
|
+
|
125
|
+
Then { expect(invocation).to raise_error(/arity == 1/) }
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe '#side_worker' do
|
131
|
+
context 'normal usage' do
|
132
|
+
Given(:source) { source_worker (0..2) }
|
133
|
+
Given(:side_effect) { [] }
|
134
|
+
Given(:worker) do
|
135
|
+
side_effect
|
136
|
+
side_worker { |v| side_effect << v * 2 }
|
137
|
+
end
|
138
|
+
|
139
|
+
When { source | worker }
|
140
|
+
|
141
|
+
Then { worker.product == 0 }
|
142
|
+
And { worker.product == 1 }
|
143
|
+
And { worker.product == 2 }
|
144
|
+
And { worker.product.nil? }
|
145
|
+
And { side_effect == [0, 2, 4] }
|
146
|
+
end
|
147
|
+
|
148
|
+
context 'illegal usage' do
|
149
|
+
context 'arity == 0' do
|
150
|
+
Given(:invocation) do
|
151
|
+
-> { side_worker() { :foo } }
|
152
|
+
end
|
153
|
+
|
154
|
+
Then { expect(invocation).to raise_error(/arity == 1/) }
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe '#filter_worker' do
|
160
|
+
context 'normal usage' do
|
161
|
+
Given(:source) { source_worker (1..3) }
|
162
|
+
|
163
|
+
When { source | filter }
|
164
|
+
|
165
|
+
Given(:filter) do
|
166
|
+
filter_worker { |v| v % 2 == 0 }
|
167
|
+
end
|
168
|
+
|
169
|
+
Then { filter.product == 2 }
|
170
|
+
And { expect(filter.product).to be_nil }
|
171
|
+
end
|
172
|
+
|
173
|
+
context 'illegal usage' do
|
174
|
+
context 'requires a callable' do
|
175
|
+
context 'with no arguments or block' do
|
176
|
+
Given(:invocation) { -> { filter_worker } }
|
177
|
+
Then { expect(invocation).to raise_error(/supply a callable/) }
|
178
|
+
end
|
179
|
+
|
180
|
+
context 'with no callable' do
|
181
|
+
Given(:invocation) { -> { filter_worker :foo } }
|
182
|
+
Then { expect(invocation).to raise_error(/supply a callable/) }
|
183
|
+
end
|
184
|
+
|
185
|
+
context 'with callable arg' do
|
186
|
+
Given(:arg) { Proc.new { true } }
|
187
|
+
Given(:invocation) { -> { filter_worker arg } }
|
188
|
+
Then { expect(invocation).to_not raise_error }
|
189
|
+
end
|
190
|
+
|
191
|
+
context 'with both callable arg and block' do
|
192
|
+
Given(:arg) { Proc.new { true } }
|
193
|
+
Given(:invocation) { -> { filter_worker(arg) do false; end } }
|
194
|
+
Then { expect(invocation).to raise_error(/two callables/) }
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
describe '#batch_worker' do
|
201
|
+
Given(:source) { source_worker (0..7) }
|
202
|
+
|
203
|
+
context 'normal usage' do
|
204
|
+
When { source | worker }
|
205
|
+
|
206
|
+
context 'with specified "gathering" batch size' do
|
207
|
+
Given(:worker) do
|
208
|
+
batch_worker gathering: 3
|
209
|
+
end
|
210
|
+
|
211
|
+
Then { worker.product == [ 0, 1, 2 ] }
|
212
|
+
And { worker.product == [ 3, 4, 5 ] }
|
213
|
+
And { worker.product == [ 6, 7 ] }
|
214
|
+
And { worker.product.nil? }
|
215
|
+
end
|
216
|
+
|
217
|
+
context 'defaults to batch size of 1' do
|
218
|
+
Given(:source) { source_worker [8,9] }
|
219
|
+
Given(:worker) { batch_worker }
|
220
|
+
|
221
|
+
Then { worker.product == [ 8 ] }
|
222
|
+
And { worker.product == [ 9 ] }
|
223
|
+
And { worker.product.nil? }
|
224
|
+
end
|
225
|
+
|
226
|
+
context 'collects until condition' do
|
227
|
+
Given(:source) { source_worker (1..5) }
|
228
|
+
Given(:worker) do
|
229
|
+
batch_worker { |n| n % 2 == 0 }
|
230
|
+
end
|
231
|
+
|
232
|
+
Then { worker.product == [ 1, 2 ] }
|
233
|
+
And { worker.product == [ 3, 4 ] }
|
234
|
+
And { worker.product == [ 5 ] }
|
235
|
+
And { worker.product.nil? }
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
context 'illegal usage' do
|
240
|
+
context 'requires a callable' do
|
241
|
+
context 'with arity == 0' do
|
242
|
+
Given(:invocation) { -> { batch_worker() { :foo } } }
|
243
|
+
Then { expect(invocation).to raise_error(/arity == 1/) }
|
244
|
+
end
|
245
|
+
|
246
|
+
context 'with arity > 1' do
|
247
|
+
Given(:invocation) { -> { batch_worker() { |a,b| :foo } } }
|
248
|
+
Then { expect(invocation).to raise_error(/arity == 1/) }
|
249
|
+
end
|
250
|
+
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
end
|
256
|
+
end
|
@@ -1,9 +1,49 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require 'stepladder/worker'
|
3
2
|
|
4
3
|
module Stepladder
|
5
4
|
describe Worker do
|
6
|
-
it { should respond_to(:product, :supplier, :supplier
|
5
|
+
it { should respond_to(:product, :supplier, :supplier=, :"|") }
|
6
|
+
|
7
|
+
describe "readiness" do
|
8
|
+
context "with no supplier" do
|
9
|
+
context "with no task" do
|
10
|
+
it { should_not be_ready_to_work }
|
11
|
+
end
|
12
|
+
context "with a task which accepts a value" do
|
13
|
+
subject do
|
14
|
+
Worker.new { |value| value.to_s }
|
15
|
+
end
|
16
|
+
it { should_not be_ready_to_work }
|
17
|
+
end
|
18
|
+
context "with a task which doesn't accept a value" do
|
19
|
+
subject do
|
20
|
+
Worker.new { "foo" }
|
21
|
+
end
|
22
|
+
it { should be_ready_to_work }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "with a supplier" do
|
27
|
+
before do
|
28
|
+
subject.supplier = Worker.new { "foofoo" }
|
29
|
+
end
|
30
|
+
context "with no task" do
|
31
|
+
it { should_not be_ready_to_work }
|
32
|
+
end
|
33
|
+
context "with a task which accepts a value" do
|
34
|
+
subject do
|
35
|
+
Worker.new { |value| value.upcase }
|
36
|
+
end
|
37
|
+
it { should be_ready_to_work }
|
38
|
+
end
|
39
|
+
context "with a task which doesn't accept a value" do
|
40
|
+
subject do
|
41
|
+
Worker.new { "bar" }
|
42
|
+
end
|
43
|
+
it { should be_ready_to_work }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
7
47
|
|
8
48
|
describe "can accept a task" do
|
9
49
|
let(:result) { double }
|
@@ -47,7 +87,7 @@ module Stepladder
|
|
47
87
|
supplier.stub(:product).and_return(result)
|
48
88
|
subject.supplier = supplier
|
49
89
|
def subject.task(value)
|
50
|
-
|
90
|
+
Fiber.yield value
|
51
91
|
end
|
52
92
|
end
|
53
93
|
its(:product) { should be_copasetic }
|
@@ -74,13 +114,13 @@ module Stepladder
|
|
74
114
|
|
75
115
|
end
|
76
116
|
|
77
|
-
describe "= WORKER TYPES =" do
|
117
|
+
describe "= EXAMPLE WORKER TYPES =" do
|
78
118
|
|
79
119
|
let(:source_worker) do
|
80
120
|
Worker.new do
|
81
121
|
numbers = (1..3).to_a
|
82
|
-
|
83
|
-
|
122
|
+
while value = numbers.shift
|
123
|
+
Fiber.yield value
|
84
124
|
end
|
85
125
|
end
|
86
126
|
end
|
@@ -166,31 +206,29 @@ module Stepladder
|
|
166
206
|
end
|
167
207
|
end
|
168
208
|
|
169
|
-
|
170
|
-
let(:subscribing_worker) { relay_worker }
|
171
|
-
let(:pipeline) { source_worker | subscribing_worker }
|
209
|
+
end
|
172
210
|
|
173
|
-
|
211
|
+
describe "#|" do
|
212
|
+
Given(:source_worker) { Worker.new { :foo } }
|
213
|
+
Given(:subscribing_worker) { Worker.new { |v| "#{v}_bar".to_sym } }
|
174
214
|
|
175
|
-
|
176
|
-
subject.inspect
|
177
|
-
subscribing_worker.supplier.should == source_worker
|
178
|
-
end
|
179
|
-
end
|
215
|
+
When(:pipeline) { source_worker | subscribing_worker }
|
180
216
|
|
217
|
+
Then { subscribing_worker.supplier == source_worker }
|
218
|
+
Then { pipeline.product == :foo_bar }
|
219
|
+
Then { pipeline == subscribing_worker }
|
181
220
|
end
|
182
221
|
|
183
222
|
describe "#product" do
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
223
|
+
Given(:work_product) { :whatever }
|
224
|
+
Given { supplier.stub(:product).and_return(work_product) }
|
225
|
+
Given { subject.supplier = supplier }
|
226
|
+
Given(:supplier) { double }
|
227
|
+
|
228
|
+
context "resumes a fiber" do
|
229
|
+
Given { Fiber.any_instance.should_receive(:resume).and_return(work_product) }
|
190
230
|
|
191
|
-
|
192
|
-
Fiber.any_instance.should_receive(:resume).and_return(result)
|
193
|
-
subject.product.should == result
|
231
|
+
Then { subject.product }
|
194
232
|
end
|
195
233
|
end
|
196
234
|
|
data/spec/spec_helper.rb
CHANGED
@@ -1 +1,16 @@
|
|
1
|
+
require 'rspec/its'
|
2
|
+
require 'rspec/given'
|
3
|
+
require 'pry'
|
4
|
+
|
1
5
|
$LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')))
|
6
|
+
|
7
|
+
require 'stepladder'
|
8
|
+
|
9
|
+
RSpec.configure do |cfg|
|
10
|
+
cfg.expect_with :rspec do |c|
|
11
|
+
c.syntax = [:should, :expect]
|
12
|
+
end
|
13
|
+
cfg.mock_with :rspec do |c|
|
14
|
+
c.syntax = [:should, :expect]
|
15
|
+
end
|
16
|
+
end
|
data/stepladder.gemspec
CHANGED
@@ -17,9 +17,9 @@ Gem::Specification.new do |s|
|
|
17
17
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
18
|
s.require_paths = ["lib"]
|
19
19
|
|
20
|
-
s.add_development_dependency '
|
21
|
-
s.add_development_dependency '
|
22
|
-
s.add_development_dependency 'rspec'
|
23
|
-
s.add_development_dependency 'rspec-
|
24
|
-
s.add_development_dependency '
|
20
|
+
s.add_development_dependency 'rspec', '3.1.0'
|
21
|
+
s.add_development_dependency 'rspec-core', '3.1.2'
|
22
|
+
s.add_development_dependency 'rspec-its', '1.0.1'
|
23
|
+
s.add_development_dependency 'rspec-given', '3.8.0'
|
24
|
+
s.add_development_dependency 'pry'
|
25
25
|
end
|
metadata
CHANGED
@@ -1,141 +1,132 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stepladder
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
5
|
-
prerelease:
|
4
|
+
version: 0.2.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Joel Helbling
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2018-03-12 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
14
|
+
name: rspec
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - '='
|
20
18
|
- !ruby/object:Gem::Version
|
21
|
-
version:
|
19
|
+
version: 3.1.0
|
22
20
|
type: :development
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
|
-
- -
|
24
|
+
- - '='
|
28
25
|
- !ruby/object:Gem::Version
|
29
|
-
version:
|
26
|
+
version: 3.1.0
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
|
-
name:
|
28
|
+
name: rspec-core
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
|
-
- -
|
31
|
+
- - '='
|
36
32
|
- !ruby/object:Gem::Version
|
37
|
-
version:
|
33
|
+
version: 3.1.2
|
38
34
|
type: :development
|
39
35
|
prerelease: false
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
37
|
requirements:
|
43
|
-
- -
|
38
|
+
- - '='
|
44
39
|
- !ruby/object:Gem::Version
|
45
|
-
version:
|
40
|
+
version: 3.1.2
|
46
41
|
- !ruby/object:Gem::Dependency
|
47
|
-
name: rspec
|
42
|
+
name: rspec-its
|
48
43
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
44
|
requirements:
|
51
|
-
- -
|
45
|
+
- - '='
|
52
46
|
- !ruby/object:Gem::Version
|
53
|
-
version:
|
47
|
+
version: 1.0.1
|
54
48
|
type: :development
|
55
49
|
prerelease: false
|
56
50
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
51
|
requirements:
|
59
|
-
- -
|
52
|
+
- - '='
|
60
53
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
54
|
+
version: 1.0.1
|
62
55
|
- !ruby/object:Gem::Dependency
|
63
|
-
name: rspec-
|
56
|
+
name: rspec-given
|
64
57
|
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
58
|
requirements:
|
67
|
-
- -
|
59
|
+
- - '='
|
68
60
|
- !ruby/object:Gem::Version
|
69
|
-
version:
|
61
|
+
version: 3.8.0
|
70
62
|
type: :development
|
71
63
|
prerelease: false
|
72
64
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
65
|
requirements:
|
75
|
-
- -
|
66
|
+
- - '='
|
76
67
|
- !ruby/object:Gem::Version
|
77
|
-
version:
|
68
|
+
version: 3.8.0
|
78
69
|
- !ruby/object:Gem::Dependency
|
79
|
-
name:
|
70
|
+
name: pry
|
80
71
|
requirement: !ruby/object:Gem::Requirement
|
81
|
-
none: false
|
82
72
|
requirements:
|
83
|
-
- -
|
73
|
+
- - ">="
|
84
74
|
- !ruby/object:Gem::Version
|
85
75
|
version: '0'
|
86
76
|
type: :development
|
87
77
|
prerelease: false
|
88
78
|
version_requirements: !ruby/object:Gem::Requirement
|
89
|
-
none: false
|
90
79
|
requirements:
|
91
|
-
- -
|
80
|
+
- - ">="
|
92
81
|
- !ruby/object:Gem::Version
|
93
82
|
version: '0'
|
94
|
-
description:
|
95
|
-
Dave Thomas'
|
83
|
+
description: " Stepladder grew out of experimentation with Ruby fibers, after readings
|
84
|
+
Dave Thomas' demo of Ruby fibers, wherein he created a pipeline of fiber processes,
|
96
85
|
emulating the style and syntax of the *nix command line. I noticed that, courtesy
|
97
|
-
of fibers'
|
98
|
-
with extremely low coupling. That was the original motivation for creating the framework.
|
86
|
+
of fibers' extremely low surface area, fiber-to-fiber collaborators could operate
|
87
|
+
with extremely low coupling. That was the original motivation for creating the framework. "
|
99
88
|
email:
|
100
89
|
- joel@joelhelbling.com
|
101
90
|
executables: []
|
102
91
|
extensions: []
|
103
92
|
extra_rdoc_files: []
|
104
93
|
files:
|
105
|
-
- .gitignore
|
94
|
+
- ".gitignore"
|
95
|
+
- ".travis.yml"
|
106
96
|
- Gemfile
|
107
97
|
- README.md
|
108
98
|
- Rakefile
|
99
|
+
- docs/stepladder/worker.md
|
100
|
+
- lib/stepladder.rb
|
101
|
+
- lib/stepladder/dsl.rb
|
109
102
|
- lib/stepladder/version.rb
|
110
103
|
- lib/stepladder/worker.rb
|
111
104
|
- pkg/.gitkeep
|
105
|
+
- spec/lib/stepladder/dsl_spec.rb
|
112
106
|
- spec/lib/stepladder/worker_spec.rb
|
113
107
|
- spec/spec_helper.rb
|
114
108
|
- stepladder.gemspec
|
115
109
|
homepage: http://github.com/joelhelbling/stepladder
|
116
110
|
licenses: []
|
111
|
+
metadata: {}
|
117
112
|
post_install_message:
|
118
113
|
rdoc_options: []
|
119
114
|
require_paths:
|
120
115
|
- lib
|
121
116
|
required_ruby_version: !ruby/object:Gem::Requirement
|
122
|
-
none: false
|
123
117
|
requirements:
|
124
|
-
- -
|
118
|
+
- - ">="
|
125
119
|
- !ruby/object:Gem::Version
|
126
120
|
version: '0'
|
127
121
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
128
|
-
none: false
|
129
122
|
requirements:
|
130
|
-
- -
|
123
|
+
- - ">="
|
131
124
|
- !ruby/object:Gem::Version
|
132
125
|
version: '0'
|
133
126
|
requirements: []
|
134
127
|
rubyforge_project: stepladder
|
135
|
-
rubygems_version:
|
128
|
+
rubygems_version: 2.7.3
|
136
129
|
signing_key:
|
137
|
-
specification_version:
|
130
|
+
specification_version: 4
|
138
131
|
summary: A ruby-fibers-based framework aimed at extremely low coupling.
|
139
|
-
test_files:
|
140
|
-
- spec/lib/stepladder/worker_spec.rb
|
141
|
-
- spec/spec_helper.rb
|
132
|
+
test_files: []
|