Skip to content

Commit

Permalink
Merge branch 'release/1.0.3'
Browse files Browse the repository at this point in the history
  • Loading branch information
mcmath committed Jan 23, 2016
2 parents 6953a23 + e24a1e0 commit 5cf6d3a
Show file tree
Hide file tree
Showing 14 changed files with 242 additions and 107 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ node_js:
- "stable"
- "5.0"
- "4.0"
- "0.12"
- "0.10"
after_script:
- npm run coveralls
Expand Down
180 changes: 85 additions & 95 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@


[Gulp][Gulp link] plugin for applying arbitrary transformations to
the contents of files. Useful for incorporating non-gulp functions and modules
into your pipeline. Files may be in streaming mode or buffer mode.
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

Expand All @@ -19,12 +25,14 @@ Install via [npm][NPM link]:
npm install --save-dev gulp-transform
```

## Example
## Examples

#### Simple transformation

Source file (caesar.txt):
Source file:

```
I am constant as the northern star...
I am constant as the northern star.
```

Transform task:
Expand All @@ -33,106 +41,85 @@ Transform task:
const gulp = require('gulp');
const transform = require('gulp-transform');

// Repeat contents three times and prepend filename.
function transformFn(contents, file) {
return [file.path, contents, contents, contents].join('\n');
}

gulp.task('silly-task', function() {
return gulp.src('/path/to/src/**/*.txt')
.pipe(transform(transformFn)) // Apply transform.
.pipe(gulp.dest('/path/to/dest'));
gulp.task('quadruple', function() {
return gulp.src('src/*.txt')
.pipe(transform(function(contents) {
return Array(4).fill(contents).join('\n');
}))
.pipe(gulp.dest('dest'));
});
```

Destination file:

```
/path/to/src/caesar.txt
I am constant as the northern star...
I am constant as the northern star...
I am constant as the northern star...
I am constant as the northern star.
I am constant as the northern star.
I am constant as the northern star.
I am constant as the northern star.
```

#### Pairing with non-gulp packages

We can pair gulp-transform with vanilla node packages to reduce dependence on
gulp-specific plugins. In this example, we use gulp-transform with
[cheerio][Cheerio link] to add a `role="main"` attribute to our `<main>` tags,
thus ensuring accessibility in older versions of Internet Explorer.

```js
const gulp = require('gulp');
const transform = require('gulp-transform');
const cheerio = require('cheerio');


function transformFn(contents) {
var $ = cheerio.load(contents);
$('main').attr('role', 'main');
return $.html();
}

gulp.task('cheerio', function() {
return gulp.src('src/**/*.html')
.pipe(transform(transformFn, {encoding: 'utf8'}))
.pipe(gulp.dest('dest'));
});
```

## API

The package exports a single plugin function that takes a callback and an
optional options object. The callback is invoked once per file. The
contents of the file are passed to the callback and replaced by the return
value.

##### Usage

##### `transform(transformFn, [options])`

<table>
<thead>
<tr>
<th>
Param
</th>
<th align="center">
Type
</th>
<th>
Details
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
transformFn
</td>
<td align="center">
<code>Function</code>
</td>
<td>
Function that transforms the contents of each file. Invoked with the
following arguments:
<ul>
<li>
<strong>contents</strong> (<code>Buffer</code>|<code>String</code>) The contents of
the file. Will be a Buffer unless otherwise specified.
</li>
<li>
<strong>file</strong> (<code>File</code>)
The <a href="https://github.com/gulpjs/vinyl">vinyl file</a> object
whose contents are to be transformed. Useful for getting metadata
about the file, such as its name or path.
</li>
</ul>
The return value will replace the file's contents and must be either
a String or a Buffer.
</td>
</tr>
<tr>
<td>
options<br>(optional)
</td>
<td align="center">
<code>Object</code>
</td>
<td>
Options to modify the behavior of the plugin.
<ul>
<li>
<strong>encoding</strong> (<code>String</code>) Casts contents to
a string with the specified
<a href="https://nodejs.org/docs/latest/api/buffer.html#buffer_buffers_and_character_encodings">encoding</a>
before it is passed to transformFn. If no encoding is specified,
contents will be passed as a Buffer.
</li>
<li>
<strong>thisArg</strong> (<code>&#42;</code>) Determines the value of
<code>this</code> within transformFn. Defaults to
<code>undefined</code>.
</li>
</ul>
</td>
</tr>
</tbody>
</table>
#### `transform(transformFn, [options])`

##### 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:

* **contents** `Buffer` | `string` <br>
The initial contents of the file. Contents are passed as a Buffer unless the
`encoding` option is set, in which case they are passed as a string.
Contents are normalized to ensure a consistent API regardless of whether
files are in streaming or buffer mode.

* **file** `File` <br>
The [Vinyl file][Vinyl link] object to which the contents belong. Useful for
getting metadata about the file, such as its path. Making changes to
`file.contents` is not recommended, however, as these contents are not
normalized and will in any case be replaced by the return value of the callback.

##### options `object` (optional)

An object for configuring the behavior of the plugin. The following keys are
accepted as options:

* **encoding** `string` <br>
Casts contents to a string with the specified
[encoding][Encoding link] before it is passed to transformFn. If no encoding is
specified, contents will be passed as a Buffer.

* **thisArg** `*` <br>
Determines the value of `this` within transformFn. If omitted,
`this` will be undefined.

## License

Expand All @@ -149,3 +136,6 @@ Copyright &copy; 2016 Akim McMath. Licensed under the [MIT License][License link
[Coverage link]: https://coveralls.io/github/akim-mcmath/gulp-transform?branch=master
[Dependencies badge]: https://img.shields.io/gemnasium/akim-mcmath/gulp-transform.svg?style=flat-square
[Dependencies link]: https://gemnasium.com/akim-mcmath/gulp-transform
[Cheerio link]: https://www.npmjs.com/package/cheerio
[Vinyl link]: https://github.com/gulpjs/vinyl
[Encoding link]: https://nodejs.org/docs/latest/api/buffer.html#buffer_buffers_and_character_encodings
38 changes: 28 additions & 10 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ var PluginError = require('gulp-util').PluginError;

// Main plugin function.
module.exports = function gulpTransform(transformFn, options) {
if (!transformFn) {
err('transformFn must be defined.');
} else if (typeof transformFn !== 'function') {
err('transformFn must be a function.');
} else if (options && !/^(?:function|object)$/.test(typeof options)) {
err('options must be an object or undefined.');
if (isNone(transformFn)) {
throwPluginError('transformFn must be defined.');
} else if (!isFunction(transformFn)) {
throwPluginError('transformFn must be a function.');
} else if (!isNone(options) && !isObject(options)) {
throwPluginError('options must be an object if defined.');
} else {
return new PluginStream(transformFn, options);
}
Expand Down Expand Up @@ -82,16 +82,34 @@ function transform(fn, contents, file, opts) {
// Ensure transformFn returns a String or a Buffer and return as a Buffer.
if (Buffer.isBuffer(contents)) {
return contents;
} else if (typeof contents === 'string' || contents instanceof String) {
} else if (isString(contents)) {
return new Buffer(contents);
} else {
err('transformFn must return a string or a buffer.');
throwPluginError('transformFn must return a string or a buffer.');
}
}



// Throws Gulp PluginError with message.
function err(message) {
function throwPluginError(message) {
throw new PluginError('gulp-transform', message);
}



function isNone(value) {
return value === undefined || value === null;
}

function isFunction(value) {
return typeof value === 'function';
}

function isObject(value) {
var type = typeof value;
return type === 'object' || type === 'function';
}

function isString(value) {
return typeof value === 'string' || value instanceof String;
}
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "gulp-transform",
"version": "1.0.2",
"version": "1.0.3",
"description": "Gulp plugin for performing arbitrary transformations on the contents of files.",
"main": "index.js",
"files": [
Expand Down Expand Up @@ -40,10 +40,13 @@
},
"devDependencies": {
"chai": "^3.4.1",
"cheerio": "^0.19.0",
"coveralls": "^2.11.6",
"event-stream": "^3.3.2",
"gulp": "^3.9.0",
"istanbul": "^0.4.1",
"mocha": "^2.3.4",
"rimraf": "^2.5.1",
"sinon": "^1.17.2",
"sinon-chai": "^2.8.0"
}
Expand Down
46 changes: 46 additions & 0 deletions test/examples.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use strict';

var fs = require('fs');
var resolve = require('path').resolve;
var exec = require('child_process').exec;
var expect = require('chai').use(require('sinon-chai')).expect;
var gulp = require('gulp');
var rimraf = require('rimraf');
var paths = require('./fixtures/paths');



describe('readme:examples', function() {

describe('example:simple', function() {

before(function(done) {
require('./fixtures/tasks/quadruple');
gulp.start('quadruple', done);
});

it('repeats contents four times and joins with newline', function() {
var expected = fs.readFileSync(resolve(paths.EXP, 'caesar.txt'), 'utf8');
var contents = fs.readFileSync(resolve(paths.DEST, 'caesar.txt'), 'utf8');
expect(contents).to.equal(expected);
});
});

describe('example:cheerio', function() {

before(function(done) {
require('./fixtures/tasks/cheerio');
gulp.start('cheerio', done);
});

it('adds role="main" to <main> tag', function() {
var expected = fs.readFileSync(resolve(paths.EXP, 'index.html'), 'utf8');
var contents = fs.readFileSync(resolve(paths.DEST, 'index.html'), 'utf8');
expect(contents).to.equal(expected);
});
});

after(function(done) {
rimraf(paths.DEST, done);
});
});
4 changes: 4 additions & 0 deletions test/fixtures/expected/caesar.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
I am constant as the northern star...
I am constant as the northern star...
I am constant as the northern star...
I am constant as the northern star...
12 changes: 12 additions & 0 deletions test/fixtures/expected/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example HTML</title>
</head>
<body>
<main role="main">
<h1>Primary content</h1>
</main>
</body>
</html>
File renamed without changes.
8 changes: 8 additions & 0 deletions test/fixtures/paths.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'use strict';

var resolve = require('path').resolve;


exports.SRC = resolve(__dirname, 'src');
exports.DEST = resolve(__dirname, 'dest');
exports.EXP = resolve(__dirname, 'expected');
1 change: 1 addition & 0 deletions test/fixtures/src/caesar.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
I am constant as the northern star...
12 changes: 12 additions & 0 deletions test/fixtures/src/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example HTML</title>
</head>
<body>
<main>
<h1>Primary content</h1>
</main>
</body>
</html>
Loading

0 comments on commit 5cf6d3a

Please sign in to comment.