Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Async #3

Merged
merged 5 commits into from
Oct 19, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/coverage/
/lib/
/node_modules/
/npm-debug.log
/npm-debug.*
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/coverage/
/src/
/test/
/.babelrc
/.editorconfig
/.eslintrc.yml
Expand Down
14 changes: 4 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,6 @@
[Gulp][Gulp link] plugin for applying arbitrary transformations to
the contents of files.

* **Simple**. Just pass a callback function that takes the current file
contents and returns the desired contents.
* **Flexible**. Receive file contents as a Buffer or a string. Compatible with
pipelines in both buffer mode and streaming mode.
* **Economical**. Reduce the need for gulp-specific plugins by pairing
gulp-transform with ordinary node packages and functions.

## Install

Install via [npm][NPM link]:
Expand Down Expand Up @@ -89,9 +82,10 @@ gulp.task('cheerio', function() {

##### transformFn `function`

The callback responsible for the transformation. The return value must be a
string or a Buffer, which will replace the file's contents. The callback
is invoked once per file with the following arguments:
The callback responsible for the transformation, whose return value will replace
the file's contents. The return value may be a string, a Buffer, or a Promise
resolvable to a string or Buffer. The callback is invoked once per file with the
following arguments:

* **contents** `Buffer` | `string` <br>
The initial contents of the file. Contents are passed as a Buffer unless the
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"gulp": "3.x"
},
"dependencies": {
"es6-promise": "^4.0.5",
"gulp-util": "^3.0.7",
"lodash": "^4.13.1"
},
Expand All @@ -49,10 +50,10 @@
"chai": "^3.5.0",
"coffee-script": "^1.10.0",
"coveralls": "^2.11.9",
"eslint": "^2.11.1",
"eslint": "^2.13.1",
"event-stream": "^3.3.2",
"istanbul": "1.0.0-alpha.2",
"mocha": "^2.5.3",
"istanbul": "1.1.0-alpha.1",
"mocha": "^3.1.2",
"rimraf": "^2.5.2",
"sinon": "^1.17.4",
"sinon-chai": "^2.8.0"
Expand Down
8 changes: 6 additions & 2 deletions src/file-stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ export class FileStream extends Transform {

_flush(done) {
let contents = Buffer.concat(this.data);
this.push(transform(this.fn, contents, this.file, this.opts));

done();
transform(this.fn, contents, this.file, this.opts).then((result) => {
this.push(result);
done();
}).catch((err) => {
done(err);
});
}

}
3 changes: 0 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,10 @@ import {err} from './err';
export default function gulpTransform(transformFn, options) {
if (isNil(transformFn)) {
err('transformFn must be defined');

} else if (!isFunction(transformFn)) {
err('transformFn must be a function');

} else if (!isNil(options) && !isObject(options)) {
err('options must be an object');

} else {
return new PluginStream(transformFn, options);
}
Expand Down
17 changes: 10 additions & 7 deletions src/plugin-stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@ export class PluginStream extends Transform {
let {fn, opts} = this;

if (file.isBuffer()) {
file.contents = transform(fn, file.contents, file, opts);
}

if (file.isStream()) {
transform(fn, file.contents, file, opts).then((result) => {
file.contents = result;
next(null, file);
}).catch((err) => {
next(err);
});
} else if (file.isStream()) {
file.contents = file.contents.pipe(new FileStream(fn, file, opts));
next(null, file);
} else {
next(null, file);
}

next(null, file);
}

}
23 changes: 16 additions & 7 deletions src/transform.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import {isBuffer, isString} from 'lodash';
import {Promise} from 'es6-promise';
import {isBuffer} from 'lodash';
import {err} from './err';

export function transform(fn, contents, file, opts) {
let encoded = opts.encoding ? contents.toString(opts.encoding) : contents;
let transformed = fn.call(opts.thisArg, encoded, file);
export function transform(fn, contents, file, {encoding, thisArg}) {
let decoded = encoding ? contents.toString(encoding) : contents;
let transformed = fn.call(thisArg, decoded, file);

return isBuffer(transformed) ? transformed :
isString(transformed) ? new Buffer(transformed) :
err('transformFn must return a string or a Buffer');
return Promise.resolve(transformed).then(toBuffer);
}

function toBuffer(contents) {
if (isBuffer(contents)) {
return contents;
} else if (contents != null) {
return new Buffer(String(contents));
} else {
err('transformFn may not return or resolve to null or undefined');
}
}
11 changes: 9 additions & 2 deletions test/fixtures/fn.coffee
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{Promise} = require 'es6-promise';
{spy} = require 'sinon'

re = /one|two|three/g
Expand All @@ -7,11 +8,17 @@ dict =
two: 'deux'
three: 'trois'

translate = (content) ->
content.replace(re, (match) -> dict[match])

exports.stringFn = ->
spy (content) ->
content.replace re, (match) ->
dict[match]
translate(content)

exports.bufferFn = ->
spy (content) ->
Buffer.concat([content, content])

exports.asyncFn = ->
spy (content) ->
return Promise.resolve(translate(content))
128 changes: 96 additions & 32 deletions test/index.coffee
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
chai = require 'chai'
sinonChai = require 'sinon-chai'
{match: {any, instanceOf}} = require 'sinon'
{File: {isVinyl}} = require 'gulp-util'
{File: {isVinyl}, PluginError} = require 'gulp-util'
{wait} = require 'event-stream'
{buffer, string} = require './fixtures/content'
{buffered, streaming} = require './fixtures/file'
{bufferFn, stringFn} = require './fixtures/fn'
{bufferFn, stringFn, asyncFn} = require './fixtures/fn'
err = require './helpers/err'
transform = require '../src'

Expand All @@ -30,10 +30,14 @@ describe 'plugin: gulp-transform', ->
it 'throws PluginError', ->
err -> transform 42

context 'returns neither a string nor a Buffer', ->
context 'returns null or undefined', ->

it 'throws PluginError', ->
err -> transform((content) -> 42).write buffered()
it 'emits PluginError', (done) ->
stream = transform((content) -> null)
stream.write buffered()
stream.on 'error', (err) ->
err.should.be.instanceOf PluginError
done()

context 'returns a Buffer or string', ->
[fn, file] = [null, null]
Expand All @@ -52,6 +56,23 @@ describe 'plugin: gulp-transform', ->
it 'is called with vinyl File as second argument', ->
fn.should.have.been.calledWith any, file

context 'returns a Promise that resolves to a string or Buffer', ->
[fn, file] = [null, null]

beforeEach ->
file = buffered()
fn = asyncFn()
transform(fn, {encoding: 'utf8'}).write(file)

it 'is called once per file', ->
fn.should.have.been.calledOnce

it 'is called with contents as first argument', ->
fn.should.have.been.calledWith string

it 'is called with vinyl File as second argument', ->
fn.should.have.been.calledWith any, file

describe 'param: options', ->

context 'not an object', ->
Expand Down Expand Up @@ -80,40 +101,83 @@ describe 'plugin: gulp-transform', ->
fn.should.have.been.calledOn undefined

describe 'mode: buffer', ->
file = null

beforeEach (done) ->
transform(bufferFn()).once('data', (_file) ->
file = _file
done()
).write buffered()
context 'synchronous', ->
file = null

beforeEach (done) ->
transform(bufferFn()).once('data', (_file) ->
file = _file
done()
).write buffered()

it 'returns a stream of vinyl Files', ->
isVinyl(file).should.be.true

it 'files are in buffer mode', ->
file.isBuffer().should.be.true;

it 'returns a stream of vinyl Files', ->
isVinyl(file).should.be.true
it 'transforms file contents', ->
file.contents.should.deep.equal Buffer.concat([buffer, buffer])

it 'files are in buffer mode', ->
file.isBuffer().should.be.true;
context 'async', ->
file = null

it 'transforms file contents', ->
file.contents.should.deep.equal Buffer.concat([buffer, buffer])
beforeEach (done) ->
transform(asyncFn(), {encoding: 'utf8'}).once('data', (_file) ->
file = _file
done()
).write buffered()

it 'returns a stream of vinyl Files', ->
isVinyl(file).should.be.true

it 'files are in buffer mode', ->
file.isBuffer().should.be.true;

it 'transforms file contents', ->
file.contents.should.deep.equal new Buffer('un deux trois')

describe 'mode: streaming', ->
file = null

beforeEach (done) ->
transform(stringFn(), {encoding: 'utf8'}).once('data', (_file) ->
file = _file
done()
).write streaming()
context 'synchronous', ->
file = null

it 'returns a stream of vinyl Files', ->
isVinyl(file).should.be.true
beforeEach (done) ->
transform(stringFn(), {encoding: 'utf8'}).once('data', (_file) ->
file = _file
done()
).write streaming()

it 'files are in streaming mode', ->
file.isStream().should.be.true
it 'returns a stream of vinyl Files', ->
isVinyl(file).should.be.true

it 'transforms file contents', (done) ->
file.pipe(wait((err, data) ->
data.should.deep.equal new Buffer('un deux trois')
done()
))
it 'files are in streaming mode', ->
file.isStream().should.be.true

it 'transforms file contents', (done) ->
file.pipe(wait((err, data) ->
data.should.deep.equal new Buffer('un deux trois')
done()
))

context 'async', ->
file = null

beforeEach (done) ->
transform(asyncFn(), {encoding: 'utf8'}).once('data', (_file) ->
file = _file
done()
).write streaming()

it 'returns a stream of vinyl Files', ->
isVinyl(file).should.be.true

it 'files are in streaming mode', ->
file.isStream().should.be.true

it 'transforms file contents', (done) ->
file.pipe(wait((err, data) ->
data.should.deep.equal new Buffer('un deux trois')
done()
))