stepladder 0.0.2 → 0.2.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.
- 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
|
+
[](https://travis-ci.org/joelhelbling/stepladder)
|
2
|
+
[](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: []
|