diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b4458e1 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = tab +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[test/fixtures/*] +insert_final_newline = false +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2125666 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d99bfce --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +node_modules/ +temp/ diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..a9ccf70 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,21 @@ +{ + "node": true, + "esnext": true, + "bitwise": true, + "camelcase": true, + "curly": true, + "eqeqeq": true, + "immed": true, + "indent": 4, + "latedef": true, + "newcap": true, + "noarg": true, + "quotmark": "double", + "regexp": true, + "undef": true, + "unused": true, + "strict": true, + "trailing": true, + "smarttabs": true, + "white": true +} diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..7b5db11 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: node_js +node_js: + - '0.8' + - '0.10' +before_install: + - currentfolder=${PWD##*/} + - if [ "$currentfolder" != 'generator-gulp-plugin' ]; then cd .. && eval "mv $currentfolder generator-gulp-plugin" && cd generator-gulp-plugin; fi diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8323845 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright 2014 Vsevolod Strukchinsky + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..59a2de1 --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# gulp-plumber [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][depstat-image]][depstat-url] + +> A plumber for [gulp](https://github.com/wearefractal/gulp) + +This plugin is fixing [issue with Node Streams piping](https://github.com/gulpjs/gulp/issues/91). + +For explanations, read [this small article](https://gist.github.com/floatdrop/8269868). + +## Usage + +First, install `gulp-plumber` as a development dependency: + +```shell +npm install --save gulp-plumber +``` + +Then, add it to your `gulpfile.js`: + +```javascript +var plumber = require('gulp-plumber'); +var coffee = require('gulp-coffee'); + +gulp.src('./src/*.ext') + .pipe(plumber()) + .pipe(coffee()) + .pipe(gulp.dest("./dist")); +``` + +## API + +### plumber(options) + +Returns Stream, that fixes `pipe` methods on Streams that are next in pipeline. + +## License + +[MIT License](http://en.wikipedia.org/wiki/MIT_License) + +[npm-url]: https://npmjs.org/package/gulp-plumber +[npm-image]: https://badge.fury.io/js/gulp-plumber.png + +[travis-url]: http://travis-ci.org/floatdrop/gulp-plumber +[travis-image]: https://secure.travis-ci.org/floatdrop/gulp-plumber.png?branch=master + +[depstat-url]: https://david-dm.org/floatdrop/gulp-plumber +[depstat-image]: https://david-dm.org/floatdrop/gulp-plumber.png diff --git a/index.js b/index.js new file mode 100644 index 0000000..20942ab --- /dev/null +++ b/index.js @@ -0,0 +1,109 @@ +'use strict'; + +var PassThrough = require('stream').PassThrough; + +module.exports = function (param) { + + var through = new PassThrough(); + + through.pipe = pipe2; + + function preventDefaultErrorHandler(stream) { + stream.listeners('error').forEach(function(item) { + if(item.name == 'onerror') this.removeListener('error', item); + }, stream); + } + + through.on('pipe', function (source) { + preventDefaultErrorHandler(source); + }; + + function pipe2(dest, options) { + var source = this; + + function ondata(chunk) { + if (dest.writable) { + if (false === dest.write(chunk) && source.pause) { + source.pause(); + } + } + } + + source.on('data', ondata); + + function ondrain() { + if (source.readable && source.resume) { + source.resume(); + } + } + + dest.on('drain', ondrain); + + // If the 'end' option is not supplied, dest.end() will be called when + // source gets the 'end' or 'close' events. Only dest.end() once. + if (!dest._isStdio && (!options || options.end !== false)) { + source.on('end', onend); + source.on('close', onclose); + } + + var didOnEnd = false; + function onend() { + if (didOnEnd) return; + didOnEnd = true; + + dest.end(); + } + + + function onclose() { + if (didOnEnd) return; + didOnEnd = true; + + if (util.isFunction(dest.destroy)) dest.destroy(); + } + + // don't leave dangling pipes when there are errors. + function onerror(er) { + if (EE.listenerCount(this, 'error') === 0) { + cleanup(); + throw er; // Unhandled stream error in pipe. + } + } + + source.on('error', onerror); + dest.on('error', onerror); + + // remove all the event listeners that were added. + function cleanup() { + source.removeListener('data', ondata); + dest.removeListener('drain', ondrain); + + source.removeListener('end', onend); + source.removeListener('close', onclose); + + source.removeListener('error', onerror); + dest.removeListener('error', onerror); + + source.removeListener('end', cleanup); + source.removeListener('close', cleanup); + + dest.removeListener('close', cleanup); + } + + source.on('end', cleanup); + source.on('close', cleanup); + + dest.on('close', cleanup); + + dest.emit('pipe', source); + + // Allow for unix-like usage: A.pipe(B).pipe(C) + + dest.pipe = pipe2; + + return dest; + }; + + return through; + +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..082fb60 --- /dev/null +++ b/package.json @@ -0,0 +1,39 @@ + +{ + "name": "gulp-plumber", + "version": "0.0.0", + "description": "A plumber, for gulp pipeline", + "keywords": [ + "gulpplugin" + ], + "homepage": "https://github.com/floatdrop/gulp-plumber", + "bugs": "https://github.com/floatdrop/gulp-plumber/issues", + "author": { + "name": "Vsevolod Strukchinsky", + "email": "floatdrop@gmail.com", + "url": "https://github.com/floatdrop" + }, + "main": "./index.js", + "repository": { + "type": "git", + "url": "git://github.com/floatdrop/gulp-plumber.git" + }, + "scripts": { + "test": "mocha" + }, + "dependencies": {}, + "devDependencies": { + "mocha": "~1.14.0", + "should": "~2.1.0", + "gulp-util": "~2.2.0" + }, + "engines": { + "node": ">=0.10", + "npm": ">=1.2.10" + }, + "licenses": [ + { + "type": "MIT" + } + ] +} diff --git a/test/expected/hello.txt b/test/expected/hello.txt new file mode 100644 index 0000000..ed81d07 --- /dev/null +++ b/test/expected/hello.txt @@ -0,0 +1,2 @@ +Hello +World \ No newline at end of file diff --git a/test/fixtures/hello.txt b/test/fixtures/hello.txt new file mode 100644 index 0000000..5ab2f8a --- /dev/null +++ b/test/fixtures/hello.txt @@ -0,0 +1 @@ +Hello \ No newline at end of file diff --git a/test/main.js b/test/main.js new file mode 100644 index 0000000..ff330a3 --- /dev/null +++ b/test/main.js @@ -0,0 +1,109 @@ +/*global describe, it*/ +"use strict"; + +var fs = require("fs"), + es = require("event-stream"), + should = require("should"); +require("mocha"); + +var gutil = require("gulp-util"), + plumber = require("../"); + +describe("gulp-plumber", function () { + + var expectedFile = new gutil.File({ + path: "test/expected/hello.txt", + cwd: "test/", + base: "test/expected", + contents: fs.readFileSync("test/expected/hello.txt") + }); + + it("should produce expected file via buffer", function (done) { + + var srcFile = new gutil.File({ + path: "test/fixtures/hello.txt", + cwd: "test/", + base: "test/fixtures", + contents: fs.readFileSync("test/fixtures/hello.txt") + }); + + var stream = plumber("World"); + + stream.on("error", function(err) { + should.exist(err); + done(err); + }); + + stream.on("data", function (newFile) { + + should.exist(newFile); + should.exist(newFile.contents); + + String(newFile.contents).should.equal(String(expectedFile.contents)); + done(); + }); + + stream.write(srcFile); + stream.end(); + }); + + it("should error on stream", function (done) { + + var srcFile = new gutil.File({ + path: "test/fixtures/hello.txt", + cwd: "test/", + base: "test/fixtures", + contents: fs.createReadStream("test/fixtures/hello.txt") + }); + + var stream = plumber("World"); + + stream.on("error", function(err) { + should.exist(err); + done(); + }); + + stream.on("data", function (newFile) { + newFile.contents.pipe(es.wait(function(err, data) { + done(err); + })); + }); + + stream.write(srcFile); + stream.end(); + }); + + /* + it("should produce expected file via stream", function (done) { + + var srcFile = new gutil.File({ + path: "test/fixtures/hello.txt", + cwd: "test/", + base: "test/fixtures", + contents: fs.createReadStream("test/fixtures/hello.txt") + }); + + var stream = plumber("World"); + + stream.on("error", function(err) { + should.exist(err); + done(); + }); + + stream.on("data", function (newFile) { + + should.exist(newFile); + should.exist(newFile.contents); + + newFile.contents.pipe(es.wait(function(err, data) { + should.not.exist(err); + data.should.equal(String(expectedFile.contents)); + done(); + })); + }); + + stream.write(srcFile); + stream.end(); + }); + */ +});