initial commit

This commit is contained in:
tmshv committed 2014-02-12 12:36:59 +04:00
commit d3fcd7f10c
3159 files changed
+349702

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
.DS_Store
*.log
node_modules
build
*.node
components
+4
View File
@@ -0,0 +1,4 @@
language: node_js
node_js:
- "0.11"
- "0.10"
Generated Vendored Executable
+20
View File
@@ -0,0 +1,20 @@
Copyright (c) 2013 Jonathan Pollack <jonathaenpollack@gmail.com>, Cloudlabs Inc.
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.
+68
View File
@@ -0,0 +1,68 @@
[![Build Status](https://travis-ci.org/jonathanepollack/gulp-minify-css.png?branch=master)](https://travis-ci.org/jonathanepollack/gulp-minify-css)
## Information
<table>
<tr>
<td>Package</td><td>gulp-minify-css</td>
</tr>
<tr>
<td>Description</td>
<td>Minify css with <a href="https://github.com/GoalSmashers/clean-css">clean-css.</a></td>
</tr>
<tr>
<td>Node Version</td>
<td>>= 0.10</td>
</tr>
</table>
## Installion
```
npm install --save-dev gulp-minify-css
```
## Usage
```js
var minifyCSS = require('gulp-minify-css');
gulp.task('minify-css', function() {
gulp.src('./static/css/*.css')
.pipe(minifyCSS(opts))
.pipe(gulp.dest('./dist/'))
});
```
### Options
* `keepSpecialComments` - `*` for keeping all (default), `1` for keeping first one, `0` for removing all
* `keepBreaks` - whether to keep line breaks (default is false)
* `removeEmpty` - whether to remove empty elements (default is false)
* `debug` - turns on debug mode measuring time spent on cleaning up
(run `npm run bench` to see example)
* `root` - path with which to resolve absolute `@import` rules
* `relativeTo` - path with which to resolve relative `@import` rules
## LICENSE
(MIT License)
Copyright (c) 2013 Jonathan Pollack (<jonathanepollack@gmail.com>), Cloublabs Inc.
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.
+52
View File
@@ -0,0 +1,52 @@
var es = require('event-stream'),
CleanCSS = require('clean-css'),
clone = require('clone'),
BufferStreams = require('bufferstreams'),
gutil = require('gulp-util');
// File level transform function
function minifyCSSTransform(opt) {
// Return a callback function handling the buffered content
return function(err, buf, cb) {
// Handle any error
if(err) cb(gutil.PluginError('minify-css', err));
// Use the buffered content
buf = Buffer(new CleanCSS(opt).minify(String(buf)));
// Bring it back to streams
cb(null, buf);
};
}
// Plugin function
function minifyCSSGulp(opt){
if (!opt) opt = {};
function modifyContents(file, cb){
if(file.isNull()) return cb(null, file);
if(file.isStream()) {
file.contents = file.contents.pipe(new BufferStreams(minifyCSSTransform(opt)));
return cb(null, file);
}
var newFile = clone(file);
var newContents = new CleanCSS(opt).minify(String(newFile.contents));
newFile.contents = new Buffer(newContents);
cb(null, newFile);
}
return es.map(modifyContents);
}
// Export the file level transform function for other plugins usage
minifyCSSGulp.fileTransform = minifyCSSTransform;
// Export the plugin main function
module.exports = minifyCSSGulp;
+1
View File
@@ -0,0 +1 @@
../clean-css/bin/cleancss
+5
View File
@@ -0,0 +1,5 @@
*~
*.swp
node_modules/
.git
tests/results
+9
View File
@@ -0,0 +1,9 @@
language: node_js
node_js:
- 0.10
before_script:
- npm install --dev
script:
- npm test
+21
View File
@@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2013 Nicolas Froidure, <http://insertafter.com/>
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.
+42
View File
@@ -0,0 +1,42 @@
# BufferStreams [![NPM version](https://badge.fury.io/js/bufferstreams.png)](https://npmjs.org/package/bufferstreams) [![Build Status](https://travis-ci.org/nfroidure/BufferStreams.png?branch=master)](https://travis-ci.org/nfroidure/BufferStreams)
BufferStreams abstracts streams to allow you to deal with the whole contents in
a single buffer when it becomes necessary (by example: a legacy library that
do not support streams).
It is not a good practice, just some glue. Using BufferStreams means:
* there is no library dealing with streams for your needs
* you filled an issue to the wrapped library to support streams
## Usage
Install the [npm module](https://npmjs.org/package/bufferstreams):
```sh
npm install bufferstreams --save
```
Then, in your scripts:
```js
var BufferStreams = require('bufferstreams');
Fs.createReadStream('input.txt')
.pipe(new BufferStreams(function(err, buf, cb) {
// err will be filled with an error if the piped in stream emits one.
if(err) {
throw err;
}
// buf will contain the whole piped in stream contents
buf = Buffer(buf.toString(utf-8).repalce('foo', 'bar'));
// cb is a callback to pass the result back to the piped out stream
// first argument is an error that wil be emitted if one
// the second argument is the modified buffer
cb(null, buf);
}))
.pipe(Fs.createWriteStream('output.txt'));
```
## Contributing
Feel free to pull your code if you agree with publishing it under the MIT license.
+45
View File
@@ -0,0 +1,45 @@
{
"name": "bufferstreams",
"version": "0.0.1",
"description": "Abstract streams to deal with the whole buffered contents.",
"homepage": "https://github.com/nfroidure/BufferStreams",
"main": "src/index.js",
"scripts": {
"test": "mocha tests/*.mocha.js"
},
"repository": {
"type": "git",
"url": "git://github.com/nfroidure/BufferStreams"
},
"keywords": [
"buffer",
"streaming",
"stream",
"async",
"abstract"
],
"devDependencies": {
"mocha": "1.x.x",
"event-stream": "1.x.x"
},
"author": {
"name": "Nicolas Froidure",
"url": "http://www.insertafter.com/blog.html"
},
"engines": {
"node": ">= 0.10.0"
},
"licenses": [
{
"type": "MIT",
"url": "https://github.com/nfroidure/BufferStreams/blob/master/LICENSE"
}
],
"bugs": {
"url": "https://github.com/nfroidure/BufferStreams/issues"
},
"readme": "# BufferStreams [![NPM version](https://badge.fury.io/js/bufferstreams.png)](https://npmjs.org/package/bufferstreams) [![Build Status](https://travis-ci.org/nfroidure/BufferStreams.png?branch=master)](https://travis-ci.org/nfroidure/BufferStreams)\n\nBufferStreams abstracts streams to allow you to deal with the whole contents in\n a single buffer when it becomes necessary (by example: a legacy library that\n do not support streams).\n\nIt is not a good practice, just some glue. Using BufferStreams means:\n* there is no library dealing with streams for your needs\n* you filled an issue to the wrapped library to support streams\n\n## Usage\nInstall the [npm module](https://npmjs.org/package/bufferstreams):\n```sh\nnpm install bufferstreams --save\n```\nThen, in your scripts:\n```js\nvar BufferStreams = require('bufferstreams');\n\nFs.createReadStream('input.txt')\n .pipe(new BufferStreams(function(err, buf, cb) {\n\n // err will be filled with an error if the piped in stream emits one.\n if(err) {\n throw err;\n }\n\n // buf will contain the whole piped in stream contents\n buf = Buffer(buf.toString(utf-8).repalce('foo', 'bar'));\n\n // cb is a callback to pass the result back to the piped out stream\n // first argument is an error that wil be emitted if one\n // the second argument is the modified buffer\n cb(null, buf);\n\n }))\n .pipe(Fs.createWriteStream('output.txt'));\n```\n\n## Contributing\nFeel free to pull your code if you agree with publishing it under the MIT license.\n\n",
"readmeFilename": "README.md",
"_id": "bufferstreams@0.0.1",
"_from": "bufferstreams@0.0.1"
}
+45
View File
@@ -0,0 +1,45 @@
var PassThrough = require('stream').PassThrough;
var util = require('util');
// Inherit of PassThrough stream
util.inherits(BufferStream, PassThrough);
// Constructor
function BufferStream(cb) {
// Ensure new were used
if (!(this instanceof BufferStream)) {
throw Error('Please use the "new" operator to instanciate a BufferStream.');
}
// Parent constructor
PassThrough.call(this);
// Keep a reference to the callback
this._cb = cb;
// Internal buffer
this._buf = Buffer('');
}
BufferStream.prototype._transform = function(chunk, encoding, done) {
this._buf = Buffer.concat([this._buf, chunk], this._buf.length + chunk.length);
done();
};
BufferStream.prototype._flush = function(done) {
var _that = this;
this._cb(null, this._buf, function(err, buf) {
if (buf && buf.length) {
_that.push(buf);
}
done();
});
};
module.exports = BufferStream;
+111
View File
@@ -0,0 +1,111 @@
var assert = require('assert')
, es = require('event-stream')
, BufferStream = require('../src')
;
// Helpers
function syncBufferPrefixer(headerText) {
return new BufferStream(function(err, buf, cb) {
assert.equal(err, null);
if(null === buf) {
cb(null, Buffer(headerText));
} else {
cb(null, Buffer.concat([Buffer(headerText), buf]));
}
});
}
function asyncBufferPrefixer(headerText) {
return new BufferStream(function(err, buf, cb) {
assert.equal(err, null);
if(null === buf) {
setTimeout(function() {
cb(null, Buffer(headerText));
}, 0);
} else {
setTimeout(function() {
cb(null, Buffer.concat([Buffer(headerText), buf]));
}, 0);
}
});
}
// Tests
describe('Abstract buffers', function() {
describe('synchonously', function() {
it('should work with one pipe', function(done) {
es.readArray(['te', 'st'])
.pipe(syncBufferPrefixer('plop'))
.pipe(es.wait(function(err, data) {
assert.equal(err, null);
assert.equal(data, 'ploptest');
done();
}));
});
it('should work when returning a null buffer', function(done) {
es.readArray(['te', 'st'])
.pipe(new BufferStream(function(err, buf, cb){
cb(null, null);
}))
.pipe(es.wait(function(err, data) {
assert.equal(err, null);
assert.equal(data, '');
done();
}));
});
it('should work with multiple pipes', function(done) {
es.readArray(['te', 'st'])
.pipe(syncBufferPrefixer('plop'))
.pipe(syncBufferPrefixer('plip'))
.pipe(syncBufferPrefixer('plap'))
.pipe(es.wait(function(err, data) {
assert.equal(err, null);
assert.equal(data, 'plapplipploptest');
done();
}));
});
});
describe('asynchonously', function() {
it('should work with one pipe', function(done) {
es.readArray(['te', 'st'])
.pipe(asyncBufferPrefixer('plop'))
.pipe(es.wait(function(err, data) {
assert.equal(err, null);
assert.equal(data, 'ploptest');
done();
}));
});
it('should work when returning a null buffer', function(done) {
es.readArray(['te', 'st'])
.pipe(new BufferStream(function(err, buf, cb){
cb(null, null);
}))
.pipe(es.wait(function(err, data) {
assert.equal(err, null);
assert.equal(data, '');
done();
}));
});
it('should work with multiple pipes', function(done) {
es.readArray(['te', 'st'])
.pipe(asyncBufferPrefixer('plop'))
.pipe(asyncBufferPrefixer('plip'))
.pipe(asyncBufferPrefixer('plap'))
.pipe(es.wait(function(err, data) {
assert.equal(err, null);
assert.equal(data, 'plapplipploptest');
done();
}));
});
});
});
+379
View File
@@ -0,0 +1,379 @@
[2.0.8 / 2014-02-07](https://github.com/GoalSmashers/clean-css/compare/v2.0.7...v2.0.8)
==================
* Fixed issue [#232](https://github.com/GoalSmashers/clean-css/issues/232) - edge case in non-adjacent selectors merging.
[2.0.7 / 2014-01-16](https://github.com/GoalSmashers/clean-css/compare/v2.0.6...v2.0.7)
==================
* Fixed issue [#208](https://github.com/GoalSmashers/clean-css/issues/208) - don't swallow @page and @viewport.
[2.0.6 / 2014-01-04](https://github.com/GoalSmashers/clean-css/compare/v2.0.5...v2.0.6)
==================
* Fixed issue [#198](https://github.com/GoalSmashers/clean-css/issues/198) - process comments and `@import`s correctly.
* Fixed issue [#205](https://github.com/GoalSmashers/clean-css/issues/205) - freeze on broken @import declaration.
[2.0.5 / 2014-01-03](https://github.com/GoalSmashers/clean-css/compare/v2.0.4...v2.0.5)
==================
* Fixed issue [#199](https://github.com/GoalSmashers/clean-css/issues/199) - keep line breaks with no advanced optimizations.
* Fixed issue [#203](https://github.com/GoalSmashers/clean-css/issues/203) - Buffer as a first argument to minify method.
[2.0.4 / 2013-12-19](https://github.com/GoalSmashers/clean-css/compare/v2.0.3...v2.0.4)
==================
* Fixed issue [#193](https://github.com/GoalSmashers/clean-css/issues/193) - HSL color space normalization.
[2.0.3 / 2013-12-18](https://github.com/GoalSmashers/clean-css/compare/v2.0.2...v2.0.3)
==================
* Fixed issue [#191](https://github.com/GoalSmashers/clean-css/issues/191) - leading numbers in font/animation names.
* Fixed issue [#192](https://github.com/GoalSmashers/clean-css/issues/192) - many `@import`s inside a comment.
[2.0.2 / 2013-11-18](https://github.com/GoalSmashers/clean-css/compare/v2.0.1...v2.0.2)
==================
* Fixed issue [#177](https://github.com/GoalSmashers/clean-css/issues/177) - process broken content correctly.
[2.0.1 / 2013-11-14](https://github.com/GoalSmashers/clean-css/compare/v2.0.0...v2.0.1)
==================
* Fixed issue [#176](https://github.com/GoalSmashers/clean-css/issues/176) - hangs on `undefined` keyword.
[2.0.0 / 2013-11-04](https://github.com/GoalSmashers/clean-css/compare/v1.1.7...v2.0.0)
==================
* Adds simplified and more advanced text escaping / restoring via `EscapeStore` class.
* Adds simplified and much faster empty elements removal.
* Adds missing `@import` processing to our benchmark (run via `npm run bench`).
* Adds CSS tokenizer which will make it possible to optimize content by reordering and/or merging selectors.
* Adds basic optimizer removing duplicate selectors from a list.
* Adds merging duplicate properties within a single selector's body.
* Adds merging adjacent selectors within a scope (single and multiple ones).
* Changes behavior of `--keep-line-breaks`/`keepBreaks` option to keep breaks after trailing braces only.
* Makes all multiple selectors ordered alphabetically (aids merging).
* Adds property overriding so more coarse properties override more granular ones.
* Adds reducing non-adjacent selectors.
* Adds `--skip-advanced`/`noAdvanced` switch to disable advanced optimizations.
* Adds reducing non-adjacent selectors when overridden by more complex selectors.
* Fixed issue [#138](https://github.com/GoalSmashers/clean-css/issues/138) - makes CleanCSS interface OO.
* Fixed issue [#139](https://github.com/GoalSmashers/clean-css/issues/138) - consistent error & warning handling.
* Fixed issue [#145](https://github.com/GoalSmashers/clean-css/issues/145) - debug mode in library too.
* Fixed issue [#157](https://github.com/GoalSmashers/clean-css/issues/157) - gets rid of `removeEmpty` option.
* Fixed issue [#159](https://github.com/GoalSmashers/clean-css/issues/159) - escaped quotes inside content.
* Fixed issue [#162](https://github.com/GoalSmashers/clean-css/issues/162) - strip quotes from Base64 encoded URLs.
* Fixed issue [#166](https://github.com/GoalSmashers/clean-css/issues/166) - `debug` formatting in CLI
* Fixed issue [#167](https://github.com/GoalSmashers/clean-css/issues/167) - `background:transparent` minification.
[1.1.7 / 2013-10-28](https://github.com/GoalSmashers/clean-css/compare/v1.1.6...v1.1.7)
==================
* Fixed issue [#156](https://github.com/GoalSmashers/clean-css/issues/156) - `@import`s inside comments.
[1.1.6 / 2013-10-26](https://github.com/GoalSmashers/clean-css/compare/v1.1.5...v1.1.6)
==================
* Fixed issue [#155](https://github.com/GoalSmashers/clean-css/issues/155) - broken irregular CSS content.
[1.1.5 / 2013-10-24](https://github.com/GoalSmashers/clean-css/compare/v1.1.4...v1.1.5)
==================
* Fixed issue [#153](https://github.com/GoalSmashers/clean-css/issues/153) - keepSpecialComments 0/1 as a string.
[1.1.4 / 2013-10-23](https://github.com/GoalSmashers/clean-css/compare/v1.1.3...v1.1.4)
==================
* Fixed issue [#152](https://github.com/GoalSmashers/clean-css/issues/152) - adds an option to disable rebasing.
[1.1.3 / 2013-10-04](https://github.com/GoalSmashers/clean-css/compare/v1.1.2...v1.1.3)
==================
* Fixed issue [#150](https://github.com/GoalSmashers/clean-css/issues/150) - minifying `background:none`.
[1.1.2 / 2013-09-29](https://github.com/GoalSmashers/clean-css/compare/v1.1.1...v1.1.2)
==================
* Fixed issue [#149](https://github.com/GoalSmashers/clean-css/issues/149) - shorthand font property.
[1.1.1 / 2013-09-07](https://github.com/GoalSmashers/clean-css/compare/v1.1.0...v1.1.1)
==================
* Fixed issue [#144](https://github.com/GoalSmashers/clean-css/issues/144) - skip URLs rebasing by default.
[1.1.0 / 2013-09-06](https://github.com/GoalSmashers/clean-css/compare/v1.0.12...v1.1.0)
==================
* Renamed lib's `debug` option to `benchmark` when doing per-minification benchmarking.
* Added simplified comments processing & imports.
* Fixed issue [#43](https://github.com/GoalSmashers/clean-css/issues/43) - `--debug` switch for minification stats.
* Fixed issue [#65](https://github.com/GoalSmashers/clean-css/issues/65) - full color name / hex shortening.
* Fixed issue [#84](https://github.com/GoalSmashers/clean-css/issues/84) - support for `@import` with media queries.
* Fixed issue [#124](https://github.com/GoalSmashers/clean-css/issues/124) - raise error on broken imports.
* Fixed issue [#126](https://github.com/GoalSmashers/clean-css/issues/126) - proper CSS expressions handling.
* Fixed issue [#129](https://github.com/GoalSmashers/clean-css/issues/129) - rebasing imported URLs.
* Fixed issue [#130](https://github.com/GoalSmashers/clean-css/issues/130) - better code modularity.
* Fixed issue [#135](https://github.com/GoalSmashers/clean-css/issues/135) - require node.js 0.8+.
[1.0.12 / 2013-07-19](https://github.com/GoalSmashers/clean-css/compare/v1.0.11...v1.0.12)
===================
* Fixed issue [#121](https://github.com/GoalSmashers/clean-css/issues/121) - ability to skip `@import` processing.
[1.0.11 / 2013-07-08](https://github.com/GoalSmashers/clean-css/compare/v1.0.10...v1.0.11)
===================
* Fixed issue [#117](https://github.com/GoalSmashers/clean-css/issues/117) - line break escaping in comments.
[1.0.10 / 2013-06-13](https://github.com/GoalSmashers/clean-css/compare/v1.0.9...v1.0.10)
===================
* Fixed issue [#114](https://github.com/GoalSmashers/clean-css/issues/114) - comments in imported stylesheets.
[1.0.9 / 2013-06-11](https://github.com/GoalSmashers/clean-css/compare/v1.0.8...v1.0.9)
==================
* Fixed issue [#113](https://github.com/GoalSmashers/clean-css/issues/113) - `@import` in comments.
[1.0.8 / 2013-06-10](https://github.com/GoalSmashers/clean-css/compare/v1.0.7...v1.0.8)
==================
* Fixed issue [#112](https://github.com/GoalSmashers/clean-css/issues/112) - reducing `box-shadow` zeros.
[1.0.7 / 2013-06-05](https://github.com/GoalSmashers/clean-css/compare/v1.0.6...v1.0.7)
==================
* Support for `@import` URLs starting with `//`.
By [@petetak](https://github.com/petetak).
[1.0.6 / 2013-06-04](https://github.com/GoalSmashers/clean-css/compare/v1.0.5...v1.0.6)
==================
* Fixed issue [#110](https://github.com/GoalSmashers/clean-css/issues/110) - data URIs in URLs.
[1.0.5 / 2013-05-26](https://github.com/GoalSmashers/clean-css/compare/v1.0.4...v1.0.5)
==================
* Fixed issue [#107](https://github.com/GoalSmashers/clean-css/issues/107) - data URIs in imported stylesheets.
1.0.4 / 2013-05-23
==================
* Rewrite relative URLs in imported stylesheets.
By [@bluej100](https://github.com/bluej100).
1.0.3 / 2013-05-20
==================
* Support alternative `@import` syntax with file name not wrapped inside `url()` statement.
By [@bluej100](https://github.com/bluej100).
1.0.2 / 2013-04-29
==================
* Fixed issue [#97](https://github.com/GoalSmashers/clean-css/issues/97) - `--remove-empty` & FontAwesome.
1.0.1 / 2013-04-08
==================
* Do not pick up `bench` and `test` while building `npm` package.
By [@sindresorhus](https://https://github.com/sindresorhus).
1.0.0 / 2013-03-30
==================
* Fixed issue [#2](https://github.com/GoalSmashers/clean-css/issues/2) - resolving `@import` rules.
* Fixed issue [#44](https://github.com/GoalSmashers/clean-css/issues/44) - examples in `--help`.
* Fixed issue [#46](https://github.com/GoalSmashers/clean-css/issues/46) - preserving special characters in URLs and attributes.
* Fixed issue [#80](https://github.com/GoalSmashers/clean-css/issues/80) - quotation in multi line strings.
* Fixed issue [#83](https://github.com/GoalSmashers/clean-css/issues/83) - HSL to hex color conversions.
* Fixed issue [#86](https://github.com/GoalSmashers/clean-css/issues/86) - broken `@charset` replacing.
* Fixed issue [#88](https://github.com/GoalSmashers/clean-css/issues/88) - removes space in `! important`.
* Fixed issue [#92](https://github.com/GoalSmashers/clean-css/issues/92) - uppercase hex to short versions.
0.10.2 / 2013-03-19
===================
* Fixed issue [#79](https://github.com/GoalSmashers/clean-css/issues/79) - node.js 0.10.x compatibility.
0.10.1 / 2013-02-14
===================
* Fixed issue [#66](https://github.com/GoalSmashers/clean-css/issues/66) - line breaks without extra spaces should
be handled correctly.
0.10.0 / 2013-02-09
===================
* Switched from [optimist](https://github.com/substack/node-optimist) to
[commander](https://github.com/visionmedia/commander.js) for CLI processing.
* Changed long options from `--removeempty` to `--remove-empty` and from `--keeplinebreaks` to `--keep-line-breaks`.
* Fixed performance issue with replacing multiple `@charset` declarations and issue
with line break after `@charset` when using `keepLineBreaks` option. By [@rrjaime](https://github.com/rrjamie).
* Removed Makefile in favor to `npm run` commands (e.g. `make check` -> `npm run check`).
* Fixed issue [#47](https://github.com/GoalSmashers/clean-css/issues/47) - commandline issues on Windows.
* Fixed issue [#49](https://github.com/GoalSmashers/clean-css/issues/49) - remove empty selectors from media query.
* Fixed issue [#52](https://github.com/GoalSmashers/clean-css/issues/52) - strip fraction zeros if not needed.
* Fixed issue [#58](https://github.com/GoalSmashers/clean-css/issues/58) - remove colon where possible.
* Fixed issue [#59](https://github.com/GoalSmashers/clean-css/issues/59) - content property handling.
0.9.1 / 2012-12-19
==================
* Fixed issue [#37](https://github.com/GoalSmashers/clean-css/issues/37) - converting
`white` and other colors in class names (reported by [@malgorithms](https://github.com/malgorithms)).
0.9.0 / 2012-12-15
==================
* Added stripping quotation from font names (if possible).
* Added stripping quotation from `@keyframes` declaration, `animation` and
`animation-name` property.
* Added stripping quotations from attributes' value (e.g. `[data-target='x']`).
* Added better hex->name and name->hex color shortening.
* Added `font: normal` and `font: bold` shortening the same way as `font-weight` is.
* Refactored shorthand selectors and added `border-radius`, `border-style`
and `border-color` shortening.
* Added `margin`, `padding` and `border-width` shortening.
* Added removing line break after commas.
* Fixed removing whitespace inside media query definition.
* Added removing line breaks after a comma, so all declarations are one-liners now.
* Speed optimizations (~10% despite many new features).
* Added [JSHint](https://github.com/jshint/jshint/) validation rules via `make check`.
0.8.3 / 2012-11-29
==================
* Fixed HSL/HSLA colors processing.
0.8.2 / 2012-10-31
==================
* Fixed shortening hex colors and their relation to hashes in URLs.
* Cleanup by [@XhmikosR](https://github.com/XhmikosR).
0.8.1 / 2012-10-28
==================
* Added better zeros processing for `rect(...)` syntax (clip property).
0.8.0 / 2012-10-21
==================
* Added removing URLs quotation if possible.
* Rewrote breaks processing.
* Added `keepBreaks`/`-b` option to keep line breaks in the minimized file.
* Reformatted [lib/clean.js](/lib/clean.js) so it's easier to follow the rules.
* Minimized test data is now minimized with line breaks so it's easier to
compare the changes line by line.
0.7.0 / 2012-10-14
==================
* Added stripping special comments to CLI (`--s0` and `--s1` options).
* Added stripping special comments to programmatic interface
(`keepSpecialComments` option).
0.6.0 / 2012-08-05
==================
* Full Windows support with tests (./test.bat).
0.5.0 / 2012-08-02
==================
* Made path to vows local.
* Explicit node.js 0.6 requirement.
0.4.2 / 2012-06-28
==================
* Updated binary `-v` option (version).
* Updated binary to output help when no options given (but not in piped mode).
* Added binary tests.
0.4.1 / 2012-06-10
==================
* Fixed stateless mode where calling `CleanCSS#process` directly was giving
errors (reported by [@facelessuser](https://github.com/facelessuser)).
0.4.0 / 2012-06-04
==================
* Speed improvements up to 4x thanks to the rewrite of comments and CSS' content
processing.
* Stripping empty CSS tags is now optional (see [bin/cleancss](/bin/cleancss) for details).
* Improved debugging mode (see [test/bench.js](/test/bench.js))
* Added `make bench` for a one-pass benchmark.
0.3.3 / 2012-05-27
==================
* Fixed tests, [package.json](/package.json) for development, and regex
for removing empty declarations (thanks to [@vvo](https://github.com/vvo)).
0.3.2 / 2012-01-17
==================
* Fixed output method under node.js 0.6 which incorrectly tried to close
`process.stdout`.
0.3.1 / 2011-12-16
==================
* Fixed cleaning up `0 0 0 0` expressions.
0.3.0 / 2011-11-29
==================
* Clean-css requires node.js 0.4.0+ to run.
* Removed node.js's 0.2.x 'sys' package dependency
(thanks to [@jmalonzo](https://github.com/jmalonzo) for a patch).
0.2.6 / 2011-11-27
==================
* Fixed expanding `+` signs in `calc()` when mixed up with adjacent `+` selector.
0.2.5 / 2011-11-27
==================
* Fixed issue with cleaning up spaces inside `calc`/`-moz-calc` declarations
(thanks to [@cvan](https://github.com/cvan) for reporting it).
* Fixed converting `#f00` to `red` in borders and gradients.
0.2.4 / 2011-05-25
==================
* Fixed problem with expanding `none` to `0` in partial/full background
declarations.
* Fixed including clean-css library from binary (global to local).
0.2.3 / 2011-04-18
==================
* Fixed problem with optimizing IE filters.
0.2.2 / 2011-04-17
==================
* Fixed problem with space before color in `border` property.
0.2.1 / 2011-03-19
==================
* Added stripping space before `!important` keyword.
* Updated repository location and author information in [package.json](/package.json).
0.2.0 / 2011-03-02
==================
* Added options parsing via optimist.
* Changed code inclusion (thus the version bump).
0.1.0 / 2011-02-27
==================
* First version of clean-css library.
* Implemented all basic CSS transformations.
+19
View File
@@ -0,0 +1,19 @@
Copyright (C) 2011-2013 GoalSmashers.com
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.
+187
View File
@@ -0,0 +1,187 @@
[![NPM version](https://badge.fury.io/js/clean-css.png)](https://badge.fury.io/js/clean-css)
[![Build Status](https://secure.travis-ci.org/GoalSmashers/clean-css.png)](https://travis-ci.org/GoalSmashers/clean-css)
[![Dependency Status](https://david-dm.org/GoalSmashers/clean-css.png)](https://david-dm.org/GoalSmashers/clean-css)
[![devDependency Status](https://david-dm.org/GoalSmashers/clean-css/dev-status.png)](https://david-dm.org/GoalSmashers/clean-css#info=devDependencies)
## What is clean-css?
Clean-css is a fast and efficient [node.js](http://nodejs.org/) library for minifying CSS files.
According to [tests](https://github.com/GoalSmashers/css-minification-benchmark) it is one of the best available.
## Usage
### What are the requirements?
```
node.js 0.8.0+ (tested on CentOS, Ubuntu, OS X 10.6+, and Windows 7+)
```
### How to install clean-css?
```
npm install clean-css
```
### How to upgrade clean-css from 1.x to 2.x?
#### Command-line interface (CLI)
```
npm update clean-css
```
or point `package.json` to version 2.x. That's it!
#### Node.js module
Update `clean-css` as for CLI above.
Then change your JavaScript code from:
```js
var minimized = CleanCSS.process(source, options);
```
into
```js
var minimized = new CleanCSS(options).minify(source);
```
And you are done.
### How to use clean-css CLI?
Clean-css accepts the following command line arguments (please make sure
you use `<source-file>` as the very last argument to avoid potential issues):
```
cleancss [options] <source-file>
-h, --help Output usage information
-v, --version Output the version number
-b, --keep-line-breaks Keep line breaks
--s0 Remove all special comments, i.e. /*! comment */
--s1 Remove all special comments but the first one
-r, --root [root-path] A root path to which resolve absolute @import rules
and rebase relative URLs
-o, --output [output-file] Use [output-file] as output instead of STDOUT
-s, --skip-import Disable @import processing
--skip-rebase Disable URLs rebasing
--skip-advanced Disable advanced optimizations - selector & property merging,
reduction, etc.
--selectors-merge-mode [ie8|*] Use `ie8` for compatibility mode, `*` for merge all (default)
-d, --debug Shows debug information (minification time & compression efficiency)
```
#### Examples:
To minify a **public.css** file into **public-min.css** do:
```
cleancss -o public-min.css public.css
```
To minify the same **public.css** into the standard output skip the `-o` parameter:
```
cleancss public.css
```
More likely you would like to concatenate a couple of files.
If you are on a Unix-like system:
```bash
cat one.css two.css three.css | cleancss -o merged-and-minified.css
```
On Windows:
```bat
type one.css two.css three.css | cleancss -o merged-and-minified.css
```
Or even gzip the result at once:
```bash
cat one.css two.css three.css | cleancss | gzip -9 -c > merged-minified-and-gzipped.css.gz
```
### How to use clean-css programmatically?
```js
var CleanCSS = require('clean-css');
var source = 'a{font-weight:bold;}';
var minimized = new CleanCSS().minify(source);
```
CleanCSS constructor accepts a hash as a parameter, i.e.,
`new CleanCSS(options).minify(source)` with the following options available:
* `keepSpecialComments` - `*` for keeping all (default), `1` for keeping first one only, `0` for removing all
* `keepBreaks` - whether to keep line breaks (default is false)
* `benchmark` - turns on benchmarking mode measuring time spent on cleaning up
(run `npm run bench` to see example)
* `root` - path to resolve absolute `@import` rules and rebase relative URLs
* `relativeTo` - path with which to resolve relative `@import` rules and URLs
* `processImport` - whether to process `@import` rules
* `noRebase` - whether to skip URLs rebasing
* `noAdvanced` - set to true to disable advanced optimizations - selector & property merging, reduction, etc.
* `selectorsMergeMode` - `ie8` for IE8 compatibility mode, `*` for merging all (default)
* `debug` - set to true to get minification statistics under `stats` property (see `test/custom-test.js` for examples)
### What are the clean-css' dev commands?
First clone the source, then run:
* `npm run bench` for clean-css benchmarks (see [test/bench.js](https://github.com/GoalSmashers/clean-css/blob/master/test/bench.js) for details)
* `npm run check` to check JS sources with [JSHint](https://github.com/jshint/jshint/)
* `npm test` for the test suite
## Tips & Tricks
### How to preserve a comment block?
Use the `/*!` notation instead of the standard one `/*`:
```css
/*!
Important comments included in minified output.
*/
```
### How to rebase relative image URLs
Clean-css will handle it automatically for you (since version 1.1) in the following cases:
* When using the CLI:
1. Use an output path via `-o`/`--output` to rebase URLs as relative to the output file.
2. Use a root path via `-r`/`--root` to rebase URLs as absolute from the given root path.
3. If you specify both then `-r`/`--root` takes precendence.
* When using clean-css as a library:
1. Use a combination of `relativeTo` and `target` options for relative rebase (same as 1 in CLI).
2. Use a combination of `relativeTo` and `root` options for absolute rebase (same as 2 in CLI).
3. `root` takes precendence over `target` as in CLI.
## Acknowledgments
* Vincent Voyer ([@vvo](https://github.com/vvo)) for a patch with better
empty element regex and for inspiring us to do many performance improvements
in 0.4 release.
* Isaac ([@facelessuser](https://github.com/facelessuser)) for pointing out
a flaw in clean-css' stateless mode.
* Jan Michael Alonzo ([@jmalonzo](https://github.com/jmalonzo)) for a patch
removing node.js' old `sys` package.
* [@XhmikosR](https://github.com/XhmikosR) for suggesting new features
(option to remove special comments and strip out URLs quotation) and
pointing out numerous improvements (JSHint, media queries).
* Anthony Barre ([@abarre](https://github.com/abarre)) for improvements to
`@import` processing, namely introducing the `--skip-import` /
`processImport` options.
* Simon Altschuler ([@altschuler](https://github.com/altschuler)) for fixing
`@import` processing inside comments.
## License
Clean-css is released under the [MIT License](https://github.com/GoalSmashers/clean-css/blob/master/LICENSE).
+138
View File
@@ -0,0 +1,138 @@
#!/usr/bin/env node
var util = require('util');
var fs = require('fs');
var path = require('path');
var CleanCSS = require('../index');
var commands = require('commander');
var packageConfig = fs.readFileSync(path.join(path.dirname(fs.realpathSync(process.argv[1])), '../package.json'));
var buildVersion = JSON.parse(packageConfig).version;
var isWindows = process.platform == 'win32';
// Specify commander options to parse command line params correctly
commands
.version(buildVersion, '-v, --version')
.usage('[options] <source-file>')
.option('-b, --keep-line-breaks', 'Keep line breaks')
.option('--s0', 'Remove all special comments, i.e. /*! comment */')
.option('--s1', 'Remove all special comments but the first one')
.option('-r, --root [root-path]', 'Set a root path to which resolve absolute @import rules')
.option('-o, --output [output-file]', 'Use [output-file] as output instead of STDOUT')
.option('-s, --skip-import', 'Disable @import processing')
.option('--skip-rebase', 'Disable URLs rebasing')
.option('--skip-advanced', 'Disable advanced optimizations - selector & property merging, reduction, etc.')
.option('--selectors-merge-mode [ie8|*]', 'Use `ie8` for compatibility mode, `*` for merge all (default).')
.option('-d, --debug', 'Shows debug information (minification time & compression efficiency)');
commands.on('--help', function() {
util.puts(' Examples:\n');
util.puts(' %> cleancss one.css');
util.puts(' %> cleancss -o one-min.css one.css');
if (isWindows) {
util.puts(' %> type one.css two.css three.css | cleancss -o merged-and-minified.css');
} else {
util.puts(' %> cat one.css two.css three.css | cleancss -o merged-and-minified.css');
util.puts(' %> cat one.css two.css three.css | cleancss | gzip -9 -c > merged-minified-and-gzipped.css.gz');
}
util.puts('');
process.exit();
});
commands.parse(process.argv);
var options = {
source: null,
target: null
};
var cleanOptions = {};
var fromStdin = !process.env['__DIRECT__'] && !process.stdin.isTTY;
// If no sensible data passed in just print help and exit
if (!fromStdin && commands.args.length == 0) {
commands.outputHelp();
return 0;
}
// Now coerce commands into CleanCSS configuration...
if (commands.output)
cleanOptions.target = options.target = commands.output;
if (commands.keepLineBreaks)
cleanOptions.keepBreaks = true;
if (commands.s1)
cleanOptions.keepSpecialComments = 1;
if (commands.s0)
cleanOptions.keepSpecialComments = 0;
if (commands.root)
cleanOptions.root = commands.root;
if (commands.skipImport)
cleanOptions.processImport = false;
if (commands.skipRebase)
cleanOptions.noRebase = true;
if (commands.skipAdvanced)
cleanOptions.noAdvanced = true;
if (commands.selectorsMergeMode)
cleanOptions.selectorsMergeMode = commands.selectorsMergeMode;
if (commands.debug)
cleanOptions.debug = true;
if (commands.args.length > 0) {
var source = commands.args[0];
options.source = source;
cleanOptions.relativeTo = path.dirname(path.resolve(source));
}
// ... and do the magic!
if (options.source) {
fs.readFile(options.source, 'utf8', function(error, data) {
if (error)
throw error;
output(minify(data));
});
} else {
var stdin = process.openStdin();
stdin.setEncoding('utf-8');
var data = '';
stdin.on('data', function(chunk) {
data += chunk;
});
stdin.on('end', function() {
output(minify(data));
});
}
function minify(data) {
var minifier = new CleanCSS(cleanOptions);
var minified = minifier.minify(data);
if (cleanOptions.debug) {
console.error('Original: %d bytes', minifier.stats.originalSize);
console.error('Minified: %d bytes', minifier.stats.minifiedSize);
console.error('Efficiency: %d%', ~~(minifier.stats.efficiency * 10000) / 100.0);
console.error('Time spent: %dms', minifier.stats.timeSpent);
}
outputFeedback(minifier.errors, true);
outputFeedback(minifier.warnings);
if (minifier.errors.length > 0)
process.exit(1);
return minified;
}
function output(minified) {
if (options.target)
fs.writeFileSync(options.target, minified, 'utf8');
else
process.stdout.write(minified);
};
function outputFeedback(messages, isError) {
var prefix = isError ? '\x1B[31mERROR\x1B[39m:' : 'WARNING:';
messages.forEach(function(message) {
console.error('%s %s', prefix, message);
});
};
+1
View File
@@ -0,0 +1 @@
module.exports = require('./lib/clean');
+329
View File
@@ -0,0 +1,329 @@
/**
* Clean-css - https://github.com/GoalSmashers/clean-css
* Released under the terms of MIT license
*
* Copyright (C) 2011-2013 GoalSmashers.com
*/
var ColorShortener = require('./colors/shortener');
var ColorHSLToHex = require('./colors/hsl-to-hex');
var ColorRGBToHex = require('./colors/rgb-to-hex');
var ColorLongToShortHex = require('./colors/long-to-short-hex');
var ShorthandNotations = require('./properties/shorthand-notations');
var ImportInliner = require('./imports/inliner');
var UrlRebase = require('./images/url-rebase');
var EmptyRemoval = require('./selectors/empty-removal');
var CommentsProcessor = require('./text/comments');
var ExpressionsProcessor = require('./text/expressions');
var FreeTextProcessor = require('./text/free');
var UrlsProcessor = require('./text/urls');
var SelectorsOptimizer = require('./selectors/optimizer');
module.exports = function(options) {
var lineBreak = process.platform == 'win32' ? '\r\n' : '\n';
var stats = {};
var context = {
errors: [],
warnings: []
};
options = options || {};
options.keepBreaks = options.keepBreaks || false;
//active by default
if (options.processImport === undefined)
options.processImport = true;
var minify = function(data) {
if (Buffer.isBuffer(data))
data = data.toString();
var startedAt;
if (options.debug) {
startedAt = process.hrtime();
stats.originalSize = data.length;
}
var replace = function() {
if (typeof arguments[0] == 'function')
arguments[0]();
else
data = data.replace.apply(data, arguments);
};
// replace function
if (options.benchmark) {
var originalReplace = replace;
replace = function(pattern, replacement) {
var name = typeof pattern == 'function' ?
/function (\w+)\(/.exec(pattern.toString())[1] :
pattern;
var start = process.hrtime();
originalReplace(pattern, replacement);
var itTook = process.hrtime(start);
console.log('%d ms: ' + name, 1000 * itTook[0] + itTook[1] / 1000000.0);
};
}
var commentsProcessor = new CommentsProcessor(
'keepSpecialComments' in options ? options.keepSpecialComments : '*',
options.keepBreaks,
lineBreak
);
var expressionsProcessor = new ExpressionsProcessor();
var freeTextProcessor = new FreeTextProcessor();
var urlsProcessor = new UrlsProcessor();
var importInliner = new ImportInliner(context);
if (options.processImport) {
// inline all imports
replace(function inlineImports() {
data = importInliner.process(data, {
root: options.root || process.cwd(),
relativeTo: options.relativeTo
});
});
}
replace(function escapeComments() {
data = commentsProcessor.escape(data);
});
// replace all escaped line breaks
replace(/\\(\r\n|\n)/mg, '');
// strip parentheses in urls if possible (no spaces inside)
replace(/url\((['"])([^\)]+)['"]\)/g, function(match, quote, url) {
var unsafeDataURI = url.indexOf('data:') === 0 && url.match(/data:\w+\/[^;]+;base64,/) === null;
if (url.match(/[ \t]/g) !== null || unsafeDataURI)
return 'url(' + quote + url + quote + ')';
else
return 'url(' + url + ')';
});
// strip parentheses in animation & font names
replace(/(animation|animation\-name|font|font\-family):([^;}]+)/g, function(match, propertyName, def) {
return propertyName + ':' + def.replace(/['"]([a-zA-Z][a-zA-Z\d\-_]+)['"]/g, '$1');
});
// strip parentheses in @keyframes
replace(/@(\-moz\-|\-o\-|\-webkit\-)?keyframes ([^{]+)/g, function(match, prefix, name) {
prefix = prefix || '';
return '@' + prefix + 'keyframes ' + (name.indexOf(' ') > -1 ? name : name.replace(/['"]/g, ''));
});
// IE shorter filters, but only if single (IE 7 issue)
replace(/progid:DXImageTransform\.Microsoft\.(Alpha|Chroma)(\([^\)]+\))([;}'"])/g, function(match, filter, args, suffix) {
return filter.toLowerCase() + args + suffix;
});
replace(function escapeExpressions() {
data = expressionsProcessor.escape(data);
});
// strip parentheses in attribute values
replace(/\[([^\]]+)\]/g, function(match, content) {
var eqIndex = content.indexOf('=');
var singleQuoteIndex = content.indexOf('\'');
var doubleQuoteIndex = content.indexOf('"');
if (eqIndex < 0 && singleQuoteIndex < 0 && doubleQuoteIndex < 0)
return match;
if (singleQuoteIndex === 0 || doubleQuoteIndex === 0)
return match;
var key = content.substring(0, eqIndex);
var value = content.substring(eqIndex + 1, content.length);
if (/^['"](?:[a-zA-Z][a-zA-Z\d\-_]+)['"]$/.test(value))
return '[' + key + '=' + value.substring(1, value.length - 1) + ']';
else
return match;
});
replace(function escapeFreeText() {
data = freeTextProcessor.escape(data);
});
replace(function escapeUrls() {
data = urlsProcessor.escape(data);
});
// line breaks
replace(/[\r]?\n/g, ' ');
// multiple whitespace
replace(/[\t ]+/g, ' ');
// multiple semicolons (with optional whitespace)
replace(/;[ ]?;+/g, ';');
// multiple line breaks to one
replace(/ (?:\r\n|\n)/g, lineBreak);
replace(/(?:\r\n|\n)+/g, lineBreak);
// remove spaces around selectors
replace(/ ([+~>]) /g, '$1');
// remove extra spaces inside content
replace(/([!\(\{\}:;=,\n]) /g, '$1');
replace(/ ([!\)\{\};=,\n])/g, '$1');
replace(/(?:\r\n|\n)\}/g, '}');
replace(/([\{;,])(?:\r\n|\n)/g, '$1');
replace(/ :([^\{\};]+)([;}])/g, ':$1$2');
// restore spaces inside IE filters (IE 7 issue)
replace(/progid:[^(]+\(([^\)]+)/g, function(match) {
return match.replace(/,/g, ', ');
});
// trailing semicolons
replace(/;\}/g, '}');
replace(function hsl2Hex() {
data = new ColorHSLToHex(data).process();
});
replace(function rgb2Hex() {
data = new ColorRGBToHex(data).process();
});
replace(function longToShortHex() {
data = new ColorLongToShortHex(data).process();
});
replace(function shortenColors() {
data = new ColorShortener(data).process();
});
// replace font weight with numerical value
replace(/(font\-weight|font):(normal|bold)([ ;\}!])(\w*)/g, function(match, property, weight, suffix, next) {
if (suffix == ' ' && next.length > 0 && !/[.\d]/.test(next))
return match;
if (weight == 'normal')
return property + ':400' + suffix + next;
else if (weight == 'bold')
return property + ':700' + suffix + next;
else
return match;
});
// zero + unit to zero
replace(/(\s|:|,)0(?:px|em|ex|cm|mm|in|pt|pc|%)/g, '$1' + '0');
replace(/rect\(0(?:px|em|ex|cm|mm|in|pt|pc|%)/g, 'rect(0');
// fraction zeros removal
replace(/\.([1-9]*)0+(\D)/g, function(match, nonZeroPart, suffix) {
return (nonZeroPart ? '.' : '') + nonZeroPart + suffix;
});
// restore 0% in hsl/hsla
replace(/(hsl|hsla)\(([^\)]+)\)/g, function(match, colorFunction, colorDef) {
var tokens = colorDef.split(',');
if (tokens[1] == '0')
tokens[1] = '0%';
if (tokens[2] == '0')
tokens[2] = '0%';
return colorFunction + '(' + tokens.join(',') + ')';
});
// none to 0
replace(/(border|border-top|border-right|border-bottom|border-left|outline):none/g, '$1:0');
// background:none to background:0 0
replace(/background:(?:none|transparent)([;}])/g, 'background:0 0$1');
// multiple zeros into one
replace(/box-shadow:0 0 0 0([^\.])/g, 'box-shadow:0 0$1');
replace(/:0 0 0 0([^\.])/g, ':0$1');
replace(/([: ,=\-])0\.(\d)/g, '$1.$2');
replace(function shorthandNotations() {
data = new ShorthandNotations(data).process();
});
// restore rect(...) zeros syntax for 4 zeros
replace(/rect\(\s?0(\s|,)0[ ,]0[ ,]0\s?\)/g, 'rect(0$10$10$10)');
// remove universal selector when not needed (*#id, *.class etc)
replace(/\*([\.#:\[])/g, '$1');
// Restore spaces inside calc back
replace(/calc\([^\}]+\}/g, function(match) {
return match.replace(/\+/g, ' + ');
});
if (options.noAdvanced) {
if (options.keepBreaks)
replace(/\}/g, '}' + lineBreak);
} else {
replace(function optimizeSelectors() {
data = new SelectorsOptimizer(data, context, {
keepBreaks: options.keepBreaks,
lineBreak: lineBreak,
selectorsMergeMode: options.selectorsMergeMode
}).process();
});
}
replace(function restoreUrls() {
data = urlsProcessor.restore(data);
});
replace(function rebaseUrls() {
data = options.noRebase ? data : new UrlRebase(options, context).process(data);
});
replace(function restoreFreeText() {
data = freeTextProcessor.restore(data);
});
replace(function restoreComments() {
data = commentsProcessor.restore(data);
});
replace(function restoreExpressions() {
data = expressionsProcessor.restore(data);
});
// move first charset to the beginning
replace(function moveCharset() {
// get first charset in stylesheet
var match = data.match(/@charset [^;]+;/);
var firstCharset = match ? match[0] : null;
if (!firstCharset)
return;
// reattach first charset and remove all subsequent
data = firstCharset +
(options.keepBreaks ? lineBreak : '') +
data.replace(new RegExp('@charset [^;]+;(' + lineBreak + ')?', 'g'), '').trim();
});
replace(function removeEmptySelectors() {
data = new EmptyRemoval(data).process();
});
// trim spaces at beginning and end
data = data.trim();
if (options.debug) {
var elapsed = process.hrtime(startedAt);
stats.timeSpent = ~~(elapsed[0] * 1e3 + elapsed[1] / 1e6);
stats.efficiency = 1 - data.length / stats.originalSize;
stats.minifiedSize = data.length;
}
return data;
};
return {
errors: context.errors,
lineBreak: lineBreak,
options: options,
minify: minify,
stats: stats,
warnings: context.warnings
};
};
@@ -0,0 +1,64 @@
module.exports = function HSLToHex(data) {
// HSL to RGB converter. Both methods adapted from:
// http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
var hslToRgb = function(h, s, l) {
var r, g, b;
// normalize hue orientation b/w 0 and 360 degrees
h = h % 360;
if (h < 0)
h += 360;
h = ~~h / 360;
if (s < 0)
s = 0;
else if (s > 100)
s = 100;
s = ~~s / 100;
if (l < 0)
l = 0;
else if (l > 100)
l = 100;
l = ~~l / 100;
if (s === 0) {
r = g = b = l; // achromatic
} else {
var q = l < 0.5 ?
l * (1 + s) :
l + s - l * s;
var p = 2 * l - q;
r = hueToRgb(p, q, h + 1/3);
g = hueToRgb(p, q, h);
b = hueToRgb(p, q, h - 1/3);
}
return [~~(r * 255), ~~(g * 255), ~~(b * 255)];
};
var hueToRgb = function(p, q, t) {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
return {
process: function() {
return data.replace(/hsl\((-?\d+),(-?\d+)%?,(-?\d+)%?\)/g, function(match, hue, saturation, lightness) {
var asRgb = hslToRgb(hue, saturation, lightness);
var redAsHex = asRgb[0].toString(16);
var greenAsHex = asRgb[1].toString(16);
var blueAsHex = asRgb[2].toString(16);
return '#' +
((redAsHex.length == 1 ? '0' : '') + redAsHex) +
((greenAsHex.length == 1 ? '0' : '') + greenAsHex) +
((blueAsHex.length == 1 ? '0' : '') + blueAsHex);
});
}
};
};
@@ -0,0 +1,12 @@
module.exports = function LongToShortHex(data) {
return {
process: function() {
return data.replace(/([,: \(])#([0-9a-f]{6})/gi, function(match, prefix, color) {
if (color[0] == color[1] && color[2] == color[3] && color[4] == color[5])
return prefix + '#' + color[0] + color[2] + color[4];
else
return prefix + '#' + color;
});
}
};
};
@@ -0,0 +1,16 @@
module.exports = function RGBToHex(data) {
return {
process: function() {
return data.replace(/rgb\((\d+),(\d+),(\d+)\)/g, function(match, red, green, blue) {
var redAsHex = parseInt(red, 10).toString(16);
var greenAsHex = parseInt(green, 10).toString(16);
var blueAsHex = parseInt(blue, 10).toString(16);
return '#' +
((redAsHex.length == 1 ? '0' : '') + redAsHex) +
((greenAsHex.length == 1 ? '0' : '') + greenAsHex) +
((blueAsHex.length == 1 ? '0' : '') + blueAsHex);
});
}
};
};
@@ -0,0 +1,174 @@
module.exports = function Shortener(data) {
var COLORS = {
aliceblue: '#f0f8ff',
antiquewhite: '#faebd7',
aqua: '#0ff',
aquamarine: '#7fffd4',
azure: '#f0ffff',
beige: '#f5f5dc',
bisque: '#ffe4c4',
black: '#000',
blanchedalmond: '#ffebcd',
blue: '#00f',
blueviolet: '#8a2be2',
brown: '#a52a2a',
burlywood: '#deb887',
cadetblue: '#5f9ea0',
chartreuse: '#7fff00',
chocolate: '#d2691e',
coral: '#ff7f50',
cornflowerblue: '#6495ed',
cornsilk: '#fff8dc',
crimson: '#dc143c',
cyan: '#0ff',
darkblue: '#00008b',
darkcyan: '#008b8b',
darkgoldenrod: '#b8860b',
darkgray: '#a9a9a9',
darkgreen: '#006400',
darkkhaki: '#bdb76b',
darkmagenta: '#8b008b',
darkolivegreen: '#556b2f',
darkorange: '#ff8c00',
darkorchid: '#9932cc',
darkred: '#8b0000',
darksalmon: '#e9967a',
darkseagreen: '#8fbc8f',
darkslateblue: '#483d8b',
darkslategray: '#2f4f4f',
darkturquoise: '#00ced1',
darkviolet: '#9400d3',
deeppink: '#ff1493',
deepskyblue: '#00bfff',
dimgray: '#696969',
dodgerblue: '#1e90ff',
firebrick: '#b22222',
floralwhite: '#fffaf0',
forestgreen: '#228b22',
fuchsia: '#f0f',
gainsboro: '#dcdcdc',
ghostwhite: '#f8f8ff',
gold: '#ffd700',
goldenrod: '#daa520',
gray: '#808080',
green: '#008000',
greenyellow: '#adff2f',
honeydew: '#f0fff0',
hotpink: '#ff69b4',
indianred: '#cd5c5c',
indigo: '#4b0082',
ivory: '#fffff0',
khaki: '#f0e68c',
lavender: '#e6e6fa',
lavenderblush: '#fff0f5',
lawngreen: '#7cfc00',
lemonchiffon: '#fffacd',
lightblue: '#add8e6',
lightcoral: '#f08080',
lightcyan: '#e0ffff',
lightgoldenrodyellow: '#fafad2',
lightgray: '#d3d3d3',
lightgreen: '#90ee90',
lightpink: '#ffb6c1',
lightsalmon: '#ffa07a',
lightseagreen: '#20b2aa',
lightskyblue: '#87cefa',
lightslategray: '#778899',
lightsteelblue: '#b0c4de',
lightyellow: '#ffffe0',
lime: '#0f0',
limegreen: '#32cd32',
linen: '#faf0e6',
magenta: '#ff00ff',
maroon: '#800000',
mediumaquamarine: '#66cdaa',
mediumblue: '#0000cd',
mediumorchid: '#ba55d3',
mediumpurple: '#9370db',
mediumseagreen: '#3cb371',
mediumslateblue: '#7b68ee',
mediumspringgreen: '#00fa9a',
mediumturquoise: '#48d1cc',
mediumvioletred: '#c71585',
midnightblue: '#191970',
mintcream: '#f5fffa',
mistyrose: '#ffe4e1',
moccasin: '#ffe4b5',
navajowhite: '#ffdead',
navy: '#000080',
oldlace: '#fdf5e6',
olive: '#808000',
olivedrab: '#6b8e23',
orange: '#ffa500',
orangered: '#ff4500',
orchid: '#da70d6',
palegoldenrod: '#eee8aa',
palegreen: '#98fb98',
paleturquoise: '#afeeee',
palevioletred: '#db7093',
papayawhip: '#ffefd5',
peachpuff: '#ffdab9',
peru: '#cd853f',
pink: '#ffc0cb',
plum: '#dda0dd',
powderblue: '#b0e0e6',
purple: '#800080',
red: '#f00',
rosybrown: '#bc8f8f',
royalblue: '#4169e1',
saddlebrown: '#8b4513',
salmon: '#fa8072',
sandybrown: '#f4a460',
seagreen: '#2e8b57',
seashell: '#fff5ee',
sienna: '#a0522d',
silver: '#c0c0c0',
skyblue: '#87ceeb',
slateblue: '#6a5acd',
slategray: '#708090',
snow: '#fffafa',
springgreen: '#00ff7f',
steelblue: '#4682b4',
tan: '#d2b48c',
teal: '#008080',
thistle: '#d8bfd8',
tomato: '#ff6347',
turquoise: '#40e0d0',
violet: '#ee82ee',
wheat: '#f5deb3',
white: '#fff',
whitesmoke: '#f5f5f5',
yellow: '#ff0',
yellowgreen: '#9acd32'
};
var toHex = {};
var toName = {};
for (var name in COLORS) {
var color = COLORS[name];
if (name.length < color.length)
toName[color] = name;
else
toHex[name] = color;
}
return {
toHex: toHex,
toName: toName,
// replace color name with hex values if shorter (or the other way around)
process: function() {
[toHex, toName].forEach(function(conversion) {
var pattern = '(' + Object.keys(conversion).join('|') + ')';
var colorSwitcher = function(match, prefix, colorValue, suffix) {
return prefix + conversion[colorValue.toLowerCase()] + suffix;
};
data = data.replace(new RegExp('([ :,\\(])' + pattern + '([;\\}!\\) ])', 'ig'), colorSwitcher);
data = data.replace(new RegExp('(,)' + pattern + '(,)', 'ig'), colorSwitcher);
});
return data;
}
};
};
@@ -0,0 +1,34 @@
var path = require('path');
var UrlRewriter = require('./url-rewriter');
module.exports = function UrlRebase(options, context) {
var process = function(data) {
var rebaseOpts = {
absolute: !!options.root,
relative: !options.root && !!options.target,
fromBase: options.relativeTo
};
if (!rebaseOpts.absolute && !rebaseOpts.relative)
return data;
if (rebaseOpts.absolute && !!options.target)
context.warnings.push('Both \'root\' and output file given so rebasing URLs as absolute paths');
if (rebaseOpts.absolute)
rebaseOpts.toBase = path.resolve(options.root);
if (rebaseOpts.relative)
rebaseOpts.toBase = path.resolve(path.dirname(options.target));
if (!rebaseOpts.fromBase || !rebaseOpts.toBase)
return data;
return UrlRewriter.process(data, rebaseOpts);
};
return {
process: process
};
};
@@ -0,0 +1,53 @@
var path = require('path');
module.exports = {
process: function(data, options) {
var tempData = [];
var nextStart = 0;
var nextEnd = 0;
var cursor = 0;
for (; nextEnd < data.length; ) {
nextStart = data.indexOf('url(', nextEnd);
if (nextStart == -1)
break;
nextEnd = data.indexOf(')', nextStart + 4);
if (nextEnd == -1)
break;
tempData.push(data.substring(cursor, nextStart));
var url = data.substring(nextStart + 4, nextEnd).replace(/['"]/g, '');
tempData.push('url(' + this._rebased(url, options) + ')');
cursor = nextEnd + 1;
}
return tempData.length > 0 ?
tempData.join('') + data.substring(cursor, data.length) :
data;
},
_rebased: function(url, options) {
var specialUrl = url[0] == '/' ||
url.substring(url.length - 4) == '.css' ||
url.indexOf('data:') === 0 ||
/^https?:\/\//.exec(url) !== null ||
/__\w+__/.exec(url) !== null;
var rebased;
if (specialUrl)
return url;
if (options.absolute) {
rebased = path
.resolve(path.join(options.fromBase, url))
.replace(options.toBase, '');
} else {
rebased = path.relative(options.toBase, path.join(options.fromBase, url));
}
return process.platform == 'win32' ?
rebased.replace(/\\/g, '/') :
rebased;
}
};
@@ -0,0 +1,152 @@
var fs = require('fs');
var path = require('path');
var UrlRewriter = require('../images/url-rewriter');
module.exports = function Inliner(context) {
var process = function(data, options) {
var tempData = [];
var nextStart = 0;
var nextEnd = 0;
var cursor = 0;
var isComment = commentScanner(data);
options.relativeTo = options.relativeTo || options.root;
options._baseRelativeTo = options._baseRelativeTo || options.relativeTo;
options.visited = options.visited || [];
for (; nextEnd < data.length; ) {
nextStart = data.indexOf('@import', cursor);
if (nextStart == -1)
break;
if (isComment(nextStart)) {
cursor = nextStart + 1;
continue;
}
nextEnd = data.indexOf(';', nextStart);
if (nextEnd == -1) {
tempData.push('');
cursor = data.length;
break;
}
tempData.push(data.substring(cursor, nextStart));
tempData.push(inlinedFile(data, nextStart, nextEnd, options));
cursor = nextEnd + 1;
}
return tempData.length > 0 ?
tempData.join('') + data.substring(cursor, data.length) :
data;
};
var commentScanner = function(data) {
var commentRegex = /(\/\*(?!\*\/)[\s\S]*?\*\/)/;
var lastStartIndex = 0;
var lastEndIndex = 0;
var noComments = false;
// test whether an index is located within a comment
var scanner = function(idx) {
var comment;
var localStartIndex = 0;
var localEndIndex = 0;
var globalStartIndex = 0;
var globalEndIndex = 0;
// return if we know there are no more comments
if (noComments)
return false;
// idx can be still within last matched comment (many @import statements inside one comment)
if (idx > lastStartIndex && idx < lastEndIndex)
return true;
comment = data.match(commentRegex);
if (!comment) {
noComments = true;
return false;
}
// get the indexes relative to the current data chunk
lastStartIndex = localStartIndex = comment.index;
localEndIndex = localStartIndex + comment[0].length;
// calculate the indexes relative to the full original data
globalEndIndex = localEndIndex + lastEndIndex;
globalStartIndex = globalEndIndex - comment[0].length;
// chop off data up to and including current comment block
data = data.substring(localEndIndex);
lastEndIndex = globalEndIndex;
// re-run scan if comment ended before the idx
if (globalEndIndex < idx)
return scanner(idx);
return globalEndIndex > idx && idx > globalStartIndex;
};
return scanner;
};
var inlinedFile = function(data, nextStart, nextEnd, options) {
var strippedImport = data
.substring(data.indexOf(' ', nextStart) + 1, nextEnd)
.replace(/^url\(/, '')
.replace(/['"]/g, '');
var separatorIndex = strippedImport.indexOf(' ');
var importedFile = strippedImport
.substring(0, separatorIndex > 0 ? separatorIndex : strippedImport.length)
.replace(')', '');
var mediaQuery = strippedImport
.substring(importedFile.length + 1)
.trim();
if (/^(http|https):\/\//.test(importedFile) || /^\/\//.test(importedFile))
return '@import url(' + importedFile + ')' + (mediaQuery.length > 0 ? ' ' + mediaQuery : '') + ';';
var relativeTo = importedFile[0] == '/' ?
options.root :
options.relativeTo;
var fullPath = path.resolve(path.join(relativeTo, importedFile));
if (!fs.existsSync(fullPath) || !fs.statSync(fullPath).isFile()) {
context.errors.push('Broken @import declaration of "' + importedFile + '"');
return '';
}
if (options.visited.indexOf(fullPath) != -1)
return '';
options.visited.push(fullPath);
var importedData = fs.readFileSync(fullPath, 'utf8');
var importRelativeTo = path.dirname(fullPath);
importedData = UrlRewriter.process(importedData, {
relative: true,
fromBase: importRelativeTo,
toBase: options._baseRelativeTo
});
var inlinedData = process(importedData, {
root: options.root,
relativeTo: importRelativeTo,
_baseRelativeTo: options.baseRelativeTo,
visited: options.visited
});
return mediaQuery.length > 0 ?
'@media ' + mediaQuery + '{' + inlinedData + '}' :
inlinedData;
};
return {
// Inlines all imports taking care of repetitions, unknown files, and circular dependencies
process: process
};
};
@@ -0,0 +1,228 @@
module.exports = function Optimizer() {
var overridable = {
'animation-delay': ['animation'],
'animation-direction': ['animation'],
'animation-duration': ['animation'],
'animation-fill-mode': ['animation'],
'animation-iteration-count': ['animation'],
'animation-name': ['animation'],
'animation-play-state': ['animation'],
'animation-timing-function': ['animation'],
'-moz-animation-delay': ['-moz-animation'],
'-moz-animation-direction': ['-moz-animation'],
'-moz-animation-duration': ['-moz-animation'],
'-moz-animation-fill-mode': ['-moz-animation'],
'-moz-animation-iteration-count': ['-moz-animation'],
'-moz-animation-name': ['-moz-animation'],
'-moz-animation-play-state': ['-moz-animation'],
'-moz-animation-timing-function': ['-moz-animation'],
'-o-animation-delay': ['-o-animation'],
'-o-animation-direction': ['-o-animation'],
'-o-animation-duration': ['-o-animation'],
'-o-animation-fill-mode': ['-o-animation'],
'-o-animation-iteration-count': ['-o-animation'],
'-o-animation-name': ['-o-animation'],
'-o-animation-play-state': ['-o-animation'],
'-o-animation-timing-function': ['-o-animation'],
'-webkit-animation-delay': ['-webkit-animation'],
'-webkit-animation-direction': ['-webkit-animation'],
'-webkit-animation-duration': ['-webkit-animation'],
'-webkit-animation-fill-mode': ['-webkit-animation'],
'-webkit-animation-iteration-count': ['-webkit-animation'],
'-webkit-animation-name': ['-webkit-animation'],
'-webkit-animation-play-state': ['-webkit-animation'],
'-webkit-animation-timing-function': ['-webkit-animation'],
'background-attachment': ['background'],
'background-clip': ['background'],
'background-color': ['background'],
'background-image': ['background'],
'background-origin': ['background'],
'background-position': ['background'],
'background-repeat': ['background'],
'background-size': ['background'],
'border-color': ['border'],
'border-style': ['border'],
'border-width': ['border'],
'border-bottom': ['border'],
'border-bottom-color': ['border-bottom', 'border-color', 'border'],
'border-bottom-style': ['border-bottom', 'border-style', 'border'],
'border-bottom-width': ['border-bottom', 'border-width', 'border'],
'border-left': ['border'],
'border-left-color': ['border-left', 'border-color', 'border'],
'border-left-style': ['border-left', 'border-style', 'border'],
'border-left-width': ['border-left', 'border-width', 'border'],
'border-right': ['border'],
'border-right-color': ['border-right', 'border-color', 'border'],
'border-right-style': ['border-right', 'border-style', 'border'],
'border-right-width': ['border-right', 'border-width', 'border'],
'border-top': ['border'],
'border-top-color': ['border-top', 'border-color', 'border'],
'border-top-style': ['border-top', 'border-style', 'border'],
'border-top-width': ['border-top', 'border-width', 'border'],
'font-family': ['font'],
'font-size': ['font'],
'font-style': ['font'],
'font-variant': ['font'],
'font-weight': ['font'],
'list-style-image': ['list'],
'list-style-position': ['list'],
'list-style-type': ['list'],
'margin-bottom': ['margin'],
'margin-left': ['margin'],
'margin-right': ['margin'],
'margin-top': ['margin'],
'outline-color': ['outline'],
'outline-style': ['outline'],
'outline-width': ['outline'],
'padding-bottom': ['padding'],
'padding-left': ['padding'],
'padding-right': ['padding'],
'padding-top': ['padding'],
'transition-delay': ['transition'],
'transition-duration': ['transition'],
'transition-property': ['transition'],
'transition-timing-function': ['transition'],
'-moz-transition-delay': ['-moz-transition'],
'-moz-transition-duration': ['-moz-transition'],
'-moz-transition-property': ['-moz-transition'],
'-moz-transition-timing-function': ['-moz-transition'],
'-o-transition-delay': ['-o-transition'],
'-o-transition-duration': ['-o-transition'],
'-o-transition-property': ['-o-transition'],
'-o-transition-timing-function': ['-o-transition'],
'-webkit-transition-delay': ['-webkit-transition'],
'-webkit-transition-duration': ['-webkit-transition'],
'-webkit-transition-property': ['-webkit-transition'],
'-webkit-transition-timing-function': ['-webkit-transition']
};
var overrides = {};
for (var granular in overridable) {
for (var i = 0; i < overridable[granular].length; i++) {
var coarse = overridable[granular][i];
var list = overrides[coarse];
if (list)
list.push(granular);
else
overrides[coarse] = [granular];
}
}
var tokenize = function(body) {
var tokens = body.split(';');
var keyValues = [];
for (var i = 0, l = tokens.length; i < l; i++) {
var token = tokens[i];
if (token === '')
continue;
var firstColon = token.indexOf(':');
keyValues.push([
token.substring(0, firstColon),
token.substring(firstColon + 1),
token.indexOf('!important') > -1
]);
}
return keyValues;
};
var optimize = function(tokens, allowAdjacent) {
var merged = [];
var properties = [];
var lastProperty = null;
var rescanTrigger = {};
var removeOverridenBy = function(property, isImportant) {
var overrided = overrides[property];
for (var i = 0, l = overrided.length; i < l; i++) {
for (var j = 0; j < properties.length; j++) {
if (properties[j] != overrided[i] || (merged[j][2] && !isImportant))
continue;
merged.splice(j, 1);
properties.splice(j, 1);
j -= 1;
}
}
};
var mergeablePosition = function(position) {
if (allowAdjacent === false || allowAdjacent === true)
return allowAdjacent;
return allowAdjacent.indexOf(position) > -1;
};
tokensLoop:
for (var i = 0, l = tokens.length; i < l; i++) {
var token = tokens[i];
var property = token[0];
var isImportant = token[2];
var _property = (property == '-ms-filter' || property == 'filter') ?
(lastProperty == 'background' || lastProperty == 'background-image' ? lastProperty : property) :
property;
var toOverridePosition = 0;
// comment is necessary - we assume that if two properties are one after another
// then it is intentional way of redefining property which may not be widely supported
// e.g. a{display:inline-block;display:-moz-inline-box}
// however if `mergeablePosition` yields true then the rule does not apply
// (e.g merging two adjacent selectors: `a{display:block}a{display:block}`)
if (_property != lastProperty || mergeablePosition(i)) {
while (true) {
toOverridePosition = properties.indexOf(_property, toOverridePosition);
if (toOverridePosition == -1)
break;
if (merged[toOverridePosition][2] && !isImportant)
continue tokensLoop;
merged.splice(toOverridePosition, 1);
properties.splice(toOverridePosition, 1);
}
}
merged.push(token);
properties.push(_property);
// certain properties (see values of `overridable`) should trigger removal of
// more granular properties (see keys of `overridable`)
if (rescanTrigger[_property])
removeOverridenBy(_property, isImportant);
// add rescan triggers - if certain property appears later in the list a rescan needs
// to be triggered, e.g 'border-top' triggers a rescan after 'border-top-width' and
// 'border-top-color' as they can be removed
for (var j = 0, list = overridable[_property] || [], m = list.length; j < m; j++)
rescanTrigger[list[j]] = true;
lastProperty = _property;
}
return merged;
};
var rebuild = function(tokens) {
var flat = [];
for (var i = 0, l = tokens.length; i < l; i++) {
flat.push(tokens[i][0] + ':' + tokens[i][1]);
}
return flat.join(';');
};
return {
process: function(body, allowAdjacent) {
var tokens = tokenize(body);
if (tokens.length < 2)
return body;
var optimized = optimize(tokens, allowAdjacent);
return rebuild(optimized);
}
};
};
@@ -0,0 +1,50 @@
module.exports = function ShorthandNotations(data) {
// shorthand notations
var shorthandRegex = function(repeats, hasSuffix) {
var pattern = '(padding|margin|border\\-width|border\\-color|border\\-style|border\\-radius):';
for (var i = 0; i < repeats; i++)
pattern += '([\\d\\w\\.%#\\(\\),]+)' + (i < repeats - 1 ? ' ' : '');
return new RegExp(pattern + (hasSuffix ? '([;}])' : ''), 'g');
};
var from4Values = function() {
return data.replace(shorthandRegex(4), function(match, property, size1, size2, size3, size4) {
if (size1 === size2 && size1 === size3 && size1 === size4)
return property + ':' + size1;
else if (size1 === size3 && size2 === size4)
return property + ':' + size1 + ' ' + size2;
else if (size2 === size4)
return property + ':' + size1 + ' ' + size2 + ' ' + size3;
else
return match;
});
};
var from3Values = function() {
return data.replace(shorthandRegex(3, true), function(match, property, size1, size2, size3, suffix) {
if (size1 === size2 && size1 === size3)
return property + ':' + size1 + suffix;
else if (size1 === size3)
return property + ':' + size1 + ' ' + size2 + suffix;
else
return match;
});
};
var from2Values = function() {
return data.replace(shorthandRegex(2, true), function(match, property, size1, size2, suffix) {
if (size1 === size2)
return property + ':' + size1 + suffix;
else
return match;
});
};
return {
process: function() {
data = from4Values();
data = from3Values();
return from2Values();
}
};
};
@@ -0,0 +1,30 @@
module.exports = function EmptyRemoval(data) {
var stripEmpty = function(cssData) {
var tempData = [];
var nextEmpty = 0;
var cursor = 0;
for (; nextEmpty < cssData.length; ) {
nextEmpty = cssData.indexOf('{}', cursor);
if (nextEmpty == -1)
break;
var startsAt = nextEmpty - 1;
while (cssData[startsAt] && cssData[startsAt] != '}' && cssData[startsAt] != '{')
startsAt--;
tempData.push(cssData.substring(cursor, startsAt + 1));
cursor = nextEmpty + 2;
}
return tempData.length > 0 ?
stripEmpty(tempData.join('') + cssData.substring(cursor, cssData.length)) :
cssData;
};
return {
process: function() {
return stripEmpty(data);
}
};
};
@@ -0,0 +1,221 @@
var Tokenizer = require('./tokenizer');
var PropertyOptimizer = require('../properties/optimizer');
module.exports = function Optimizer(data, context, options) {
var specialSelectors = {
'*': /\-(moz|ms|o|webkit)\-/,
'ie8': /(\-moz\-|\-ms\-|\-o\-|\-webkit\-|:not|:target|:visited|:empty|:first\-of|:last|:nth|:only|:root)/
};
var propertyOptimizer = new PropertyOptimizer();
var cleanUpSelector = function(selectors) {
var plain = [];
selectors = selectors.split(',');
for (var i = 0, l = selectors.length; i < l; i++) {
var sel = selectors[i];
if (plain.indexOf(sel) == -1)
plain.push(sel);
}
return plain.sort().join(',');
};
var isSpecial = function(selector) {
return specialSelectors[options.selectorsMergeMode || '*'].test(selector);
};
var removeDuplicates = function(tokens) {
var matched = {};
var forRemoval = [];
for (var i = 0, l = tokens.length; i < l; i++) {
if (typeof(tokens[i]) == 'string' || tokens[i].block)
continue;
var selector = tokens[i].selector;
var body = tokens[i].body;
var id = body + '@' + selector;
var alreadyMatched = matched[id];
if (alreadyMatched) {
forRemoval.push(alreadyMatched[alreadyMatched.length - 1]);
alreadyMatched.push(i);
} else {
matched[id] = [i];
}
}
forRemoval = forRemoval.sort(function(a, b) { return a > b ? 1 : -1; });
for (var j = 0, n = forRemoval.length; j < n; j++) {
tokens.splice(forRemoval[j] - j, 1);
}
};
var mergeAdjacent = function(tokens) {
var forRemoval = [];
var lastToken = { selector: null, body: null };
for (var i = 0, l = tokens.length; i < l; i++) {
var token = tokens[i];
if (typeof(token) == 'string' || token.block)
continue;
if (token.selector == lastToken.selector) {
var joinAt = [lastToken.body.split(';').length];
lastToken.body = propertyOptimizer.process(lastToken.body + ';' + token.body, joinAt);
forRemoval.push(i);
} else if (token.body == lastToken.body && !isSpecial(token.selector) && !isSpecial(lastToken.selector)) {
lastToken.selector = cleanUpSelector(lastToken.selector + ',' + token.selector);
forRemoval.push(i);
} else {
lastToken = token;
}
}
for (var j = 0, m = forRemoval.length; j < m; j++) {
tokens.splice(forRemoval[j] - j, 1);
}
};
var reduceNonAdjacent = function(tokens) {
var matched = {};
var matchedMoreThanOnce = [];
var partiallyReduced = [];
var token, selector, selectors;
var removeEmpty = function(value) {
return value.length > 0 ? value : '';
};
for (var i = 0, l = tokens.length; i < l; i++) {
token = tokens[i];
selector = token.selector;
if (typeof(token) == 'string' || token.block)
continue;
selectors = selector.split(',');
if (selectors.length > 1)
selectors.push(selector);
for (var j = 0, m = selectors.length; j < m; j++) {
var sel = selectors[j];
var alreadyMatched = matched[sel];
if (alreadyMatched) {
if (alreadyMatched.length == 1)
matchedMoreThanOnce.push(sel);
alreadyMatched.push(i);
} else {
matched[sel] = [i];
}
}
}
matchedMoreThanOnce.forEach(function(selector) {
var matchPositions = matched[selector];
var bodies = [];
var joinsAt = [];
for (var j = 0, m = matchPositions.length; j < m; j++) {
var body = tokens[matchPositions[j]].body;
bodies.push(body);
joinsAt.push((joinsAt[j - 1] || 0) + body.split(';').length);
}
var optimizedBody = propertyOptimizer.process(bodies.join(';'), joinsAt);
var optimizedTokens = optimizedBody.split(';');
var k = optimizedTokens.length - 1;
var currentMatch = matchPositions.length - 1;
while (currentMatch >= 0) {
if (bodies[currentMatch].indexOf(optimizedTokens[k]) > -1 && k > -1) {
k -= 1;
continue;
}
var tokenIndex = matchPositions[currentMatch];
var token = tokens[tokenIndex];
var reducedBody = optimizedTokens
.splice(k + 1)
.filter(removeEmpty)
.join(';');
if (token.selector == selector) {
token.body = reducedBody;
} else {
token._partials = token._partials || [];
token._partials.push(reducedBody);
if (partiallyReduced.indexOf(tokenIndex) == -1)
partiallyReduced.push(tokenIndex);
}
currentMatch -= 1;
}
});
// process those tokens which were partially reduced
// i.e. at least one of token's selectors saw reduction
// if all selectors were reduced to same value we can override it
for (i = 0, l = partiallyReduced.length; i < l; i++) {
token = tokens[partiallyReduced[i]];
selectors = token.selector.split(',');
if (token._partials.length == selectors.length && token.body != token._partials[0]) {
var newBody = token._partials[0];
for (var k = 1, n = token._partials.length; k < n; k++) {
if (token._partials[k] != newBody)
break;
}
if (k == n)
token.body = newBody;
}
delete token._partials;
}
};
var optimize = function(tokens) {
tokens = (Array.isArray(tokens) ? tokens : [tokens]);
for (var i = 0, l = tokens.length; i < l; i++) {
var token = tokens[i];
if (token.selector) {
token.selector = cleanUpSelector(token.selector);
token.body = propertyOptimizer.process(token.body, false);
} else if (token.block) {
optimize(token.body);
}
}
removeDuplicates(tokens);
mergeAdjacent(tokens);
reduceNonAdjacent(tokens);
};
var rebuild = function(tokens) {
return (Array.isArray(tokens) ? tokens : [tokens])
.map(function(token) {
if (typeof token == 'string')
return token;
if (token.block)
return token.block + '{' + rebuild(token.body) + '}';
else
return token.selector + '{' + token.body + '}';
})
.join(options.keepBreaks ? options.lineBreak : '');
};
return {
process: function() {
var tokenized = new Tokenizer(data, context).process();
optimize(tokenized);
return rebuild(tokenized);
}
};
};
@@ -0,0 +1,129 @@
module.exports = function Tokenizer(data, minifyContext) {
var flatBlock = /^@(font\-face|page|\-ms\-viewport|\-o\-viewport|viewport)/;
var whatsNext = function(context) {
var cursor = context.cursor;
var mode = context.mode;
var closest;
if (mode == 'body') {
closest = data.indexOf('}', cursor);
return closest > -1 ?
[closest, 'bodyEnd'] :
null;
}
var nextSpecial = data.indexOf('@', cursor);
var nextEscape = mode == 'top' ? data.indexOf('__ESCAPED_COMMENT_CLEAN_CSS', cursor) : -1;
var nextBodyStart = data.indexOf('{', cursor);
var nextBodyEnd = data.indexOf('}', cursor);
closest = nextSpecial;
if (closest == -1 || (nextEscape > -1 && nextEscape < closest))
closest = nextEscape;
if (closest == -1 || (nextBodyStart > -1 && nextBodyStart < closest))
closest = nextBodyStart;
if (closest == -1 || (nextBodyEnd > -1 && nextBodyEnd < closest))
closest = nextBodyEnd;
if (closest == -1)
return;
if (nextEscape === closest)
return [closest, 'escape'];
if (nextBodyStart === closest)
return [closest, 'bodyStart'];
if (nextBodyEnd === closest)
return [closest, 'bodyEnd'];
if (nextSpecial === closest)
return [closest, 'special'];
};
var tokenize = function(context) {
var tokenized = [];
context = context || { cursor: 0, mode: 'top' };
while (true) {
var next = whatsNext(context);
if (!next) {
var whatsLeft = data.substring(context.cursor);
if (whatsLeft.length > 0) {
tokenized.push(whatsLeft);
context.cursor += whatsLeft.length;
}
break;
}
var nextSpecial = next[0];
var what = next[1];
var nextEnd;
var oldMode;
if (what == 'special') {
var fragment = data.substring(nextSpecial, context.cursor + '@font-face'.length + 1);
var isSingle = fragment.indexOf('@import') === 0 || fragment.indexOf('@charset') === 0;
if (isSingle) {
nextEnd = data.indexOf(';', nextSpecial + 1);
tokenized.push(data.substring(context.cursor, nextEnd + 1));
context.cursor = nextEnd + 1;
} else {
nextEnd = data.indexOf('{', nextSpecial + 1);
var block = data.substring(context.cursor, nextEnd).trim();
var isFlat = flatBlock.test(block);
oldMode = context.mode;
context.cursor = nextEnd + 1;
context.mode = isFlat ? 'body' : 'block';
var specialBody = tokenize(context);
context.mode = oldMode;
tokenized.push({ block: block, body: specialBody });
}
} else if (what == 'escape') {
nextEnd = data.indexOf('__', nextSpecial + 1);
var escaped = data.substring(context.cursor, nextEnd + 2);
tokenized.push(escaped);
context.cursor = nextEnd + 2;
} else if (what == 'bodyStart') {
var selector = data.substring(context.cursor, nextSpecial).trim();
oldMode = context.mode;
context.cursor = nextSpecial + 1;
context.mode = 'body';
var body = tokenize(context);
context.mode = oldMode;
tokenized.push({ selector: selector, body: body });
} else if (what == 'bodyEnd') {
// extra closing brace at the top level can be safely ignored
if (context.mode == 'top') {
var at = context.cursor;
var warning = data[context.cursor] == '}' ?
'Unexpected \'}\' in \'' + data.substring(at - 20, at + 20) + '\'. Ignoring.' :
'Unexpected content: \'' + data.substring(at, nextSpecial + 1) + '\'. Ignoring.';
minifyContext.warnings.push(warning);
context.cursor = nextSpecial + 1;
continue;
}
if (context.mode != 'block')
tokenized = data.substring(context.cursor, nextSpecial);
context.cursor = nextSpecial + 1;
break;
}
}
return tokenized;
};
return {
process: function() {
return tokenize();
}
};
};
@@ -0,0 +1,59 @@
var EscapeStore = require('./escape-store');
module.exports = function Comments(keepSpecialComments, keepBreaks, lineBreak) {
var comments = new EscapeStore('COMMENT');
return {
// Strip special comments (/*! ... */) by replacing them by a special marker
// for further restoring. Plain comments are removed. It's done by scanning data using
// String#indexOf scanning instead of regexps to speed up the process.
escape: function(data) {
var tempData = [];
var nextStart = 0;
var nextEnd = 0;
var cursor = 0;
for (; nextEnd < data.length; ) {
nextStart = data.indexOf('/*', nextEnd);
nextEnd = data.indexOf('*/', nextStart + 2);
if (nextStart == -1 || nextEnd == -1)
break;
tempData.push(data.substring(cursor, nextStart));
if (data[nextStart + 2] == '!') {
// in case of special comments, replace them with a placeholder
var comment = data.substring(nextStart, nextEnd + 2);
var placeholder = comments.store(comment);
tempData.push(placeholder);
}
cursor = nextEnd + 2;
}
return tempData.length > 0 ?
tempData.join('') + data.substring(cursor, data.length) :
data;
},
restore: function(data) {
var restored = 0;
var breakSuffix = keepBreaks ? lineBreak : '';
return data.replace(new RegExp(comments.placeholderPattern + '(' + lineBreak + '| )?', 'g'), function(match, placeholder) {
restored++;
switch (keepSpecialComments) {
case '*':
return comments.restore(placeholder) + breakSuffix;
case 1:
case '1':
return restored == 1 ?
comments.restore(placeholder) + breakSuffix :
'';
case 0:
case '0':
return '';
}
});
}
};
};
@@ -0,0 +1,32 @@
module.exports = function EscapeStore(placeholderRoot) {
placeholderRoot = 'ESCAPED_' + placeholderRoot + '_CLEAN_CSS';
var placeholderToData = {};
var dataToPlaceholder = {};
var count = 0;
var nextPlaceholder = function() {
return '__' + placeholderRoot + (count++) + '__';
};
var pattern = '(__' + placeholderRoot + '\\d{1,}__)';
return {
placeholderPattern: pattern,
placeholderRegExp: new RegExp(pattern, 'g'),
store: function(data) {
var placeholder = dataToPlaceholder[data];
if (!placeholder) {
placeholder = nextPlaceholder();
placeholderToData[placeholder] = data;
dataToPlaceholder[data] = placeholder;
}
return placeholder;
},
restore: function(placeholder) {
return placeholderToData[placeholder];
}
};
};
@@ -0,0 +1,69 @@
var EscapeStore = require('./escape-store');
module.exports = function Expressions() {
var expressions = new EscapeStore('EXPRESSION');
var findEnd = function(data, start) {
var end = start + 'expression'.length;
var level = 0;
var quoted = false;
while(true) {
var next = data[end++];
if (quoted) {
quoted = next != '\'' && next != '"';
} else {
quoted = next == '\'' || next == '"';
if (next == '(')
level++;
if (next == ')')
level--;
if (next == '}' && level == 1) {
end--;
level--;
}
}
if (level === 0 || !next)
break;
}
return end;
};
return {
// Escapes expressions by replacing them by a special
// marker for further restoring. It's done via string scanning
// instead of regexps to speed up the process.
escape: function(data) {
var nextStart = 0;
var nextEnd = 0;
var cursor = 0;
var tempData = [];
for (; nextEnd < data.length; ) {
nextStart = data.indexOf('expression(', nextEnd);
if (nextStart == -1)
break;
nextEnd = findEnd(data, nextStart);
var expression = data.substring(nextStart, nextEnd);
var placeholder = expressions.store(expression);
tempData.push(data.substring(cursor, nextStart));
tempData.push(placeholder);
cursor = nextEnd;
}
return tempData.length > 0 ?
tempData.join('') + data.substring(cursor, data.length) :
data;
},
restore: function(data) {
return data.replace(expressions.placeholderRegExp, expressions.restore);
}
};
};
+76
View File
@@ -0,0 +1,76 @@
var EscapeStore = require('./escape-store');
module.exports = function Free() {
var texts = new EscapeStore('FREE_TEXT');
var findNonEscapedEnd = function(data, matched, start) {
var end = start;
while (true) {
end = data.indexOf(matched, end);
if (end > -1 && data[end - 1] == '\\') {
end += 1;
continue;
} else {
break;
}
}
return end;
};
return {
// Strip content tags by replacing them by the a special
// marker for further restoring. It's done via string scanning
// instead of regexps to speed up the process.
escape: function(data) {
var tempData = [];
var nextStart = 0;
var nextEnd = 0;
var cursor = 0;
var matchedParenthesis = null;
var singleParenthesis = '\'';
var doubleParenthesis = '"';
var dataLength = data.length;
for (; nextEnd < data.length; ) {
var nextStartSingle = data.indexOf(singleParenthesis, nextEnd + 1);
var nextStartDouble = data.indexOf(doubleParenthesis, nextEnd + 1);
if (nextStartSingle == -1)
nextStartSingle = dataLength;
if (nextStartDouble == -1)
nextStartDouble = dataLength;
if (nextStartSingle < nextStartDouble) {
nextStart = nextStartSingle;
matchedParenthesis = singleParenthesis;
} else {
nextStart = nextStartDouble;
matchedParenthesis = doubleParenthesis;
}
if (nextStart == -1)
break;
nextEnd = findNonEscapedEnd(data, matchedParenthesis, nextStart + 1);
if (nextEnd == -1)
break;
var text = data.substring(nextStart, nextEnd + 1);
var placeholder = texts.store(text);
tempData.push(data.substring(cursor, nextStart));
tempData.push(placeholder);
cursor = nextEnd + 1;
}
return tempData.length > 0 ?
tempData.join('') + data.substring(cursor, data.length) :
data;
},
restore: function(data) {
return data.replace(texts.placeholderRegExp, texts.restore);
}
};
};
+39
View File
@@ -0,0 +1,39 @@
var EscapeStore = require('./escape-store');
module.exports = function Urls() {
var urls = new EscapeStore('URL');
return {
// Strip urls by replacing them by a special
// marker for further restoring. It's done via string scanning
// instead of regexps to speed up the process.
escape: function(data) {
var nextStart = 0;
var nextEnd = 0;
var cursor = 0;
var tempData = [];
for (; nextEnd < data.length; ) {
nextStart = data.indexOf('url(', nextEnd);
if (nextStart == -1)
break;
nextEnd = data.indexOf(')', nextStart);
var url = data.substring(nextStart, nextEnd + 1);
var placeholder = urls.store(url);
tempData.push(data.substring(cursor, nextStart));
tempData.push(placeholder);
cursor = nextEnd + 1;
}
return tempData.length > 0 ?
tempData.join('') + data.substring(cursor, data.length) :
data;
},
restore: function(data) {
return data.replace(urls.placeholderRegExp, urls.restore);
}
};
};
@@ -0,0 +1,179 @@
2.0.0 / 2013-07-18
==================
* remove input methods (.prompt, .confirm, etc)
1.3.2 / 2013-07-18
==================
* add support for sub-commands to co-exist with the original command
1.3.1 / 2013-07-18
==================
* add quick .runningCommand hack so you can opt-out of other logic when running a sub command
1.3.0 / 2013-07-09
==================
* add EACCES error handling
* fix sub-command --help
1.2.0 / 2013-06-13
==================
* allow "-" hyphen as an option argument
* support for RegExp coercion
1.1.1 / 2012-11-20
==================
* add more sub-command padding
* fix .usage() when args are present. Closes #106
1.1.0 / 2012-11-16
==================
* add git-style executable subcommand support. Closes #94
1.0.5 / 2012-10-09
==================
* fix `--name` clobbering. Closes #92
* fix examples/help. Closes #89
1.0.4 / 2012-09-03
==================
* add `outputHelp()` method.
1.0.3 / 2012-08-30
==================
* remove invalid .version() defaulting
1.0.2 / 2012-08-24
==================
* add `--foo=bar` support [arv]
* fix password on node 0.8.8. Make backward compatible with 0.6 [focusaurus]
1.0.1 / 2012-08-03
==================
* fix issue #56
* fix tty.setRawMode(mode) was moved to tty.ReadStream#setRawMode() (i.e. process.stdin.setRawMode())
1.0.0 / 2012-07-05
==================
* add support for optional option descriptions
* add defaulting of `.version()` to package.json's version
0.6.1 / 2012-06-01
==================
* Added: append (yes or no) on confirmation
* Added: allow node.js v0.7.x
0.6.0 / 2012-04-10
==================
* Added `.prompt(obj, callback)` support. Closes #49
* Added default support to .choose(). Closes #41
* Fixed the choice example
0.5.1 / 2011-12-20
==================
* Fixed `password()` for recent nodes. Closes #36
0.5.0 / 2011-12-04
==================
* Added sub-command option support [itay]
0.4.3 / 2011-12-04
==================
* Fixed custom help ordering. Closes #32
0.4.2 / 2011-11-24
==================
* Added travis support
* Fixed: line-buffered input automatically trimmed. Closes #31
0.4.1 / 2011-11-18
==================
* Removed listening for "close" on --help
0.4.0 / 2011-11-15
==================
* Added support for `--`. Closes #24
0.3.3 / 2011-11-14
==================
* Fixed: wait for close event when writing help info [Jerry Hamlet]
0.3.2 / 2011-11-01
==================
* Fixed long flag definitions with values [felixge]
0.3.1 / 2011-10-31
==================
* Changed `--version` short flag to `-V` from `-v`
* Changed `.version()` so it's configurable [felixge]
0.3.0 / 2011-10-31
==================
* Added support for long flags only. Closes #18
0.2.1 / 2011-10-24
==================
* "node": ">= 0.4.x < 0.7.0". Closes #20
0.2.0 / 2011-09-26
==================
* Allow for defaults that are not just boolean. Default peassignment only occurs for --no-*, optional, and required arguments. [Jim Isaacs]
0.1.0 / 2011-08-24
==================
* Added support for custom `--help` output
0.0.5 / 2011-08-18
==================
* Changed: when the user enters nothing prompt for password again
* Fixed issue with passwords beginning with numbers [NuckChorris]
0.0.4 / 2011-08-15
==================
* Fixed `Commander#args`
0.0.3 / 2011-08-15
==================
* Added default option value support
0.0.2 / 2011-08-15
==================
* Added mask support to `Command#password(str[, mask], fn)`
* Added `Command#password(str, fn)`
0.0.1 / 2010-01-03
==================
* Initial release
@@ -0,0 +1,195 @@
# Commander.js
The complete solution for [node.js](http://nodejs.org) command-line interfaces, inspired by Ruby's [commander](https://github.com/visionmedia/commander).
[![Build Status](https://secure.travis-ci.org/visionmedia/commander.js.png)](http://travis-ci.org/visionmedia/commander.js)
## Installation
$ npm install commander
## Option parsing
Options with commander are defined with the `.option()` method, also serving as documentation for the options. The example below parses args and options from `process.argv`, leaving remaining args as the `program.args` array which were not consumed by options.
```js
#!/usr/bin/env node
/**
* Module dependencies.
*/
var program = require('commander');
program
.version('0.0.1')
.option('-p, --peppers', 'Add peppers')
.option('-P, --pineapple', 'Add pineapple')
.option('-b, --bbq', 'Add bbq sauce')
.option('-c, --cheese [type]', 'Add the specified type of cheese [marble]', 'marble')
.parse(process.argv);
console.log('you ordered a pizza with:');
if (program.peppers) console.log(' - peppers');
if (program.pineapple) console.log(' - pineapple');
if (program.bbq) console.log(' - bbq');
console.log(' - %s cheese', program.cheese);
```
Short flags may be passed as a single arg, for example `-abc` is equivalent to `-a -b -c`. Multi-word options such as "--template-engine" are camel-cased, becoming `program.templateEngine` etc.
## Automated --help
The help information is auto-generated based on the information commander already knows about your program, so the following `--help` info is for free:
```
$ ./examples/pizza --help
Usage: pizza [options]
Options:
-V, --version output the version number
-p, --peppers Add peppers
-P, --pineapple Add pineapple
-b, --bbq Add bbq sauce
-c, --cheese <type> Add the specified type of cheese [marble]
-h, --help output usage information
```
## Coercion
```js
function range(val) {
return val.split('..').map(Number);
}
function list(val) {
return val.split(',');
}
program
.version('0.0.1')
.usage('[options] <file ...>')
.option('-i, --integer <n>', 'An integer argument', parseInt)
.option('-f, --float <n>', 'A float argument', parseFloat)
.option('-r, --range <a>..<b>', 'A range', range)
.option('-l, --list <items>', 'A list', list)
.option('-o, --optional [value]', 'An optional value')
.parse(process.argv);
console.log(' int: %j', program.integer);
console.log(' float: %j', program.float);
console.log(' optional: %j', program.optional);
program.range = program.range || [];
console.log(' range: %j..%j', program.range[0], program.range[1]);
console.log(' list: %j', program.list);
console.log(' args: %j', program.args);
```
## Custom help
You can display arbitrary `-h, --help` information
by listening for "--help". Commander will automatically
exit once you are done so that the remainder of your program
does not execute causing undesired behaviours, for example
in the following executable "stuff" will not output when
`--help` is used.
```js
#!/usr/bin/env node
/**
* Module dependencies.
*/
var program = require('../');
function list(val) {
return val.split(',').map(Number);
}
program
.version('0.0.1')
.option('-f, --foo', 'enable some foo')
.option('-b, --bar', 'enable some bar')
.option('-B, --baz', 'enable some baz');
// must be before .parse() since
// node's emit() is immediate
program.on('--help', function(){
console.log(' Examples:');
console.log('');
console.log(' $ custom-help --help');
console.log(' $ custom-help -h');
console.log('');
});
program.parse(process.argv);
console.log('stuff');
```
yielding the following help output:
```
Usage: custom-help [options]
Options:
-h, --help output usage information
-V, --version output the version number
-f, --foo enable some foo
-b, --bar enable some bar
-B, --baz enable some baz
Examples:
$ custom-help --help
$ custom-help -h
```
## .outputHelp()
Output help information without exiting.
## .help()
Output help information and exit immediately.
## Links
- [API documentation](http://visionmedia.github.com/commander.js/)
- [ascii tables](https://github.com/LearnBoost/cli-table)
- [progress bars](https://github.com/visionmedia/node-progress)
- [more progress bars](https://github.com/substack/node-multimeter)
- [examples](https://github.com/visionmedia/commander.js/tree/master/examples)
## License
(The MIT License)
Copyright (c) 2011 TJ Holowaychuk &lt;tj@vision-media.ca&gt;
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.
@@ -0,0 +1,847 @@
/**
* Module dependencies.
*/
var EventEmitter = require('events').EventEmitter;
var spawn = require('child_process').spawn;
var fs = require('fs');
var exists = fs.existsSync;
var path = require('path');
var dirname = path.dirname;
var basename = path.basename;
/**
* Expose the root command.
*/
exports = module.exports = new Command;
/**
* Expose `Command`.
*/
exports.Command = Command;
/**
* Expose `Option`.
*/
exports.Option = Option;
/**
* Initialize a new `Option` with the given `flags` and `description`.
*
* @param {String} flags
* @param {String} description
* @api public
*/
function Option(flags, description) {
this.flags = flags;
this.required = ~flags.indexOf('<');
this.optional = ~flags.indexOf('[');
this.bool = !~flags.indexOf('-no-');
flags = flags.split(/[ ,|]+/);
if (flags.length > 1 && !/^[[<]/.test(flags[1])) this.short = flags.shift();
this.long = flags.shift();
this.description = description || '';
}
/**
* Return option name.
*
* @return {String}
* @api private
*/
Option.prototype.name = function(){
return this.long
.replace('--', '')
.replace('no-', '');
};
/**
* Check if `arg` matches the short or long flag.
*
* @param {String} arg
* @return {Boolean}
* @api private
*/
Option.prototype.is = function(arg){
return arg == this.short
|| arg == this.long;
};
/**
* Initialize a new `Command`.
*
* @param {String} name
* @api public
*/
function Command(name) {
this.commands = [];
this.options = [];
this._execs = [];
this._args = [];
this._name = name;
}
/**
* Inherit from `EventEmitter.prototype`.
*/
Command.prototype.__proto__ = EventEmitter.prototype;
/**
* Add command `name`.
*
* The `.action()` callback is invoked when the
* command `name` is specified via __ARGV__,
* and the remaining arguments are applied to the
* function for access.
*
* When the `name` is "*" an un-matched command
* will be passed as the first arg, followed by
* the rest of __ARGV__ remaining.
*
* Examples:
*
* program
* .version('0.0.1')
* .option('-C, --chdir <path>', 'change the working directory')
* .option('-c, --config <path>', 'set config path. defaults to ./deploy.conf')
* .option('-T, --no-tests', 'ignore test hook')
*
* program
* .command('setup')
* .description('run remote setup commands')
* .action(function(){
* console.log('setup');
* });
*
* program
* .command('exec <cmd>')
* .description('run the given remote command')
* .action(function(cmd){
* console.log('exec "%s"', cmd);
* });
*
* program
* .command('*')
* .description('deploy the given env')
* .action(function(env){
* console.log('deploying "%s"', env);
* });
*
* program.parse(process.argv);
*
* @param {String} name
* @param {String} [desc]
* @return {Command} the new command
* @api public
*/
Command.prototype.command = function(name, desc){
var args = name.split(/ +/);
var cmd = new Command(args.shift());
if (desc) cmd.description(desc);
if (desc) this.executables = true;
if (desc) this._execs[cmd._name] = true;
this.commands.push(cmd);
cmd.parseExpectedArgs(args);
cmd.parent = this;
if (desc) return this;
return cmd;
};
/**
* Add an implicit `help [cmd]` subcommand
* which invokes `--help` for the given command.
*
* @api private
*/
Command.prototype.addImplicitHelpCommand = function() {
this.command('help [cmd]', 'display help for [cmd]');
};
/**
* Parse expected `args`.
*
* For example `["[type]"]` becomes `[{ required: false, name: 'type' }]`.
*
* @param {Array} args
* @return {Command} for chaining
* @api public
*/
Command.prototype.parseExpectedArgs = function(args){
if (!args.length) return;
var self = this;
args.forEach(function(arg){
switch (arg[0]) {
case '<':
self._args.push({ required: true, name: arg.slice(1, -1) });
break;
case '[':
self._args.push({ required: false, name: arg.slice(1, -1) });
break;
}
});
return this;
};
/**
* Register callback `fn` for the command.
*
* Examples:
*
* program
* .command('help')
* .description('display verbose help')
* .action(function(){
* // output help here
* });
*
* @param {Function} fn
* @return {Command} for chaining
* @api public
*/
Command.prototype.action = function(fn){
var self = this;
this.parent.on(this._name, function(args, unknown){
// Parse any so-far unknown options
unknown = unknown || [];
var parsed = self.parseOptions(unknown);
// Output help if necessary
outputHelpIfNecessary(self, parsed.unknown);
// If there are still any unknown options, then we simply
// die, unless someone asked for help, in which case we give it
// to them, and then we die.
if (parsed.unknown.length > 0) {
self.unknownOption(parsed.unknown[0]);
}
// Leftover arguments need to be pushed back. Fixes issue #56
if (parsed.args.length) args = parsed.args.concat(args);
self._args.forEach(function(arg, i){
if (arg.required && null == args[i]) {
self.missingArgument(arg.name);
}
});
// Always append ourselves to the end of the arguments,
// to make sure we match the number of arguments the user
// expects
if (self._args.length) {
args[self._args.length] = self;
} else {
args.push(self);
}
fn.apply(this, args);
});
return this;
};
/**
* Define option with `flags`, `description` and optional
* coercion `fn`.
*
* The `flags` string should contain both the short and long flags,
* separated by comma, a pipe or space. The following are all valid
* all will output this way when `--help` is used.
*
* "-p, --pepper"
* "-p|--pepper"
* "-p --pepper"
*
* Examples:
*
* // simple boolean defaulting to false
* program.option('-p, --pepper', 'add pepper');
*
* --pepper
* program.pepper
* // => Boolean
*
* // simple boolean defaulting to false
* program.option('-C, --no-cheese', 'remove cheese');
*
* program.cheese
* // => true
*
* --no-cheese
* program.cheese
* // => true
*
* // required argument
* program.option('-C, --chdir <path>', 'change the working directory');
*
* --chdir /tmp
* program.chdir
* // => "/tmp"
*
* // optional argument
* program.option('-c, --cheese [type]', 'add cheese [marble]');
*
* @param {String} flags
* @param {String} description
* @param {Function|Mixed} fn or default
* @param {Mixed} defaultValue
* @return {Command} for chaining
* @api public
*/
Command.prototype.option = function(flags, description, fn, defaultValue){
var self = this
, option = new Option(flags, description)
, oname = option.name()
, name = camelcase(oname);
// default as 3rd arg
if ('function' != typeof fn) defaultValue = fn, fn = null;
// preassign default value only for --no-*, [optional], or <required>
if (false == option.bool || option.optional || option.required) {
// when --no-* we make sure default is true
if (false == option.bool) defaultValue = true;
// preassign only if we have a default
if (undefined !== defaultValue) self[name] = defaultValue;
}
// register the option
this.options.push(option);
// when it's passed assign the value
// and conditionally invoke the callback
this.on(oname, function(val){
// coercion
if (null != val && fn) val = fn(val);
// unassigned or bool
if ('boolean' == typeof self[name] || 'undefined' == typeof self[name]) {
// if no value, bool true, and we have a default, then use it!
if (null == val) {
self[name] = option.bool
? defaultValue || true
: false;
} else {
self[name] = val;
}
} else if (null !== val) {
// reassign
self[name] = val;
}
});
return this;
};
/**
* Parse `argv`, settings options and invoking commands when defined.
*
* @param {Array} argv
* @return {Command} for chaining
* @api public
*/
Command.prototype.parse = function(argv){
// implicit help
if (this.executables) this.addImplicitHelpCommand();
// store raw args
this.rawArgs = argv;
// guess name
this._name = this._name || basename(argv[1]);
// process argv
var parsed = this.parseOptions(this.normalize(argv.slice(2)));
var args = this.args = parsed.args;
var result = this.parseArgs(this.args, parsed.unknown);
// executable sub-commands
var name = result.args[0];
if (this._execs[name]) return this.executeSubCommand(argv, args, parsed.unknown);
return result;
};
/**
* Execute a sub-command executable.
*
* @param {Array} argv
* @param {Array} args
* @param {Array} unknown
* @api private
*/
Command.prototype.executeSubCommand = function(argv, args, unknown) {
args = args.concat(unknown);
if (!args.length) this.help();
if ('help' == args[0] && 1 == args.length) this.help();
// <cmd> --help
if ('help' == args[0]) {
args[0] = args[1];
args[1] = '--help';
}
// executable
var dir = dirname(argv[1]);
var bin = basename(argv[1]) + '-' + args[0];
// check for ./<bin> first
var local = path.join(dir, bin);
// run it
args = args.slice(1);
var proc = spawn(local, args, { stdio: 'inherit', customFds: [0, 1, 2] });
proc.on('error', function(err){
if (err.code == "ENOENT") {
console.error('\n %s(1) does not exist, try --help\n', bin);
} else if (err.code == "EACCES") {
console.error('\n %s(1) not executable. try chmod or run with root\n', bin);
}
});
this.runningCommand = proc;
};
/**
* Normalize `args`, splitting joined short flags. For example
* the arg "-abc" is equivalent to "-a -b -c".
* This also normalizes equal sign and splits "--abc=def" into "--abc def".
*
* @param {Array} args
* @return {Array}
* @api private
*/
Command.prototype.normalize = function(args){
var ret = []
, arg
, index;
for (var i = 0, len = args.length; i < len; ++i) {
arg = args[i];
if (arg.length > 1 && '-' == arg[0] && '-' != arg[1]) {
arg.slice(1).split('').forEach(function(c){
ret.push('-' + c);
});
} else if (/^--/.test(arg) && ~(index = arg.indexOf('='))) {
ret.push(arg.slice(0, index), arg.slice(index + 1));
} else {
ret.push(arg);
}
}
return ret;
};
/**
* Parse command `args`.
*
* When listener(s) are available those
* callbacks are invoked, otherwise the "*"
* event is emitted and those actions are invoked.
*
* @param {Array} args
* @return {Command} for chaining
* @api private
*/
Command.prototype.parseArgs = function(args, unknown){
var cmds = this.commands
, len = cmds.length
, name;
if (args.length) {
name = args[0];
if (this.listeners(name).length) {
this.emit(args.shift(), args, unknown);
} else {
this.emit('*', args);
}
} else {
outputHelpIfNecessary(this, unknown);
// If there were no args and we have unknown options,
// then they are extraneous and we need to error.
if (unknown.length > 0) {
this.unknownOption(unknown[0]);
}
}
return this;
};
/**
* Return an option matching `arg` if any.
*
* @param {String} arg
* @return {Option}
* @api private
*/
Command.prototype.optionFor = function(arg){
for (var i = 0, len = this.options.length; i < len; ++i) {
if (this.options[i].is(arg)) {
return this.options[i];
}
}
};
/**
* Parse options from `argv` returning `argv`
* void of these options.
*
* @param {Array} argv
* @return {Array}
* @api public
*/
Command.prototype.parseOptions = function(argv){
var args = []
, len = argv.length
, literal
, option
, arg;
var unknownOptions = [];
// parse options
for (var i = 0; i < len; ++i) {
arg = argv[i];
// literal args after --
if ('--' == arg) {
literal = true;
continue;
}
if (literal) {
args.push(arg);
continue;
}
// find matching Option
option = this.optionFor(arg);
// option is defined
if (option) {
// requires arg
if (option.required) {
arg = argv[++i];
if (null == arg) return this.optionMissingArgument(option);
if ('-' == arg[0] && '-' != arg) return this.optionMissingArgument(option, arg);
this.emit(option.name(), arg);
// optional arg
} else if (option.optional) {
arg = argv[i+1];
if (null == arg || ('-' == arg[0] && '-' != arg)) {
arg = null;
} else {
++i;
}
this.emit(option.name(), arg);
// bool
} else {
this.emit(option.name());
}
continue;
}
// looks like an option
if (arg.length > 1 && '-' == arg[0]) {
unknownOptions.push(arg);
// If the next argument looks like it might be
// an argument for this option, we pass it on.
// If it isn't, then it'll simply be ignored
if (argv[i+1] && '-' != argv[i+1][0]) {
unknownOptions.push(argv[++i]);
}
continue;
}
// arg
args.push(arg);
}
return { args: args, unknown: unknownOptions };
};
/**
* Argument `name` is missing.
*
* @param {String} name
* @api private
*/
Command.prototype.missingArgument = function(name){
console.error();
console.error(" error: missing required argument `%s'", name);
console.error();
process.exit(1);
};
/**
* `Option` is missing an argument, but received `flag` or nothing.
*
* @param {String} option
* @param {String} flag
* @api private
*/
Command.prototype.optionMissingArgument = function(option, flag){
console.error();
if (flag) {
console.error(" error: option `%s' argument missing, got `%s'", option.flags, flag);
} else {
console.error(" error: option `%s' argument missing", option.flags);
}
console.error();
process.exit(1);
};
/**
* Unknown option `flag`.
*
* @param {String} flag
* @api private
*/
Command.prototype.unknownOption = function(flag){
console.error();
console.error(" error: unknown option `%s'", flag);
console.error();
process.exit(1);
};
/**
* Set the program version to `str`.
*
* This method auto-registers the "-V, --version" flag
* which will print the version number when passed.
*
* @param {String} str
* @param {String} flags
* @return {Command} for chaining
* @api public
*/
Command.prototype.version = function(str, flags){
if (0 == arguments.length) return this._version;
this._version = str;
flags = flags || '-V, --version';
this.option(flags, 'output the version number');
this.on('version', function(){
console.log(str);
process.exit(0);
});
return this;
};
/**
* Set the description `str`.
*
* @param {String} str
* @return {String|Command}
* @api public
*/
Command.prototype.description = function(str){
if (0 == arguments.length) return this._description;
this._description = str;
return this;
};
/**
* Set / get the command usage `str`.
*
* @param {String} str
* @return {String|Command}
* @api public
*/
Command.prototype.usage = function(str){
var args = this._args.map(function(arg){
return arg.required
? '<' + arg.name + '>'
: '[' + arg.name + ']';
});
var usage = '[options'
+ (this.commands.length ? '] [command' : '')
+ ']'
+ (this._args.length ? ' ' + args : '');
if (0 == arguments.length) return this._usage || usage;
this._usage = str;
return this;
};
/**
* Return the largest option length.
*
* @return {Number}
* @api private
*/
Command.prototype.largestOptionLength = function(){
return this.options.reduce(function(max, option){
return Math.max(max, option.flags.length);
}, 0);
};
/**
* Return help for options.
*
* @return {String}
* @api private
*/
Command.prototype.optionHelp = function(){
var width = this.largestOptionLength();
// Prepend the help information
return [pad('-h, --help', width) + ' ' + 'output usage information']
.concat(this.options.map(function(option){
return pad(option.flags, width)
+ ' ' + option.description;
}))
.join('\n');
};
/**
* Return command help documentation.
*
* @return {String}
* @api private
*/
Command.prototype.commandHelp = function(){
if (!this.commands.length) return '';
return [
''
, ' Commands:'
, ''
, this.commands.map(function(cmd){
var args = cmd._args.map(function(arg){
return arg.required
? '<' + arg.name + '>'
: '[' + arg.name + ']';
}).join(' ');
return pad(cmd._name
+ (cmd.options.length
? ' [options]'
: '') + ' ' + args, 22)
+ (cmd.description()
? ' ' + cmd.description()
: '');
}).join('\n').replace(/^/gm, ' ')
, ''
].join('\n');
};
/**
* Return program help documentation.
*
* @return {String}
* @api private
*/
Command.prototype.helpInformation = function(){
return [
''
, ' Usage: ' + this._name + ' ' + this.usage()
, '' + this.commandHelp()
, ' Options:'
, ''
, '' + this.optionHelp().replace(/^/gm, ' ')
, ''
, ''
].join('\n');
};
/**
* Output help information for this command
*
* @api public
*/
Command.prototype.outputHelp = function(){
process.stdout.write(this.helpInformation());
this.emit('--help');
};
/**
* Output help information and exit.
*
* @api public
*/
Command.prototype.help = function(){
this.outputHelp();
process.exit();
};
/**
* Camel-case the given `flag`
*
* @param {String} flag
* @return {String}
* @api private
*/
function camelcase(flag) {
return flag.split('-').reduce(function(str, word){
return str + word[0].toUpperCase() + word.slice(1);
});
}
/**
* Pad `str` to `width`.
*
* @param {String} str
* @param {Number} width
* @return {String}
* @api private
*/
function pad(str, width) {
var len = Math.max(0, width - str.length);
return str + Array(len + 1).join(' ');
}
/**
* Output help information if necessary
*
* @param {Command} command to output help for
* @param {Array} array of options to search for -h or --help
* @api private
*/
function outputHelpIfNecessary(cmd, options) {
options = options || [];
for (var i = 0; i < options.length; i++) {
if (options[i] == '--help' || options[i] == '-h') {
cmd.outputHelp();
process.exit(0);
}
}
}
@@ -0,0 +1,38 @@
{
"name": "commander",
"version": "2.0.0",
"description": "the complete solution for node.js command-line programs",
"keywords": [
"command",
"option",
"parser",
"prompt",
"stdin"
],
"author": {
"name": "TJ Holowaychuk",
"email": "tj@vision-media.ca"
},
"repository": {
"type": "git",
"url": "https://github.com/visionmedia/commander.js.git"
},
"devDependencies": {
"should": ">= 0.0.1"
},
"scripts": {
"test": "make test"
},
"main": "index",
"engines": {
"node": ">= 0.6.x"
},
"readme": "# Commander.js\n\n The complete solution for [node.js](http://nodejs.org) command-line interfaces, inspired by Ruby's [commander](https://github.com/visionmedia/commander).\n\n [![Build Status](https://secure.travis-ci.org/visionmedia/commander.js.png)](http://travis-ci.org/visionmedia/commander.js)\n\n## Installation\n\n $ npm install commander\n\n## Option parsing\n\n Options with commander are defined with the `.option()` method, also serving as documentation for the options. The example below parses args and options from `process.argv`, leaving remaining args as the `program.args` array which were not consumed by options.\n\n```js\n#!/usr/bin/env node\n\n/**\n * Module dependencies.\n */\n\nvar program = require('commander');\n\nprogram\n .version('0.0.1')\n .option('-p, --peppers', 'Add peppers')\n .option('-P, --pineapple', 'Add pineapple')\n .option('-b, --bbq', 'Add bbq sauce')\n .option('-c, --cheese [type]', 'Add the specified type of cheese [marble]', 'marble')\n .parse(process.argv);\n\nconsole.log('you ordered a pizza with:');\nif (program.peppers) console.log(' - peppers');\nif (program.pineapple) console.log(' - pineapple');\nif (program.bbq) console.log(' - bbq');\nconsole.log(' - %s cheese', program.cheese);\n```\n\n Short flags may be passed as a single arg, for example `-abc` is equivalent to `-a -b -c`. Multi-word options such as \"--template-engine\" are camel-cased, becoming `program.templateEngine` etc.\n\n## Automated --help\n\n The help information is auto-generated based on the information commander already knows about your program, so the following `--help` info is for free:\n\n``` \n $ ./examples/pizza --help\n\n Usage: pizza [options]\n\n Options:\n\n -V, --version output the version number\n -p, --peppers Add peppers\n -P, --pineapple Add pineapple\n -b, --bbq Add bbq sauce\n -c, --cheese <type> Add the specified type of cheese [marble]\n -h, --help output usage information\n\n```\n\n## Coercion\n\n```js\nfunction range(val) {\n return val.split('..').map(Number);\n}\n\nfunction list(val) {\n return val.split(',');\n}\n\nprogram\n .version('0.0.1')\n .usage('[options] <file ...>')\n .option('-i, --integer <n>', 'An integer argument', parseInt)\n .option('-f, --float <n>', 'A float argument', parseFloat)\n .option('-r, --range <a>..<b>', 'A range', range)\n .option('-l, --list <items>', 'A list', list)\n .option('-o, --optional [value]', 'An optional value')\n .parse(process.argv);\n\nconsole.log(' int: %j', program.integer);\nconsole.log(' float: %j', program.float);\nconsole.log(' optional: %j', program.optional);\nprogram.range = program.range || [];\nconsole.log(' range: %j..%j', program.range[0], program.range[1]);\nconsole.log(' list: %j', program.list);\nconsole.log(' args: %j', program.args);\n```\n\n## Custom help\n\n You can display arbitrary `-h, --help` information\n by listening for \"--help\". Commander will automatically\n exit once you are done so that the remainder of your program\n does not execute causing undesired behaviours, for example\n in the following executable \"stuff\" will not output when\n `--help` is used.\n\n```js\n#!/usr/bin/env node\n\n/**\n * Module dependencies.\n */\n\nvar program = require('../');\n\nfunction list(val) {\n return val.split(',').map(Number);\n}\n\nprogram\n .version('0.0.1')\n .option('-f, --foo', 'enable some foo')\n .option('-b, --bar', 'enable some bar')\n .option('-B, --baz', 'enable some baz');\n\n// must be before .parse() since\n// node's emit() is immediate\n\nprogram.on('--help', function(){\n console.log(' Examples:');\n console.log('');\n console.log(' $ custom-help --help');\n console.log(' $ custom-help -h');\n console.log('');\n});\n\nprogram.parse(process.argv);\n\nconsole.log('stuff');\n```\n\nyielding the following help output:\n\n```\n\nUsage: custom-help [options]\n\nOptions:\n\n -h, --help output usage information\n -V, --version output the version number\n -f, --foo enable some foo\n -b, --bar enable some bar\n -B, --baz enable some baz\n\nExamples:\n\n $ custom-help --help\n $ custom-help -h\n\n```\n\n## .outputHelp()\n\n Output help information without exiting.\n\n## .help()\n\n Output help information and exit immediately.\n\n## Links\n\n - [API documentation](http://visionmedia.github.com/commander.js/)\n - [ascii tables](https://github.com/LearnBoost/cli-table)\n - [progress bars](https://github.com/visionmedia/node-progress)\n - [more progress bars](https://github.com/substack/node-multimeter)\n - [examples](https://github.com/visionmedia/commander.js/tree/master/examples)\n\n## License \n\n(The MIT License)\n\nCopyright (c) 2011 TJ Holowaychuk &lt;tj@vision-media.ca&gt;\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software withLine truncated
"readmeFilename": "Readme.md",
"bugs": {
"url": "https://github.com/visionmedia/commander.js/issues"
},
"homepage": "https://github.com/visionmedia/commander.js",
"_id": "commander@2.0.0",
"_from": "commander@2.0.x"
}
+72
View File
@@ -0,0 +1,72 @@
{
"name": "clean-css",
"version": "2.0.8",
"author": {
"name": "Jakub Pawlowicz",
"email": "jakub@goalsmashers.com",
"url": "http://twitter.com/GoalSmashers"
},
"description": "A well-tested CSS minifier",
"license": "MIT",
"keywords": [
"css",
"minifier"
],
"homepage": "https://github.com/GoalSmashers/clean-css",
"repository": {
"type": "git",
"url": "https://github.com/GoalSmashers/clean-css.git"
},
"bugs": {
"url": "https://github.com/GoalSmashers/clean-css/issues"
},
"bin": {
"cleancss": "./bin/cleancss"
},
"main": "index.js",
"files": [
"bin",
"lib",
"History.md",
"index.js",
"LICENSE"
],
"scripts": {
"bench": "node ./test/bench.js",
"check": "jshint .",
"prepublish": "jshint .",
"test": "vows"
},
"dependencies": {
"commander": "2.0.x"
},
"devDependencies": {
"jshint": "2.3.x",
"vows": "0.7.x"
},
"jshintConfig": {
"browser": false,
"camelcase": true,
"curly": false,
"eqeqeq": false,
"eqnull": true,
"immed": true,
"latedef": true,
"multistr": true,
"noarg": true,
"node": true,
"plusplus": false,
"quotmark": "single",
"strict": false,
"trailing": true,
"undef": true,
"unused": true
},
"engines": {
"node": ">=0.8.0"
},
"readme": "[![NPM version](https://badge.fury.io/js/clean-css.png)](https://badge.fury.io/js/clean-css)\n[![Build Status](https://secure.travis-ci.org/GoalSmashers/clean-css.png)](https://travis-ci.org/GoalSmashers/clean-css)\n[![Dependency Status](https://david-dm.org/GoalSmashers/clean-css.png)](https://david-dm.org/GoalSmashers/clean-css)\n[![devDependency Status](https://david-dm.org/GoalSmashers/clean-css/dev-status.png)](https://david-dm.org/GoalSmashers/clean-css#info=devDependencies)\n\n## What is clean-css?\n\nClean-css is a fast and efficient [node.js](http://nodejs.org/) library for minifying CSS files.\n\nAccording to [tests](https://github.com/GoalSmashers/css-minification-benchmark) it is one of the best available.\n\n\n## Usage\n\n### What are the requirements?\n\n```\nnode.js 0.8.0+ (tested on CentOS, Ubuntu, OS X 10.6+, and Windows 7+)\n```\n\n### How to install clean-css?\n\n```\nnpm install clean-css\n```\n\n### How to upgrade clean-css from 1.x to 2.x?\n\n#### Command-line interface (CLI)\n\n```\nnpm update clean-css\n```\n\nor point `package.json` to version 2.x. That's it!\n\n#### Node.js module\n\nUpdate `clean-css` as for CLI above.\nThen change your JavaScript code from:\n\n```js\nvar minimized = CleanCSS.process(source, options);\n```\n\ninto\n\n```js\nvar minimized = new CleanCSS(options).minify(source);\n```\n\nAnd you are done.\n\n### How to use clean-css CLI?\n\nClean-css accepts the following command line arguments (please make sure\nyou use `<source-file>` as the very last argument to avoid potential issues):\n\n```\ncleancss [options] <source-file>\n\n-h, --help Output usage information\n-v, --version Output the version number\n-b, --keep-line-breaks Keep line breaks\n--s0 Remove all special comments, i.e. /*! comment */\n--s1 Remove all special comments but the first one\n-r, --root [root-path] A root path to which resolve absolute @import rules\n and rebase relative URLs\n-o, --output [output-file] Use [output-file] as output instead of STDOUT\n-s, --skip-import Disable @import processing\n--skip-rebase Disable URLs rebasing\n--skip-advanced Disable advanced optimizations - selector & property merging,\n reduction, etc.\n--selectors-merge-mode [ie8|*] Use `ie8` for compatibility mode, `*` for merge all (default)\n-d, --debug Shows debug information (minification time & compression efficiency)\n```\n\n#### Examples:\n\nTo minify a **public.css** file into **public-min.css** do:\n\n```\ncleancss -o public-min.css public.css\n```\n\nTo minify the same **public.css** into the standard output skip the `-o` parameter:\n\n```\ncleancss public.css\n```\n\nMore likely you would like to concatenate a couple of files.\nIf you are on a Unix-like system:\n\n```bash\ncat one.css two.css three.css | cleancss -o merged-and-minified.css\n```\n\nOn Windows:\n\n```bat\ntype one.css two.css three.css | cleancss -o merged-and-minified.css\n```\n\nOr even gzip the result at once:\n\n```bash\ncat one.css two.css three.css | cleancss | gzip -9 -c > merged-minified-and-gzipped.css.gz\n```\n\n### How to use clean-css programmatically?\n\n```js\nvar CleanCSS = require('clean-css');\nvar source = 'a{font-weight:bold;}';\nvar minimized = new CleanCSS().minify(source);\n```\n\nCleanCSS constructor accepts a hash as a parameter, i.e.,\n`new CleanCSS(options).minify(source)` with the following options available:\n\n* `keepSpecialComments` - `*` for keeping all (default), `1` for keeping first one only, `0` for removing all\n* `keepBreaks` - whether to keep line breaks (default is false)\n* `benchmark` - turns on benchmarking mode measuring time spent on cleaning up\n (run `npm run bench` to see example)\n* `root` - path to resolve absolute `@import` rules and rebase relative URLs\n* `relativeTo` - path with which to resolve relative `@import` rules and URLs\n* `processImport` - whether to process `@import` rules\n* `noRebase` - whether to skip URLs rebasing\n* `noAdvanced` - set to true to disable advanced optimizations - selector & property merging, reduction, etc.\n* `selectorsMergeMode` - `ie8` for IE8 compatibility mode, `*` for merging all (default)\n* `debug` - set to true to get minification statistics under `stats` property (see `test/custom-test.js` for examples)\n\n### What are the clean-css' dev commands?\n\nFirst clone the source, then run:\n\n* `npm run bench` for clean-css benchmarks (see [test/bench.js](https://github.com/GoalSmashers/clean-css/blob/master/test/bench.js) for details)\n* `npm run check` to check JS sources with [JSHint](https://github.com/jshint/jshint/)\n* `npm test` for the test suite\n\n## Tips & Tricks\n\n### How to preserve a comment block?\n\nUse the `/*!` notation instead of the standard one `/*`:\n\n```css\n/*!\n ImporLine truncated
"readmeFilename": "README.md",
"_id": "clean-css@2.0.8",
"_from": "clean-css@~2.0.7"
}
+1
View File
@@ -0,0 +1 @@
node_modules/
+5
View File
@@ -0,0 +1,5 @@
language: node_js
node_js:
- 0.6
- 0.8
- 0.10
+18
View File
@@ -0,0 +1,18 @@
Copyright © 2011-2013 Paul Vorbach <paul@vorb.de>
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, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+124
View File
@@ -0,0 +1,124 @@
# clone
[![build status](https://secure.travis-ci.org/pvorb/node-clone.png)](http://travis-ci.org/pvorb/node-clone)
offers foolproof _deep cloning_ of variables in JavaScript.
## Installation
npm install clone
or
ender build clone
## Example
~~~ javascript
var clone = require('clone');
var a, b;
a = { foo: { bar: 'baz' } }; // inital value of a
b = clone(a); // clone a -> b
a.foo.bar = 'foo'; // change a
console.log(a); // show a
console.log(b); // show b
~~~
This will print:
~~~ javascript
{ foo: { bar: 'foo' } }
{ foo: { bar: 'baz' } }
~~~
**clone** masters cloning simple objects (even with custom prototype), arrays,
Date objects, and RegExp objects. Everything is cloned recursively, so that you
can clone dates in arrays in objects, for example.
## API
`clone(val, circular)`
* `val` -- the value that you want to clone, any type allowed
* `circular` -- boolean
Call `clone` with `circular` set to `false` if you are certain that `obj`
contains no circular references. This will give better performance if needed.
There is no error if `undefined` or `null` is passed as `obj`.
`clone.clonePrototype(obj)`
* `obj` -- the object that you want to clone
Does a prototype clone as
[described by Oran Looney](http://oranlooney.com/functional-javascript/).
## Circular References
~~~ javascript
var a, b;
a = { hello: 'world' };
a.myself = a;
b = clone(a);
console.log(b);
~~~
This will print:
~~~ javascript
{ hello: "world", myself: [Circular] }
~~~
So, `b.myself` points to `b`, not `a`. Neat!
## Test
npm test
## Caveat
Some special objects like a socket or `process.stdout`/`stderr` are known to not
be cloneable. If you find other objects that cannot be cloned, please [open an
issue](https://github.com/pvorb/node-clone/issues/new).
## Bugs and Issues
If you encounter any bugs or issues, feel free to
[open an issue at github](https://github.com/pvorb/node-clone/issues) or send me
an email to <paul@vorb.de>. I also always like to hear from you, if you’re using
my code.
## License
Copyright © 2011-2013 Paul Vorbach
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, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+159
View File
@@ -0,0 +1,159 @@
"use strict";
function objectToString(o) {
return Object.prototype.toString.call(o);
}
var util = {
isArray: function (ar) {
return Array.isArray(ar) || (typeof ar === 'object' && objectToString(ar) === '[object Array]');
},
isDate: function (d) {
return typeof d === 'object' && objectToString(d) === '[object Date]';
},
isRegExp: function (re) {
return typeof re === 'object' && objectToString(re) === '[object RegExp]';
},
getRegExpFlags: function (re) {
var flags = '';
re.global && (flags += 'g');
re.ignoreCase && (flags += 'i');
re.multiline && (flags += 'm');
return flags;
}
};
if (typeof module === 'object')
module.exports = clone;
/**
* Clones (copies) an Object using deep copying.
*
* This function supports circular references by default, but if you are certain
* there are no circular references in your object, you can save some CPU time
* by calling clone(obj, false).
*
* Caution: if `circular` is false and `parent` contains circular references,
* your program may enter an infinite loop and crash.
*
* @param `parent` - the object to be cloned
* @param `circular` - set to true if the object to be cloned may contain
* circular references. (optional - true by default)
*/
function clone(parent, circular) {
if (typeof circular == 'undefined')
circular = true;
var useBuffer = typeof Buffer != 'undefined';
var circularParent = {};
var circularResolved = {};
var circularReplace = [];
function _clone(parent, context, child, cIndex) {
var i; // Use local context within this function
// Deep clone all properties of parent into child
if (typeof parent == 'object') {
if (parent == null)
return parent;
// Check for circular references
for(i in circularParent)
if (circularParent[i] === parent) {
// We found a circular reference
circularReplace.push({'resolveTo': i, 'child': child, 'i': cIndex});
return null; //Just return null for now...
// we will resolve circular references later
}
// Add to list of all parent objects
circularParent[context] = parent;
// Now continue cloning...
if (util.isArray(parent)) {
child = [];
for(i in parent)
child[i] = _clone(parent[i], context + '[' + i + ']', child, i);
}
else if (util.isDate(parent))
child = new Date(parent.getTime());
else if (util.isRegExp(parent)) {
child = new RegExp(parent.source, util.getRegExpFlags(parent));
if (parent.lastIndex) child.lastIndex = parent.lastIndex;
} else if (useBuffer && Buffer.isBuffer(parent))
{
child = new Buffer(parent.length);
parent.copy(child);
}
else {
child = {};
// Also copy prototype over to new cloned object
child.__proto__ = parent.__proto__;
for(i in parent)
child[i] = _clone(parent[i], context + '[' + i + ']', child, i);
}
// Add to list of all cloned objects
circularResolved[context] = child;
}
else
child = parent; //Just a simple shallow copy will do
return child;
}
var i;
if (circular) {
var cloned = _clone(parent, '*');
// Now this object has been cloned. Let's check to see if there are any
// circular references for it
for(i in circularReplace) {
var c = circularReplace[i];
if (c && c.child && c.i in c.child) {
c.child[c.i] = circularResolved[c.resolveTo];
}
}
return cloned;
} else {
// Deep clone all properties of parent into child
var child;
if (typeof parent == 'object') {
if (parent == null)
return parent;
if (parent.constructor.name === 'Array') {
child = [];
for(i in parent)
child[i] = clone(parent[i], circular);
}
else if (util.isDate(parent))
child = new Date(parent.getTime() );
else if (util.isRegExp(parent)) {
child = new RegExp(parent.source, util.getRegExpFlags(parent));
if (parent.lastIndex) child.lastIndex = parent.lastIndex;
} else {
child = {};
child.__proto__ = parent.__proto__;
for(i in parent)
child[i] = clone(parent[i], circular);
}
}
else
child = parent; // Just a simple shallow clone will do
return child;
}
}
/**
* Simple flat clone using prototype, accepts only objects, usefull for property
* override on FLAT configuration object (no nested props).
*
* USE WITH CAUTION! This may not behave as you wish if you do not know how this
* works.
*/
clone.clonePrototype = function(parent) {
if (parent === null)
return null;
var c = function () {};
c.prototype = parent;
return new c();
};
+89
View File
@@ -0,0 +1,89 @@
{
"name": "clone",
"description": "deep cloning of objects and arrays",
"tags": [
"clone",
"object",
"array",
"function",
"date"
],
"version": "0.1.11",
"repository": {
"type": "git",
"url": "git://github.com/pvorb/node-clone.git"
},
"bugs": {
"url": "https://github.com/pvorb/node-clone/issues"
},
"main": "clone.js",
"author": {
"name": "Paul Vorbach",
"email": "paul@vorb.de",
"url": "http://vorb.de"
},
"contributors": [
{
"name": "Blake Miner",
"email": "miner.blake@gmail.com",
"url": "http://www.blakeminer.com/"
},
{
"name": "Tian You",
"email": "axqd001@gmail.com",
"url": "http://blog.axqd.net/"
},
{
"name": "George Stagas",
"email": "gstagas@gmail.com",
"url": "http://stagas.com/"
},
{
"name": "Tobiasz Cudnik",
"email": "tobiasz.cudnik@gmail.com",
"url": "https://github.com/TobiaszCudnik"
},
{
"name": "Pavel Lang",
"email": "langpavel@phpskelet.org",
"url": "https://github.com/langpavel"
},
{
"name": "Dan MacTough",
"url": "http://yabfog.com/"
},
{
"name": "w1nk",
"url": "https://github.com/w1nk"
},
{
"name": "Hugh Kennedy",
"url": "http://twitter.com/hughskennedy"
},
{
"name": "Dustin Diaz",
"url": "http://dustindiaz.com"
},
{
"name": "Ilya Shaisultanov",
"url": "https://github.com/diversario"
}
],
"engines": {
"node": "*"
},
"dependencies": {},
"devDependencies": {
"underscore": "*",
"nodeunit": "*"
},
"optionalDependencies": {},
"scripts": {
"test": "nodeunit test.js"
},
"readme": "# clone\n\n[![build status](https://secure.travis-ci.org/pvorb/node-clone.png)](http://travis-ci.org/pvorb/node-clone)\n\noffers foolproof _deep cloning_ of variables in JavaScript.\n\n\n## Installation\n\n npm install clone\n\nor\n\n ender build clone\n\n\n## Example\n\n~~~ javascript\nvar clone = require('clone');\n\nvar a, b;\n\na = { foo: { bar: 'baz' } }; // inital value of a\n\nb = clone(a); // clone a -> b\na.foo.bar = 'foo'; // change a\n\nconsole.log(a); // show a\nconsole.log(b); // show b\n~~~\n\nThis will print:\n\n~~~ javascript\n{ foo: { bar: 'foo' } }\n{ foo: { bar: 'baz' } }\n~~~\n\n**clone** masters cloning simple objects (even with custom prototype), arrays,\nDate objects, and RegExp objects. Everything is cloned recursively, so that you\ncan clone dates in arrays in objects, for example.\n\n\n## API\n\n`clone(val, circular)`\n\n * `val` -- the value that you want to clone, any type allowed\n * `circular` -- boolean\n\n Call `clone` with `circular` set to `false` if you are certain that `obj`\n contains no circular references. This will give better performance if needed.\n There is no error if `undefined` or `null` is passed as `obj`.\n\n`clone.clonePrototype(obj)`\n\n * `obj` -- the object that you want to clone\n\nDoes a prototype clone as\n[described by Oran Looney](http://oranlooney.com/functional-javascript/).\n\n\n## Circular References\n\n~~~ javascript\nvar a, b;\n\na = { hello: 'world' };\n\na.myself = a;\nb = clone(a);\n\nconsole.log(b);\n~~~\n\nThis will print:\n\n~~~ javascript\n{ hello: \"world\", myself: [Circular] }\n~~~\n\nSo, `b.myself` points to `b`, not `a`. Neat!\n\n\n## Test\n\n npm test\n\n\n## Caveat\n\nSome special objects like a socket or `process.stdout`/`stderr` are known to not\nbe cloneable. If you find other objects that cannot be cloned, please [open an\nissue](https://github.com/pvorb/node-clone/issues/new).\n\n\n## Bugs and Issues\n\nIf you encounter any bugs or issues, feel free to\n[open an issue at github](https://github.com/pvorb/node-clone/issues) or send me\nan email to <paul@vorb.de>. I also always like to hear from you, if you’re using\nmy code.\n\n## License\n\nCopyright © 2011-2013 Paul Vorbach\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the “Software”), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n",
"readmeFilename": "README.md",
"homepage": "https://github.com/pvorb/node-clone",
"_id": "clone@0.1.11",
"_from": "clone@~0.1.11"
}
+217
View File
@@ -0,0 +1,217 @@
if(module.parent === null) {
console.log('Run this test file with nodeunit:');
console.log('$ nodeunit test.js');
}
var clone = require('./');
var util = require('util');
var _ = require('underscore');
exports["clone string"] = function(test) {
test.expect(2); // how many tests?
var a = "foo";
test.strictEqual(clone(a), a);
a = "";
test.strictEqual(clone(a), a);
test.done();
};
exports["clone number"] = function(test) {
test.expect(5); // how many tests?
var a = 0;
test.strictEqual(clone(a), a);
a = 1;
test.strictEqual(clone(a), a);
a = -1000;
test.strictEqual(clone(a), a);
a = 3.1415927;
test.strictEqual(clone(a), a);
a = -3.1415927;
test.strictEqual(clone(a), a);
test.done();
};
exports["clone date"] = function(test) {
test.expect(3); // how many tests?
var a = new Date;
var c = clone(a);
test.ok(a instanceof Date);
test.ok(c instanceof Date);
test.equal(c.getTime(), a.getTime());
test.done();
};
exports["clone object"] = function(test) {
test.expect(2); // how many tests?
var a = { foo: { bar: "baz" } };
var b = clone(a);
test.ok(_(a).isEqual(b), "underscore equal");
test.deepEqual(b, a);
test.done();
};
exports["clone array"] = function(test) {
test.expect(2); // how many tests?
var a = [
{ foo: "bar" },
"baz"
];
var b = clone(a);
test.ok(_(a).isEqual(b), "underscore equal");
test.deepEqual(b, a);
test.done();
};
exports["clone buffer"] = function(test) {
test.expect(1);
var a = new Buffer("this is a test buffer");
var b = clone(a);
// no underscore equal since it has no concept of Buffers
test.deepEqual(b, a);
test.done();
};
exports["clone regexp"] = function(test) {
test.expect(5);
var a = /abc123/gi;
var b = clone(a);
test.deepEqual(b, a);
var c = /a/g;
test.ok(c.lastIndex === 0);
c.exec('123a456a');
test.ok(c.lastIndex === 4);
var d = clone(c);
test.ok(d.global);
test.ok(d.lastIndex === 4);
test.done();
};
exports["clone object containing array"] = function(test) {
test.expect(2); // how many tests?
var a = {
arr1: [ { a: '1234', b: '2345' } ],
arr2: [ { c: '345', d: '456' } ]
};
var b = clone(a);
test.ok(_(a).isEqual(b), "underscore equal");
test.deepEqual(b, a);
test.done();
};
exports["clone object with circular reference"] = function(test) {
test.expect(8); // how many tests?
var _ = test.ok;
var c = [1, "foo", {'hello': 'bar'}, function() {}, false, [2]];
var b = [c, 2, 3, 4];
var a = {'b': b, 'c': c};
a.loop = a;
a.loop2 = a;
c.loop = c;
c.aloop = a;
var aCopy = clone(a);
_(a != aCopy);
_(a.c != aCopy.c);
_(aCopy.c == aCopy.b[0]);
_(aCopy.c.loop.loop.aloop == aCopy);
_(aCopy.c[0] == a.c[0]);
//console.log(util.inspect(aCopy, true, null) );
//console.log("------------------------------------------------------------");
//console.log(util.inspect(a, true, null) );
_(eq(a, aCopy));
aCopy.c[0] = 2;
_(!eq(a, aCopy));
aCopy.c = "2";
_(!eq(a, aCopy));
//console.log("------------------------------------------------------------");
//console.log(util.inspect(aCopy, true, null) );
function eq(x, y) {
return util.inspect(x, true, null) === util.inspect(y, true, null);
}
test.done();
};
exports['clonePrototype'] = function(test) {
test.expect(3); // how many tests?
var a = {
a: "aaa",
x: 123,
y: 45.65
};
var b = clone.clonePrototype(a);
test.strictEqual(b.a, a.a);
test.strictEqual(b.x, a.x);
test.strictEqual(b.y, a.y);
test.done();
}
exports['cloneWithinNewVMContext'] = function(test) {
test.expect(3);
var vm = require('vm');
var ctx = vm.createContext({ clone: clone });
var script = "clone( {array: [1, 2, 3], date: new Date(), regex: /^foo$/ig} );";
var results = vm.runInContext(script, ctx);
test.ok(results.array instanceof Array);
test.ok(results.date instanceof Date);
test.ok(results.regex instanceof RegExp);
test.done();
}
exports['cloneObjectWithNoConstructor'] = function(test) {
test.expect(3);
var n = null;
var a = { foo: 'bar' };
a.__proto__ = n;
test.ok(typeof a === 'object');
test.ok(typeof a !== null);
var b = clone(a);
test.ok(a.foo, b.foo);
test.done();
}
+3
View File
@@ -0,0 +1,3 @@
node_modules
node_modules/*
npm_debug.log
+4
View File
@@ -0,0 +1,4 @@
language: node_js
node_js:
- 0.6
- 0.8
+22
View File
@@ -0,0 +1,22 @@
Copyright (c) 2011 Dominic Tarr
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.
@@ -0,0 +1,25 @@
var inspect = require('util').inspect
if(!module.parent) {
var es = require('..') //load event-stream
es.pipe( //pipe joins streams together
process.openStdin(), //open stdin
es.split(), //split stream to break on newlines
es.map(function (data, callback) {//turn this async function into a stream
var j
try {
j = JSON.parse(data) //try to parse input into json
} catch (err) {
return callback(null, data) //if it fails just pass it anyway
}
callback(null, inspect(j)) //render it nicely
}),
process.stdout // pipe it to stdout !
)
}
// run this
//
// curl -sS registry.npmjs.org/event-stream | node pretty.js
//
+302
View File
@@ -0,0 +1,302 @@
//filter will reemit the data if cb(err,pass) pass is truthy
// reduce is more tricky
// maybe we want to group the reductions or emit progress updates occasionally
// the most basic reduce just emits one 'data' event after it has recieved 'end'
var Stream = require('stream').Stream
, es = exports
, through = require('through')
, from = require('from')
, duplex = require('duplexer')
, map = require('map-stream')
, pause = require('pause-stream')
, split = require('split')
, pipeline = require('stream-combiner')
es.Stream = Stream //re-export Stream from core
es.through = through
es.from = from
es.duplex = duplex
es.map = map
es.pause = pause
es.split = split
es.pipeline = es.connect = es.pipe = pipeline
// merge / concat
//
// combine multiple streams into a single stream.
// will emit end only once
es.concat = //actually this should be called concat
es.merge = function (/*streams...*/) {
var toMerge = [].slice.call(arguments)
var stream = new Stream()
var endCount = 0
stream.writable = stream.readable = true
toMerge.forEach(function (e) {
e.pipe(stream, {end: false})
var ended = false
e.on('end', function () {
if(ended) return
ended = true
endCount ++
if(endCount == toMerge.length)
stream.emit('end')
})
})
stream.write = function (data) {
this.emit('data', data)
}
stream.destroy = function () {
merge.forEach(function (e) {
if(e.destroy) e.destroy()
})
}
return stream
}
// writable stream, collects all events into an array
// and calls back when 'end' occurs
// mainly I'm using this to test the other functions
es.writeArray = function (done) {
if ('function' !== typeof done)
throw new Error('function writeArray (done): done must be function')
var a = new Stream ()
, array = [], isDone = false
a.write = function (l) {
array.push(l)
}
a.end = function () {
isDone = true
done(null, array)
}
a.writable = true
a.readable = false
a.destroy = function () {
a.writable = a.readable = false
if(isDone) return
done(new Error('destroyed before end'), array)
}
return a
}
//return a Stream that reads the properties of an object
//respecting pause() and resume()
es.readArray = function (array) {
var stream = new Stream()
, i = 0
, paused = false
, ended = false
stream.readable = true
stream.writable = false
if(!Array.isArray(array))
throw new Error('event-stream.read expects an array')
stream.resume = function () {
if(ended) return
paused = false
var l = array.length
while(i < l && !paused && !ended) {
stream.emit('data', array[i++])
}
if(i == l && !ended)
ended = true, stream.readable = false, stream.emit('end')
}
process.nextTick(stream.resume)
stream.pause = function () {
paused = true
}
stream.destroy = function () {
ended = true
stream.emit('close')
}
return stream
}
//
// readable (asyncFunction)
// return a stream that calls an async function while the stream is not paused.
//
// the function must take: (count, callback) {...
//
es.readable =
function (func, continueOnError) {
var stream = new Stream()
, i = 0
, paused = false
, ended = false
, reading = false
stream.readable = true
stream.writable = false
if('function' !== typeof func)
throw new Error('event-stream.readable expects async function')
stream.on('end', function () { ended = true })
function get (err, data) {
if(err) {
stream.emit('error', err)
if(!continueOnError) stream.emit('end')
} else if (arguments.length > 1)
stream.emit('data', data)
process.nextTick(function () {
if(ended || paused || reading) return
try {
reading = true
func.call(stream, i++, function () {
reading = false
get.apply(null, arguments)
})
} catch (err) {
stream.emit('error', err)
}
})
}
stream.resume = function () {
paused = false
get()
}
process.nextTick(get)
stream.pause = function () {
paused = true
}
stream.destroy = function () {
stream.emit('end')
stream.emit('close')
ended = true
}
return stream
}
//
// map sync
//
es.mapSync = function (sync) {
return es.through(function write(data) {
var mappedData = sync(data)
if (typeof mappedData !== 'undefined')
this.emit('data', mappedData)
})
}
//
// log just print out what is coming through the stream, for debugging
//
es.log = function (name) {
return es.through(function (data) {
var args = [].slice.call(arguments)
if(name) console.error(name, data)
else console.error(data)
this.emit('data', data)
})
}
//
// child -- pipe through a child process
//
es.child = function (child) {
return es.duplex(child.stdin, child.stdout)
}
//
// parse
//
// must be used after es.split() to ensure that each chunk represents a line
// source.pipe(es.split()).pipe(es.parse())
es.parse = function () {
return es.through(function (data) {
var obj
try {
if(data) //ignore empty lines
obj = JSON.parse(data.toString())
} catch (err) {
return console.error(err, 'attemping to parse:', data)
}
//ignore lines that where only whitespace.
if(obj !== undefined)
this.emit('data', obj)
})
}
//
// stringify
//
es.stringify = function () {
var Buffer = require('buffer').Buffer
return es.mapSync(function (e){
return JSON.stringify(Buffer.isBuffer(e) ? e.toString() : e) + '\n'
})
}
//
// replace a string within a stream.
//
// warn: just concatenates the string and then does str.split().join().
// probably not optimal.
// for smallish responses, who cares?
// I need this for shadow-npm so it's only relatively small json files.
es.replace = function (from, to) {
return es.pipeline(es.split(from), es.join(to))
}
//
// join chunks with a joiner. just like Array#join
// also accepts a callback that is passed the chunks appended together
// this is still supported for legacy reasons.
//
es.join = function (str) {
//legacy api
if('function' === typeof str)
return es.wait(str)
var first = true
return es.through(function (data) {
if(!first)
this.emit('data', str)
first = false
this.emit('data', data)
return true
})
}
//
// wait. callback when 'end' is emitted, with all chunks appended as string.
//
es.wait = function (callback) {
var body = ''
return es.through(function (data) { body += data },
function () {
this.emit('data', body)
this.emit('end')
if(callback) callback(null, body)
})
}
es.pipeable = function () {
throw new Error('[EVENT-STREAM] es.pipeable is deprecated')
}
@@ -0,0 +1,3 @@
node_modules
*.log
*.err
@@ -0,0 +1,6 @@
language: node_js
node_js:
- "0.11"
- "0.10"
- "0.8"
- "0.6"
@@ -0,0 +1,19 @@
Copyright (c) 2012 Raynos.
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.
@@ -0,0 +1,47 @@
# duplexer
[![build status][1]][2] [![dependency status][3]][4]
[![browser support][5]][6]
Creates a duplex stream
Taken from [event-stream][7]
## duplex (writeStream, readStream)
Takes a writable stream and a readable stream and makes them appear as a readable writable stream.
It is assumed that the two streams are connected to each other in some way.
## Example
```js
var grep = cp.exec('grep Stream')
duplex(grep.stdin, grep.stdout)
```
## Installation
`npm install duplexer`
## Tests
`npm test`
## Contributors
- Dominictarr
- Raynos
- samccone
## MIT Licenced
[1]: https://secure.travis-ci.org/Raynos/duplexer.png
[2]: https://travis-ci.org/Raynos/duplexer
[3]: https://david-dm.org/Raynos/duplexer.png
[4]: https://david-dm.org/Raynos/duplexer
[5]: https://ci.testling.com/Raynos/duplexer.png
[6]: https://ci.testling.com/Raynos/duplexer
[7]: https://github.com/dominictarr/event-stream#duplex-writestream-readstream
@@ -0,0 +1,87 @@
var Stream = require("stream")
var writeMethods = ["write", "end", "destroy"]
var readMethods = ["resume", "pause"]
var readEvents = ["data", "close"]
var slice = Array.prototype.slice
module.exports = duplex
function forEach (arr, fn) {
if (arr.forEach) {
return arr.forEach(fn)
}
for (var i = 0; i < arr.length; i++) {
fn(arr[i], i)
}
}
function duplex(writer, reader) {
var stream = new Stream()
var ended = false
forEach(writeMethods, proxyWriter)
forEach(readMethods, proxyReader)
forEach(readEvents, proxyStream)
reader.on("end", handleEnd)
writer.on("drain", function() {
stream.emit("drain")
})
writer.on("error", reemit)
reader.on("error", reemit)
stream.writable = writer.writable
stream.readable = reader.readable
return stream
function proxyWriter(methodName) {
stream[methodName] = method
function method() {
return writer[methodName].apply(writer, arguments)
}
}
function proxyReader(methodName) {
stream[methodName] = method
function method() {
stream.emit(methodName)
var func = reader[methodName]
if (func) {
return func.apply(reader, arguments)
}
reader.emit(methodName)
}
}
function proxyStream(methodName) {
reader.on(methodName, reemit)
function reemit() {
var args = slice.call(arguments)
args.unshift(methodName)
stream.emit.apply(stream, args)
}
}
function handleEnd() {
if (ended) {
return
}
ended = true
var args = slice.call(arguments)
args.unshift("end")
stream.emit.apply(stream, args)
}
function reemit(err) {
stream.emit("error", err)
}
}
@@ -0,0 +1,57 @@
{
"name": "duplexer",
"version": "0.1.1",
"description": "Creates a duplex stream",
"keywords": [],
"author": {
"name": "Raynos",
"email": "raynos2@gmail.com"
},
"repository": {
"type": "git",
"url": "git://github.com/Raynos/duplexer.git"
},
"main": "index",
"homepage": "https://github.com/Raynos/duplexer",
"contributors": [
{
"name": "Jake Verbaten"
}
],
"bugs": {
"url": "https://github.com/Raynos/duplexer/issues",
"email": "raynos2@gmail.com"
},
"devDependencies": {
"tape": "0.3.3",
"through": "~0.1.4"
},
"licenses": [
{
"type": "MIT",
"url": "http://github.com/Raynos/duplexer/raw/master/LICENSE"
}
],
"scripts": {
"test": "node test"
},
"testling": {
"files": "test/index.js",
"browsers": [
"ie/8..latest",
"firefox/16..latest",
"firefox/nightly",
"chrome/22..latest",
"chrome/canary",
"opera/12..latest",
"opera/next",
"safari/5.1..latest",
"ipad/6.0..latest",
"iphone/6.0..latest"
]
},
"readme": "# duplexer\n\n[![build status][1]][2] [![dependency status][3]][4]\n\n[![browser support][5]][6]\n\nCreates a duplex stream\n\nTaken from [event-stream][7]\n\n## duplex (writeStream, readStream)\n\nTakes a writable stream and a readable stream and makes them appear as a readable writable stream.\n\nIt is assumed that the two streams are connected to each other in some way.\n\n## Example\n\n```js\nvar grep = cp.exec('grep Stream')\n\nduplex(grep.stdin, grep.stdout)\n```\n\n## Installation\n\n`npm install duplexer`\n\n## Tests\n\n`npm test`\n\n## Contributors\n\n - Dominictarr\n - Raynos\n - samccone\n\n## MIT Licenced\n\n [1]: https://secure.travis-ci.org/Raynos/duplexer.png\n [2]: https://travis-ci.org/Raynos/duplexer\n [3]: https://david-dm.org/Raynos/duplexer.png\n [4]: https://david-dm.org/Raynos/duplexer\n [5]: https://ci.testling.com/Raynos/duplexer.png\n [6]: https://ci.testling.com/Raynos/duplexer\n [7]: https://github.com/dominictarr/event-stream#duplex-writestream-readstream\n",
"readmeFilename": "README.md",
"_id": "duplexer@0.1.1",
"_from": "duplexer@~0.1.1"
}
@@ -0,0 +1,31 @@
var through = require("through")
var test = require("tape")
var duplex = require("../index")
var readable = through()
var writable = through(write)
var written = 0
var data = 0
var stream = duplex(writable, readable)
function write() {
written++
}
stream.on("data", ondata)
function ondata() {
data++
}
test("emit and write", function(t) {
t.plan(2)
stream.write()
readable.emit("data")
t.equal(written, 1, "should have written once")
t.equal(data, 1, "should have recived once")
})
@@ -0,0 +1 @@
node_modules
@@ -0,0 +1,15 @@
Apache License, Version 2.0
Copyright (c) 2011 Dominic Tarr
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,24 @@
The MIT License
Copyright (c) 2011 Dominic Tarr
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.
@@ -0,0 +1,68 @@
'use strict';
var Stream = require('stream')
// from
//
// a stream that reads from an source.
// source may be an array, or a function.
// from handles pause behaviour for you.
module.exports =
function from (source) {
if(Array.isArray(source)) {
source = source.slice()
return from (function (i) {
if(source.length)
this.emit('data', source.shift())
else
this.emit('end')
return true
})
}
var s = new Stream(), i = 0
s.ended = false
s.started = false
s.readable = true
s.writable = false
s.paused = false
s.ended = false
s.pause = function () {
s.started = true
s.paused = true
}
function next () {
s.started = true
if(s.ended) return
while(!s.ended && !s.paused && source.call(s, i++, function () {
if(!s.ended && !s.paused)
next()
}))
;
}
s.resume = function () {
s.started = true
s.paused = false
next()
}
s.on('end', function () {
s.ended = true
s.readable = false
process.nextTick(s.destroy)
})
s.destroy = function () {
s.ended = true
s.emit('close')
}
/*
by default, the stream will start emitting at nextTick
if you want, you can pause it, after pipeing.
you can also resume before next tick, and that will also
work.
*/
process.nextTick(function () {
if(!s.started) s.resume()
})
return s
}
@@ -0,0 +1,38 @@
{
"name": "from",
"version": "0.1.3",
"description": "Easy way to make a Readable Stream",
"main": "index.js",
"scripts": {
"test": "asynct test/*.js"
},
"repository": {
"type": "git",
"url": "git://github.com/dominictarr/from.git"
},
"keywords": [
"stream",
"streams",
"readable",
"easy"
],
"devDependencies": {
"asynct": "1",
"stream-spec": "0",
"assertions": "~2.3.0"
},
"author": {
"name": "Dominic Tarr",
"email": "dominic.tarr@gmail.com",
"url": "dominictarr.com"
},
"license": "MIT",
"readme": "# from\n\nAn easy way to create a `readable Stream`.\n\n## from(function getChunk(count, next))\n\nfrom takes a `getChunk` function and returns a stream. \n\n`getChunk` is called again and again, after each time the user calls `next()`, \nuntil the user emits `'end'`\n\nif `pause()` is called, the `getChunk` won't be called again untill `resume()` is called.\n\n\n```js\nvar from = require('from')\n\nvar stream = \n from(function getChunk(count, next) {\n //do some sort of data\n this.emit('data', whatever)\n \n if(itsOver)\n this.emit('end')\n\n //ready to handle the next chunk\n next()\n //or, if it's sync:\n return true \n })\n```\n\n## from(array)\n\nfrom also takes an `Array` whose elements it emits one after another.\n\n## License\nMIT / Apache2\n",
"readmeFilename": "readme.markdown",
"bugs": {
"url": "https://github.com/dominictarr/from/issues"
},
"homepage": "https://github.com/dominictarr/from",
"_id": "from@0.1.3",
"_from": "from@~0"
}
@@ -0,0 +1,38 @@
# from
An easy way to create a `readable Stream`.
## from(function getChunk(count, next))
from takes a `getChunk` function and returns a stream.
`getChunk` is called again and again, after each time the user calls `next()`,
until the user emits `'end'`
if `pause()` is called, the `getChunk` won't be called again untill `resume()` is called.
```js
var from = require('from')
var stream =
from(function getChunk(count, next) {
//do some sort of data
this.emit('data', whatever)
if(itsOver)
this.emit('end')
//ready to handle the next chunk
next()
//or, if it's sync:
return true
})
```
## from(array)
from also takes an `Array` whose elements it emits one after another.
## License
MIT / Apache2
@@ -0,0 +1,141 @@
var from = require('..')
var spec = require('stream-spec')
var a = require('assertions')
function read(stream, callback) {
var actual = []
stream.on('data', function (data) {
actual.push(data)
})
stream.once('end', function () {
callback(null, actual)
})
stream.once('error', function (err) {
callback(err)
})
}
function pause(stream) {
stream.on('data', function () {
if(Math.random() > 0.1) return
stream.pause()
process.nextTick(function () {
stream.resume()
})
})
}
exports['inc'] = function (test) {
var fs = from(function (i) {
this.emit('data', i)
if(i >= 99)
return this.emit('end')
return true
})
spec(fs).readable().validateOnExit()
read(fs, function (err, arr) {
test.equal(arr.length, 100)
test.done()
})
}
exports['simple'] = function (test) {
var l = 1000
, expected = []
while(l--) expected.push(l * Math.random())
var t = from(expected.slice())
spec(t)
.readable()
.pausable({strict: true})
.validateOnExit()
read(t, function (err, actual) {
if(err) test.error(err) //fail
a.deepEqual(actual, expected)
test.done()
})
}
exports['simple pausable'] = function (test) {
var l = 1000
, expected = []
while(l--) expected.push(l * Math.random())
var t = from(expected.slice())
spec(t)
.readable()
.pausable({strict: true})
.validateOnExit()
pause(t)
read(t, function (err, actual) {
if(err) test.error(err) //fail
a.deepEqual(actual, expected)
test.done()
})
}
exports['simple (not strictly pausable) setTimeout'] = function (test) {
var l = 10
, expected = []
while(l--) expected.push(l * Math.random())
var _expected = expected.slice()
var t = from(function (i, n) {
var self = this
setTimeout(function () {
if(_expected.length)
self.emit('data', _expected.shift())
else
self.emit('end')
n()
}, 3)
})
/*
using from in this way will not be strictly pausable.
it could be extended to buffer outputs, but I think a better
way would be to use a PauseStream that implements strict pause.
*/
spec(t)
.readable()
.pausable({strict: false })
.validateOnExit()
//pause(t)
var paused = false
var i = setInterval(function () {
if(!paused) t.pause()
else t.resume()
paused = !paused
}, 2)
t.on('end', function () {
clearInterval(i)
})
read(t, function (err, actual) {
if(err) test.error(err) //fail
a.deepEqual(actual, expected)
test.done()
})
}
@@ -0,0 +1,3 @@
node_modules
node_modules/*
npm_debug.log
@@ -0,0 +1,4 @@
language: node_js
node_js:
- 0.6
- 0.8
@@ -0,0 +1,22 @@
Copyright (c) 2011 Dominic Tarr
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.
@@ -0,0 +1,26 @@
var inspect = require('util').inspect
if(!module.parent) {
var map = require('..') //load map-stream
var es = require('event-stream') //load event-stream
es.pipe( //pipe joins streams together
process.openStdin(), //open stdin
es.split(), //split stream to break on newlines
map(function (data, callback) { //turn this async function into a stream
var j
try {
j = JSON.parse(data) //try to parse input into json
} catch (err) {
return callback(null, data) //if it fails just pass it anyway
}
callback(null, inspect(j)) //render it nicely
}),
process.stdout // pipe it to stdout !
)
}
// run this
//
// curl -sS registry.npmjs.org/event-stream | node pretty.js
//
@@ -0,0 +1,145 @@
//filter will reemit the data if cb(err,pass) pass is truthy
// reduce is more tricky
// maybe we want to group the reductions or emit progress updates occasionally
// the most basic reduce just emits one 'data' event after it has recieved 'end'
var Stream = require('stream').Stream
//create an event stream and apply function to each .write
//emitting each response as data
//unless it's an empty callback
module.exports = function (mapper, opts) {
var stream = new Stream()
, self = this
, inputs = 0
, outputs = 0
, ended = false
, paused = false
, destroyed = false
, lastWritten = 0
, inNext = false
this.opts = opts || {};
var errorEventName = this.opts.failures ? 'failure' : 'error';
// Items that are not ready to be written yet (because they would come out of
// order) get stuck in a queue for later.
var writeQueue = {}
stream.writable = true
stream.readable = true
function queueData (data, number) {
var nextToWrite = lastWritten + 1
if (number === nextToWrite) {
// If it's next, and its not undefined write it
if (data !== undefined) {
stream.emit.apply(stream, ['data', data])
}
lastWritten ++
nextToWrite ++
} else {
// Otherwise queue it for later.
writeQueue[number] = data
}
// If the next value is in the queue, write it
if (writeQueue.hasOwnProperty(nextToWrite)) {
var dataToWrite = writeQueue[nextToWrite]
delete writeQueue[nextToWrite]
return queueData(dataToWrite, nextToWrite)
}
outputs ++
if(inputs === outputs) {
if(paused) paused = false, stream.emit('drain') //written all the incoming events
if(ended) end()
}
}
function next (err, data, number) {
if(destroyed) return
inNext = true
if (!err || self.opts.failures) {
queueData(data, number)
}
if (err) {
stream.emit.apply(stream, [ errorEventName, err ]);
}
inNext = false;
}
// Wrap the mapper function by calling its callback with the order number of
// the item in the stream.
function wrappedMapper (input, number, callback) {
return mapper.call(null, input, function(err, data){
callback(err, data, number)
})
}
stream.write = function (data) {
if(ended) throw new Error('map stream is not writable')
inNext = false
inputs ++
try {
//catch sync errors and handle them like async errors
var written = wrappedMapper(data, inputs, next)
paused = (written === false)
return !paused
} catch (err) {
//if the callback has been called syncronously, and the error
//has occured in an listener, throw it again.
if(inNext)
throw err
next(err)
return !paused
}
}
function end (data) {
//if end was called with args, write it,
ended = true //write will emit 'end' if ended is true
stream.writable = false
if(data !== undefined) {
return queueData(data, inputs)
} else if (inputs == outputs) { //wait for processing
stream.readable = false, stream.emit('end'), stream.destroy()
}
}
stream.end = function (data) {
if(ended) return
end()
}
stream.destroy = function () {
ended = destroyed = true
stream.writable = stream.readable = paused = false
process.nextTick(function () {
stream.emit('close')
})
}
stream.pause = function () {
paused = true
}
stream.resume = function () {
paused = false
}
return stream
}
@@ -0,0 +1,34 @@
{
"name": "map-stream",
"version": "0.1.0",
"description": "construct pipes of streams of events",
"homepage": "http://github.com/dominictarr/map-stream",
"repository": {
"type": "git",
"url": "git://github.com/dominictarr/map-stream.git"
},
"dependencies": {},
"devDependencies": {
"asynct": "*",
"it-is": "1",
"ubelt": "~2.9",
"stream-spec": "~0.2",
"event-stream": "~2.1",
"from": "0.0.2"
},
"scripts": {
"test": "asynct test/"
},
"author": {
"name": "Dominic Tarr",
"email": "dominic.tarr@gmail.com",
"url": "http://dominictarr.com"
},
"readme": "# MapStream\n\nRefactored out of [event-stream](https://github.com/dominictarr/event-stream)\n\n##map (asyncFunction[, options])\n\nCreate a through stream from an asyncronous function. \n\n``` js\nvar map = require('map-stream')\n\nmap(function (data, callback) {\n //transform data\n // ...\n callback(null, data)\n})\n\n```\n\nEach map MUST call the callback. It may callback with data, with an error or with no arguments, \n\n * `callback()` drop this data. \n this makes the map work like `filter`, \n note:`callback(null,null)` is not the same, and will emit `null`\n\n * `callback(null, newData)` turn data into newData\n \n * `callback(error)` emit an error for this item.\n\n>Note: if a callback is not called, `map` will think that it is still being processed, \n>every call must be answered or the stream will not know when to end. \n>\n>Also, if the callback is called more than once, every call but the first will be ignored.\n\n##Options \n\n * `failures` - `boolean` continue mapping even if error occured. On error `map-stream` will emit `failure` event. (default: `false`)\n",
"readmeFilename": "readme.markdown",
"bugs": {
"url": "https://github.com/dominictarr/map-stream/issues"
},
"_id": "map-stream@0.1.0",
"_from": "map-stream@~0.1.0"
}
@@ -0,0 +1,37 @@
# MapStream
Refactored out of [event-stream](https://github.com/dominictarr/event-stream)
##map (asyncFunction[, options])
Create a through stream from an asyncronous function.
``` js
var map = require('map-stream')
map(function (data, callback) {
//transform data
// ...
callback(null, data)
})
```
Each map MUST call the callback. It may callback with data, with an error or with no arguments,
* `callback()` drop this data.
this makes the map work like `filter`,
note:`callback(null,null)` is not the same, and will emit `null`
* `callback(null, newData)` turn data into newData
* `callback(error)` emit an error for this item.
>Note: if a callback is not called, `map` will think that it is still being processed,
>every call must be answered or the stream will not know when to end.
>
>Also, if the callback is called more than once, every call but the first will be ignored.
##Options
* `failures` - `boolean` continue mapping even if error occured. On error `map-stream` will emit `failure` event. (default: `false`)
@@ -0,0 +1,318 @@
'use strict';
var map = require('../')
, it = require('it-is')
, u = require('ubelt')
, spec = require('stream-spec')
, from = require('from')
, Stream = require('stream')
, es = require('event-stream')
//REFACTOR THIS TEST TO USE es.readArray and es.writeArray
function writeArray(array, stream) {
array.forEach( function (j) {
stream.write(j)
})
stream.end()
}
function readStream(stream, done) {
var array = []
stream.on('data', function (data) {
array.push(data)
})
stream.on('error', done)
stream.on('end', function (data) {
done(null, array)
})
}
//call sink on each write,
//and complete when finished.
function pauseStream (prob, delay) {
var pauseIf = (
'number' == typeof prob
? function () {
return Math.random() < prob
}
: 'function' == typeof prob
? prob
: 0.1
)
var delayer = (
!delay
? process.nextTick
: 'number' == typeof delay
? function (next) { setTimeout(next, delay) }
: delay
)
return es.through(function (data) {
if(!this.paused && pauseIf()) {
console.log('PAUSE STREAM PAUSING')
this.pause()
var self = this
delayer(function () {
console.log('PAUSE STREAM RESUMING')
self.resume()
})
}
console.log("emit ('data', " + data + ')')
this.emit('data', data)
})
}
exports ['simple map applied to a stream'] = function (test) {
var input = [1,2,3,7,5,3,1,9,0,2,4,6]
//create event stream from
var doubler = map(function (data, cb) {
cb(null, data * 2)
})
spec(doubler).through().validateOnExit()
//a map is only a middle man, so it is both readable and writable
it(doubler).has({
readable: true,
writable: true,
})
readStream(doubler, function (err, output) {
it(output).deepEqual(input.map(function (j) {
return j * 2
}))
// process.nextTick(x.validate)
test.done()
})
writeArray(input, doubler)
}
exports ['stream comes back in the correct order'] = function (test) {
var input = [3, 2, 1]
var delayer = map(function(data, cb){
setTimeout(function () {
cb(null, data)
}, 100 * data)
})
readStream(delayer, function (err, output) {
it(output).deepEqual(input)
test.done()
})
writeArray(input, delayer)
}
exports ['continues on error event with failures `true`'] = function (test) {
var input = [1, 2, 3]
var delayer = map(function(data, cb){
cb(new Error('Something gone wrong'), data)
}, { failures: true })
readStream(delayer, function (err, output) {
it(output).deepEqual(input)
test.done()
})
writeArray(input, delayer)
}
exports['pipe two maps together'] = function (test) {
var input = [1,2,3,7,5,3,1,9,0,2,4,6]
//create event stream from
function dd (data, cb) {
cb(null, data * 2)
}
var doubler1 = map(dd), doubler2 = map(dd)
doubler1.pipe(doubler2)
spec(doubler1).through().validateOnExit()
spec(doubler2).through().validateOnExit()
readStream(doubler2, function (err, output) {
it(output).deepEqual(input.map(function (j) {
return j * 4
}))
test.done()
})
writeArray(input, doubler1)
}
//next:
//
// test pause, resume and drian.
//
// then make a pipe joiner:
//
// plumber (evStr1, evStr2, evStr3, evStr4, evStr5)
//
// will return a single stream that write goes to the first
exports ['map will not call end until the callback'] = function (test) {
var ticker = map(function (data, cb) {
process.nextTick(function () {
cb(null, data * 2)
})
})
spec(ticker).through().validateOnExit()
ticker.write('x')
ticker.end()
ticker.on('end', function () {
test.done()
})
}
exports ['emit failures with opts.failures === `ture`'] = function (test) {
var err = new Error('INTENSIONAL ERROR')
, mapper =
map(function () {
throw err
}, { failures: true })
mapper.on('failure', function (_err) {
it(_err).equal(err)
test.done()
})
mapper.write('hello')
}
exports ['emit error thrown'] = function (test) {
var err = new Error('INTENSIONAL ERROR')
, mapper =
map(function () {
throw err
})
mapper.on('error', function (_err) {
it(_err).equal(err)
test.done()
})
mapper.write('hello')
}
exports ['emit error calledback'] = function (test) {
var err = new Error('INTENSIONAL ERROR')
, mapper =
map(function (data, callback) {
callback(err)
})
mapper.on('error', function (_err) {
it(_err).equal(err)
test.done()
})
mapper.write('hello')
}
exports ['do not emit drain if not paused'] = function (test) {
var maps = map(function (data, callback) {
u.delay(callback)(null, 1)
return true
})
spec(maps).through().pausable().validateOnExit()
maps.on('drain', function () {
it(false).ok('should not emit drain unless the stream is paused')
})
it(maps.write('hello')).equal(true)
it(maps.write('hello')).equal(true)
it(maps.write('hello')).equal(true)
setTimeout(function () {maps.end()},10)
maps.on('end', test.done)
}
exports ['emits drain if paused, when all '] = function (test) {
var active = 0
var drained = false
var maps = map(function (data, callback) {
active ++
u.delay(function () {
active --
callback(null, 1)
})()
console.log('WRITE', false)
return false
})
spec(maps).through().validateOnExit()
maps.on('drain', function () {
drained = true
it(active).equal(0, 'should emit drain when all maps are done')
})
it(maps.write('hello')).equal(false)
it(maps.write('hello')).equal(false)
it(maps.write('hello')).equal(false)
process.nextTick(function () {maps.end()},10)
maps.on('end', function () {
console.log('end')
it(drained).ok('shoud have emitted drain before end')
test.done()
})
}
exports ['map applied to a stream with filtering'] = function (test) {
var input = [1,2,3,7,5,3,1,9,0,2,4,6]
var doubler = map(function (data, callback) {
if (data % 2)
callback(null, data * 2)
else
callback()
})
readStream(doubler, function (err, output) {
it(output).deepEqual(input.filter(function (j) {
return j % 2
}).map(function (j) {
return j * 2
}))
test.done()
})
spec(doubler).through().validateOnExit()
writeArray(input, doubler)
}
@@ -0,0 +1,3 @@
node_modules
node_modules/*
npm_debug.log
@@ -0,0 +1,231 @@
Dual Licensed MIT and Apache 2
The MIT License
Copyright (c) 2013 Dominic Tarr
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.
-----------------------------------------------------------------------
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright (c) 2013 Dominic Tarr
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,3 @@
//through@2 handles this by default!
module.exports = require('through')
@@ -0,0 +1,47 @@
{
"name": "pause-stream",
"version": "0.0.11",
"description": "a ThroughStream that strictly buffers all readable events when paused.",
"main": "index.js",
"directories": {
"test": "test"
},
"devDependencies": {
"stream-tester": "0.0.2",
"stream-spec": "~0.2.0"
},
"scripts": {
"test": "node test/index.js && node test/pause-end.js"
},
"repository": {
"type": "git",
"url": "git://github.com/dominictarr/pause-stream.git"
},
"keywords": [
"stream",
"pipe",
"pause",
"drain",
"buffer"
],
"author": {
"name": "Dominic Tarr",
"email": "dominic.tarr@gmail.com",
"url": "dominictarr.com"
},
"license": [
"MIT",
"Apache2"
],
"dependencies": {
"through": "~2.3"
},
"readme": "# PauseStream\n\nThis is a `Stream` that will strictly buffer when paused.\nConnect it to anything you need buffered.\n\n``` js\n var ps = require('pause-stream')();\n\n badlyBehavedStream.pipe(ps.pause())\n\n aLittleLater(function (err, data) {\n ps.pipe(createAnotherStream(data))\n ps.resume()\n })\n```\n\n`PauseStream` will buffer whenever paused.\nit will buffer when yau have called `pause` manually.\nbut also when it's downstream `dest.write()===false`.\nit will attempt to drain the buffer when you call resume\nor the downstream emits `'drain'`\n\n`PauseStream` is tested using [stream-spec](https://github.com/dominictarr/stream-spec)\nand [stream-tester](https://github.com/dominictarr/stream-tester)\n\nThis is now the default case of \n[through](https://github.com/dominictarr/through)\n\nhttps://github.com/dominictarr/pause-stream/commit/4a6fe3dc2c11091b1efbfde912e0473719ed9cc0\n",
"readmeFilename": "readme.markdown",
"bugs": {
"url": "https://github.com/dominictarr/pause-stream/issues"
},
"homepage": "https://github.com/dominictarr/pause-stream",
"_id": "pause-stream@0.0.11",
"_from": "pause-stream@0.0.11"
}
@@ -0,0 +1,29 @@
# PauseStream
This is a `Stream` that will strictly buffer when paused.
Connect it to anything you need buffered.
``` js
var ps = require('pause-stream')();
badlyBehavedStream.pipe(ps.pause())
aLittleLater(function (err, data) {
ps.pipe(createAnotherStream(data))
ps.resume()
})
```
`PauseStream` will buffer whenever paused.
it will buffer when yau have called `pause` manually.
but also when it's downstream `dest.write()===false`.
it will attempt to drain the buffer when you call resume
or the downstream emits `'drain'`
`PauseStream` is tested using [stream-spec](https://github.com/dominictarr/stream-spec)
and [stream-tester](https://github.com/dominictarr/stream-tester)
This is now the default case of
[through](https://github.com/dominictarr/through)
https://github.com/dominictarr/pause-stream/commit/4a6fe3dc2c11091b1efbfde912e0473719ed9cc0
@@ -0,0 +1,17 @@
var spec = require('stream-spec')
var tester = require('stream-tester')
var ps = require('..')()
spec(ps)
.through({strict: false})
.validateOnExit()
var master = tester.createConsistent
tester.createRandomStream(1000) //1k random numbers
.pipe(master = tester.createConsistentStream())
.pipe(tester.createUnpauseStream())
.pipe(ps)
.pipe(tester.createPauseStream())
.pipe(master.createSlave())
@@ -0,0 +1,33 @@
var pause = require('..')
var assert = require('assert')
var ps = pause()
var read = [], ended = false
ps.on('data', function (i) {
read.push(i)
})
ps.on('end', function () {
ended = true
})
assert.deepEqual(read, [])
ps.write(0)
ps.write(1)
ps.write(2)
assert.deepEqual(read, [0, 1, 2])
ps.pause()
assert.deepEqual(read, [0, 1, 2])
ps.end()
assert.equal(ended, false)
ps.resume()
assert.equal(ended, true)
@@ -0,0 +1,3 @@
node_modules
node_modules/*
npm_debug.log
@@ -0,0 +1,4 @@
language: node_js
node_js:
- 0.8
- "0.10"
@@ -0,0 +1,22 @@
Copyright (c) 2011 Dominic Tarr
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.
@@ -0,0 +1,26 @@
var inspect = require('util').inspect
var es = require('event-stream') //load event-stream
var split = require('../')
if(!module.parent) {
es.pipe( //pipe joins streams together
process.openStdin(), //open stdin
split(), //split stream to break on newlines
es.map(function (data, callback) {//turn this async function into a stream
var j
try {
j = JSON.parse(data) //try to parse input into json
} catch (err) {
return callback(null, data) //if it fails just pass it anyway
}
callback(null, inspect(j)) //render it nicely
}),
process.stdout // pipe it to stdout !
)
}
// run this
//
// curl -sS registry.npmjs.org/event-stream | node pretty.js
//
@@ -0,0 +1,59 @@
//filter will reemit the data if cb(err,pass) pass is truthy
// reduce is more tricky
// maybe we want to group the reductions or emit progress updates occasionally
// the most basic reduce just emits one 'data' event after it has recieved 'end'
var through = require('through')
var Decoder = require('string_decoder').StringDecoder
module.exports = split
//TODO pass in a function to map across the lines.
function split (matcher, mapper) {
var decoder = new Decoder()
var soFar = ''
if('function' === typeof matcher)
mapper = matcher, matcher = null
if (!matcher)
matcher = /\r?\n/
function emit(stream, piece) {
if(mapper) {
try {
piece = mapper(piece)
}
catch (err) {
return stream.emit('error', err)
}
if('undefined' !== typeof piece)
stream.queue(piece)
}
else
stream.queue(piece)
}
function next (stream, buffer) {
var pieces = (soFar + buffer).split(matcher)
soFar = pieces.pop()
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i]
emit(stream, piece)
}
}
return through(function (b) {
next(this, decoder.write(b))
},
function () {
if(decoder.end)
next(this, decoder.end())
if(soFar != null)
emit(this, soFar)
this.queue(null)
})
}
@@ -0,0 +1,39 @@
{
"name": "split",
"version": "0.2.10",
"description": "split a Text Stream into a Line Stream",
"homepage": "http://github.com/dominictarr/split",
"repository": {
"type": "git",
"url": "git://github.com/dominictarr/split.git"
},
"dependencies": {
"through": "2"
},
"devDependencies": {
"asynct": "*",
"it-is": "1",
"ubelt": "~2.9",
"stream-spec": "~0.2",
"event-stream": "~3.0.2"
},
"scripts": {
"test": "asynct test/"
},
"author": {
"name": "Dominic Tarr",
"email": "dominic.tarr@gmail.com",
"url": "http://bit.ly/dominictarr"
},
"optionalDependencies": {},
"engines": {
"node": "*"
},
"readme": "# Split (matcher)\n\n[![build status](https://secure.travis-ci.org/dominictarr/split.png)](http://travis-ci.org/dominictarr/split)\n\nBreak up a stream and reassemble it so that each line is a chunk. matcher may be a `String`, or a `RegExp` \n\nExample, read every line in a file ...\n\n``` js\n fs.createReadStream(file)\n .pipe(split())\n .on('data', function (line) {\n //each chunk now is a seperate line!\n })\n\n```\n\n`split` takes the same arguments as `string.split` except it defaults to '/\\r?\\n/' instead of ',', and the optional `limit` paremeter is ignored.\n[String#split](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/split)\n\n# NDJ - Newline Delimited Json\n\n`split` accepts a function which transforms each line.\n\n``` js\nfs.createReadStream(file)\n .pipe(split(JSON.parse))\n .on('data', function (obj) {\n //each chunk now is a a js object\n })\n .on('error', function (err) {\n //syntax errors will land here\n //note, this ends the stream.\n })\n```\n\n# License\n\nMIT\n",
"readmeFilename": "readme.markdown",
"bugs": {
"url": "https://github.com/dominictarr/split/issues"
},
"_id": "split@0.2.10",
"_from": "split@0.2"
}
@@ -0,0 +1,39 @@
# Split (matcher)
[![build status](https://secure.travis-ci.org/dominictarr/split.png)](http://travis-ci.org/dominictarr/split)
Break up a stream and reassemble it so that each line is a chunk. matcher may be a `String`, or a `RegExp`
Example, read every line in a file ...
``` js
fs.createReadStream(file)
.pipe(split())
.on('data', function (line) {
//each chunk now is a seperate line!
})
```
`split` takes the same arguments as `string.split` except it defaults to '/\r?\n/' instead of ',', and the optional `limit` paremeter is ignored.
[String#split](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/split)
# NDJ - Newline Delimited Json
`split` accepts a function which transforms each line.
``` js
fs.createReadStream(file)
.pipe(split(JSON.parse))
.on('data', function (obj) {
//each chunk now is a a js object
})
.on('error', function (err) {
//syntax errors will land here
//note, this ends the stream.
})
```
# License
MIT
@@ -0,0 +1,34 @@
var it = require('it-is').style('colour')
, split = require('..')
exports ['split data with partitioned unicode character'] = function (test) {
var s = split(/,/g)
, caughtError = false
, rows = []
s.on('error', function (err) {
caughtError = true
})
s.on('data', function (row) { rows.push(row) })
var x = 'テスト試験今日とても,よい天気で'
unicodeData = new Buffer(x);
// partition of 日
piece1 = unicodeData.slice(0, 20);
piece2 = unicodeData.slice(20, unicodeData.length);
s.write(piece1);
s.write(piece2);
s.end()
it(caughtError).equal(false)
it(rows).deepEqual(['テスト試験今日とても', 'よい天気で']);
it(rows).deepEqual(x.split(','))
test.done()
}
@@ -0,0 +1,85 @@
var es = require('event-stream')
, it = require('it-is').style('colour')
, d = require('ubelt')
, split = require('..')
, join = require('path').join
, fs = require('fs')
, Stream = require('stream').Stream
, spec = require('stream-spec')
exports ['split() works like String#split'] = function (test) {
var readme = join(__filename)
, expected = fs.readFileSync(readme, 'utf-8').split('\n')
, cs = split()
, actual = []
, ended = false
, x = spec(cs).through()
var a = new Stream ()
a.write = function (l) {
actual.push(l.trim())
}
a.end = function () {
ended = true
expected.forEach(function (v,k) {
//String.split will append an empty string ''
//if the string ends in a split pattern.
//es.split doesn't which was breaking this test.
//clearly, appending the empty string is correct.
//tests are passing though. which is the current job.
if(v)
it(actual[k]).like(v)
})
//give the stream time to close
process.nextTick(function () {
test.done()
x.validate()
})
}
a.writable = true
fs.createReadStream(readme, {flags: 'r'}).pipe(cs)
cs.pipe(a)
}
exports ['split() takes mapper function'] = function (test) {
var readme = join(__filename)
, expected = fs.readFileSync(readme, 'utf-8').split('\n')
, cs = split(function (line) { return line.toUpperCase() })
, actual = []
, ended = false
, x = spec(cs).through()
var a = new Stream ()
a.write = function (l) {
actual.push(l.trim())
}
a.end = function () {
ended = true
expected.forEach(function (v,k) {
//String.split will append an empty string ''
//if the string ends in a split pattern.
//es.split doesn't which was breaking this test.
//clearly, appending the empty string is correct.
//tests are passing though. which is the current job.
if(v)
it(actual[k]).equal(v.trim().toUpperCase())
})
//give the stream time to close
process.nextTick(function () {
test.done()
x.validate()
})
}
a.writable = true
fs.createReadStream(readme, {flags: 'r'}).pipe(cs)
cs.pipe(a)
}
@@ -0,0 +1,51 @@
var it = require('it-is').style('colour')
, split = require('..')
exports ['emit mapper exceptions as error events'] = function (test) {
var s = split(JSON.parse)
, caughtError = false
, rows = []
s.on('error', function (err) {
caughtError = true
})
s.on('data', function (row) { rows.push(row) })
s.write('{"a":1}\n{"')
it(caughtError).equal(false)
it(rows).deepEqual([ { a: 1 } ])
s.write('b":2}\n{"c":}\n')
it(caughtError).equal(true)
it(rows).deepEqual([ { a: 1 }, { b: 2 } ])
s.end()
test.done()
}
exports ['mapper error events on trailing chunks'] = function (test) {
var s = split(JSON.parse)
, caughtError = false
, rows = []
s.on('error', function (err) {
caughtError = true
})
s.on('data', function (row) { rows.push(row) })
s.write('{"a":1}\n{"')
it(caughtError).equal(false)
it(rows).deepEqual([ { a: 1 } ])
s.write('b":2}\n{"c":}')
it(caughtError).equal(false)
it(rows).deepEqual([ { a: 1 }, { b: 2 } ])
s.end()
it(caughtError).equal(true)
it(rows).deepEqual([ { a: 1 }, { b: 2 } ])
test.done()
}
@@ -0,0 +1,3 @@
node_modules
node_modules/*
npm_debug.log
@@ -0,0 +1,4 @@
language: node_js
node_js:
- 0.6
- 0.8
@@ -0,0 +1,22 @@
Copyright (c) 2012 'Dominic Tarr'
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.
@@ -0,0 +1,29 @@
# stream-combiner
<img src=https://secure.travis-ci.org/dominictarr/stream-combiner.png?branch=master>
## Combine (stream1,...,streamN)
Turn a pipeline into a single stream. `pipeline` returns a stream that writes to the first stream
and reads from the last stream.
Listening for 'error' will recieve errors from all streams inside the pipe.
``` js
var Combine = require('stream-combiner')
var es = require('event-stream')
Combine( //connect streams together with `pipe`
process.openStdin(), //open stdin
es.split(), //split stream to break on newlines
es.map(function (data, callback) {//turn this async function into a stream
callback(null
, inspect(JSON.parse(data))) //render it nicely
}),
process.stdout // pipe it to stdout !
)
```
## License
MIT
@@ -0,0 +1,39 @@
var duplexer = require('duplexer')
module.exports = function () {
var streams = [].slice.call(arguments)
, first = streams[0]
, last = streams[streams.length - 1]
, thepipe = duplexer(first, last)
if(streams.length == 1)
return streams[0]
else if (!streams.length)
throw new Error('connect called with empty args')
//pipe all the streams together
function recurse (streams) {
if(streams.length < 2)
return
streams[0].pipe(streams[1])
recurse(streams.slice(1))
}
recurse(streams)
function onerror () {
var args = [].slice.call(arguments)
args.unshift('error')
thepipe.emit.apply(thepipe, args)
}
//es.duplex already reemits the error from the first and last stream.
//add a listener for the inner streams in the pipeline.
for(var i = 1; i < streams.length - 1; i ++)
streams[i].on('error', onerror)
return thepipe
}
@@ -0,0 +1,33 @@
{
"name": "stream-combiner",
"version": "0.0.4",
"homepage": "https://github.com/dominictarr/stream-combiner",
"repository": {
"type": "git",
"url": "git://github.com/dominictarr/stream-combiner.git"
},
"dependencies": {
"duplexer": "~0.1.1"
},
"devDependencies": {
"tape": "~2.3.0",
"event-stream": "~3.0.7"
},
"scripts": {
"test": "set -e; for t in test/*.js; do node $t; done"
},
"author": {
"name": "'Dominic Tarr'",
"email": "dominic.tarr@gmail.com",
"url": "http://dominictarr.com"
},
"license": "MIT",
"readme": "# stream-combiner\n\n<img src=https://secure.travis-ci.org/dominictarr/stream-combiner.png?branch=master>\n\n## Combine (stream1,...,streamN)\n\nTurn a pipeline into a single stream. `pipeline` returns a stream that writes to the first stream\nand reads from the last stream. \n\nListening for 'error' will recieve errors from all streams inside the pipe.\n\n``` js\n var Combine = require('stream-combiner')\n var es = require('event-stream')\n\n Combine( //connect streams together with `pipe`\n process.openStdin(), //open stdin\n es.split(), //split stream to break on newlines\n es.map(function (data, callback) {//turn this async function into a stream\n callback(null\n , inspect(JSON.parse(data))) //render it nicely\n }),\n process.stdout // pipe it to stdout !\n )\n```\n\n## License\n\nMIT\n",
"readmeFilename": "README.md",
"description": "<img src=https://secure.travis-ci.org/dominictarr/stream-combiner.png?branch=master>",
"bugs": {
"url": "https://github.com/dominictarr/stream-combiner/issues"
},
"_id": "stream-combiner@0.0.4",
"_from": "stream-combiner@~0.0.4"
}
@@ -0,0 +1,52 @@
var es = require('event-stream')
var combine = require('..')
var test = require('tape')
test('do not duplicate errors', function (test) {
var errors = 0;
var pipe = combine(
es.through(function(data) {
return this.emit('data', data);
}),
es.through(function(data) {
return this.emit('error', new Error(data));
})
)
pipe.on('error', function(err) {
errors++
test.ok(errors, 'expected error count')
process.nextTick(function () {
return test.end();
})
})
return pipe.write('meh');
})
test('3 pipe do not duplicate errors', function (test) {
var errors = 0;
var pipe = combine(
es.through(function(data) {
return this.emit('data', data);
}),
es.through(function(data) {
return this.emit('error', new Error(data));
}),
es.through()
)
pipe.on('error', function(err) {
errors++
test.ok(errors, 'expected error count')
process.nextTick(function () {
return test.end();
})
})
return pipe.write('meh');
})
@@ -0,0 +1,5 @@
language: node_js
node_js:
- 0.6
- 0.8
- "0.10"
Loaded 100 of 3159 files, more files were not shown because too many files have changed in this diff. Show more