├── .jshintignore ├── .travis.yml ├── .gitignore ├── .npmignore ├── .jshintrc ├── package.json ├── LICENSE ├── test ├── condition.js ├── ternary.js └── index.js ├── index.js ├── README.md └── img ├── condition.svg └── ternary.svg /.jshintignore: -------------------------------------------------------------------------------- 1 | node_modules/** 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | - "10" 5 | - "12" 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | node_modules 4 | build 5 | *.node 6 | components 7 | *.orig 8 | .idea 9 | temp.txt* 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | node_modules 4 | build 5 | *.node 6 | components 7 | *.orig 8 | .idea 9 | temp.txt* 10 | test 11 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "camelcase": true, 4 | "curly": true, 5 | "eqeqeq": true, 6 | "forin": true, 7 | "immed": true, 8 | "latedef": true, 9 | "newcap": true, 10 | "noarg": true, 11 | "noempty": true, 12 | "nonew": true, 13 | "regexp": true, 14 | "strict": true, 15 | "trailing": true, 16 | "undef": true, 17 | "unused": true, 18 | "node": true 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ternary-stream", 3 | "description": "Fork stream based on passed condition, and collect down-stream", 4 | "version": "3.0.0", 5 | "homepage": "https://github.com/robrich/ternary-stream", 6 | "repository": "git://github.com/robrich/ternary-stream.git", 7 | "author": "Rob Richardson (http://robrich.org/)", 8 | "main": "./index.js", 9 | "keywords": [ 10 | "conditional", 11 | "if", 12 | "ternary", 13 | "stream" 14 | ], 15 | "dependencies": { 16 | "duplexify": "^4.1.1", 17 | "fork-stream": "^0.0.4", 18 | "merge-stream": "^2.0.0", 19 | "through2": "^3.0.1" 20 | }, 21 | "devDependencies": { 22 | "jshint": "^2.9.4", 23 | "mocha": "^6.1.4", 24 | "should": "^13.2.3" 25 | }, 26 | "scripts": { 27 | "test": "mocha && jshint ." 28 | }, 29 | "license": "MIT" 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 [Richardson & Sons, LLC](http://richardsonandsons.com/) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /test/condition.js: -------------------------------------------------------------------------------- 1 | /*global describe:false, it:false */ 2 | 3 | 'use strict'; 4 | 5 | var ternaryStream = require('../'); 6 | 7 | var through = require('through2'); 8 | require('should'); 9 | 10 | describe('ternary-stream', function() { 11 | describe('condition', function() { 12 | 13 | function runTest(answer, expected, done) { 14 | // arrange 15 | var called = 0; 16 | var theData = {the:'data'}; 17 | 18 | var condition = function (data) { 19 | data.should.equal(theData); 20 | called++; 21 | return answer; 22 | }; 23 | 24 | var childStream = through.obj(function (data, enc, cb) { 25 | data.should.equal(theData); 26 | called+=10; 27 | this.push(data); 28 | cb(); 29 | }); 30 | 31 | // act 32 | var s = ternaryStream(condition, childStream); 33 | 34 | s.once('data', function (data) { 35 | data.should.equal(theData); 36 | called+=100; 37 | }); 38 | 39 | // assert 40 | s.once('end', function(){ 41 | 42 | // Test that command executed 43 | called.should.equal(expected); 44 | done(); 45 | }); 46 | 47 | // act 48 | s.write(theData); 49 | s.end(); 50 | } 51 | 52 | it('should call the function when passed truthy', function(done) { 53 | // arrange 54 | var answer = true; 55 | 56 | // act, assert 57 | runTest(answer, 111, done); 58 | }); 59 | 60 | it('should not call the function when passed falsey', function(done) { 61 | // arrange 62 | var answer = false; 63 | 64 | // act, assert 65 | runTest(answer, 101, done); 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /test/ternary.js: -------------------------------------------------------------------------------- 1 | /*global describe:false, it:false */ 2 | 3 | 'use strict'; 4 | 5 | var ternaryStream = require('../'); 6 | 7 | var through = require('through2'); 8 | require('should'); 9 | 10 | describe('ternary-stream', function() { 11 | describe('ternary,', function() { 12 | 13 | function runTest(answer, expected, done) { 14 | // arrange 15 | var called = 0; 16 | var theData = {the:'data'}; 17 | 18 | var condition = function (data) { 19 | data.should.equal(theData); 20 | called++; 21 | return answer; 22 | }; 23 | 24 | var trueStream = through.obj(function (data, enc, cb) { 25 | data.should.equal(theData); 26 | called+=10; 27 | this.push(data); 28 | cb(); 29 | }); 30 | var falseStream = through.obj(function (data, enc, cb) { 31 | data.should.equal(theData); 32 | called+=20; 33 | this.push(data); 34 | cb(); 35 | }); 36 | 37 | // act 38 | var s = ternaryStream(condition, trueStream, falseStream); 39 | 40 | s.once('data', function (data) { 41 | data.should.equal(theData); 42 | called+=100; 43 | }); 44 | 45 | // assert 46 | s.once('end', function(){ 47 | 48 | // Test that command executed 49 | called.should.equal(expected); 50 | done(); 51 | }); 52 | 53 | // act 54 | s.write(theData); 55 | s.end(); 56 | } 57 | 58 | it('should call the function when passed truthy', function(done) { 59 | // arrange 60 | var answer = true; 61 | 62 | // act, assert 63 | runTest(answer, 111, done); 64 | }); 65 | 66 | it('should not call the function when passed falsey', function(done) { 67 | // arrange 68 | var answer = false; 69 | 70 | // act, assert 71 | runTest(answer, 121, done); 72 | }); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var through2 = require('through2'); 4 | var ForkStream = require('fork-stream'); 5 | var mergeStream = require('merge-stream'); 6 | var duplexify = require('duplexify'); 7 | 8 | module.exports = function (condition, trueStream, falseStream) { 9 | if (!trueStream) { 10 | throw new Error('fork-stream: child action is required'); 11 | } 12 | 13 | // output stream 14 | var outStream = through2.obj(); 15 | 16 | // create fork-stream 17 | var forkStream = new ForkStream({ 18 | classifier: function (e, cb) { 19 | var ans = !!condition(e); 20 | return cb(null, ans); 21 | } 22 | }); 23 | 24 | // if condition is true, pipe input to trueStream 25 | forkStream.a.pipe(trueStream); 26 | 27 | var mergedStream; 28 | 29 | if (falseStream) { 30 | // if there's an 'else' condition 31 | // if condition is false 32 | // pipe input to falseStream 33 | forkStream.b.pipe(falseStream); 34 | // merge output with trueStream's output 35 | mergedStream = mergeStream(falseStream, trueStream); 36 | // redirect falseStream errors to mergedStream 37 | falseStream.on('error', function(err) { mergedStream.emit('error', err); }); 38 | } else { 39 | // if there's no 'else' condition 40 | // if condition is false 41 | // merge output with trueStream's output 42 | mergedStream = mergeStream(forkStream.b, trueStream); 43 | } 44 | 45 | // redirect trueStream errors to mergedStream 46 | trueStream.on('error', function(err) { mergedStream.emit('error', err); }); 47 | 48 | // send everything down-stream 49 | mergedStream.pipe(outStream); 50 | // redirect mergedStream errors to outStream 51 | mergedStream.on('error', function(err) { outStream.emit('error', err); }); 52 | 53 | // consumers write in to forkStream, we write out to outStream 54 | return duplexify.obj(forkStream, outStream); 55 | }; 56 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | /*global describe:false, it:false */ 2 | 3 | 'use strict'; 4 | 5 | var ternaryStream = require('../'); 6 | 7 | var through = require('through2'); 8 | var should = require('should'); 9 | 10 | describe('ternary-stream', function() { 11 | describe('smoke test', function() { 12 | 13 | it('should call the function when passed truthy', function(done) { 14 | // arrange 15 | var called = 0; 16 | 17 | var condition = function (data) { 18 | return data.answer; 19 | }; 20 | 21 | var childStream = through.obj(function (data, enc, cb) { 22 | called++; 23 | this.push(data); 24 | cb(); 25 | }); 26 | 27 | // act 28 | var s = ternaryStream(condition, childStream); 29 | 30 | s.on('data', function (/*data*/) { 31 | called += 10; 32 | }); 33 | 34 | // assert 35 | s.once('finish', function () { 36 | 37 | // Test that command executed 38 | called.should.equal(21); 39 | done(); 40 | }); 41 | 42 | // act 43 | s.write({answer:true}); 44 | s.write({answer:false}); 45 | s.end(); 46 | }); 47 | 48 | it('should properly redirect trueStream errors', function(done) { 49 | // arrange 50 | var called = 0; 51 | 52 | var condition = function (data) { 53 | return data.answer; 54 | }; 55 | 56 | var trueStream = through.obj(function (data, enc, cb) { 57 | called++; 58 | this.emit('error', new Error('foo')); 59 | cb(); 60 | }); 61 | 62 | // act 63 | var s = ternaryStream(condition, trueStream); 64 | 65 | s.on('error', function (/*data*/) { 66 | done(); 67 | }); 68 | 69 | // act 70 | s.write({answer:true}); 71 | s.write({answer:false}); 72 | s.end(); 73 | }); 74 | 75 | it('should properly redirect falseStream errors', function(done) { 76 | // arrange 77 | var called = 0; 78 | 79 | var condition = function (data) { 80 | return data.answer; 81 | }; 82 | 83 | var falseStream = through.obj(function (data, enc, cb) { 84 | called++; 85 | this.emit('error', new Error('foo')); 86 | cb(); 87 | }); 88 | 89 | // act 90 | var s = ternaryStream(condition, through.obj(), falseStream); 91 | 92 | s.on('error', function (/*data*/) { 93 | done(); 94 | }); 95 | 96 | // act 97 | s.write({answer:true}); 98 | s.write({answer:false}); 99 | s.end(); 100 | }); 101 | 102 | it('should error if no parameters passed', function(done) { 103 | // arrange 104 | var caughtErr; 105 | 106 | // act 107 | try { 108 | ternaryStream(); 109 | } catch (err) { 110 | caughtErr = err; 111 | } 112 | 113 | // assert 114 | should.exist(caughtErr); 115 | caughtErr.message.indexOf('required').should.be.above(-1); 116 | done(); 117 | }); 118 | 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ternary-stream ![status](https://secure.travis-ci.org/robrich/ternary-stream.png?branch=master) 2 | ======= 3 | 4 | A ternary stream: conditionally control the flow of stream data 5 | 6 | ## Usage 7 | 8 | 1: Conditionally filter content 9 | 10 | **Condition** 11 | 12 | ![][condition] 13 | 14 | if the condition returns truthy, data is piped to the child stream 15 | 16 | ```js 17 | var ternaryStream = require('ternary-stream'); 18 | 19 | var condition = function (data) { 20 | return true; 21 | }; 22 | 23 | process.stdin 24 | .pipe(ternaryStream(condition, process.stdout)) 25 | .pipe(fs.createWriteStream('./out.txt')); 26 | ``` 27 | 28 | Data will conditionally go to stdout, and always go to the file 29 | 30 | 2: Ternary stream 31 | 32 | **Ternary** 33 | 34 | ![][ternary] 35 | 36 | 37 | ```javascript 38 | var ternaryStream = require('ternary-stream'); 39 | var through2 = require('through2'); 40 | 41 | var count = 0; 42 | var condition = function (data) { 43 | count++; 44 | return count % 2; 45 | }; 46 | 47 | process.stdin 48 | .pipe(ternaryStream(condition, fs.createWriteStream('./truthy.txt'), fs.createWriteStream('./falsey.txt'))) 49 | .pipe(process.stdout); 50 | ``` 51 | 52 | Data will either go to truthy.txt (if condition is true) or falsey.txt (if condition is false) and will always go to stdout 53 | 54 | ## API 55 | 56 | ### ternaryStream(condition, stream [, elseStream]) 57 | 58 | ternary-stream will pipe data to `stream` whenever `condition` is truthy. 59 | 60 | If `condition` is falsey and `elseStream` is passed, data will pipe to `elseStream`. 61 | 62 | After data is piped to `stream` or `elseStream` or neither, data is piped down-stream. 63 | 64 | #### Parameters 65 | 66 | ##### condition 67 | 68 | Type: `function`: takes in stream data and returns `boolean` 69 | 70 | ```js 71 | function (data) { 72 | return true; // or false 73 | } 74 | ``` 75 | 76 | ##### stream 77 | 78 | Stream for ternary-stream to pipe data into when condition is truthy. 79 | 80 | ##### elseStream 81 | 82 | Optional, Stream for ternary-stream to pipe data into when condition is falsey. 83 | 84 | 85 | LICENSE 86 | ------- 87 | 88 | (MIT License) 89 | 90 | Copyright (c) 2014 [Richardson & Sons, LLC](http://richardsonandsons.com/) 91 | 92 | Permission is hereby granted, free of charge, to any person obtaining 93 | a copy of this software and associated documentation files (the 94 | "Software"), to deal in the Software without restriction, including 95 | without limitation the rights to use, copy, modify, merge, publish, 96 | distribute, sublicense, and/or sell copies of the Software, and to 97 | permit persons to whom the Software is furnished to do so, subject to 98 | the following conditions: 99 | 100 | The above copyright notice and this permission notice shall be 101 | included in all copies or substantial portions of the Software. 102 | 103 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 104 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 105 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 106 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 107 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 108 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 109 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 110 | 111 | [condition]: https://rawgithub.com/robrich/ternary-stream/master/img/condition.svg 112 | [ternary]: https://rawgithub.com/robrich/ternary-stream/master/img/ternary.svg 113 | -------------------------------------------------------------------------------- /img/condition.svg: -------------------------------------------------------------------------------- 1 | noinputcondition()is truthyyesstreamoutput -------------------------------------------------------------------------------- /img/ternary.svg: -------------------------------------------------------------------------------- 1 | noinputcondition()is truthyyesstreamelse streamoutput --------------------------------------------------------------------------------