initial commit
This commit is contained in:
commit
d3fcd7f10c
3159 files changed
+349702
No files matched your search
+6
@@ -0,0 +1,6 @@
|
||||
.DS_Store
|
||||
*.log
|
||||
node_modules
|
||||
build
|
||||
*.node
|
||||
components
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.11"
|
||||
- "0.10"
|
||||
+20
@@ -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
@@ -0,0 +1,68 @@
|
||||
[](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
@@ -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
@@ -0,0 +1 @@
|
||||
../clean-css/bin/cleancss
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
*~
|
||||
*.swp
|
||||
node_modules/
|
||||
.git
|
||||
tests/results
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 0.10
|
||||
|
||||
before_script:
|
||||
- npm install --dev
|
||||
|
||||
script:
|
||||
- npm test
|
||||
+21
@@ -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
@@ -0,0 +1,42 @@
|
||||
# BufferStreams [](https://npmjs.org/package/bufferstreams) [](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
@@ -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 [](https://npmjs.org/package/bufferstreams) [](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
@@ -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;
|
||||
Generated
Vendored
Executable
+111
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,187 @@
|
||||
[](https://badge.fury.io/js/clean-css)
|
||||
[](https://travis-ci.org/GoalSmashers/clean-css)
|
||||
[](https://david-dm.org/GoalSmashers/clean-css)
|
||||
[](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
@@ -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
@@ -0,0 +1 @@
|
||||
module.exports = require('./lib/clean');
|
||||
+329
@@ -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
|
||||
};
|
||||
};
|
||||
+64
@@ -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);
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
Generated
Vendored
+12
@@ -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;
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
+16
@@ -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);
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
+174
@@ -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;
|
||||
}
|
||||
};
|
||||
};
|
||||
+34
@@ -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
|
||||
};
|
||||
};
|
||||
+53
@@ -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;
|
||||
}
|
||||
};
|
||||
+152
@@ -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
|
||||
};
|
||||
};
|
||||
+228
@@ -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);
|
||||
}
|
||||
};
|
||||
};
|
||||
Generated
Vendored
+50
@@ -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();
|
||||
}
|
||||
};
|
||||
};
|
||||
Generated
Vendored
+30
@@ -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);
|
||||
}
|
||||
};
|
||||
};
|
||||
+221
@@ -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);
|
||||
}
|
||||
};
|
||||
};
|
||||
+129
@@ -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();
|
||||
}
|
||||
};
|
||||
};
|
||||
+59
@@ -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 '';
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
+32
@@ -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];
|
||||
}
|
||||
};
|
||||
};
|
||||
+69
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
};
|
||||
};
|
||||
Generated
Vendored
+179
@@ -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
|
||||
Generated
Vendored
+195
@@ -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).
|
||||
|
||||
[](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 <tj@vision-media.ca>
|
||||
|
||||
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.
|
||||
Generated
Vendored
+847
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+38
@@ -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 [](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 <tj@vision-media.ca>\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
@@ -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": "[](https://badge.fury.io/js/clean-css)\n[](https://travis-ci.org/GoalSmashers/clean-css)\n[](https://david-dm.org/GoalSmashers/clean-css)\n[](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
@@ -0,0 +1 @@
|
||||
node_modules/
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 0.6
|
||||
- 0.8
|
||||
- 0.10
|
||||
+18
@@ -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
@@ -0,0 +1,124 @@
|
||||
# clone
|
||||
|
||||
[](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
@@ -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
@@ -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[](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
@@ -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
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
node_modules/*
|
||||
npm_debug.log
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 0.6
|
||||
- 0.8
|
||||
+22
@@ -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.
|
||||
+25
@@ -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
@@ -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')
|
||||
}
|
||||
Generated
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
*.log
|
||||
*.err
|
||||
Generated
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.11"
|
||||
- "0.10"
|
||||
- "0.8"
|
||||
- "0.6"
|
||||
Generated
Vendored
+19
@@ -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.
|
||||
Generated
Vendored
+47
@@ -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
|
||||
Generated
Vendored
+87
@@ -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)
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+57
@@ -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"
|
||||
}
|
||||
Generated
Vendored
+31
@@ -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")
|
||||
})
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
Generated
Vendored
+15
@@ -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.
|
||||
Generated
Vendored
+24
@@ -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.
|
||||
Generated
Vendored
+68
@@ -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
|
||||
}
|
||||
Generated
Vendored
+38
@@ -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"
|
||||
}
|
||||
Generated
Vendored
+38
@@ -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
|
||||
Generated
Vendored
+141
@@ -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()
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
Generated
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
node_modules/*
|
||||
npm_debug.log
|
||||
Generated
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 0.6
|
||||
- 0.8
|
||||
Generated
Vendored
+22
@@ -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.
|
||||
Generated
Vendored
+26
@@ -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
|
||||
//
|
||||
Generated
Vendored
+145
@@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Generated
Vendored
+34
@@ -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"
|
||||
}
|
||||
Generated
Vendored
+37
@@ -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`)
|
||||
Generated
Vendored
+318
@@ -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)
|
||||
|
||||
}
|
||||
|
||||
|
||||
Generated
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
node_modules/*
|
||||
npm_debug.log
|
||||
Generated
Vendored
+231
@@ -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.
|
||||
Generated
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
//through@2 handles this by default!
|
||||
module.exports = require('through')
|
||||
|
||||
Generated
Vendored
+47
@@ -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"
|
||||
}
|
||||
Generated
Vendored
+29
@@ -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
|
||||
Generated
Vendored
+17
@@ -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())
|
||||
|
||||
Generated
Vendored
+33
@@ -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)
|
||||
|
||||
|
||||
Generated
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
node_modules/*
|
||||
npm_debug.log
|
||||
Generated
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 0.8
|
||||
- "0.10"
|
||||
Generated
Vendored
+22
@@ -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.
|
||||
Generated
Vendored
+26
@@ -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
|
||||
//
|
||||
Generated
Vendored
+59
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
Generated
Vendored
+39
@@ -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[](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"
|
||||
}
|
||||
Generated
Vendored
+39
@@ -0,0 +1,39 @@
|
||||
# Split (matcher)
|
||||
|
||||
[](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
|
||||
Generated
Vendored
+34
@@ -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()
|
||||
}
|
||||
Generated
Vendored
+85
@@ -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)
|
||||
|
||||
}
|
||||
|
||||
Generated
Vendored
+51
@@ -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()
|
||||
}
|
||||
Generated
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
node_modules/*
|
||||
npm_debug.log
|
||||
Generated
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 0.6
|
||||
- 0.8
|
||||
Generated
Vendored
+22
@@ -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.
|
||||
Generated
Vendored
+29
@@ -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
|
||||
Generated
Vendored
+39
@@ -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
|
||||
}
|
||||
|
||||
Generated
Vendored
+33
@@ -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"
|
||||
}
|
||||
Generated
Vendored
+52
@@ -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');
|
||||
|
||||
})
|
||||
|
||||
Generated
Vendored
+5
@@ -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
Reference in new issue
Block a user