├── LICENSE ├── README.md ├── helpers ├── map.js ├── pull.js ├── random.js ├── sink.js ├── source.js └── take.js ├── package.json ├── solutions ├── 01.js ├── 02.js ├── 03.js ├── 04.js ├── 05.js ├── 06.js ├── 07.js ├── 08.js ├── 09.js ├── 10.js ├── 11.js └── 12.js └── verify.js /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 'Dominic Tarr' 2 | 3 | Permission is hereby granted, free of charge, 4 | to any person obtaining a copy of this software and 5 | associated documentation files (the "Software"), to 6 | deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom 10 | the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 20 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pull-stream-workshop 2 | 3 | `pull-streams` are a simple streaming pattern. 4 | You do not need to depend on a module to create a pull-stream, 5 | you just use functions in a certain way. 6 | 7 | pull-stream can do everything node streams can do, but they also 8 | propogate errors, and tidy themselves up after use. 9 | Node streams don't even do that! 10 | 11 | This workshop will first take you through the basic internals of pull-streams, 12 | you'll implement the main patterns in just a few lines. 13 | The official pull-stream module uses a few more to optimize, but not much more. 14 | 15 | Part 2 is a walk around the pull-stream ecosystem. We'll demonstrate some of the most useful 16 | pull-stream modules, to recreate some familiar UNIX tools, such as `ls`, `wc`, `grep`, `tail -f` and `less` 17 | 18 | Part 1 is probably more challenging, and because it needs to be exactly right, there are tests 19 | to check your implementation is correct. 20 | To run a test, run `node verify {exercise_number} {your_script.js}` 21 | 22 | There are no tests provided by Part 2, but you should run them on your 23 | local file system and see what they do. 24 | 25 | 26 | ## contents 27 | 28 | ### part 1 - introduction 29 | 30 | * sink stream to console.log 31 | 32 | * source stream of an array 33 | 34 | * map stream 35 | 36 | * pull (combine pull-streams) 37 | 38 | * take N elements from infinite source 39 | 40 | ### part 2 - unix 41 | 42 | * ls (pull-defer) 43 | 44 | * ls -l (..., pull-paramap 45 | 46 | * wc (stream-to-pull-stream, pull-split, pull.reduce) 47 | 48 | * grep (pull-file, pull.filter) 49 | 50 | * cat (pull-cat) 51 | 52 | * tail -f (pull-file) 53 | 54 | * less (pull-group) 55 | 56 | ### part 3 - challenges 57 | 58 | the following section is 59 | 60 | * trees: pull-traverse (recursively read all node_modules folders and output the current version) 61 | 62 | * fan in: pull-many (take many streams and read from each of them in parallel, as fast as possible) 63 | 64 | * fan out: split one stream into many, and read as fast as the slowest stream. 65 | 66 | * multiplexing: create a stream that multiplexes multiple streams through a single duplex stream. 67 | 68 | ## part 1 69 | 70 | ### exercise 1 - a logging sink 71 | 72 | A pull-stream is just an async function that is repeatedly called, 73 | until it says "stop!". 74 | 75 | Write a function that takes a callback, 76 | and returns a function that is passed a `read` function, 77 | and then calls it with a callback `cb(end, data)` 78 | and if `end` is truthy then stop. 79 | 80 | Otherwise, 81 | print out `data` with `console.log(data)`, 82 | and then read again. 83 | 84 | (`cb` must be in the second position. 85 | The first argument is `abort` 86 | but we will come back to that later.) 87 | 88 | to get a pull stream, use `helpers/random.js` 89 | 90 | you can start with this code: 91 | 92 | ``` js 93 | 94 | module.exports = function (cb) { 95 | return function (read) { 96 | //your pull-stream reader... 97 | read(null, function next (end, data) { 98 | ... 99 | 100 | }) 101 | } 102 | } 103 | ``` 104 | 105 | to test, run `node verify 1 exercise01.js` 106 | 107 | Congratulations, you have just written a pull-stream sink! 108 | 109 | --- 110 | 111 | 112 | ### exercise 2 - source an array 113 | 114 | a stream is like an array in time. 115 | write a function that takes an array, 116 | and returns an async function named `read` that calls back each item in the array in turn. 117 | 118 | ``` js 119 | module.exports = function (array) { 120 | return function read (abort, cb) { 121 | // read the next item 122 | } 123 | } 124 | 125 | ``` 126 | 127 | #### rules 128 | 129 | 1. the `read` function must take two arguments. `abort` and `cb`. 130 | you can ignore abort for now, but we will come back to it. 131 | 132 | 2. when there is no more data, callback `cb(true)` to indicate the end of the stream. 133 | 134 | when all the items are read, cb(true) to indicate the end of the stream. 135 | 136 | to run test `node verify.js 2 exercise02.js` 137 | 138 | --- 139 | 140 | transform streams represent _throughput_. 141 | 142 | a transform stream takes a source, and returns a sink. 143 | 144 | ### exersice 3 - map a source stream 145 | 146 | implement a stream that takes a map function, 147 | 148 | a sink stream is a function that takes a source stream and calls it. 149 | 150 | ``` js 151 | function sink (read) { 152 | ... 153 | } 154 | ``` 155 | 156 | a source stream is an async function to be called repeatedly. 157 | 158 | 159 | ``` js 160 | function source (abort, cb) { 161 | ... 162 | } 163 | ``` 164 | 165 | a _through_ stream is a sink stream that returns a source stream. 166 | 167 | 168 | ``` js 169 | function sink (read) { 170 | return function source (abort, cb) { 171 | ... 172 | } 173 | } 174 | ``` 175 | 176 | Implement a through stream that takes a map function, and applies it to the source stream... 177 | 178 | ``` js 179 | module.exports = function (map) { 180 | return function (read) { 181 | return function (abort, cb) { 182 | ... 183 | } 184 | } 185 | } 186 | ``` 187 | 188 | --- 189 | 190 | ### exercise 4 - pull it all together. 191 | 192 | a sink takes a source, you can just pass it directly. 193 | 194 | `sink(source)` and that is a valid way to connect a pull-stream. 195 | 196 | a map returns a source too, so you can connect a pipeline like that. 197 | 198 | `sink(map(source))` but that reads right to left, and we are used to left to write. 199 | 200 | implement a function, `pull()` that takes streams, and passes one to another. 201 | 202 | `pull(source, map, sink)` 203 | 204 | 205 | ``` js 206 | module.exports = function pull () { 207 | var args = [].slice.call(arguments) 208 | ... 209 | } 210 | ``` 211 | 212 | to test, run `node verify.js 4 exercise04.js` 213 | 214 | --- 215 | 216 | ### exercise 5 - take some items from a stream and then stop. 217 | 218 | sometimes we don't want to read the entire stream. 219 | 220 | when calling a source stream, the first argument is called `abort`. 221 | If that argument is true, it tells the stream to end. 222 | 223 | write a through stream that reads N items, then calls `read(true, cb)` instead, terminating the stream. 224 | 225 | ``` js 226 | module.exports = function (n) { 227 | return function (read) { 228 | return function (abort, cb) { 229 | //how many times called? 230 | read(abort, cb) 231 | } 232 | } 233 | } 234 | ``` 235 | 236 | to test `node verify.js 5 exercise05.js` 237 | 238 | this allows us to read from infinite streams! 239 | 240 | --- 241 | 242 | ## part 2 - unix tools 243 | 244 | ### exercise 6 - ls 245 | 246 | `ls` lists a directory. given a directory, it outputs the files in that directory. 247 | 248 | use node's fs module to read create a stream of filenames in a directory. 249 | 250 | create a function that returns a pull-stream: 251 | 252 | ``` js 253 | var pull = require('pull-stream') 254 | 255 | pull( 256 | LS(dir), 257 | pull.log() 258 | ) 259 | 260 | ``` 261 | 262 | use `fs.readdir` to read a directory (which takes a callback) 263 | but we also want to return a stream immediately. How to do this? 264 | one way is to use [pull-defer](https://www.npmjs.com/package/pull-defer) 265 | 266 | also see solution in solutions/06.js 267 | 268 | --- 269 | 270 | ### exercise 7 - list directory, long output. 271 | 272 | the `ls -l` option outputs extra information about files. 273 | this comes from the `fs.stat` system call. 274 | 275 | we want to do lots of async calls to `fs.stat` so lets do them in parallel. 276 | How do we do that? well, the answer to any pull-stream question is: there is a module for that! 277 | 278 | [pull-paramap](https://npm.im/pull-paramap) 279 | 280 | also see solution in solutions/07.js 281 | 282 | ### exercise 8 - word count 283 | 284 | the `wc` command counts characters, words, and lines in an input stream. 285 | 286 | split the input stream into lines, and then count them. (you'll need [pull-split](https://www.npmjs.com/package/pull-split)) 287 | output the total number of lines. (for extra points, also output characters and words) 288 | 289 | to convert standard input into a pull-stream, use [stream-to-pull-stream](https://www.npmjs.com/package/stream-to-pull-stream) 290 | 291 | ``` js 292 | var toPull = require('stream-to-pull-stream') 293 | var pull = require('pull-stream') 294 | 295 | pull( 296 | toPull.source(process.stdin), 297 | //your wc implementation 298 | ) 299 | ``` 300 | 301 | --- 302 | 303 | ### exercise 9 - grep 304 | 305 | `grep` _filters_ lines that patch patterns inside streams. 306 | 307 | take a string or regular expression as the first argument, read stdin and output lines 308 | that match the pattern. 309 | 310 | ``` 311 | SOURCE | node 09.js PATTERN 312 | ``` 313 | where `SOURCE` is a stream, i.e. `cat {file}` or the output of another streaming program. 314 | 315 | also see solution is solutions/09.js 316 | 317 | --- 318 | 319 | ### exercise 10 - concatenating streams 320 | 321 | the `cat` command takes multiple input streams and concatenates them into a single stream. 322 | 323 | `cat foo.txt bar.js baz.html` should concatenate those files. read each input with [pull-file](https://npm.im/pull-file), 324 | and output a stream that is all of each stream in sequence. 325 | 326 | hint: there is a pull-stream module that implements cat, but for a learning challenge, 327 | you may implement it yourself! 328 | 329 | --- 330 | 331 | ### exercise 11 - real time streams with `tail -f` 332 | 333 | with `tail -f` you can appends to an open file as they come. 334 | 335 | ``` 336 | > tail -f log 337 | ... 338 | ``` 339 | 340 | then in another terminal: 341 | 342 | ``` 343 | > date >> log 344 | > date >> log 345 | > date >> log 346 | ``` 347 | and see the date show up in the terminal in which `tail -f log` is running in. 348 | 349 | implement `tail -f` a pull-stream using [pull-file](https://www.npmjs.com/package/pull-file) 350 | 351 | 352 | --- 353 | 354 | ### exercise 12 - human powered back pressure 355 | 356 | `more` is a "pager". taking a long input file, it displays one page worth at a time, 357 | and no more until the next user input. so a human is able to read it. 358 | `more` is a way to get less data, it provides "back pressure". 359 | 360 | create a pull-stream that reads standard input, and outputs one page and no more, 361 | until the user presses a key (hint: read data on process.stdin) 362 | 363 | ``` js 364 | pull( 365 | File(process.argv[2]), 366 | //Read a N line page and output it when user reads more. 367 | Page(40), 368 | toPull.sink(process.stdout) 369 | ) 370 | ``` 371 | 372 | ## part 3 - extra challenges 373 | 374 | ### exercise 13 - traversing trees 375 | 376 | take your code for exercise 7, and extend it to read directories recursively. 377 | if a file is a node_modules directory, it should expand it, and stream inside of it. 378 | 379 | there are different ways you can traverse a tree, choose the way that seems most appropriate! 380 | 381 | see also [pull-traverse](https://npm.im/pull-traverse) and [pull-glob](https://npm.im/pull-glob) 382 | 383 | --- 384 | 385 | ### exercise 14 - fan in - read from many streams 386 | 387 | take an array of pull-streams, and read them into one stream. 388 | read the streams as fast as possible, but you must still respect back pressure. 389 | if the sink stops, you must wait, but if they are reading, give them the fastest stream that responds. 390 | 391 | also see [pull-many](https://npm.im/pull-many) 392 | 393 | --- 394 | 395 | ### exercise 15 - fan out - split one stream out to many 396 | 397 | the opposite of exercise 14, make one stream that expands out. 398 | 399 | write an essay about what makes this more difficult to implement, 400 | and are there situations where you genuinely need this? 401 | 402 | should a fan out stream go as fast as the slowest stream, or as fast as the fastest stream? 403 | 404 | If you read as the fastest stream, what happens to the data the slower stream was waiting for? 405 | 406 | --- 407 | 408 | ### exercise 16 - multiplexing 409 | 410 | pull-stream all the things! pull-streams are great! make all your apis pull-streams. 411 | but does that mean you need one connection per pull-stream? no, use multiplexing! 412 | 413 | [muxrpc](https://npm.im/muxrpc) allows you to expose many pull-stream apis over one tcp or web socket connection. 414 | 415 | but there is one problem, to my shame I got stumped when I tried to implement correct back pressure on it. 416 | 417 | Solve the problem of multiplexed back pressure and make a pull request to muxrpc. 418 | 419 | if you complete this exercise, you get an instant A, and are not required to complete the other tasks 420 | 421 | ## License 422 | 423 | MIT 424 | 425 | 426 | 427 | 428 | -------------------------------------------------------------------------------- /helpers/map.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape') 2 | var pull = require('pull-stream') 3 | 4 | module.exports = function (mapper) { 5 | 6 | tape('mapper returns source stream', function (t) { 7 | if(typeof mapper !== 'function') 8 | throw new Error('mapper should be a function') 9 | 10 | var map = mapper(function (e) { return e * -1 }) 11 | 12 | t.equal(typeof mapper, 'function', 'map returns a sink stream') 13 | t.equal(map.length, 1, 'mapper returns a sink stream - 1 arg') 14 | 15 | var source = function (abort, cb) {} 16 | var sink = map(source) 17 | t.equal(sink.length, 2, 'when given a source, mapper returns a sink') 18 | 19 | t.end() 20 | }) 21 | 22 | tape('pull through', function (t) { 23 | var map = mapper(function (e) { return e * -1 }) 24 | pull( 25 | pull.values([1,2,3]), 26 | map, 27 | pull.collect(function (err, ary) { 28 | t.notOk(err, 'should not error') 29 | if(err) throw err 30 | t.deepEqual(ary, [-1,-2,-3]) 31 | t.end() 32 | }) 33 | ) 34 | }) 35 | } 36 | 37 | -------------------------------------------------------------------------------- /helpers/pull.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape') 2 | var p = require('pull-stream') 3 | 4 | module.exports = function (pull) { 5 | 6 | console.error('check that pull passes the first stream to another') 7 | 8 | var A = function (abort, cb) {} 9 | var B = function (abort, cb) {} 10 | 11 | tape('connect the right streams', function (t) { 12 | 13 | var _B = 14 | pull( 15 | A, 16 | function (read) { 17 | if(read !== A) 18 | throw new Error('pull must pass the first stream, to the second...') 19 | return B 20 | } 21 | ) 22 | 23 | if(_B !== B) 24 | throw new Error('pull must return the last stream') 25 | 26 | t.end() 27 | }) 28 | 29 | tape('actually connect streams', function (t) { 30 | pull( 31 | p.values([1,2,3]), 32 | p.map(function (e) { return e }), 33 | p.collect(function (err, ary) { 34 | if(err) throw err 35 | t.deepEqual(ary, [1,2,3]) 36 | t.end() 37 | }) 38 | ) 39 | 40 | }) 41 | 42 | } 43 | 44 | 45 | -------------------------------------------------------------------------------- /helpers/random.js: -------------------------------------------------------------------------------- 1 | var source = require('./source') 2 | 3 | module.exports = function (n) { 4 | return source(function (abort, cb) { 5 | if(abort) cb(abort) 6 | else if(Math.random() < 0.1) 7 | cb(true) 8 | else cb(null, Math.random()) 9 | }) 10 | } 11 | 12 | -------------------------------------------------------------------------------- /helpers/sink.js: -------------------------------------------------------------------------------- 1 | 2 | var tape = require('tape') 3 | var pull = require('pull-stream') 4 | 5 | module.exports = function (Log) { 6 | tape('log sink is correct inteface', function (t) { 7 | t.equal(typeof Log, 'function', 'log is a function') 8 | var ended = false 9 | var sink = Log(function () { 10 | t.ok(ended) 11 | t.end() 12 | }) 13 | t.equal(typeof sink, 'function', 'log() returns a sink stream') 14 | t.equal(sink.length, 1, 'sink has a single argument') 15 | //test sink with a function that aborts immediately. 16 | sink(function (abort, cb) { 17 | t.equal(typeof cb, 'function', 'sink is called with a cb function') 18 | cb(ended = true) 19 | }) 20 | }) 21 | 22 | tape('sink source function', function (t) { 23 | var n = 0 24 | pull( 25 | pull.values([1,2,3]), 26 | pull.through(function (d) { 27 | n++ 28 | }), 29 | Log(function (err) { 30 | t.equal(n, 3) 31 | t.end() 32 | }) 33 | ) 34 | 35 | }) 36 | 37 | tape('waits for async call', function (t) { 38 | var n = 0, async = false 39 | pull( 40 | function (abort, cb) { 41 | if(async) throw new Error('did not wait until after read called back') 42 | async = true 43 | setTimeout(function () { 44 | async = false 45 | if(abort) cb(abort) 46 | else if(++n>3) cb(true) 47 | else cb(null, n) 48 | }, 10) 49 | }, 50 | Log(function (err) { 51 | t.equal(n, 4) 52 | t.end() 53 | }) 54 | ) 55 | 56 | 57 | }) 58 | 59 | } 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /helpers/source.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape') 2 | 3 | module.exports = function (Source) { 4 | 5 | tape('source is correct interface', function (t) { 6 | 7 | t.equal(typeof Source, 'function', 'Source is a function') 8 | var read = Source([1,2,3]) 9 | t.equal(typeof read, 'function', 'read is a source stream') 10 | t.equal(read.length, 2, 'source streams take 2 arguments') 11 | read(null, function (err, item) { 12 | t.notOk(err, 'did not cb error') 13 | t.equal(item, 1, 'returned first item') 14 | read(null, function (err, item) { 15 | t.notOk(err, 'did not cb error') 16 | t.equal(item, 2, 'returned first item') 17 | read(null, function (err, item) { 18 | t.notOk(err, 'did not cb error') 19 | t.equal(item, 3, 'returned first item') 20 | read(null, function (end, item) { 21 | t.equal(end, true, 'stream ended') 22 | t.end() 23 | }) 24 | }) 25 | }) 26 | }) 27 | }) 28 | 29 | } 30 | 31 | 32 | -------------------------------------------------------------------------------- /helpers/take.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape') 2 | var pull = require('pull-stream') 3 | 4 | module.exports = function (take) { 5 | 6 | tape('take 5 items from an infinite stream', function (t) { 7 | var aborted, n = 0 8 | pull( 9 | function (end, cb) { 10 | if(end) { 11 | t.ok(aborted = end, 'source stream is aborted') 12 | cb(end) 13 | } 14 | else cb(null, ++n) 15 | }, 16 | take(5), 17 | pull.collect(function (err, ary) { 18 | if(err) throw err 19 | t.ok(aborted) 20 | t.deepEqual(ary, [1,2,3,4,5]) 21 | t.end() 22 | }) 23 | ) 24 | }) 25 | 26 | } 27 | 28 | 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pull-stream-meditation-worshop", 3 | "description": "", 4 | "version": "0.0.0", 5 | "homepage": "https://github.com/dominictarr/pull-stream-meditation-worshop", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/dominictarr/pull-stream-meditation-worshop.git" 9 | }, 10 | "dependencies": {}, 11 | "devDependencies": { 12 | "pull-defer": "^0.2.2", 13 | "pull-file": "^1.0.0", 14 | "pull-glob": "^1.0.6", 15 | "pull-stream": "^3.4.5", 16 | "stream-to-pull-stream": "^1.7.2", 17 | "tape": "^4.6.0" 18 | }, 19 | "scripts": { 20 | "test": "set -e; for t in test/*.js; do node $t; done" 21 | }, 22 | "author": "'Dominic Tarr' (dominictarr.com)", 23 | "license": "MIT" 24 | } 25 | -------------------------------------------------------------------------------- /solutions/01.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (cb) { 3 | return function (read) { 4 | read(null, function more (end, data) { 5 | if(end) return cb() 6 | console.log(data) 7 | read(null, more) 8 | }) 9 | } 10 | } 11 | 12 | //to test: node verify.js 1 solutions/01.js 13 | if(!module.parent) 14 | module.exports(require('../helpers/random')()) 15 | -------------------------------------------------------------------------------- /solutions/02.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = function (array) { 4 | var i = 0 5 | return function (abort, cb) { 6 | if(abort) 7 | cb(abort) 8 | else if(i >= array.length) 9 | cb(true) 10 | else 11 | cb(null, array[i++]) 12 | } 13 | 14 | } 15 | 16 | -------------------------------------------------------------------------------- /solutions/03.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (map) { 3 | return function (read) { 4 | return function (abort, cb) { 5 | read(abort, function (end, data) { 6 | if(end) return cb(end) 7 | else cb(null, map(data)) 8 | }) 9 | } 10 | } 11 | } 12 | 13 | if(!module.parent) 14 | require('../helpers/map')(module.exports) 15 | -------------------------------------------------------------------------------- /solutions/04.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function () { 3 | var args = [].slice.call(arguments) 4 | 5 | var stream = args.shift() 6 | while(args.length) 7 | stream = args.shift()(stream) 8 | 9 | return stream 10 | } 11 | 12 | 13 | if(!module.parent) 14 | require('../helpers/pull')(module.exports) 15 | -------------------------------------------------------------------------------- /solutions/05.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (n) { 3 | return function (read) { 4 | return function (abort, cb) { 5 | //once we have read n items, abort the stream! 6 | read(n-- ? abort : true, cb) 7 | } 8 | } 9 | } 10 | 11 | //is your source stream compatible with abort? 12 | 13 | 14 | if(!module.parent) 15 | require('../helpers/take')(module.exports) 16 | -------------------------------------------------------------------------------- /solutions/06.js: -------------------------------------------------------------------------------- 1 | var defer = require('pull-defer') 2 | var pull = require('pull-stream') 3 | var fs = require('fs') 4 | 5 | module.exports = function ls (dir) { 6 | 7 | var source = defer.source() 8 | 9 | fs.readdir(dir, function (err, ls) { 10 | 11 | if(err) source.resolve(pull.error(err)) 12 | else source.resolve(pull.values(ls)) 13 | 14 | }) 15 | 16 | return source 17 | 18 | } 19 | 20 | if(!module.parent) { 21 | pull( 22 | module.exports(process.argv[2]), 23 | pull.log() 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /solutions/07.js: -------------------------------------------------------------------------------- 1 | 2 | var LS = require('./06') //load previous solution! 3 | var Paramap = require('pull-paramap') 4 | var pull = require('pull-stream') 5 | 6 | var fs = require('fs') 7 | var path = require('path') 8 | 9 | //implement ls -l (long listing format) 10 | function Ls_l (dir) { 11 | return pull( 12 | LS(dir), 13 | Paramap(function (file, cb) { 14 | var filename = path.join(dir, file) 15 | fs.stat(filename, function (err, stat) { 16 | if(err) return cb(err) 17 | stat.filename = filename 18 | cb(null, stat) 19 | }) 20 | }, 10) //10 things in parallel 21 | ) 22 | 23 | } 24 | 25 | pull( 26 | Ls_l(process.argv[2]), 27 | pull.log() 28 | ) 29 | 30 | -------------------------------------------------------------------------------- /solutions/08.js: -------------------------------------------------------------------------------- 1 | var toPull = require('stream-to-pull-stream') 2 | var Split = require('pull-split') 3 | var pull = require('pull-stream') 4 | 5 | pull( 6 | toPull.source(process.stdin), 7 | Split(), 8 | pull.reduce(function (acc, line) { 9 | //ignore empty lines 10 | return line ? acc + 1 : acc 11 | }, 0, function (err, acc) { 12 | console.log(acc) 13 | }) 14 | ) 15 | -------------------------------------------------------------------------------- /solutions/09.js: -------------------------------------------------------------------------------- 1 | 2 | var pull = require('pull-stream') 3 | var Split = require('pull-split') 4 | var toPull = require('stream-to-pull-stream') 5 | 6 | var pattern = new RegExp(process.argv[2]) 7 | 8 | pull( 9 | toPull.source(process.stdin), 10 | Split(), 11 | pull.filter(function (line) { 12 | return pattern.exec(line) 13 | }), 14 | pull.log() 15 | ) 16 | -------------------------------------------------------------------------------- /solutions/10.js: -------------------------------------------------------------------------------- 1 | 2 | var File = require('pull-file') 3 | var Cat = require('pull-cat') 4 | var toPull = require('stream-to-pull-stream') 5 | var pull = require('pull-stream') 6 | 7 | pull( 8 | Cat(process.argv.slice(2).map(File)), 9 | toPull.sink(process.stdout) 10 | ) 11 | -------------------------------------------------------------------------------- /solutions/11.js: -------------------------------------------------------------------------------- 1 | 2 | var File = require('pull-file') 3 | var pull = require('pull-stream') 4 | var toPull = require('stream-to-pull-stream') 5 | 6 | // easy! 7 | 8 | pull( 9 | File(process.argv[2], {live: true, encoding: 'utf8'}), 10 | toPull.sink(process.stdout) 11 | ) 12 | -------------------------------------------------------------------------------- /solutions/12.js: -------------------------------------------------------------------------------- 1 | var Split = require('pull-split') 2 | var File = require('pull-file') 3 | var toPull = require('stream-to-pull-stream') 4 | 5 | var pull = require('pull-stream') 6 | var Group = require('pull-group') 7 | 8 | function Next () { 9 | var ended 10 | var read_stdin = toPull.source(process.stdin) 11 | 12 | return function (read) { 13 | return function (abort, cb) { 14 | read_stdin(null, function () { 15 | read(abort, function (end, data) { 16 | if(end) 17 | read_stdin(true, function () { 18 | cb(end, data) 19 | }) 20 | else 21 | cb(end, data) 22 | }) 23 | }) 24 | } 25 | } 26 | } 27 | 28 | 29 | 30 | function Page (lines) { 31 | return pull( 32 | Split(), 33 | Group(40), 34 | pull.map(function (e) { 35 | return e.join('\n') 36 | }), 37 | Next() 38 | ) 39 | } 40 | 41 | 42 | pull( 43 | File(process.argv[2]), 44 | Page(40), 45 | toPull.sink(process.stdout) 46 | ) 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /verify.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var tests = [ 3 | require('./helpers/sink'), 4 | require('./helpers/source'), 5 | require('./helpers/map'), 6 | require('./helpers/pull'), 7 | require('./helpers/take') 8 | ] 9 | 10 | if(process.argv.length !== 4) { 11 | console.error('usage: verify.js [1-5] script.js') 12 | process.exit(1) 13 | } 14 | 15 | var i = +process.argv[2] - 1 16 | 17 | if(isNaN(i)) throw new Error('first argument must be integer, 1 - 5') 18 | if(!tests[i]) 19 | throw new Error('there are no tests for that exercise, expected') 20 | 21 | var test = tests[i] 22 | console.log('', i, test) 23 | console.log(path.resolve(process.argv[3])) 24 | test(require(path.resolve(process.argv[3]))) 25 | --------------------------------------------------------------------------------