This commit is contained in:
2021-07-23 02:36:56 +02:00
commit 4d622c5291
4878 changed files with 1849508 additions and 0 deletions

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,8 @@
language: node_js
node_js:
- "0.8"
- "0.10"
- "0.12"
- "iojs"
before_install:
- npm install -g npm@~1.4.6

View File

@ -0,0 +1,18 @@
This software is released under the MIT license:
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.

View File

@ -0,0 +1,2 @@
var argv = require('../')(process.argv.slice(2));
console.dir(argv);

View File

@ -0,0 +1,224 @@
module.exports = function (args, opts) {
if (!opts) opts = {};
var flags = { bools : {}, strings : {}, unknownFn: null };
if (typeof opts['unknown'] === 'function') {
flags.unknownFn = opts['unknown'];
}
if (typeof opts['boolean'] === 'boolean' && opts['boolean']) {
flags.allBools = true;
} else {
[].concat(opts['boolean']).filter(Boolean).forEach(function (key) {
flags.bools[key] = true;
});
}
var aliases = {};
Object.keys(opts.alias || {}).forEach(function (key) {
aliases[key] = [].concat(opts.alias[key]);
aliases[key].forEach(function (x) {
aliases[x] = [key].concat(aliases[key].filter(function (y) {
return x !== y;
}));
});
});
[].concat(opts.string).filter(Boolean).forEach(function (key) {
flags.strings[key] = true;
if (aliases[key]) {
flags.strings[aliases[key]] = true;
}
});
var defaults = opts['default'] || {};
var argv = { _ : [] };
Object.keys(flags.bools).forEach(function (key) {
setArg(key, defaults[key] === undefined ? false : defaults[key]);
});
var notFlags = [];
if (args.indexOf('--') !== -1) {
notFlags = args.slice(args.indexOf('--')+1);
args = args.slice(0, args.indexOf('--'));
}
function argDefined(key, arg) {
return (flags.allBools && /^--[^=]+$/.test(arg)) ||
flags.strings[key] || flags.bools[key] || aliases[key];
}
function setArg (key, val, arg) {
if (arg && flags.unknownFn && !argDefined(key, arg)) {
if (flags.unknownFn(arg) === false) return;
}
var value = !flags.strings[key] && isNumber(val)
? Number(val) : val
;
setKey(argv, key.split('.'), value);
(aliases[key] || []).forEach(function (x) {
setKey(argv, x.split('.'), value);
});
}
function setKey (obj, keys, value) {
var o = obj;
keys.slice(0,-1).forEach(function (key) {
if (o[key] === undefined) o[key] = {};
o = o[key];
});
var key = keys[keys.length - 1];
if (o[key] === undefined || flags.bools[key] || typeof o[key] === 'boolean') {
o[key] = value;
}
else if (Array.isArray(o[key])) {
o[key].push(value);
}
else {
o[key] = [ o[key], value ];
}
}
for (var i = 0; i < args.length; i++) {
var arg = args[i];
if (/^--.+=/.test(arg)) {
// Using [\s\S] instead of . because js doesn't support the
// 'dotall' regex modifier. See:
// http://stackoverflow.com/a/1068308/13216
var m = arg.match(/^--([^=]+)=([\s\S]*)$/);
var key = m[1];
var value = m[2];
if (flags.bools[key]) {
value = value !== 'false';
}
setArg(key, value, arg);
}
else if (/^--no-.+/.test(arg)) {
var key = arg.match(/^--no-(.+)/)[1];
setArg(key, false, arg);
}
else if (/^--.+/.test(arg)) {
var key = arg.match(/^--(.+)/)[1];
var next = args[i + 1];
if (next !== undefined && !/^-/.test(next)
&& !flags.bools[key]
&& !flags.allBools
&& (aliases[key] ? !flags.bools[aliases[key]] : true)) {
setArg(key, next, arg);
i++;
}
else if (/^(true|false)$/.test(next)) {
setArg(key, next === 'true', arg);
i++;
}
else {
setArg(key, flags.strings[key] ? '' : true, arg);
}
}
else if (/^-[^-]+/.test(arg)) {
var letters = arg.slice(1,-1).split('');
var broken = false;
for (var j = 0; j < letters.length; j++) {
var next = arg.slice(j+2);
if (next === '-') {
setArg(letters[j], next, arg)
continue;
}
if (/[A-Za-z]/.test(letters[j])
&& /-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) {
setArg(letters[j], next, arg);
broken = true;
break;
}
if (letters[j+1] && letters[j+1].match(/\W/)) {
setArg(letters[j], arg.slice(j+2), arg);
broken = true;
break;
}
else {
setArg(letters[j], flags.strings[letters[j]] ? '' : true, arg);
}
}
var key = arg.slice(-1)[0];
if (!broken && key !== '-') {
if (args[i+1] && !/^(-|--)[^-]/.test(args[i+1])
&& !flags.bools[key]
&& (aliases[key] ? !flags.bools[aliases[key]] : true)) {
setArg(key, args[i+1], arg);
i++;
}
else if (args[i+1] && /true|false/.test(args[i+1])) {
setArg(key, args[i+1] === 'true', arg);
i++;
}
else {
setArg(key, flags.strings[key] ? '' : true, arg);
}
}
}
else {
if (!flags.unknownFn || flags.unknownFn(arg) !== false) {
argv._.push(
flags.strings['_'] || !isNumber(arg) ? arg : Number(arg)
);
}
if (opts.stopEarly) {
argv._.push.apply(argv._, args.slice(i + 1));
break;
}
}
}
Object.keys(defaults).forEach(function (key) {
if (!hasKey(argv, key.split('.'))) {
setKey(argv, key.split('.'), defaults[key]);
(aliases[key] || []).forEach(function (x) {
setKey(argv, x.split('.'), defaults[key]);
});
}
});
if (opts['--']) {
argv['--'] = new Array();
notFlags.forEach(function(key) {
argv['--'].push(key);
});
}
else {
notFlags.forEach(function(key) {
argv._.push(key);
});
}
return argv;
};
function hasKey (obj, keys) {
var o = obj;
keys.slice(0,-1).forEach(function (key) {
o = (o[key] || {});
});
var key = keys[keys.length - 1];
return key in o;
}
function isNumber (x) {
if (typeof x === 'number') return true;
if (/^0x[0-9a-f]+$/i.test(x)) return true;
return /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(x);
}

View File

@ -0,0 +1,71 @@
{
"name": "minimist",
"version": "1.1.2",
"description": "parse argument options",
"main": "index.js",
"devDependencies": {
"covert": "^1.0.0",
"tap": "~0.4.0",
"tape": "^3.5.0"
},
"scripts": {
"test": "tap test/*.js",
"coverage": "covert test/*.js"
},
"testling": {
"files": "test/*.js",
"browsers": [
"ie/6..latest",
"ff/5",
"firefox/latest",
"chrome/10",
"chrome/latest",
"safari/5.1",
"safari/latest",
"opera/12"
]
},
"repository": {
"type": "git",
"url": "git://github.com/substack/minimist.git"
},
"homepage": "https://github.com/substack/minimist",
"keywords": [
"argv",
"getopt",
"parser",
"optimist"
],
"author": {
"name": "James Halliday",
"email": "mail@substack.net",
"url": "http://substack.net"
},
"license": "MIT",
"gitHead": "a8e2fe153a22dad7a0da67fd6465fab4cfa63e37",
"bugs": {
"url": "https://github.com/substack/minimist/issues"
},
"_id": "minimist@1.1.2",
"_shasum": "af960b80caf71b38236352af7fef10a8efceeae3",
"_from": "minimist@>=1.1.2 <2.0.0",
"_npmVersion": "3.1.2",
"_nodeVersion": "2.0.0",
"_npmUser": {
"name": "substack",
"email": "substack@gmail.com"
},
"dist": {
"shasum": "af960b80caf71b38236352af7fef10a8efceeae3",
"tarball": "http://registry.npmjs.org/minimist/-/minimist-1.1.2.tgz"
},
"maintainers": [
{
"name": "substack",
"email": "mail@substack.net"
}
],
"directories": {},
"_resolved": "https://registry.npmjs.org/minimist/-/minimist-1.1.2.tgz",
"readme": "ERROR: No README data found!"
}

View File

@ -0,0 +1,91 @@
# minimist
parse argument options
This module is the guts of optimist's argument parser without all the
fanciful decoration.
[![browser support](https://ci.testling.com/substack/minimist.png)](http://ci.testling.com/substack/minimist)
[![build status](https://secure.travis-ci.org/substack/minimist.png)](http://travis-ci.org/substack/minimist)
# example
``` js
var argv = require('minimist')(process.argv.slice(2));
console.dir(argv);
```
```
$ node example/parse.js -a beep -b boop
{ _: [], a: 'beep', b: 'boop' }
```
```
$ node example/parse.js -x 3 -y 4 -n5 -abc --beep=boop foo bar baz
{ _: [ 'foo', 'bar', 'baz' ],
x: 3,
y: 4,
n: 5,
a: true,
b: true,
c: true,
beep: 'boop' }
```
# methods
``` js
var parseArgs = require('minimist')
```
## var argv = parseArgs(args, opts={})
Return an argument object `argv` populated with the array arguments from `args`.
`argv._` contains all the arguments that didn't have an option associated with
them.
Numeric-looking arguments will be returned as numbers unless `opts.string` or
`opts.boolean` is set for that argument name.
Any arguments after `'--'` will not be parsed and will end up in `argv._`.
options can be:
* `opts.string` - a string or array of strings argument names to always treat as
strings
* `opts.boolean` - a boolean, string or array of strings to always treat as
booleans. if `true` will treat all double hyphenated arguments without equal signs
as boolean (e.g. affects `--foo`, not `-f` or `--foo=bar`)
* `opts.alias` - an object mapping string names to strings or arrays of string
argument names to use as aliases
* `opts.default` - an object mapping string argument names to default values
* `opts.stopEarly` - when true, populate `argv._` with everything after the
first non-option
* `opts['--']` - when true, populate `argv._` with everything before the `--`
and `argv['--']` with everything after the `--`. Here's an example:
* `opts.unknown` - a function which is invoked with a command line parameter not
defined in the `opts` configuration object. If the function returns `false`, the
unknown option is not added to `argv`.
```
> require('./')('one two three -- four five --six'.split(' '), { '--': true })
{ _: [ 'one', 'two', 'three' ],
'--': [ 'four', 'five', '--six' ] }
```
Note that with `opts['--']` set, parsing for arguments still stops after the
`--`.
# install
With [npm](https://npmjs.org) do:
```
npm install minimist
```
# license
MIT

View File

@ -0,0 +1,32 @@
var parse = require('../');
var test = require('tape');
test('flag boolean true (default all --args to boolean)', function (t) {
var argv = parse(['moo', '--honk', 'cow'], {
boolean: true
});
t.deepEqual(argv, {
honk: true,
_: ['moo', 'cow']
});
t.deepEqual(typeof argv.honk, 'boolean');
t.end();
});
test('flag boolean true only affects double hyphen arguments without equals signs', function (t) {
var argv = parse(['moo', '--honk', 'cow', '-p', '55', '--tacos=good'], {
boolean: true
});
t.deepEqual(argv, {
honk: true,
tacos: 'good',
p: 55,
_: ['moo', 'cow']
});
t.deepEqual(typeof argv.honk, 'boolean');
t.end();
});

View File

@ -0,0 +1,143 @@
var parse = require('../');
var test = require('tape');
test('flag boolean default false', function (t) {
var argv = parse(['moo'], {
boolean: ['t', 'verbose'],
default: { verbose: false, t: false }
});
t.deepEqual(argv, {
verbose: false,
t: false,
_: ['moo']
});
t.deepEqual(typeof argv.verbose, 'boolean');
t.deepEqual(typeof argv.t, 'boolean');
t.end();
});
test('boolean groups', function (t) {
var argv = parse([ '-x', '-z', 'one', 'two', 'three' ], {
boolean: ['x','y','z']
});
t.deepEqual(argv, {
x : true,
y : false,
z : true,
_ : [ 'one', 'two', 'three' ]
});
t.deepEqual(typeof argv.x, 'boolean');
t.deepEqual(typeof argv.y, 'boolean');
t.deepEqual(typeof argv.z, 'boolean');
t.end();
});
test('boolean and alias with chainable api', function (t) {
var aliased = [ '-h', 'derp' ];
var regular = [ '--herp', 'derp' ];
var opts = {
herp: { alias: 'h', boolean: true }
};
var aliasedArgv = parse(aliased, {
boolean: 'herp',
alias: { h: 'herp' }
});
var propertyArgv = parse(regular, {
boolean: 'herp',
alias: { h: 'herp' }
});
var expected = {
herp: true,
h: true,
'_': [ 'derp' ]
};
t.same(aliasedArgv, expected);
t.same(propertyArgv, expected);
t.end();
});
test('boolean and alias with options hash', function (t) {
var aliased = [ '-h', 'derp' ];
var regular = [ '--herp', 'derp' ];
var opts = {
alias: { 'h': 'herp' },
boolean: 'herp'
};
var aliasedArgv = parse(aliased, opts);
var propertyArgv = parse(regular, opts);
var expected = {
herp: true,
h: true,
'_': [ 'derp' ]
};
t.same(aliasedArgv, expected);
t.same(propertyArgv, expected);
t.end();
});
test('boolean and alias using explicit true', function (t) {
var aliased = [ '-h', 'true' ];
var regular = [ '--herp', 'true' ];
var opts = {
alias: { h: 'herp' },
boolean: 'h'
};
var aliasedArgv = parse(aliased, opts);
var propertyArgv = parse(regular, opts);
var expected = {
herp: true,
h: true,
'_': [ ]
};
t.same(aliasedArgv, expected);
t.same(propertyArgv, expected);
t.end();
});
// regression, see https://github.com/substack/node-optimist/issues/71
test('boolean and --x=true', function(t) {
var parsed = parse(['--boool', '--other=true'], {
boolean: 'boool'
});
t.same(parsed.boool, true);
t.same(parsed.other, 'true');
parsed = parse(['--boool', '--other=false'], {
boolean: 'boool'
});
t.same(parsed.boool, true);
t.same(parsed.other, 'false');
t.end();
});
test('boolean --boool=true', function (t) {
var parsed = parse(['--boool=true'], {
default: {
boool: false
},
boolean: ['boool']
});
t.same(parsed.boool, true);
t.end();
});
test('boolean --boool=false', function (t) {
var parsed = parse(['--boool=false'], {
default: {
boool: true
},
boolean: ['boool']
});
t.same(parsed.boool, false);
t.end();
});

View File

@ -0,0 +1,31 @@
var parse = require('../');
var test = require('tape');
test('-', function (t) {
t.plan(5);
t.deepEqual(parse([ '-n', '-' ]), { n: '-', _: [] });
t.deepEqual(parse([ '-' ]), { _: [ '-' ] });
t.deepEqual(parse([ '-f-' ]), { f: '-', _: [] });
t.deepEqual(
parse([ '-b', '-' ], { boolean: 'b' }),
{ b: true, _: [ '-' ] }
);
t.deepEqual(
parse([ '-s', '-' ], { string: 's' }),
{ s: '-', _: [] }
);
});
test('-a -- b', function (t) {
t.plan(3);
t.deepEqual(parse([ '-a', '--', 'b' ]), { a: true, _: [ 'b' ] });
t.deepEqual(parse([ '--a', '--', 'b' ]), { a: true, _: [ 'b' ] });
t.deepEqual(parse([ '--a', '--', 'b' ]), { a: true, _: [ 'b' ] });
});
test('move arguments after the -- into their own `--` array', function(t) {
t.plan(1);
t.deepEqual(
parse([ '--name', 'John', 'before', '--', 'after' ], { '--': true }),
{ name: 'John', _: [ 'before' ], '--': [ 'after' ] });
});

View File

@ -0,0 +1,35 @@
var test = require('tape');
var parse = require('../');
test('boolean default true', function (t) {
var argv = parse([], {
boolean: 'sometrue',
default: { sometrue: true }
});
t.equal(argv.sometrue, true);
t.end();
});
test('boolean default false', function (t) {
var argv = parse([], {
boolean: 'somefalse',
default: { somefalse: false }
});
t.equal(argv.somefalse, false);
t.end();
});
test('boolean default to null', function (t) {
var argv = parse([], {
boolean: 'maybe',
default: { maybe: null }
});
t.equal(argv.maybe, null);
var argv = parse(['--maybe'], {
boolean: 'maybe',
default: { maybe: null }
});
t.equal(argv.maybe, true);
t.end();
})

View File

@ -0,0 +1,22 @@
var parse = require('../');
var test = require('tape');
test('dotted alias', function (t) {
var argv = parse(['--a.b', '22'], {default: {'a.b': 11}, alias: {'a.b': 'aa.bb'}});
t.equal(argv.a.b, 22);
t.equal(argv.aa.bb, 22);
t.end();
});
test('dotted default', function (t) {
var argv = parse('', {default: {'a.b': 11}, alias: {'a.b': 'aa.bb'}});
t.equal(argv.a.b, 11);
t.equal(argv.aa.bb, 11);
t.end();
});
test('dotted default with no alias', function (t) {
var argv = parse('', {default: {'a.b': 11}});
t.equal(argv.a.b, 11);
t.end();
});

View File

@ -0,0 +1,31 @@
var test = require('tape');
var parse = require('../');
test('long opts', function (t) {
t.deepEqual(
parse([ '--bool' ]),
{ bool : true, _ : [] },
'long boolean'
);
t.deepEqual(
parse([ '--pow', 'xixxle' ]),
{ pow : 'xixxle', _ : [] },
'long capture sp'
);
t.deepEqual(
parse([ '--pow=xixxle' ]),
{ pow : 'xixxle', _ : [] },
'long capture eq'
);
t.deepEqual(
parse([ '--host', 'localhost', '--port', '555' ]),
{ host : 'localhost', port : 555, _ : [] },
'long captures sp'
);
t.deepEqual(
parse([ '--host=localhost', '--port=555' ]),
{ host : 'localhost', port : 555, _ : [] },
'long captures eq'
);
t.end();
});

View File

@ -0,0 +1,36 @@
var parse = require('../');
var test = require('tape');
test('nums', function (t) {
var argv = parse([
'-x', '1234',
'-y', '5.67',
'-z', '1e7',
'-w', '10f',
'--hex', '0xdeadbeef',
'789'
]);
t.deepEqual(argv, {
x : 1234,
y : 5.67,
z : 1e7,
w : '10f',
hex : 0xdeadbeef,
_ : [ 789 ]
});
t.deepEqual(typeof argv.x, 'number');
t.deepEqual(typeof argv.y, 'number');
t.deepEqual(typeof argv.z, 'number');
t.deepEqual(typeof argv.w, 'string');
t.deepEqual(typeof argv.hex, 'number');
t.deepEqual(typeof argv._[0], 'number');
t.end();
});
test('already a number', function (t) {
var argv = parse([ '-x', 1234, 789 ]);
t.deepEqual(argv, { x : 1234, _ : [ 789 ] });
t.deepEqual(typeof argv.x, 'number');
t.deepEqual(typeof argv._[0], 'number');
t.end();
});

View File

@ -0,0 +1,197 @@
var parse = require('../');
var test = require('tape');
test('parse args', function (t) {
t.deepEqual(
parse([ '--no-moo' ]),
{ moo : false, _ : [] },
'no'
);
t.deepEqual(
parse([ '-v', 'a', '-v', 'b', '-v', 'c' ]),
{ v : ['a','b','c'], _ : [] },
'multi'
);
t.end();
});
test('comprehensive', function (t) {
t.deepEqual(
parse([
'--name=meowmers', 'bare', '-cats', 'woo',
'-h', 'awesome', '--multi=quux',
'--key', 'value',
'-b', '--bool', '--no-meep', '--multi=baz',
'--', '--not-a-flag', 'eek'
]),
{
c : true,
a : true,
t : true,
s : 'woo',
h : 'awesome',
b : true,
bool : true,
key : 'value',
multi : [ 'quux', 'baz' ],
meep : false,
name : 'meowmers',
_ : [ 'bare', '--not-a-flag', 'eek' ]
}
);
t.end();
});
test('flag boolean', function (t) {
var argv = parse([ '-t', 'moo' ], { boolean: 't' });
t.deepEqual(argv, { t : true, _ : [ 'moo' ] });
t.deepEqual(typeof argv.t, 'boolean');
t.end();
});
test('flag boolean value', function (t) {
var argv = parse(['--verbose', 'false', 'moo', '-t', 'true'], {
boolean: [ 't', 'verbose' ],
default: { verbose: true }
});
t.deepEqual(argv, {
verbose: false,
t: true,
_: ['moo']
});
t.deepEqual(typeof argv.verbose, 'boolean');
t.deepEqual(typeof argv.t, 'boolean');
t.end();
});
test('newlines in params' , function (t) {
var args = parse([ '-s', "X\nX" ])
t.deepEqual(args, { _ : [], s : "X\nX" });
// reproduce in bash:
// VALUE="new
// line"
// node program.js --s="$VALUE"
args = parse([ "--s=X\nX" ])
t.deepEqual(args, { _ : [], s : "X\nX" });
t.end();
});
test('strings' , function (t) {
var s = parse([ '-s', '0001234' ], { string: 's' }).s;
t.equal(s, '0001234');
t.equal(typeof s, 'string');
var x = parse([ '-x', '56' ], { string: 'x' }).x;
t.equal(x, '56');
t.equal(typeof x, 'string');
t.end();
});
test('stringArgs', function (t) {
var s = parse([ ' ', ' ' ], { string: '_' })._;
t.same(s.length, 2);
t.same(typeof s[0], 'string');
t.same(s[0], ' ');
t.same(typeof s[1], 'string');
t.same(s[1], ' ');
t.end();
});
test('empty strings', function(t) {
var s = parse([ '-s' ], { string: 's' }).s;
t.equal(s, '');
t.equal(typeof s, 'string');
var str = parse([ '--str' ], { string: 'str' }).str;
t.equal(str, '');
t.equal(typeof str, 'string');
var letters = parse([ '-art' ], {
string: [ 'a', 't' ]
});
t.equal(letters.a, '');
t.equal(letters.r, true);
t.equal(letters.t, '');
t.end();
});
test('string and alias', function(t) {
var x = parse([ '--str', '000123' ], {
string: 's',
alias: { s: 'str' }
});
t.equal(x.str, '000123');
t.equal(typeof x.str, 'string');
t.equal(x.s, '000123');
t.equal(typeof x.s, 'string');
var y = parse([ '-s', '000123' ], {
string: 'str',
alias: { str: 's' }
});
t.equal(y.str, '000123');
t.equal(typeof y.str, 'string');
t.equal(y.s, '000123');
t.equal(typeof y.s, 'string');
t.end();
});
test('slashBreak', function (t) {
t.same(
parse([ '-I/foo/bar/baz' ]),
{ I : '/foo/bar/baz', _ : [] }
);
t.same(
parse([ '-xyz/foo/bar/baz' ]),
{ x : true, y : true, z : '/foo/bar/baz', _ : [] }
);
t.end();
});
test('alias', function (t) {
var argv = parse([ '-f', '11', '--zoom', '55' ], {
alias: { z: 'zoom' }
});
t.equal(argv.zoom, 55);
t.equal(argv.z, argv.zoom);
t.equal(argv.f, 11);
t.end();
});
test('multiAlias', function (t) {
var argv = parse([ '-f', '11', '--zoom', '55' ], {
alias: { z: [ 'zm', 'zoom' ] }
});
t.equal(argv.zoom, 55);
t.equal(argv.z, argv.zoom);
t.equal(argv.z, argv.zm);
t.equal(argv.f, 11);
t.end();
});
test('nested dotted objects', function (t) {
var argv = parse([
'--foo.bar', '3', '--foo.baz', '4',
'--foo.quux.quibble', '5', '--foo.quux.o_O',
'--beep.boop'
]);
t.same(argv.foo, {
bar : 3,
baz : 4,
quux : {
quibble : 5,
o_O : true
}
});
t.same(argv.beep, { boop : true });
t.end();
});

View File

@ -0,0 +1,9 @@
var parse = require('../');
var test = require('tape');
test('parse with modifier functions' , function (t) {
t.plan(1);
var argv = parse([ '-b', '123' ], { boolean: 'b' });
t.deepEqual(argv, { b: true, _: [123] });
});

View File

@ -0,0 +1,67 @@
var parse = require('../');
var test = require('tape');
test('numeric short args', function (t) {
t.plan(2);
t.deepEqual(parse([ '-n123' ]), { n: 123, _: [] });
t.deepEqual(
parse([ '-123', '456' ]),
{ 1: true, 2: true, 3: 456, _: [] }
);
});
test('short', function (t) {
t.deepEqual(
parse([ '-b' ]),
{ b : true, _ : [] },
'short boolean'
);
t.deepEqual(
parse([ 'foo', 'bar', 'baz' ]),
{ _ : [ 'foo', 'bar', 'baz' ] },
'bare'
);
t.deepEqual(
parse([ '-cats' ]),
{ c : true, a : true, t : true, s : true, _ : [] },
'group'
);
t.deepEqual(
parse([ '-cats', 'meow' ]),
{ c : true, a : true, t : true, s : 'meow', _ : [] },
'short group next'
);
t.deepEqual(
parse([ '-h', 'localhost' ]),
{ h : 'localhost', _ : [] },
'short capture'
);
t.deepEqual(
parse([ '-h', 'localhost', '-p', '555' ]),
{ h : 'localhost', p : 555, _ : [] },
'short captures'
);
t.end();
});
test('mixed short bool and capture', function (t) {
t.same(
parse([ '-h', 'localhost', '-fp', '555', 'script.js' ]),
{
f : true, p : 555, h : 'localhost',
_ : [ 'script.js' ]
}
);
t.end();
});
test('short and long', function (t) {
t.deepEqual(
parse([ '-h', 'localhost', '-fp', '555', 'script.js' ]),
{
f : true, p : 555, h : 'localhost',
_ : [ 'script.js' ]
}
);
t.end();
});

View File

@ -0,0 +1,15 @@
var parse = require('../');
var test = require('tape');
test('stops parsing on the first non-option when stopEarly is set', function (t) {
var argv = parse(['--aaa', 'bbb', 'ccc', '--ddd'], {
stopEarly: true
});
t.deepEqual(argv, {
aaa: 'bbb',
_: ['ccc', '--ddd']
});
t.end();
});

View File

@ -0,0 +1,102 @@
var parse = require('../');
var test = require('tape');
test('boolean and alias is not unknown', function (t) {
var unknown = [];
function unknownFn(arg) {
unknown.push(arg);
return false;
}
var aliased = [ '-h', 'true', '--derp', 'true' ];
var regular = [ '--herp', 'true', '-d', 'true' ];
var opts = {
alias: { h: 'herp' },
boolean: 'h',
unknown: unknownFn
};
var aliasedArgv = parse(aliased, opts);
var propertyArgv = parse(regular, opts);
t.same(unknown, ['--derp', '-d']);
t.end();
});
test('flag boolean true any double hyphen argument is not unknown', function (t) {
var unknown = [];
function unknownFn(arg) {
unknown.push(arg);
return false;
}
var argv = parse(['--honk', '--tacos=good', 'cow', '-p', '55'], {
boolean: true,
unknown: unknownFn
});
t.same(unknown, ['--tacos=good', 'cow', '-p']);
t.same(argv, {
honk: true,
_: []
});
t.end();
});
test('string and alias is not unknown', function (t) {
var unknown = [];
function unknownFn(arg) {
unknown.push(arg);
return false;
}
var aliased = [ '-h', 'hello', '--derp', 'goodbye' ];
var regular = [ '--herp', 'hello', '-d', 'moon' ];
var opts = {
alias: { h: 'herp' },
string: 'h',
unknown: unknownFn
};
var aliasedArgv = parse(aliased, opts);
var propertyArgv = parse(regular, opts);
t.same(unknown, ['--derp', '-d']);
t.end();
});
test('default and alias is not unknown', function (t) {
var unknown = [];
function unknownFn(arg) {
unknown.push(arg);
return false;
}
var aliased = [ '-h', 'hello' ];
var regular = [ '--herp', 'hello' ];
var opts = {
default: { 'h': 'bar' },
alias: { 'h': 'herp' },
unknown: unknownFn
};
var aliasedArgv = parse(aliased, opts);
var propertyArgv = parse(regular, opts);
t.same(unknown, []);
t.end();
unknownFn(); // exercise fn for 100% coverage
});
test('value following -- is not unknown', function (t) {
var unknown = [];
function unknownFn(arg) {
unknown.push(arg);
return false;
}
var aliased = [ '--bad', '--', 'good', 'arg' ];
var opts = {
'--': true,
unknown: unknownFn
};
var argv = parse(aliased, opts);
t.same(unknown, ['--bad']);
t.same(argv, {
'--': ['good', 'arg'],
'_': []
})
t.end();
});

View File

@ -0,0 +1,8 @@
var parse = require('../');
var test = require('tape');
test('whitespace should be whitespace' , function (t) {
t.plan(1);
var x = parse([ '-x', '\t' ]).x;
t.equal(x, '\t');
});

View File

@ -0,0 +1,10 @@
docs
examples
test
.editorconfig
.jshintignore
.jshintrc
.travis.yml
lcov.info
logo.svg
CHANGELOG.MD

View File

@ -0,0 +1,53 @@
# replace default config
# full: true
plugins:
# - name
#
# or:
# - name: false
# - name: true
#
# or:
# - name:
# param1: 1
# param2: 2
- removeDoctype
- removeXMLProcInst
- removeComments
- removeMetadata
- removeEditorsNSData
- cleanupAttrs
- convertStyleToAttrs
- cleanupIDs
- removeRasterImages
- removeUselessDefs
- cleanupNumericValues
- cleanupListOfValues
- convertColors
- removeUnknownsAndDefaults
- removeNonInheritableGroupAttrs
- removeUselessStrokeAndFill
- removeViewBox
- cleanupEnableBackground
- removeHiddenElems
- removeEmptyText
- convertShapeToPath
- moveElemsAttrsToGroup
- moveGroupAttrsToElems
- collapseGroups
- convertPathData
- convertTransform
- removeEmptyAttrs
- removeEmptyContainers
- mergePaths
- removeUnusedNS
- transformsWithOnePath
- sortAttrs
- removeTitle
- removeDesc
- removeDimensions
- removeAttrs
- addClassesToSVGElement

View File

@ -0,0 +1,335 @@
### [ [>](https://github.com/svg/svgo/tree/v0.5.3) ] 0.5.3 / 21.06.2015
* Fixed breaking related to rounding functions in “[convertTransform](https://github.com/svg/svgo/blob/master/plugins/convertTransform.js)”.
* Fixed a bug with ID in animations not being worked on by “[cleanupIDs](https://github.com/svg/svgo/blob/master/plugins/cleanupIDs.js)”.
* Fixed a bug with quoted reference in `url()`.
* Now, if there are several same IDs in the document, then the first one is used and others are being removed.
* New command-line option `--show-plugins` displaying list of plugins.
* Two new optional plugins: “[removeDimensions](removeDimensions.js)” (removes `width` and `height` if there is `viewBox`) and “[removeAttrsPlugin](removeAttrsPlugin.js)” (by @bennyschudel).
### [ [>](https://github.com/svg/svgo/tree/v0.5.2) ] 0.5.2 / 24.05.2015
* Introduced new `transformPrecision` option for better image quality (defaults to 5) in “[convertTransform](https://github.com/svg/svgo/blob/master/plugins/convertTransform.js)” and “[convertPathData](https://github.com/svg/svgo/blob/master/plugins/convertPathData.js)” (for the purpose of applying transformations) plugins.
* Matrix transformations now can be decomposed into a combination of few simple transforms like `translate`, `rotate`, `scale`.
* Arcs (paths `arcto` command) are now correctly being transformed into another arcs without being converting to Bezier curves.
* Fixed an issue with “[mergePaths](https://github.com/svg/svgo/blob/master/plugins/mergePaths.js)” failing to detect paths intersection in some cases.
* Fixed a bug with “[removeUnknownsAndDefaults](https://github.com/svg/svgo/blob/master/plugins/removeUnknownsAndDefaults.js)” removing some paths, which was introduced in [v0.5.1](https://github.com/svg/svgo/tree/v0.5.1).
* Fixed a bug with transformation having `rotate()` with optional parameters.
* Patterns with inherited attributes are no longer being removed.
* Styles are no longer being removed from `<desc>` (by @dennari).
* SVGO no longer breaks during parsing.
* Added `clone()` method to JSAPI (by @jakearchibald)
### [ [>](https://github.com/svg/svgo/tree/v0.5.1) ] 0.5.1 / 30.03.2015
* added new command-line option to set precision in floating point numbers.
* fixed all known image-disruptive bugs
* Notably [mergePaths](https://github.com/svg/svgo/blob/master/plugins/mergePaths.js) plugin now checks for possible intersections to avoid side-effects
* new plugin [removeUselessDefs](https://github.com/svg/svgo/blob/master/plugins/removeUselessDefs.js) to remove elements in ``<defs>`` and similar non-rendering elements without an ``id`` and thus cannot be used
* fix for ``--multipass`` command line option (by @dfilatov)
* improved [cleanupEnableBackground](https://github.com/svg/svgo/blob/master/plugins/cleanupEnableBackground.js) and [convertColors](https://github.com/svg/svgo/blob/master/plugins/convertColors.js) plugins (by @YetiOr)
* new plugin for image manipulation [cleanupListOfValues](https://github.com/svg/svgo/blob/master/plugins/cleanupListOfValues.js) (by @kiyopikko)
* fixed fail on comments after closing root ``</svg>`` tag
* updated parsing to account meaningful spaces in ``<text>``
* ``data-*`` attributes are now preserved in [removeUnknownsAndDefaults](https://github.com/svg/svgo/blob/master/plugins/removeUnknownsAndDefaults.js)
* prevented plugins from failing in ``<foreignObject>``
* [cleanupNumericValues](https://github.com/svg/svgo/blob/master/plugins/cleanupNumericValues.js) plugin now converts other units to pixels (if it's better)
* [removeUselessStrokeAndFill](https://github.com/svg/svgo/blob/master/plugins/removeUselessStrokeAndFill.js) plugin is enabled again with correct work in case of inherited attributes
* fixed fail on images with incorrect paths like ``<path d="z"/>``
* svgo now understands if an input is a folder (remember, you can set output to folder as well)
* added support for some properties from SVG 2 like ``vector-effect="non-scaling-stroke"``
* removed option to remove an ``id`` on root ``<svg>`` tag in [removeUnknownsAndDefaults](https://github.com/svg/svgo/blob/master/plugins/removeUnknownsAndDefaults.js) since it's already being done in [cleanupIDs](https://github.com/svg/svgo/blob/master/plugins/cleanupIDs.js)
### [ [>](https://github.com/svg/svgo/tree/v0.5.0) ] 0.5.0 / 05.11.2014
* added ``--multipass`` command line option which repeatedly applies optimizations like collapsing groups (by @dfilatov)
* exposed JSAPI as a factory method (by @mistakster)
* added removeDesc plugin (by @dwabyick), disabled by default
* [removeUselessStrokeAndFill](https://github.com/svg/svgo/blob/master/plugins/removeUselessStrokeAndFill.js) plugin is disabled by default since it's unable to check inherited properties
* transformations now apply to paths with arcs in [plugins/convertPathData](https://github.com/svg/svgo/blob/master/plugins/convertPathData.js)
* a lot of bug fixes mostly related to transformations
### [ [>](https://github.com/svg/svgo/tree/v0.4.5) ] 0.4.5 / 02.08.2014
* significally improved plugin [plugins/convertPathData](https://github.com/svg/svgo/blob/master/plugins/convertPathData.js):
- Now data is being written relative or absolute whichever is shorter. You can turn it off by setting ``utilizeAbsolute`` to ``false``.
- Smarter rounding: values like 2.499 now rounds to 2.5. Rounding now takes in account accumulutive error meaning that points will not be misplaced due to rounding more than it neccessary.
- Fixed couple bugs.
* ``--output`` option now can be a folder along with ``--folder``, thanks to @mako-taco.
* [plugins/cleanupIDs](https://github.com/svg/svgo/blob/master/plugins/cleanupIDs.js) now have ``prefix`` option in case you want to combine multiple svg later (by @DanielMazurkiewicz).
* Quotes now being escaped in attributes (by @ditesh).
* Minor bugfixes.
### [ [>](https://github.com/svg/svgo/tree/v0.4.4) ] 0.4.4 / 14.01.2014
* new plugin [plugins/removeTitle](https://github.com/svg/svgo/blob/master/plugins/removeTitle.js) (disabled by default, close [#159](https://github.com/svg/svgo/issues/159))
* plugins/convertPathData: skip data concatenation for z instruction in collapseRepeated
* plugins/removeUnknownsAndDefaults: do not remove overriden attributes with default values (fix [#161](https://github.com/svg/svgo/issues/161) and [#168](https://github.com/svg/svgo/issues/168))
* plugins/removeViewBox: disable by default (fix [#139](https://github.com/svg/svgo/issues/139))
* update README with [gulp task](https://github.com/ben-eb/gulp-svgmin)
### [ [>](https://github.com/svg/svgo/tree/v0.4.3) ] 0.4.3 / 02.01.2014
* new plugin [plugins/convertShapeToPath](https://github.com/svg/svgo/blob/master/plugins/convertShapeToPath.js) (close [#96](https://github.com/svg/svgo/issues/96))
* update sax version to fix [#140](https://github.com/svg/svgo/issues/140)
* update deps
### [ [>](https://github.com/svg/svgo/tree/v0.4.2) ] 0.4.2 / 19.12.2013
* add `lcov.info` to npmignore
* fix `js-yaml` version to suppress deprecation warning in stdout
### [ [>](https://github.com/svg/svgo/tree/v0.4.1) ] 0.4.1 / 18.11.2013
* node >=0.8.0
### [ [>](https://github.com/svg/svgo/tree/v0.4.0) ] 0.4.0 / 18.11.2013
* merge almost all pull-requests
* update dependencies
### [ [>](https://github.com/svg/svgo/tree/v0.3.7) ] 0.3.7 / 24.06.2013
* do not remove `result` attribute from filter primitives (fix [#122](https://github.com/svg/svgo/issues/122))
* plugins/cleanupAttrs: replace newline with space when needed (fix [#119](https://github.com/svg/svgo/issues/119))
* lib/coa: look for config file in current folder
* lib/coa: always traverse all files in the given folder
* deprecate svgo-grunt in favor of [grunt-svgmin](https://github.com/sindresorhus/grunt-svgmin)
* re-enable node-coveralls
### [ [>](https://github.com/svg/svgo/tree/v0.3.6) ] 0.3.6 / 06.06.2013
* plugins/removeNonInheritableGroupAttrs: more attrs groups to exclude (fix [#116](https://github.com/svg/svgo/issues/116) & [#118](https://github.com/svg/svgo/issues/118))
* lib/coa: optimize folder file by file (temp fix [#114](https://github.com/svg/svgo/issues/114))
* `.jshintrc`: JSHint 2.0
* temporarily disable node-coveralls
### [ [>](https://github.com/svg/svgo/tree/v0.3.5) ] 0.3.5 / 07.05.2013
* plugins/transformsWithOnePath: fix curves bounding box calculation
* plugins/transformsWithOnePath: fix possible c+t or q+s bug
### [ [>](https://github.com/svg/svgo/tree/v0.3.4) ] 0.3.4 / 06.05.2013
* plugins/convertPathData: fix m->M bug in some cases
* plugins/transformsWithOnePath: fix last point calculation for C/S/Q/T
* plugins/mergePaths: add space delimiter between z and m
### [ [>](https://github.com/svg/svgo/tree/v0.3.3) ] 0.3.3 / 05.05.2013
* plugins/convertPathData: convert very first m to M, fix applyTransforms with translate() (fix [#112](https://github.com/svg/svgo/issues/112))
* plugins/transformsWithOnePath: fix real width/height rounding; fix scale transform origin; reorder transforms
* plugins/transformsWithOnePath: ability to set new width or height independently with auto rescaling
### [ [>](https://github.com/svg/svgo/tree/v0.3.2) ] 0.3.2 / 03.05.2013
* new plugin [plugins/sortAttrs](https://github.com/svg/svgo/blob/master/plugins/sortAttrs.js)
* plugins/transformsWithOnePath: buggy hcrop (fix [#111](https://github.com/svg/svgo/issues/111))
* Impossible to set output presision to 0 (no fractional part) (fix [#110](https://github.com/svg/svgo/issues/110))
* Istanbul + coveralls.io
* update README with NPM version from badge.fury.io
* update README with dependency status from gemnasium.com
* npmignore unneeded files
* reoptimized project logo
### [ [>](https://github.com/svg/svgo/tree/v0.3.1) ] 0.3.1 / 15.04.2013
* plugins/transformsWithOnePath: resize SVG and automatically rescale inner Path
* better errors handling
### [ [>](https://github.com/svg/svgo/tree/v0.3.0) ] 0.3.0 / 12.04.2013
* global refactoring: getting rid of the many dependencies
* new plugin [plugins/mergePaths](https://github.com/svg/svgo/blob/master/plugins/mergePaths.js)
* new plugin [plugins/transformsWithOnePath](https://github.com/svg/svgo/blob/master/plugins/transformsWithOnePath.js) (renamed and featured `cropAndCenterAlongPath`)
* config: replace default config with `full: true`
* coa: JSON string as value of `--config`
* coa: different types of Data URI strings (close [#105](https://github.com/svg/svgo/issues/105))
* plugins/_transforms: allow spaces at the beginning of transform
* Travis CI: Nodejs 0.10 & 0.11
* `node.extend``whet.extend`
* update `.gitignore`
* update docs
### [ [>](https://github.com/svg/svgo/tree/v0.2.4) ] 0.2.4 / 05.04.2013
* new plugin [plugins/cropAndCenterAlongPath](https://github.com/svg/svgo/blob/master/plugins/cropAndCenterAlongPath.js) for the [Fontello](https://github.com/fontello) project
### [ [>](https://github.com/svg/svgo/tree/v0.2.3) ] 0.2.3 / 22.02.2013
* new plugin [plugins/removeNonInheritableGroupAttrs](https://github.com/svg/svgo/blob/master/plugins/removeNonInheritableGroupAttrs.js) (fix [#101](https://github.com/svg/svgo/issues/101))
* new plugin [plugins/removeRasterImages](https://github.com/svg/svgo/blob/master/plugins/removeRasterImages.js) (close [#98](https://github.com/svg/svgo/issues/98))
* plugins/convertTransform: bug with trailing spaces in transform value string (fix [#103](https://github.com/svg/svgo/issues/103))
### [ [>](https://github.com/svg/svgo/tree/v0.2.2) ] 0.2.2 / 09.02.2013
* plugins/convertTransforms: wrong translate() shorthand (fix [#94](https://github.com/svg/svgo/issues/94))
* [yaml.js](https://github.com/jeremyfa/yaml.js) → [js-yaml](https://github.com/nodeca/js-yaml)
* update outdated deps
### [ [>](https://github.com/svg/svgo/tree/v0.2.1) ] 0.2.1 / 18.01.2013
* plugins/moveElemsAttrsToGroup + plugins/moveGroupAttrsToElems: move or just leave transform attr from Group to the inner Path Elems (close [#86](https://github.com/svg/svgo/issues/86))
* plugins/removeViewBox: doesn't catch floating-point numbers (fix [#88](https://github.com/svg/svgo/issues/88))
* plugins/cleanupEnableBackground: doesn't catch floating-point numbers (fix [#89](https://github.com/svg/svgo/issues/89))
* plugins/cleanupNumericValues: wrong floating-point numbers regexp (fix [#92](https://github.com/svg/svgo/issues/92))
* SVG file generated by fontcustom.com not properly compressed (fix [#90](https://github.com/svg/svgo/issues/90))
* `README.ru.md`: стилизация русского языка, улучшение языковых конструкций, правка ошибок (close [#91](https://github.com/svg/svgo/issues/91))
* minor JSHint warning fix
### [ [>](https://github.com/svg/svgo/tree/v0.2.0) ] 0.2.0 / 23.12.2012
* plugins/convertPathData: apply transforms to Path pata (close [#33](https://github.com/svg/svgo/issues/33))
* plugins/convertPathData: `-1.816-9.278.682-13.604` parsing error (fix [#85](https://github.com/svg/svgo/issues/85))
* plugins/convertTransform: `translate(10, 0)` eq `translate(10)`, but not `translate(10, 10)` eq `translate(10)` (fix [#83](https://github.com/svg/svgo/issues/83))
* run plugins/cleanupIDs before plugins/collapseGroups (fix [#84](https://github.com/svg/svgo/issues/84))
* update `.gitignore`
### [ [>](https://github.com/svg/svgo/tree/v0.1.9) ] 0.1.9 / 17.12.2012
* plugins/cleanupIDs: renamed from removeUnusedIDs; minify used IDs (fix [#7](https://github.com/svg/svgo/issues/7))
* lib/svgo/js2svg: restore HTML entities back (fix [#80](https://github.com/svg/svgo/issues/80) + [#81](https://github.com/svg/svgo/issues/81))
* plugins/removeDoctype: do not remove if custom XML entities presents (fix [#77](https://github.com/svg/svgo/issues/77))
* lib/svgo/coa: refactoring, colors and fix [#70](https://github.com/svg/svgo/issues/70)
* lib/svgo: store elapsed time in result object
* usage examples with SVGZ (close [#18](https://github.com/svg/svgo/issues/18))
* more optimized logo
* update `.gitignore`
### [ [>](https://github.com/svg/svgo/tree/v0.1.8) ] 0.1.8 / 11.12.2012
* new plugin [plugins/removeUselessStrokeAndFill](https://github.com/svg/svgo/blob/master/plugins/removeUselessStrokeAndFill.js) (close [#75](https://github.com/svg/svgo/issues/75))
* new plugin [plugins/removeUnusedIDs](https://github.com/svg/svgo/blob/master/plugins/removeUnusedIDs.js) (close [#76](https://github.com/svg/svgo/issues/76))
* plugins/convertPathData: wrong M interpretation in some cases (fix [#73](https://github.com/svg/svgo/issues/73))
* plugins/cleanupAttrs: use `isElem()` API
* `.travis.yml`: check all branches
### [ [>](https://github.com/svg/svgo/tree/v0.1.7) ] 0.1.7 / 08.12.2012
* plugins/convertPathData: incorrect interpretation of `z + m` (fix [#69](https://github.com/svg/svgo/issues/69))
* plugins/convertTransform: do a more accurate floating numbers rounding in `matrixToTransform()` (fix [#68](https://github.com/svg/svgo/issues/68))
### [ [>](https://github.com/svg/svgo/tree/v0.1.6) ] 0.1.6 / 07.12.2012
* plugins/convertPathData: collapse repeated instructions only after curveSmoothShorthands (fix [#64](https://github.com/svg/svgo/issues/64))
* lib/svgo/coa: handle 'there is nothing to optimize' case and display a message about it (fix [#61](https://github.com/svg/svgo/issues/61))
* plugins/cleanupSVGElem: delete as useless artefact
### [ [>](https://github.com/svg/svgo/tree/v0.1.5) ] 0.1.5 / 06.12.2012
* E-notated numbers in paths not recognised (fix [#63](https://github.com/svg/svgo/issues/63))
* update README with `svgo-grunt` and `svgo-osx-folder-action`
* fix `mocha-as-promised` plug in node 0.6
### [ [>](https://github.com/svg/svgo/tree/v0.1.4) ] 0.1.4 / 05.12.2012
* plugins/_collections: more defaults
* `README.ru.md`
* `docs/how-it-works/ru.md`
* mocha + mocha-as-promised + chai + chai-as-promised + should + istanbul = <3
* update dependencies semvers in `package.json`
* `v0.1.x` and `v0.2.x` milestones
### [ [>](https://github.com/svg/svgo/tree/v0.1.3) ] 0.1.3 / 30.11.2012
* new plugin [plugins/cleanupNumericValues](https://github.com/svg/svgo/blob/master/plugins/cleanupNumericValues.js) (close [#8](https://github.com/svg/svgo/issues/8))
* plugins/removeDefaultPx functionality now included in plugins/removeUnknownsAndDefaults
* plugins/removeUnknownsAndDefaults: refactoring and picking up the complete elems+attrs collection (close [#59](https://github.com/svg/svgo/issues/59))
* plugins/convertTransform: error in matrices multiplication (fix [#58](https://github.com/svg/svgo/issues/58))
* plugins/convertTransform: mark translate() and scale() as useless only with one param (fix [#57](https://github.com/svg/svgo/issues/57))
* plugins/convertPathData: drastic speed improvement with huge Path data
* plugins/convertPathData: fix the very first Mm with multiple points (fix [#56](https://github.com/svg/svgo/issues/56))
* plugins/moveElemsAttrsToGroup: additional check for transform attr
* brand-new project `logo.svg`
* `.travis.yml`: build only master branch
* global `'use strict'`
* `.jshintignore`
* README and CHANGELOG: minor corrections
### [ [>](https://github.com/svg/svgo/tree/v0.1.2) ] 0.1.2 / 24.11.2012
* lib/svgo/svg2js: correct 'onerror' failure (fix [#51](https://github.com/svg/svgo/issues/51))
* config: disable sax-js position tracking by default (fix [#52](https://github.com/svg/svgo/issues/52))
* lib/svgo: rename 'startBytes' to 'inBytes' and 'endBytes' to 'outBytes' (close [#53](https://github.com/svg/svgo/issues/53))
* plugins/removeUnknownsAndDefaults: remove SVG id attr (close [#54](https://github.com/svg/svgo/issues/54))
### [ [>](https://github.com/svg/svgo/tree/v0.1.1) ] 0.1.1 / 23.11.2012
* plugins/moveElemsAttrsToGroup: fix inheitable only attrs array (fix [#47](https://github.com/svg/svgo/issues/47))
* plugins/removeEmptyContainers: do not remove an empty 'svg' element (fix [#48](https://github.com/svg/svgo/issues/48))
* plugins/removeDefaultPx: should also understand a floating-numbers too (fix [#49](https://github.com/svg/svgo/issues/49))
* plugins/removeUnknownsAndDefaults: merge multiple groupDefaults attrs (close [#50](https://github.com/svg/svgo/issues/50))
### [ [>](https://github.com/svg/svgo/tree/v0.1.0) ] 0.1.0 / 22.11.2012
* new plugin [plugins/removeUnknownsAndDefaults](https://github.com/svg/svgo/blob/master/plugins/removeUnknownsAndDefaults.js) (close [#6](https://github.com/svg/svgo/issues/6))
* plugins/convertPathData: convert straight curves into lines segments (close [#17](https://github.com/svg/svgo/issues/17)); remove an absolute coords conversions
* plugins/convertPathData: convert quadratic Bézier curveto into smooth shorthand (close [#31](https://github.com/svg/svgo/issues/31))
* plugins/convertPathData: convert curveto into smooth shorthand (close [#30](https://github.com/svg/svgo/issues/30))
* lib/svgo: global API refactoring (close [#37](https://github.com/svg/svgo/issues/37))
* lib/svgo: fatal and stupid error in stream chunks concatenation (fix [#40](https://github.com/svg/svgo/issues/40))
* lib/coa: batch folder optimization (close [#29](https://github.com/svg/svgo/issues/29))
* lib/coa: support arguments as aliases to `--input` and `--output` (close [#28](https://github.com/svg/svgo/issues/28))
* project logo by [Egor Bolhshakov](http://xizzzy.ru/)
* move modules to `./lib/svgo/`
* rename and convert `config.json` to `.svgo.yml`
* add [./docs/](https://github.com/svg/svgo/tree/master/docs)
* plugins/convertPathData: don't remove first `M` even if it's `0,0`
* plugins/convertPathData: stronger defense from infinite loop
* plugins/moveElemsAttrsToGroup: should affect only inheritable attributes (fix [#46](https://github.com/svg/svgo/issues/46))*
* plugins/removeComments: ignore comments which starts with '!' (close [#43](https://github.com/svg/svgo/issues/43))
* config: `cleanupAttrs` should be before `convertStyleToAttrs` (fix [#44](https://github.com/svg/svgo/issues/44))*
* lib/svgo/jsAPI: add `eachAttr()` optional context param
* temporarily remove PhantomJS and `--test` (close [#38](https://github.com/svg/svgo/issues/38))
* q@0.8.10 compatibility: 'end is deprecated, use done instead' fix
* add [Istanbul](https://github.com/gotwarlost/istanbul) code coverage
* update dependencies versions and gitignore
* README: add TODO section with versions milestones
* update README with License section
* update LICENSE with russian translation
* `.editorconfig`: 2 spaces for YAML
### [ [>](https://github.com/svg/svgo/tree/v0.0.9) ] 0.0.9 / 29.10.2012
* [plugins how-to](https://github.com/svg/svgo/tree/master/plugins#readme) (close [#27](https://github.com/svg/svgo/issues/27))
* allow any plugin of any type to go in any order (close [#14](https://github.com/svg/svgo/issues/14))
* allow to do a multiple optimizations with one init (close [#25](https://github.com/svg/svgo/issues/25))
* plugins/convertPathData: global refactoring
* plugins/convertPathData: do all the tricks with absolute coords too (fix [#22](https://github.com/svg/svgo/issues/22))
* plugins/convertPathData: accumulation of rounding errors (fix [#23](https://github.com/svg/svgo/issues/23))
* plugins/convertPathData: prevent an infinity loop on invalid path data (fix [#26](https://github.com/svg/svgo/issues/26))
* plugins/convertPathData: do not remove very first M from the path data (fix [#24](https://github.com/svg/svgo/issues/24))
* plugins/convertPathData: optimize path data in &lt;glyph&gt; and &lt;missing-glyph&gt; (close [#20](https://github.com/svg/svgo/issues/20))
* plugins/convertTransform: add patternTransform attribute to the process (close [#15](https://github.com/svg/svgo/issues/15))
* plugins/convertTransform: Firefox: removing extra space in front of negative number is alowed only in path data, but not in transform (fix [#12](https://github.com/svg/svgo/issues/12))
* plugins/removeXMLProcInst: remove only 'xml' but not 'xml-stylesheet' (fix [#21](https://github.com/svg/svgo/issues/15))
* plugins/collapseGroups: merge split-level transforms (fix [#13](https://github.com/svg/svgo/issues/13))
* jsdoc corrections
### [ [>](https://github.com/svg/svgo/tree/v0.0.8) ] 0.0.8 / 20.10.2012
* new plugin [convertTransform](plugins/convertTransform.js) (close [#5](https://github.com/svg/svgo/issues/5))
* new plugin [removeUnusedNS](plugins/removeUnusedNS.js)
* plugins/convertPathData: remove useless segments
* plugins/convertPathData: a lot of refactoring
* plugins/convertPathData: round numbers before conditions because of exponential notation (fix [#3](https://github.com/svg/svgo/issues/3))
* plugins/moveElemsAttrsToGroup: merge split-level transforms instead of replacing (fix [#10](https://github.com/svg/svgo/issues/10))
* lib/svg2js: catch and output xml parser errors (fix [#4](https://github.com/svg/svgo/issues/4))
* lib/coa: open file for writing only when we are ready (fix [#2](https://github.com/svg/svgo/issues/2))
* lib/tools: node.extend module
* lib/plugins: refactoring
* lib/js2svg: refactoring
* lib/jsAPI: simplification and refactoring
* absolute urls in README
* update .editorconfig
* update .travis.yml with nodejs 0.9
### [ [>](https://github.com/svg/svgo/tree/v0.0.7) ] 0.0.7 / 14.10.2012
* new plugin [convertPathData](plugins/convertPathData.js)
* --input data now can be a Data URI base64 string
* --output data now can be a Data URI base64 string with --datauri flag
* Travis CI
* JSHint corrections + .jshintrc
* [.editorconfig](http://editorconfig.org/)
* display time spent on optimization
* .svgo config.json
* lib/phantom_wrapper.js lib/phantom.js
### [ [>](https://github.com/svg/svgo/tree/v0.0.6) ] 0.0.6 / 04.10.2012
* add --test option to make a visual comparison of two files (PhantomJS pre-required)
* update README and CHANGELOG with the correct relative urls
### [ [>](https://github.com/svg/svgo/tree/v0.0.5) ] 0.0.5 / 03.10.2012
* every plugin now has [at least one test](plugins)
* removeViewBox, cleanupEnableBackground, removeEditorsNSData, convertStyleToAttrs and collapseGroups plugins fixes
* new --pretty option for the pretty printed SVG
* lib/config refactoring
### [ [>](https://github.com/svg/svgo/tree/v0.0.4) ] 0.0.4 / 30.09.2012
* new plugin [removeViewBox](plugins/removeViewBox.js)
* new plugin [cleanupEnableBackground](plugins/cleanupEnableBackground.js)
* display useful info after successful optimization
* 'npm test' with 'spec' mocha output by default
### [ [>](https://github.com/svg/svgo/tree/v0.0.3) ] 0.0.3 / 29.09.2012
* plugins/collapseGroups bugfix
* plugins/moveElemsAttrsToGroup bugfix
* svgo now display --help if running w/o arguments
* massive jsdoc updates
* plugins engine main filter function optimization
### [ [>](https://github.com/svg/svgo/tree/v0.0.2) ] 0.0.2 / 28.09.2012
* add --disable and --enable command line options
* add an empty values rejecting to coa.js
* update README
### [ [>](https://github.com/svg/svgo/tree/v0.0.1) ] 0.0.1 / 27.09.2012
* initial public version

View File

@ -0,0 +1,55 @@
The MIT License
Copyright © 2012 Kir Belevich
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.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Лицензия MIT
Copyright © 2012 Кир Белевич
Данная лицензия разрешает лицам, получившим копию данного
программного обеспечения и сопутствующей документации
(в дальнейшем именуемыми «Программное Обеспечение»), безвозмездно
использовать Программное Обеспечение без ограничений, включая
неограниченное право на использование, копирование, изменение,
добавление, публикацию, распространение, сублицензирование
и/или продажу копий Программного Обеспечения, также как и лицам,
которым предоставляется данное Программное Обеспечение,
при соблюдении следующих условий:
Указанное выше уведомление об авторском праве и данные условия
должны быть включены во все копии или значимые части данного
Программного Обеспечения.
ДАННОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ «КАК ЕСТЬ»,
БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, ЯВНО ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ,
ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ ГАРАНТИЯМИ ТОВАРНОЙ ПРИГОДНОСТИ,
СООТВЕТСТВИЯ ПО ЕГО КОНКРЕТНОМУ НАЗНАЧЕНИЮ И ОТСУТСТВИЯ НАРУШЕНИЙ
ПРАВ. НИ В КАКОМ СЛУЧАЕ АВТОРЫ ИЛИ ПРАВООБЛАДАТЕЛИ НЕ НЕСУТ
ОТВЕТСТВЕННОСТИ ПО ИСКАМ О ВОЗМЕЩЕНИИ УЩЕРБА, УБЫТКОВ ИЛИ ДРУГИХ
ТРЕБОВАНИЙ ПО ДЕЙСТВУЮЩИМ КОНТРАКТАМ, ДЕЛИКТАМ ИЛИ ИНОМУ,
ВОЗНИКШИМ ИЗ, ИМЕЮЩИМ ПРИЧИНОЙ ИЛИ СВЯЗАННЫМ С ПРОГРАММНЫМ
ОБЕСПЕЧЕНИЕМ ИЛИ ИСПОЛЬЗОВАНИЕМ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ
ИЛИ ИНЫМИ ДЕЙСТВИЯМИ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ.

View File

@ -0,0 +1,21 @@
test:
@NODE_ENV=test ./node_modules/.bin/mocha
lib-cov:
@./node_modules/.bin/istanbul instrument --output lib-cov --no-compact --variable global.__coverage__ lib
coverage: lib-cov
@COVERAGE=1 ISTANBUL_REPORTERS=text-summary ./node_modules/.bin/mocha --reporter mocha-istanbul
@rm -rf lib-cov
coveralls: lib-cov
@COVERAGE=1 ISTANBUL_REPORTERS=lcovonly ./node_modules/.bin/mocha --reporter mocha-istanbul
@cat lcov.info | ./node_modules/.bin/coveralls
@rm -rf lib-cov lcov.info
travis: jshint test coveralls
jshint:
@jshint --show-non-errors .
.PHONY: test

View File

@ -0,0 +1,146 @@
**english** | [русский](https://github.com/svg/svgo/blob/master/README.ru.md)
- - -
<img src="https://svg.github.io/svgo-logo.svg" width="200" height="200" alt="logo"/>
## SVGO [![NPM version](https://badge.fury.io/js/svgo.svg)](https://npmjs.org/package/svgo) [![Dependency Status](https://gemnasium.com/svg/svgo.png)](https://gemnasium.com/svg/svgo) [![Build Status](https://secure.travis-ci.org/svg/svgo.svg)](https://travis-ci.org/svg/svgo) [![Coverage Status](https://img.shields.io/coveralls/svg/svgo.svg)](https://coveralls.io/r/svg/svgo?branch=master)
**SVG O**ptimizer is a Nodejs-based tool for optimizing SVG vector graphics files.
![](https://mc.yandex.ru/watch/18431326)
## Why?
SVG files, especially exported from various editors, usually contains a lot of redundant and useless information such as editor metadata, comments, hidden elements, default or non-optimal values and other stuff that can be safely removed or converted without affecting SVG rendering result.
## What it can do
SVGO has a plugin-based architecture, so almost every optimization is a separate plugin.
Today we have:
* [ [ cleanupAttrs](https://github.com/svg/svgo/blob/master/plugins/cleanupAttrs.js) ] cleanup attributes from newlines, trailing and repeating spaces
* [ [ removeDoctype](https://github.com/svg/svgo/blob/master/plugins/removeDoctype.js) ] remove doctype declaration
* [ [ removeXMLProcInst](https://github.com/svg/svgo/blob/master/plugins/removeXMLProcInst.js) ] remove XML processing instructions
* [ [ removeComments](https://github.com/svg/svgo/blob/master/plugins/removeComments.js) ] remove comments
* [ [ removeMetadata](https://github.com/svg/svgo/blob/master/plugins/removeMetadata.js) ] remove `<metadata>`
* [ [ removeTitle](https://github.com/svg/svgo/blob/master/plugins/removeTitle.js) ] remove `<title>` (disabled by default)
* [ [ removeDesc](https://github.com/svg/svgo/blob/master/plugins/removeDesc.js) ] remove `<desc>` (only non-meaningful by default)
* [ [ removeUselessDefs](https://github.com/svg/svgo/blob/master/plugins/removeUselessDefs.js) ] remove elements of `<defs>` without `id`
* [ [ removeEditorsNSData](https://github.com/svg/svgo/blob/master/plugins/removeEditorsNSData.js) ] remove editors namespaces, elements and attributes
* [ [ removeEmptyAttrs](https://github.com/svg/svgo/blob/master/plugins/removeEmptyAttrs.js) ] remove empty attributes
* [ [ removeHiddenElems](https://github.com/svg/svgo/blob/master/plugins/removeHiddenElems.js) ] remove hidden elements
* [ [ removeEmptyText](https://github.com/svg/svgo/blob/master/plugins/removeEmptyText.js) ] remove empty Text elements
* [ [ removeEmptyContainers](https://github.com/svg/svgo/blob/master/plugins/removeEmptyContainers.js) ] remove empty Container elements
* [ [ removeViewBox](https://github.com/svg/svgo/blob/master/plugins/removeViewBox.js) ] remove `viewBox` attribute when possible (disabled by default)
* [ [ cleanUpEnableBackground](https://github.com/svg/svgo/blob/master/plugins/cleanupEnableBackground.js) ] remove or cleanup `enable-background` attribute when possible
* [ [ convertTyleToAttrs](https://github.com/svg/svgo/blob/master/plugins/convertStyleToAttrs.js) ] convert styles into attributes
* [ [ convertColors](https://github.com/svg/svgo/blob/master/plugins/convertColors.js) ] convert colors (from `rgb()` to `#rrggbb`, from `#rrggbb` to `#rgb`)
* [ [ convertPathData](https://github.com/svg/svgo/blob/master/plugins/convertPathData.js) ] convert Path data to relative or absolute whichever is shorter, convert one segment to another, trim useless delimiters, smart rounding and much more
* [ [ convertTransform](https://github.com/svg/svgo/blob/master/plugins/convertTransform.js) ] collapse multiple transforms into one, convert matrices to the short aliases and much more
* [ [ removeUnknownsAndDefaults](https://github.com/svg/svgo/blob/master/plugins/removeUnknownsAndDefaults.js) ] remove unknown elements content and attributes, remove attrs with default values
* [ [ removeNonInheritableGroupAttrs](https://github.com/svg/svgo/blob/master/plugins/removeNonInheritableGroupAttrs.js) ] remove non-inheritable group's "presentation" attributes
* [ [ removeUselessStrokeAndFill](https://github.com/svg/svgo/blob/master/plugins/removeUselessStrokeAndFill.js) ] remove useless stroke and fill attrs
* [ [ removeUnusedNS](https://github.com/svg/svgo/blob/master/plugins/removeUnusedNS.js) ] remove unused namespaces declaration
* [ [ cleanupIDs](https://github.com/svg/svgo/blob/master/plugins/cleanupIDs.js) ] remove unused and minify used IDs
* [ [ cleanupNumericValues](https://github.com/svg/svgo/blob/master/plugins/cleanupNumericValues.js) ] round numeric values to the fixed precision, remove default 'px' units
* [ [ moveElemsAttrsToGroup](https://github.com/svg/svgo/blob/master/plugins/moveElemsAttrsToGroup.js) ] move elements attributes to the existing group wrapper
* [ [ moveGroupAttrsToElems](https://github.com/svg/svgo/blob/master/plugins/moveGroupAttrsToElems.js) ] move some group attributes to the content elements
* [ [ collapseGroups](https://github.com/svg/svgo/blob/master/plugins/collapseGroups.js) ] collapse useless groups
* [ [ removeRasterImages](https://github.com/svg/svgo/blob/master/plugins/removeRasterImages.js) ] remove raster images (disabled by default)
* [ [ mergePaths](https://github.com/svg/svgo/blob/master/plugins/mergePaths.js) ] merge multiple Paths into one
* [ [ convertShapeToPath](https://github.com/svg/svgo/blob/master/plugins/convertShapeToPath.js) ] convert some basic shapes to path
* [ [ sortAttrs](https://github.com/svg/svgo/blob/master/plugins/sortAttrs.js) ] sort element attributes for epic readability (disabled by default)
* [ [ transformsWithOnePath](https://github.com/svg/svgo/blob/master/plugins/transformsWithOnePath.js) ] apply transforms, crop by real width, center vertical alignment and resize SVG with one Path inside (disabled by default)
* [ [ removeDimensions](https://github.com/svg/svgo/blob/master/plugins/removeDimensions.js) ] remove width/height attributes if viewBox is present (disabled by default)
* [ [ removeAttrs](https://github.com/svg/svgo/blob/master/plugins/removeAttrs.js) ] remove attributes by pattern (disabled by default)
* [ [ addClassesToSVGElement](https://github.com/svg/svgo/blob/master/plugins/addClassesToSVGElement.js) ] add classnames to an outer `<svg>` element (disabled by default)
Want to know how it works and how to write your own plugin? [Of course you want to](https://github.com/svg/svgo/blob/master/docs/how-it-works/en.md).
## How to use
```sh
$ [sudo] npm install -g svgo
```
```
Usage:
svgo [OPTIONS] [ARGS]
Options:
-h, --help : Help
-v, --version : Version
-i INPUT, --input=INPUT : Input file, "-" for STDIN
-s STRING, --string=STRING : Input SVG data string
-f FOLDER, --folder=FOLDER : Input folder, optimize and rewrite all *.svg files
-o OUTPUT, --output=OUTPUT : Output file or folder (by default the same as the input), "-" for STDOUT
-p PRECISION, --precision=PRECISION : Set number of digits in the fractional part, overrides plugins params
--config=CONFIG : Config file to extend or replace default
--disable=DISABLE : Disable plugin by name
--enable=ENABLE : Enable plugin by name
--datauri=DATAURI : Output as Data URI string (base64, URI encoded or unencoded)
--pretty : Make SVG pretty printed
--show-plugins : Show available plugins and exit
Arguments:
INPUT : Alias to --input
OUTPUT : Alias to --output
```
* with files:
$ svgo test.svg
or:
$ svgo test.svg test.min.svg
* with STDIN / STDOUT:
$ cat test.svg | svgo -i - -o - > test.min.svg
* with folder
$ svgo -f ../path/to/folder/with/svg/files
or:
$ svgo -f ../path/to/folder/with/svg/files -o ../path/to/folder/with/svg/output
* with strings:
$ svgo -s '<svg version="1.1">test</svg>' -o test.min.svg
or even with Data URI base64:
$ svgo -s 'data:image/svg+xml;base64,…' -o test.min.svg
* with SVGZ:
from `.svgz` to `.svg`:
$ gunzip -c test.svgz | svgo -i - -o test.min.svg
from `.svg` to `.svgz`:
$ svgo test.svg -o - | gzip -cfq9 > test.svgz
* with GUI [svgo-gui](https://github.com/svg/svgo-gui)
* as a web app - [SVGOMG](https://jakearchibald.github.io/svgomg/)
* as a Nodejs module [examples](https://github.com/svg/svgo/tree/master/examples)
* as a Grunt task [grunt-svgmin](https://github.com/sindresorhus/grunt-svgmin)
* as a Gulp task [gulp-svgmin](https://github.com/ben-eb/gulp-svgmin)
* as a Mimosa module [mimosa-minify-svg](https://github.com/dbashford/mimosa-minify-svg)
* as an OSX Folder Action [svgo-osx-folder-action](https://github.com/svg/svgo-osx-folder-action)
* as a webpack loader [image-webpack-loader](https://github.com/tcoopman/image-webpack-loader)
## Donate
BTC `1zVZYqRSzQ4aaL27rp3PLwFFSXpfs5H8r`
## License and copyrights
This software is released under the terms of the [MIT license](https://github.com/svg/svgo/blob/master/LICENSE).
Logo by [Yegor Bolshakov](http://xizzzy.ru/).

View File

@ -0,0 +1,145 @@
[english](https://github.com/svg/svgo/blob/master/README.md) | **русский**
- - -
<img src="https://svg.github.io/svgo-logo.svg" width="200" height="200" alt="logo"/>
## SVGO [![NPM version](https://badge.fury.io/js/svgo.svg)](https://npmjs.org/package/svgo) [![Dependency Status](https://gemnasium.com/svg/svgo.png)](https://gemnasium.com/svg/svgo) [![Build Status](https://secure.travis-ci.org/svg/svgo.svg)](https://travis-ci.org/svg/svgo) [![Coverage Status](https://img.shields.io/coveralls/svg/svgo.svg)](https://coveralls.io/r/svg/svgo?branch=master)
**SVG** **O**ptimizer это инструмент для оптимизации векторной графики в формате SVG, написанный на Node.js.
![](https://mc.yandex.ru/watch/18431326)
## Зачем?
SVG-файлы, особенно экспортированные из различных редакторов, содержат много избыточной и бесполезной информации, комментариев, скрытых элементов, неоптимальные или стандартные значения и другой мусор, удаление которого безопасно и не влияет на конечный результат отрисовки.
## Возможности
SVGO имеет расширяемую архитектуру, в которой почти каждая оптимизация является отдельным расширением.
Сегодня у нас есть:
* [ [ cleanupAttrs](https://github.com/svg/svgo/blob/master/plugins/cleanupAttrs.js) ] удаление переносов строк и лишних пробелов
* [ [ removeDoctype](https://github.com/svg/svgo/blob/master/plugins/removeDoctype.js) ] удаление doctype
* [ [ removeXMLProcInst](https://github.com/svg/svgo/blob/master/plugins/removeXMLProcInst.js) ] удаление XML-инструкций
* [ [ removeComments](https://github.com/svg/svgo/blob/master/plugins/removeComments.js) ] удаление комментариев
* [ [ removeMetadata](https://github.com/svg/svgo/blob/master/plugins/removeMetadata.js) ] удаление `<metadata>`
* [ [ removeTitle](https://github.com/svg/svgo/blob/master/plugins/removeTitle.js) ] удаление `<title>` (отключена по умолчанию)
* [ [ removeDesc](https://github.com/svg/svgo/blob/master/plugins/removeDesc.js) ] удаление `<desc>` (по умолчанию только незначимых)
* [ [ removeUselessDefs](https://github.com/svg/svgo/blob/master/plugins/removeUselessDefs.js) ] удаление элементов в `<defs>` без `id`
* [ [ removeEditorsNSData](https://github.com/svg/svgo/blob/master/plugins/removeEditorsNSData.js) ] удаление пространств имён различных редакторов, их элементов и атрибутов
* [ [ removeEmptyAttrs](https://github.com/svg/svgo/blob/master/plugins/removeEmptyAttrs.js) ] удаление пустых атрибутов
* [ [ removeHiddenElems](https://github.com/svg/svgo/blob/master/plugins/removeHiddenElems.js) ] удаление скрытых элементов
* [ [ removeEmptyText](https://github.com/svg/svgo/blob/master/plugins/removeEmptyText.js) ] удаление пустых текстовых элементов
* [ [ removeEmptyContainers](https://github.com/svg/svgo/blob/master/plugins/removeEmptyContainers.js) ] удаление пустых элементов-контейнеров
* [ [ removeViewBox](https://github.com/svg/svgo/blob/master/plugins/removeViewBox.js) ] удаление атрибута `viewBox`, когда это возможно
* [ [ cleanupEnableBackground](https://github.com/svg/svgo/blob/master/plugins/cleanupEnableBackground.js) ] удаление или оптимизация атрибута `enable-background`, когда это возможно
* [ [ convertStyleToAttrs](https://github.com/svg/svgo/blob/master/plugins/convertStyleToAttrs.js) ] конвертирование стилей в атрибуте `style` в отдельные svg-атрибуты
* [ [ convertColors](https://github.com/svg/svgo/blob/master/plugins/convertColors.js) ] конвертирование цветовых значений: из `rgb()` в `#rrggbb`, из `#rrggbb` в `#rgb`
* [ [ convertPathData](https://github.com/svg/svgo/blob/master/plugins/convertPathData.js) ] конвертирование данных Path в относительные или абсолютные координаты, смотря что короче, конвертирование одних типов сегментов в другие, удаление ненужных разделителей, умное округление и тому подобное
* [ [ convertTransform](https://github.com/svg/svgo/blob/master/plugins/convertTransform.js) ] схлопывание нескольких трансформаций в одну, конвертирование матриц в короткие алиасы и многое другое
* [ [ removeUnknownsAndDefaults](https://github.com/svg/svgo/blob/master/plugins/removeUnknownsAndDefaults.js) ] удаление неизвестных элементов, контента и атрибутов
* [ [ removeNonInheritableGroupAttrs](https://github.com/svg/svgo/blob/master/plugins/removeNonInheritableGroupAttrs.js) ] удаление ненаследуемых "презентационных" атрибутов групп
* [ [ removeUselessStrokeAndFill](https://github.com/svg/svgo/blob/master/plugins/removeUselessStrokeAndFill.js) ] удаление неиспользуемых атрибутов stroke-* и fill-*
* [ [ removeUnusedNS](https://github.com/svg/svgo/blob/master/plugins/removeUnusedNS.js) ] удаление деклараций неиспользуемых пространств имён
* [ [ cleanupIDs](https://github.com/svg/svgo/blob/master/plugins/cleanupIDs.js) ] удаление неиспользуемых и сокращение используемых ID
* [ [ cleanupNumericValues](https://github.com/svg/svgo/blob/master/plugins/cleanupNumericValues.js) ] округление дробных чисел до заданной точности, удаление `px` как единицы измерения по-умолчанию
* [ [ moveElemsAttrsToGroup](https://github.com/svg/svgo/blob/master/plugins/moveElemsAttrsToGroup.js) ] перемещение совпадающих атрибутов у всех элементов внутри группы `<g>`
* [ [ moveGroupAttrsToElems](https://github.com/svg/svgo/blob/master/plugins/moveGroupAttrsToElems.js) ] перемещение некоторых атрибутов группы на элементы внутри
* [ [ collapseGroups](https://github.com/svg/svgo/blob/master/plugins/collapseGroups.js) ] схлопывание бесполезных групп `<g>`
* [ [ removeRasterImage](https://github.com/svg/svgo/blob/master/plugins/removeRasterImages.js) ] удаление растровых изображений (выключено по умолчанию)
* [ [ mergePaths](https://github.com/svg/svgo/blob/master/plugins/mergePaths.js) ] склеивание нескольких Path в одну кривую
* [ [ convertShapeToPath](https://github.com/svg/svgo/blob/master/plugins/convertShapeToPath.js) ] конвертирование простых форм в Path
* [ [ sortAttrs](https://github.com/svg/svgo/blob/master/plugins/sortAttrs.js) ] сортировка атрибутов элементов для удобочитаемости (выключено по умолчанию)
* [ [ transformsWithOnePath](https://github.com/svg/svgo/blob/master/plugins/transformsWithOnePath.js) ] применение трансформаций, обрезка по реальной ширине, вертикальное выравнивание по центру и изменение размеров SVG с одним Path внутри
* [ [ removeDimensions](https://github.com/svg/svgo/blob/master/plugins/removeDimensions.js) ] удаляет атрибуты width/height при наличии viewBox (выключено по умолчанию)
* [ [ removeAttrs](https://github.com/svg/svgo/blob/master/plugins/removeAttrs.js) ] удаляет атрибуты по указанному паттерну (выключено по умолчанию)
* [ [ addClassesToSVGElement](https://github.com/svg/svgo/blob/master/plugins/addClassesToSVGElement.js) ] добавляет имена классов корневому элементу `<svg>` (выключено по умолчанию)
Хотите узнать, как это работает и как написать свой плагин? [Конечно же, да!](https://github.com/svg/svgo/blob/master/docs/how-it-works/ru.md).
## Как использовать
```sh
$ [sudo] npm install -g svgo
```
```
Выполнение:
svgo [OPTIONS] [ARGS]
Параметры:
-h, --help : Помощь
-v, --version : Версия программы
-i INPUT, --input=INPUT : Входной файл, "-" для STDIN
-s STRING, --string=STRING : Входная строка SVG
-f FOLDER, --folder=FOLDER : Входная папка, оптимизирует и перезаписывает все файлы *.svg
-o OUTPUT, --output=OUTPUT : Выходной файл или папка (совпадает с входным по умолчанию), "-" для STDOUT
-p PRECISION, --precision=PRECISION : Число цифр после запятой, переопределяет параметры плагинов
--config=CONFIG : Файл конфигурации для расширения и замены настроек
--disable=DISABLE : Выключение плагина по имени
--enable=ENABLE : Включение плагина по имени
--datauri=DATAURI : Результат в виде строки Data URI (base64, URI encoded или unencoded)
--pretty : Удобочитаемое форматирование SVG
--show-plugins : доступные плагины
Аргументы:
INPUT : Аналогично --input
OUTPUT : Аналогично --output
```
* с файлами:
$ svgo test.svg
или:
$ svgo test.svg test.min.svg
* со STDIN / STDOUT:
$ cat test.svg | svgo -i - -o - > test.min.svg
* с папками
$ svgo -f ../path/to/folder/with/svg/files
или:
$ svgo -f ../path/to/folder/with/svg/files -o ../path/to/folder/with/svg/output
* со строками:
$ svgo -s '<svg version="1.1">test</svg>' -o test.min.svg
или даже с Data URI base64:
$ svgo -s 'data:image/svg+xml;base64,…' -o test.min.svg
* с SVGZ:
из `.svgz` в `.svg`:
$ gunzip -c test.svgz | svgo -i - -o test.min.svg
из `.svg` в `.svgz`:
$ svgo test.svg -o - | gzip -cfq9 > test.svgz
* с помощью GUI [svgo-gui](https://github.com/svg/svgo-gui)
* в виде веб-приложения - [SVGOMG](https://jakearchibald.github.io/svgomg/)
* как модуль Node.js [examples](https://github.com/svg/svgo/tree/master/examples)
* как таск для Grunt [grunt-svgmin](https://github.com/sindresorhus/grunt-svgmin)
* как таск для Gulp [gulp-svgmin](https://github.com/ben-eb/gulp-svgmin)
* как таск для Mimosa [mimosa-minify-svg](https://github.com/dbashford/mimosa-minify-svg)
* как действие папки в OSX [svgo-osx-folder-action](https://github.com/svg/svgo-osx-folder-action)
## Пожертвования
BTC `1zVZYqRSzQ4aaL27rp3PLwFFSXpfs5H8r`
## Лицензия и копирайты
Данное программное обеспечение выпускается под [лицензией MIT](https://github.com/svg/svgo/blob/master/LICENSE).
Логотип [Егор Большаков](http://xizzzy.ru/).

View File

@ -0,0 +1,3 @@
#!/usr/bin/env node
require('../lib/svgo/coa').run();

View File

@ -0,0 +1,81 @@
'use strict';
/**
* SVGO is a Nodejs-based tool for optimizing SVG vector graphics files.
*
* @see https://github.com/svg/svgo
*
* @author Kir Belevich <kir@soulshine.in> (https://github.com/deepsweet)
* @copyright © 2012 Kir Belevich
* @license MIT https://raw.githubusercontent.com/svg/svgo/master/LICENSE
*/
var CONFIG = require('./svgo/config'),
SVG2JS = require('./svgo/svg2js'),
PLUGINS = require('./svgo/plugins'),
JSAPI = require('./svgo/jsAPI.js'),
JS2SVG = require('./svgo/js2svg');
var SVGO = module.exports = function(config) {
this.config = CONFIG(config);
};
SVGO.prototype.optimize = function(svgstr, callback) {
var _this = this,
config = this.config,
maxPassCount = config.multipass ? 10 : 1,
counter = 0,
prevResultSize = Number.POSITIVE_INFINITY,
optimizeOnceCallback = function(svgjs) {
if (svgjs.error) {
callback(svgjs);
return;
}
if (++counter < maxPassCount && svgjs.data.length < prevResultSize) {
prevResultSize = svgjs.data.length;
_this._optimizeOnce(svgjs.data, optimizeOnceCallback);
} else {
callback(svgjs);
}
};
_this._optimizeOnce(svgstr, optimizeOnceCallback);
};
SVGO.prototype._optimizeOnce = function(svgstr, callback) {
var config = this.config;
SVG2JS(svgstr, function(svgjs) {
if (svgjs.error) {
callback(svgjs);
return;
}
svgjs = PLUGINS(svgjs, config.plugins);
callback(JS2SVG(svgjs, config.js2svg));
});
};
/**
* The factory that creates a content item with the helper methods.
*
* @param {Object} data which passed to jsAPI constructor
* @returns {JSAPI} content item
*/
SVGO.prototype.createContentItem = function(data) {
return new JSAPI(data);
};

View File

@ -0,0 +1,547 @@
/* jshint quotmark: false */
'use strict';
require('colors');
var FS = require('fs'),
PATH = require('path'),
SVGO = require('../svgo'),
YAML = require('js-yaml'),
PKG = require('../../package.json'),
mkdirp = require('mkdirp'),
encodeSVGDatauri = require('./tools').encodeSVGDatauri,
decodeSVGDatauri = require('./tools').decodeSVGDatauri,
regSVGFile = /\.svg$/;
/**
* Command-Option-Argument.
*
* @see https://github.com/veged/coa
*/
module.exports = require('coa').Cmd()
.helpful()
.name(PKG.name)
.title(PKG.description)
.opt()
.name('version').title('Version')
.short('v').long('version')
.only()
.flag()
.act(function() {
return PKG.version;
})
.end()
.opt()
.name('input').title('Input file, "-" for STDIN')
.short('i').long('input')
.val(function(val) {
return val || this.reject("Option '--input' must have a value.");
})
.end()
.opt()
.name('string').title('Input SVG data string')
.short('s').long('string')
.end()
.opt()
.name('folder').title('Input folder, optimize and rewrite all *.svg files')
.short('f').long('folder')
.val(function(val) {
return val || this.reject("Option '--folder' must have a value.");
})
.end()
.opt()
.name('output').title('Output file or folder (by default the same as the input), "-" for STDOUT')
.short('o').long('output')
.val(function(val) {
return val || this.reject("Option '--output' must have a value.");
})
.end()
.opt()
.name('precision').title('Set number of digits in the fractional part, overrides plugins params')
.short('p').long('precision')
.val(function(val) {
return !isNaN(val) ? val : this.reject("Option '--precision' must be an integer number");
})
.end()
.opt()
.name('config').title('Config file to extend or replace default')
.long('config')
.val(function(val) {
return val || this.reject("Option '--config' must have a value.");
})
.end()
.opt()
.name('disable').title('Disable plugin by name')
.long('disable')
.arr()
.val(function(val) {
return val || this.reject("Option '--disable' must have a value.");
})
.end()
.opt()
.name('enable').title('Enable plugin by name')
.long('enable')
.arr()
.val(function(val) {
return val || this.reject("Option '--enable' must have a value.");
})
.end()
.opt()
.name('datauri').title('Output as Data URI string (base64, URI encoded or unencoded)')
.long('datauri')
.val(function(val) {
return val || this.reject("Option '--datauri' must have one of the following values: 'base64', 'enc' or 'unenc'");
})
.end()
.opt()
.name('multipass').title('Enable multipass')
.long('multipass')
.flag()
.end()
.opt()
.name('pretty').title('Make SVG pretty printed')
.long('pretty')
.flag()
.end()
.opt()
.name('show-plugins').title('Show available plugins and exit')
.long('show-plugins')
.flag()
.end()
.arg()
.name('input').title('Alias to --input')
.end()
.arg()
.name('output').title('Alias to --output')
.end()
.act(function(opts, args) {
var input = args && args.input ? args.input : opts.input,
output = args && args.output ? args.output : opts.output,
config = {};
// --show-plugins
if (opts['show-plugins']) {
showAvailablePlugins();
process.exit(0);
}
// w/o anything
if (
(!input || input === '-') &&
!opts.string &&
!opts.stdin &&
!opts.folder &&
process.stdin.isTTY
) return this.usage();
// --config
if (opts.config) {
// string
if (opts.config.charAt(0) === '{') {
config = JSON.parse(opts.config);
// external file
} else {
var configPath = PATH.resolve(opts.config);
try {
// require() adds some weird output on YML files
config = JSON.parse(FS.readFileSync(configPath, 'utf8'));
} catch (err) {
if (err.code === 'ENOENT') {
console.error('Error: couldn\'t find config file \'' + opts.config + '\'.');
return;
} else if (err.code === 'EISDIR') {
console.error('Error: directory \'' + opts.config + '\' is not a config file.');
return;
}
config = YAML.safeLoad(FS.readFileSync(configPath, 'utf8'));
if (!config || Array.isArray(config)) {
console.error('Error: invalid config file \'' + opts.config + '\'.');
return;
}
}
}
}
// --precision
if (opts.precision) {
config.floatPrecision = Math.max(0, parseInt(opts.precision));
}
// --disable
if (opts.disable) {
config = changePluginsState(opts.disable, false, config);
}
// --enable
if (opts.enable) {
config = changePluginsState(opts.enable, true, config);
}
// --multipass
if (opts.multipass) {
config.multipass = true;
}
// --pretty
if (opts.pretty) {
config.js2svg = config.js2svg || {};
config.js2svg.pretty = true;
}
// --output
if (opts.output) {
config.output = opts.output;
}
// --folder
if (opts.folder) {
optimizeFolder(opts.folder, config, output);
return;
}
// --input
if (input) {
// STDIN
if (input === '-') {
var data = '';
process.stdin.pause();
process.stdin
.on('data', function(chunk) {
data += chunk;
})
.once('end', function() {
optimizeFromString(data, config, opts.datauri, input, output);
})
.resume();
// file
} else {
FS.readFile(input, 'utf8', function(err, data) {
if (err) {
if (err.code === 'EISDIR')
optimizeFolder(input, config, output);
else if (err.code === 'ENOENT')
console.error('Error: no such file or directory \'' + input + '\'.');
else
console.error(err);
return;
}
optimizeFromString(data, config, opts.datauri, input, output);
});
}
// --string
} else if (opts.string) {
opts.string = decodeSVGDatauri(opts.string);
optimizeFromString(opts.string, config, opts.datauri, input, output);
}
});
function optimizeFromString(svgstr, config, datauri, input, output) {
var startTime = Date.now(config),
time,
inBytes = Buffer.byteLength(svgstr, 'utf8'),
outBytes,
svgo = new SVGO(config);
svgo.optimize(svgstr, function(result) {
if (result.error) {
console.error(result.error);
return;
}
if (datauri) {
result.data = encodeSVGDatauri(result.data, datauri);
}
outBytes = Buffer.byteLength(result.data, 'utf8');
time = Date.now() - startTime;
// stdout
if (output === '-' || (input === '-' && !output)) {
process.stdout.write(result.data + '\n');
// file
} else {
// overwrite input file if there is no output
if (!output && input) {
output = input;
}
console.log('\r');
saveFileAndPrintInfo(result.data, output, inBytes, outBytes, time);
}
});
}
function saveFileAndPrintInfo(data, path, inBytes, outBytes, time) {
FS.writeFile(path, data, 'utf8', function() {
// print time info
printTimeInfo(time);
// print optimization profit info
printProfitInfo(inBytes, outBytes);
});
}
function printTimeInfo(time) {
console.log('Done in ' + time + ' ms!');
}
function printProfitInfo(inBytes, outBytes) {
var profitPercents = 100 - outBytes * 100 / inBytes;
console.log(
(Math.round((inBytes / 1024) * 1000) / 1000) + ' KiB' +
(profitPercents < 0 ? ' + ' : ' - ') +
String(Math.abs((Math.round(profitPercents * 10) / 10)) + '%').green + ' = ' +
(Math.round((outBytes / 1024) * 1000) / 1000) + ' KiB\n'
);
}
/**
* Change plugins state by names array.
*
* @param {Array} names plugins names
* @param {Boolean} state active state
* @param {Object} config original config
* @return {Object} changed config
*/
function changePluginsState(names, state, config) {
// extend config
if (config && config.plugins) {
names.forEach(function(name) {
var matched,
key;
config.plugins.forEach(function(plugin) {
// get plugin name
if (typeof plugin === 'object') {
key = Object.keys(plugin)[0];
} else {
key = plugin;
}
// if there are such plugin name
if (key === name) {
// do not replace plugin's params with true
if (typeof plugin[key] !== 'object' || !state) {
plugin[key] = state;
}
// mark it as matched
matched = true;
}
});
// if not matched and current config is not full
if (!matched && !config.full) {
var obj = {};
obj[name] = state;
// push new plugin Object
config.plugins.push(obj);
matched = true;
}
});
// just push
} else {
config = { plugins: [] };
names.forEach(function(name) {
var obj = {};
obj[name] = state;
config.plugins.push(obj);
});
}
return config;
}
function optimizeFolder(dir, config, output) {
var svgo = new SVGO(config);
console.log('Processing directory \'' + dir + '\':\n');
// absoluted folder path
var path = PATH.resolve(dir);
// list folder content
FS.readdir(path, function(err, files) {
if (err) {
console.error(err);
return;
}
if (!files.length) {
console.log('Directory \'' + dir + '\' is empty.');
return;
}
var i = 0,
found = false;
function optimizeFile(file) {
// absoluted file path
var filepath = PATH.resolve(path, file);
var outfilepath = output ? PATH.resolve(output, file) : filepath;
// check if file name matches *.svg
if (regSVGFile.test(filepath)) {
found = true;
FS.readFile(filepath, 'utf8', function(err, data) {
if (err) {
console.error(err);
return;
}
var startTime = Date.now(),
time,
inBytes = Buffer.byteLength(data, 'utf8'),
outBytes;
svgo.optimize(data, function(result) {
if (result.error) {
console.error(result.error);
return;
}
outBytes = Buffer.byteLength(result.data, 'utf8');
time = Date.now() - startTime;
writeOutput();
function writeOutput() {
FS.writeFile(outfilepath, result.data, 'utf8', report);
}
function report(err) {
if (err) {
if (err.code === 'ENOENT') {
mkdirp(output, writeOutput);
return;
} else if (err.code === 'ENOTDIR') {
console.error('Error: output \'' + output + '\' is not a directory.');
return;
}
console.error(err);
return;
}
console.log(file + ':');
// print time info
printTimeInfo(time);
// print optimization profit info
printProfitInfo(inBytes, outBytes);
//move on to the next file
if (++i < files.length) {
optimizeFile(files[i]);
}
}
});
});
}
//move on to the next file
else if (++i < files.length) {
optimizeFile(files[i]);
} else if (!found) {
console.log('No SVG files have been found.');
}
}
optimizeFile(files[i]);
});
}
var showAvailablePlugins = function () {
var svgo = new SVGO();
console.log('Currently available plugins:');
svgo.config.plugins.forEach(function (plugins) {
plugins.forEach(function (plugin) {
console.log(' [ ' + plugin.name.green + ' ] ' + plugin.description);
});
});
//console.log(JSON.stringify(svgo, null, 4));
};

View File

@ -0,0 +1,224 @@
'use strict';
var FS = require('fs');
var yaml = require('js-yaml');
var EXTEND = require('whet.extend');
/**
* Read and/or extend/replace default config file,
* prepare and optimize plugins array.
*
* @param {Object} [config] input config
* @return {Object} output config
*/
module.exports = function(config) {
var defaults;
if (config && config.full) {
defaults = config;
if (defaults.plugins) {
defaults.plugins = preparePluginsArray(defaults.plugins);
defaults.plugins = optimizePluginsArray(defaults.plugins);
}
defaults.multipass = config.multipass;
} else {
defaults = EXTEND({}, yaml.safeLoad(FS.readFileSync(__dirname + '/../../.svgo.yml', 'utf8')));
defaults.plugins = preparePluginsArray(defaults.plugins);
if (config) {
defaults = extendConfig(defaults, config);
defaults.multipass = config.multipass;
if ('floatPrecision' in config) {
defaults.plugins.forEach(function(plugin) {
if (plugin.params && ('floatPrecision' in plugin.params)) {
plugin.params.floatPrecision = config.floatPrecision;
}
});
}
}
defaults.plugins = optimizePluginsArray(defaults.plugins);
}
return defaults;
};
/**
* Require() all plugins in array.
*
* @param {Array} plugins input plugins array
* @return {Array} input plugins array of arrays
*/
function preparePluginsArray(plugins) {
var plugin,
key;
return plugins.map(function(item) {
// {}
if (typeof item === 'object') {
key = Object.keys(item)[0];
// custom
if (typeof item[key] === 'object' && item[key].fn && typeof item[key].fn === 'function') {
plugin = setupCustomPlugin(key, item[key]);
} else {
plugin = EXTEND({}, require('../../plugins/' + key));
// name: {}
if (typeof item[key] === 'object') {
plugin.params = EXTEND({}, plugin.params || {}, item[key]);
plugin.active = true;
// name: false
} else if (item[key] === false) {
plugin.active = false;
// name: true
} else if (item[key] === true) {
plugin.active = true;
}
plugin.name = key;
}
// name
} else {
plugin = EXTEND({}, require('../../plugins/' + item));
plugin.name = item;
}
return plugin;
});
}
/**
* Extend plugins with the custom config object.
*
* @param {Array} plugins input plugins
* @param {Object} config config
* @return {Array} output plugins
*/
function extendConfig(defaults, config) {
var key;
// plugins
if (config.plugins) {
config.plugins.forEach(function(item) {
// {}
if (typeof item === 'object') {
key = Object.keys(item)[0];
// custom
if (typeof item[key] === 'object' && item[key].fn && typeof item[key].fn === 'function') {
defaults.plugins.push(setupCustomPlugin(key, item[key]));
} else {
defaults.plugins.forEach(function(plugin) {
if (plugin.name === key) {
// name: {}
if (typeof item[key] === 'object') {
plugin.params = EXTEND({}, plugin.params || {}, item[key]);
plugin.active = true;
// name: false
} else if (item[key] === false) {
plugin.active = false;
// name: true
} else if (item[key] === true) {
plugin.active = true;
}
}
});
}
}
});
}
// svg2js
if (config.svg2js) {
defaults.svg2js = config.svg2js;
}
// js2svg
if (config.js2svg) {
defaults.js2svg = config.js2svg;
}
return defaults;
}
/**
* Setup and enable a custom plugin
*
* @param {String} plugin name
* @param {Object} custom plugin
* @return {Array} enabled plugin
*/
function setupCustomPlugin(name, plugin) {
plugin.active = true;
plugin.params = EXTEND({}, plugin.params || {});
plugin.name = name;
return plugin;
}
/**
* Try to group sequential elements of plugins array.
*
* @param {Object} plugins input plugins
* @return {Array} output plugins
*/
function optimizePluginsArray(plugins) {
var prev;
plugins = plugins.map(function(item) {
return [item];
});
plugins = plugins.filter(function(item) {
if (prev && item[0].type === prev[0].type) {
prev.push(item[0]);
return false;
}
prev = item;
return true;
});
return plugins;
}

View File

@ -0,0 +1,325 @@
'use strict';
var EXTEND = require('whet.extend'),
textElem = require('../../plugins/_collections.js').elemsGroups.textContent.concat('title');
var defaults = {
doctypeStart: '<!DOCTYPE',
doctypeEnd: '>',
procInstStart: '<?',
procInstEnd: '?>',
tagOpenStart: '<',
tagOpenEnd: '>',
tagCloseStart: '</',
tagCloseEnd: '>',
tagShortStart: '<',
tagShortEnd: '/>',
attrStart: '="',
attrEnd: '"',
commentStart: '<!--',
commentEnd: '-->',
cdataStart: '<![CDATA[',
cdataEnd: ']]>',
textStart: '',
textEnd: '',
indent: ' ',
regEntities: /[&'"<>]/g,
regValEntities: /[&"<>]/g,
encodeEntity: encodeEntity,
pretty: false
};
var entities = {
'&': '&amp;',
'\'': '&apos;',
'"': '&quot;',
'>': '&gt;',
'<': '&lt;',
};
/**
* Convert SVG-as-JS object to SVG (XML) string.
*
* @param {Object} data input data
* @param {Object} config config
*
* @return {Object} output data
*/
module.exports = function(data, config) {
return new JS2SVG(config).convert(data);
};
function JS2SVG(config) {
if (config) {
this.config = EXTEND(true, {}, defaults, config);
} else {
this.config = defaults;
}
if (this.config.pretty) {
this.config.doctypeEnd += '\n';
this.config.procInstEnd += '\n';
this.config.commentEnd += '\n';
this.config.cdataEnd += '\n';
this.config.tagShortEnd += '\n';
this.config.tagOpenEnd += '\n';
this.config.tagCloseEnd += '\n';
this.config.textEnd += '\n';
}
this.indentLevel = 0;
this.textContext = null;
}
function encodeEntity(char) {
return entities[char];
}
/**
* Start conversion.
*
* @param {Object} data input data
*
* @return {String}
*/
JS2SVG.prototype.convert = function(data) {
var svg = '';
if (data.content) {
this.indentLevel++;
data.content.forEach(function(item) {
if (item.elem) {
svg += this.createElem(item);
} else if (item.text) {
svg += this.createText(item.text);
} else if (item.doctype) {
svg += this.createDoctype(item.doctype);
} else if (item.processinginstruction) {
svg += this.createProcInst(item.processinginstruction);
} else if (item.comment) {
svg += this.createComment(item.comment);
} else if (item.cdata) {
svg += this.createCDATA(item.cdata);
}
}, this);
}
this.indentLevel--;
return {
data: svg,
info: {
width: this.width,
height: this.height
}
};
};
/**
* Create indent string in accordance with the current node level.
*
* @return {String}
*/
JS2SVG.prototype.createIndent = function() {
var indent = '';
if (this.config.pretty && !this.textContext) {
for (var i = 1; i < this.indentLevel; i++) {
indent += this.config.indent;
}
}
return indent;
};
/**
* Create doctype tag.
*
* @param {String} doctype doctype body string
*
* @return {String}
*/
JS2SVG.prototype.createDoctype = function(doctype) {
return this.config.doctypeStart +
doctype +
this.config.doctypeEnd;
};
/**
* Create XML Processing Instruction tag.
*
* @param {Object} instruction instruction object
*
* @return {String}
*/
JS2SVG.prototype.createProcInst = function(instruction) {
return this.config.procInstStart +
instruction.name +
' ' +
instruction.body +
this.config.procInstEnd;
};
/**
* Create comment tag.
*
* @param {String} comment comment body
*
* @return {String}
*/
JS2SVG.prototype.createComment = function(comment) {
return this.config.commentStart +
comment +
this.config.commentEnd;
};
/**
* Create CDATA section.
*
* @param {String} cdata CDATA body
*
* @return {String}
*/
JS2SVG.prototype.createCDATA = function(cdata) {
return this.config.cdataStart +
cdata +
this.config.cdataEnd;
};
/**
* Create element tag.
*
* @param {Object} data element object
*
* @return {String}
*/
JS2SVG.prototype.createElem = function(data) {
// beautiful injection for obtaining SVG information :)
if (
data.isElem('svg') &&
data.hasAttr('width') &&
data.hasAttr('height')
) {
this.width = data.attr('width').value;
this.height = data.attr('height').value;
}
// empty element and short tag
if (data.isEmpty()) {
return this.createIndent() +
this.config.tagShortStart +
data.elem +
this.createAttrs(data) +
this.config.tagShortEnd;
// non-empty element
} else {
var tagOpenStart = this.config.tagOpenStart,
tagOpenEnd = this.config.tagOpenEnd,
tagCloseStart = this.config.tagCloseStart,
tagCloseEnd = this.config.tagCloseEnd,
openIndent = this.createIndent(),
textIndent = '',
processedData = '',
dataEnd = '';
if (this.textContext) {
tagOpenStart = defaults.tagOpenStart;
tagOpenEnd = defaults.tagOpenEnd;
tagCloseStart = defaults.tagCloseStart;
tagCloseEnd = defaults.tagCloseEnd;
openIndent = '';
} else if (data.isElem(textElem)) {
if (this.config.pretty) {
textIndent += openIndent + this.config.indent;
}
this.textContext = data;
}
processedData += this.convert(data).data;
if (this.textContext == data) {
this.textContext = null;
if (this.config.pretty) dataEnd = '\n';
}
return openIndent +
tagOpenStart +
data.elem +
this.createAttrs(data) +
tagOpenEnd +
textIndent +
processedData +
dataEnd +
this.createIndent() +
tagCloseStart +
data.elem +
tagCloseEnd;
}
};
/**
* Create element attributes.
*
* @param {Object} elem attributes object
*
* @return {String}
*/
JS2SVG.prototype.createAttrs = function(elem) {
var attrs = '';
elem.eachAttr(function(attr) {
attrs += ' ' +
attr.name +
this.config.attrStart +
String(attr.value).replace(this.config.regValEntities, this.config.encodeEntity) +
this.config.attrEnd;
}, this);
return attrs;
};
/**
* Create text node.
*
* @param {String} text text
*
* @return {String}
*/
JS2SVG.prototype.createText = function(text) {
return this.createIndent() +
this.config.textStart +
text.replace(this.config.regEntities, this.config.encodeEntity) +
(this.textContext ? '' : this.config.textEnd);
};

View File

@ -0,0 +1,256 @@
'use strict';
var EXTEND = require('whet.extend');
var JSAPI = module.exports = function(data, parentNode) {
EXTEND(this, data);
if (parentNode) {
Object.defineProperty(this, 'parentNode', {
writable: true,
value: parentNode
});
}
};
/**
* Perform a deep clone of this node.
*
* @return {Object} element
*/
JSAPI.prototype.clone = function() {
var node = this;
var nodeData = {};
Object.keys(node).forEach(function(key) {
if (key != 'content') {
nodeData[key] = node[key];
}
});
// Deep-clone node data
// This is still faster than using EXTEND(true…)
nodeData = JSON.parse(JSON.stringify(nodeData));
// parentNode gets set to a proper object by the parent clone,
// but it needs to be true/false now to do the right thing
// in the constructor.
var clonedNode = new JSAPI(nodeData, !!node.parentNode);
if (node.content) {
clonedNode.content = node.content.map(function(childNode) {
var clonedChild = childNode.clone();
clonedChild.parentNode = clonedNode;
return clonedChild;
});
}
return clonedNode;
};
/**
* Determine if item is an element
* (any, with a specific name or in a names array).
*
* @param {String|Array} [param] element name or names arrays
* @return {Boolean}
*/
JSAPI.prototype.isElem = function(param) {
if (!param) return !!this.elem;
if (Array.isArray(param)) return !!this.elem && (param.indexOf(this.elem) > -1);
return !!this.elem && this.elem === param;
};
/**
* Renames an element
*
* @param {String} name new element name
* @return {Object} element
*/
JSAPI.prototype.renameElem = function(name) {
if (typeof name == 'string' && name != '')
this.elem = this.local = name;
return this;
};
/**
* Determine if element is empty.
*
* @return {Boolean}
*/
JSAPI.prototype.isEmpty = function() {
return !this.content || !this.content.length;
};
/**
* Changes content by removing elements and/or adding new elements.
*
* @param {Number} start Index at which to start changing the content.
* @param {Number} n Number of elements to remove.
* @param {Array|Object} [insertion] Elements to add to the content.
* @return {Array} Removed elements.
*/
JSAPI.prototype.spliceContent = function(start, n, insertion) {
if (arguments.length < 2) return [];
if (!Array.isArray(insertion))
insertion = Array.apply(null, arguments).slice(2);
insertion.forEach(function(inner) { inner.parentNode = this }, this);
return this.content.splice.apply(this.content, [start, n].concat(insertion));
};
/**
* Determine if element has an attribute
* (any, or by name or by name + value).
*
* @param {String} [name] attribute name
* @param {String} [val] attribute value (will be toString()'ed)
* @return {Boolean}
*/
JSAPI.prototype.hasAttr = function(name, val) {
if (!this.attrs || !Object.keys(this.attrs).length) return false;
if (!arguments.length) return !!this.attrs;
if (val !== undefined) return !!this.attrs[name] && this.attrs[name].value === val.toString();
return !!this.attrs[name];
};
/**
* Get a specific attribute from an element
* (by name or name + value).
*
* @param {String} name attribute name
* @param {String} [val] attribute value (will be toString()'ed)
* @return {Object|Undefined}
*/
JSAPI.prototype.attr = function(name, val) {
if (!this.hasAttr() || !arguments.length) return undefined;
if (val !== undefined) return this.hasAttr(name, val) ? this.attrs[name] : undefined;
return this.attrs[name];
};
/**
* Get computed attribute value from an element
*
* @param {String} name attribute name
* @return {Object|Undefined}
*/
JSAPI.prototype.computedAttr = function(name, val) {
/* jshint eqnull: true */
if (!arguments.length) return;
for (var elem = this; elem && (!elem.hasAttr(name) || !elem.attr(name).value); elem = elem.parentNode);
if (val != null) {
return elem ? elem.hasAttr(name, val) : false;
} else if (elem && elem.hasAttr(name)) {
return elem.attrs[name].value;
}
};
/**
* Remove a specific attribute.
*
* @param {String|Array} name attribute name
* @param {String} [val] attribute value
* @return {Boolean}
*/
JSAPI.prototype.removeAttr = function(name, val, recursive) {
if (!arguments.length) return false;
if (Array.isArray(name)) name.forEach(this.removeAttr, this);
if (!this.hasAttr(name)) return false;
if (!recursive && val && this.attrs[name].value !== val) return false;
delete this.attrs[name];
if (!Object.keys(this.attrs).length) delete this.attrs;
return true;
};
/**
* Add attribute.
*
* @param {Object} attr attribute object
* @return {Object} created attribute
*/
JSAPI.prototype.addAttr = function(attr) {
if (!attr ||
(attr && attr.name === undefined) ||
(attr && attr.value === undefined) ||
(attr && attr.prefix === undefined) ||
(attr && attr.local === undefined)
) return false;
this.attrs = this.attrs || {};
this.attrs[attr.name] = attr;
return this.attrs[attr.name];
};
/**
* Iterates over all attributes.
*
* @param {Function} callback callback
* @param {Object} [context] callback context
* @return {Boolean} false if there are no any attributes
*/
JSAPI.prototype.eachAttr = function(callback, context) {
if (!this.hasAttr()) return false;
for (var name in this.attrs) {
callback.call(context, this.attrs[name]);
}
return true;
};
/**
* Tests whether some attribute passes the test.
*
* @param {Function} callback callback
* @param {Object} [context] callback context
* @return {Boolean} false if there are no any attributes
*/
JSAPI.prototype.someAttr = function(callback, context) {
if (!this.hasAttr()) return false;
for (var name in this.attrs) {
if (callback.call(context, this.attrs[name])) return true;
}
return false;
};

View File

@ -0,0 +1,98 @@
'use strict';
/**
* Plugins engine.
*
* @module plugins
*
* @param {Object} data input data
* @param {Object} plugins plugins object from config
* @return {Object} output data
*/
module.exports = function(data, plugins) {
plugins.forEach(function(group) {
switch(group[0].type) {
case 'perItem':
data = perItem(data, group);
break;
case 'perItemReverse':
data = perItem(data, group, true);
break;
case 'full':
data = full(data, group);
break;
}
});
return data;
};
/**
* Direct or reverse per-item loop.
*
* @param {Object} data input data
* @param {Array} plugins plugins list to process
* @param {Boolean} [reverse] reverse pass?
* @return {Object} output data
*/
function perItem(data, plugins, reverse) {
function monkeys(items) {
items.content = items.content.filter(function(item) {
// reverse pass
if (reverse && item.content && item.elem != 'foreignObject') {
monkeys(item);
}
// main filter
var filter = true;
for (var i = 0; filter && i < plugins.length; i++) {
var plugin = plugins[i];
if (plugin.active && plugin.fn(item, plugin.params) === false) {
filter = false;
}
}
// direct pass
if (!reverse && item.content && item.elem != 'foreignObject') {
monkeys(item);
}
return filter;
});
return items;
}
return monkeys(data);
}
/**
* "Full" plugins.
*
* @param {Object} data input data
* @param {Array} plugins plugins list to process
* @return {Object} output data
*/
function full(data, plugins) {
plugins.forEach(function(plugin) {
if (plugin.active) {
data = plugin.fn(data, plugin.params);
}
});
return data;
}

View File

@ -0,0 +1,162 @@
'use strict';
var SAX = require('sax'),
JSAPI = require('./jsAPI');
var config = {
strict: true,
trim: false,
normalize: true,
lowercase: true,
xmlns: true,
position: false
};
/**
* Convert SVG (XML) string to SVG-as-JS object.
*
* @param {String} data input data
* @param {Function} callback
*/
module.exports = function(data, callback) {
var sax = SAX.parser(config.strict, config),
root = new JSAPI({ elem: '#document' }),
current = root,
stack = [root],
textContext = null;
function pushToContent(content) {
content = new JSAPI(content, current);
(current.content = current.content || []).push(content);
return content;
}
sax.ondoctype = function(doctype) {
pushToContent({
doctype: doctype
});
};
sax.onprocessinginstruction = function(data) {
pushToContent({
processinginstruction: data
});
};
sax.oncomment = function(comment) {
pushToContent({
comment: comment.trim()
});
};
sax.oncdata = function(cdata) {
pushToContent({
cdata: cdata
});
};
sax.onopentag = function(data) {
var elem = {
elem: data.name,
prefix: data.prefix,
local: data.local
};
if (Object.keys(data.attributes).length) {
elem.attrs = {};
for (var name in data.attributes) {
elem.attrs[name] = {
name: name,
value: data.attributes[name].value,
prefix: data.attributes[name].prefix,
local: data.attributes[name].local
};
}
}
elem = pushToContent(elem);
current = elem;
// Save info about <text> tag to prevent trimming of meaningful whitespace
if (data.name == 'text' && !data.prefix) {
textContext = current;
}
stack.push(elem);
};
sax.ontext = function(text) {
if (/\S/.test(text) || textContext) {
if (!textContext)
text = text.trim();
pushToContent({
text: text
});
}
};
sax.onclosetag = function() {
var last = stack.pop();
// Trim text inside <text> tag.
if (last == textContext) {
trim(textContext);
textContext = null;
}
current = stack[stack.length - 1];
};
sax.onerror = function(e) {
callback({ error: 'Error in parsing: ' + e.message });
};
sax.onend = function() {
if (!this.error) callback(root);
};
sax.write(data).close();
function trim(elem) {
if (!elem.content) return elem;
var start = elem.content[0],
end = elem.content[elem.content.length - 1];
while (start && start.content && !start.text) start = start.content[0];
if (start && start.text) start.text = start.text.replace(/^\s+/, '');
while (end && end.content && !end.text) end = end.content[end.content.length - 1];
if (end && end.text) end.text = end.text.replace(/\s+$/, '');
return elem;
}
};

View File

@ -0,0 +1,142 @@
'use strict';
/**
* Encode plain SVG data string into Data URI string.
*
* @param {String} str input string
* @param {String} type Data URI type
* @return {String} output string
*/
exports.encodeSVGDatauri = function(str, type) {
var prefix = 'data:image/svg+xml';
// base64
if (!type || type === 'base64') {
prefix += ';base64,';
str = prefix + new Buffer(str).toString('base64');
// URI encoded
} else if (type === 'enc') {
str = prefix + ',' + encodeURIComponent(str);
// unencoded
} else if (type === 'unenc') {
str = prefix + ',' + str;
}
return str;
};
/**
* Decode SVG Data URI string into plain SVG string.
*
* @param {string} str input string
* @return {String} output string
*/
exports.decodeSVGDatauri = function(str) {
var prefix = 'data:image/svg+xml';
// base64
if (str.substring(0, 26) === (prefix + ';base64,')) {
str = new Buffer(str.substring(26), 'base64').toString('utf8');
// URI encoded
} else if (str.substring(0, 20) === (prefix + ',%')) {
str = decodeURIComponent(str.substring(19));
// unencoded
} else if (str.substring(0, 20) === (prefix + ',<')) {
str = str.substring(19);
}
return str;
};
exports.intersectArrays = function(a, b) {
return a.filter(function(n) {
return b.indexOf(n) > -1;
});
};
exports.cleanupOutData = function(data, params) {
var str = '',
delimiter,
prev;
data.forEach(function(item, i) {
// space delimiter by default
delimiter = ' ';
// no extra space in front of first number
if (i === 0) {
delimiter = '';
}
// no extra space in front of negative number or
// in front of a floating number if a previous number is floating too
if (
params.negativeExtraSpace &&
(item < 0 ||
(item > 0 && item < 1 && prev % 1 !== 0)
)
) {
delimiter = '';
}
// save prev item value
prev = item;
// remove floating-point numbers leading zeros
// 0.5 → .5
// -0.5 → -.5
if (params.leadingZero) {
item = removeLeadingZero(item);
}
str += delimiter + item;
});
return str;
};
/**
* Remove floating-point numbers leading zero.
*
* @example
* 0.5 → .5
*
* @example
* -0.5 → -.5
*
* @param {Float} num input number
*
* @return {String} output number as string
*/
var removeLeadingZero = exports.removeLeadingZero = function(num) {
if (num > 0 && num < 1) {
num = ('' + num).slice(1);
} else if (num < 0 && num > -1) {
num = '-' + ('' + num).slice(2);
}
return num;
};

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,6 @@
.idea
*.iml
node_modules/
!node_modules/coa*.js
lib-cov/
html-report/

View File

@ -0,0 +1,9 @@
language: node_js
node_js:
- "0.10"
- "0.11"
matrix:
allow_failures:
- node_js: 0.11

View File

@ -0,0 +1,34 @@
BIN = ./node_modules/.bin
.PHONY: all
all: lib
lib: $(foreach s,$(wildcard src/*.coffee),$(patsubst src/%.coffee,lib/%.js,$s))
lib-cov: clean-coverage lib
$(BIN)/istanbul instrument --output lib-cov --no-compact --variable global.__coverage__ lib
lib/%.js: src/%.coffee
$(BIN)/coffee -cb -o $(@D) $<
.PHONY: test
test: lib
$(BIN)/mocha
.PHONY: coverage
coverage: lib-cov
COVER=1 $(BIN)/mocha --reporter mocha-istanbul
@echo
@echo Open html-report/index.html file in your browser
.PHONY: watch
watch:
$(BIN)/coffee --watch --bare --output lib src/*.coffee
.PHONY: clean
clean: clean-coverage
.PHONY: clean-coverage
clean-coverage:
-rm -rf lib-cov
-rm -rf html-report

View File

@ -0,0 +1,322 @@
# Command-Option-Argument
[![build status](https://secure.travis-ci.org/veged/coa.png)](http://travis-ci.org/veged/coa)
## What is it?
COA is a parser for command line options that aim to get maximum profit from formalization your program API.
Once you write definition in terms of commands, options and arguments you automaticaly get:
* Command line help text
* Program API for use COA-based programs as modules
* Shell completion
### Other features
* Rich types for options and arguments, such as arrays, boolean flags and required
* Commands can be async throught using promising (powered by [Q](https://github.com/kriskowal/q))
* Easy submoduling some existing commands to new top-level one
* Combined validation and complex parsing of values
### TODO
* Localization
* Shell-mode
* Configs
* Aliases
* Defaults
## Examples
````javascript
require('coa').Cmd() // main (top level) command declaration
.name(process.argv[1]) // set top level command name from program name
.title('My awesome command line util') // title for use in text messages
.helpful() // make command "helpful", i.e. options -h --help with usage message
.opt() // add some option
.name('version') // name for use in API
.title('Version') // title for use in text messages
.short('v') // short key: -v
.long('version') // long key: --version
.flag() // for options without value
.act(function(opts) { // add action for option
// return message as result of action
return JSON.parse(require('fs').readFileSync(__dirname + '/package.json'))
.version;
})
.end() // end option chain and return to main command
.cmd().name('subcommand').apply(require('./subcommand').COA).end() // load subcommand from module
.cmd() // inplace subcommand declaration
.name('othercommand').title('Awesome other subcommand').helpful()
.opt()
.name('input').title('input file, required')
.short('i').long('input')
.val(function(v) { // validator function, also for translate simple values
return require('fs').createReadStream(v) })
.req() // make option required
.end() // end option chain and return to command
.end() // end subcommand chain and return to parent command
.run(process.argv.slice(2)); // parse and run on process.argv
````
````javascript
// subcommand.js
exports.COA = function() {
this
.title('Awesome subcommand').helpful()
.opt()
.name('output').title('output file')
.short('o').long('output')
.output() // use default preset for "output" option declaration
.end()
};
````
## API reference
### Cmd
Command is a top level entity. Commands may have options and arguments.
#### Cmd.api
Returns object containing all its subcommands as methods to use from other programs.<br>
**@returns** *{Object}*
#### Cmd.name
Set a canonical command identifier to be used anywhere in the API.<br>
**@param** *String* `_name` command name<br>
**@returns** *COA.Cmd* `this` instance (for chainability)
#### Cmd.title
Set a long description for command to be used anywhere in text messages.<br>
**@param** *String* `_title` command title<br>
**@returns** *COA.Cmd* `this` instance (for chainability)
#### Cmd.cmd
Create new or add existing subcommand for current command.<br>
**@param** *COA.Cmd* `[cmd]` existing command instance<br>
**@returns** *COA.Cmd* new or added subcommand instance
#### Cmd.opt
Create option for current command.<br>
**@returns** *COA.Opt* `new` option instance
#### Cmd.arg
Create argument for current command.<br>
**@returns** *COA.Opt* `new` argument instance
#### Cmd.act
Add (or set) action for current command.<br>
**@param** *Function* `act` action function,
invoked in the context of command instance
and has the parameters:<br>
- *Object* `opts` parsed options<br>
- *Array* `args` parsed arguments<br>
- *Object* `res` actions result accumulator<br>
It can return rejected promise by Cmd.reject (in case of error)
or any other value treated as result.<br>
**@param** *{Boolean}* [force=false] flag for set action instead add to existings<br>
**@returns** *COA.Cmd* `this` instance (for chainability)
#### Cmd.apply
Apply function with arguments in context of command instance.<br>
**@param** *Function* `fn`<br>
**@param** *Array* `args`<br>
**@returns** *COA.Cmd* `this` instance (for chainability)
#### Cmd.comp
Set custom additional completion for current command.<br>
**@param** *Function* `fn` completion generation function,
invoked in the context of command instance.
Accepts parameters:<br>
- *Object* `opts` completion options<br>
It can return promise or any other value treated as result.<br>
**@returns** *COA.Cmd* `this` instance (for chainability)
#### Cmd.helpful
Make command "helpful", i.e. add -h --help flags for print usage.<br>
**@returns** *COA.Cmd* `this` instance (for chainability)
#### Cmd.completable
Adds shell completion to command, adds "completion" subcommand, that makes all the magic.<br>
Must be called only on root command.<br>
**@returns** *COA.Cmd* `this` instance (for chainability)
#### Cmd.usage
Build full usage text for current command instance.<br>
**@returns** *String* `usage` text
#### Cmd.run
Parse arguments from simple format like NodeJS process.argv
and run ahead current program, i.e. call process.exit when all actions done.<br>
**@param** *Array* `argv`<br>
**@returns** *COA.Cmd* `this` instance (for chainability)
#### Cmd.invoke
Invoke specified (or current) command using provided options and arguments.<br>
**@param** *String|Array* `cmds` subcommand to invoke (optional)<br>
**@param** *Object* `opts` command options (optional)<br>
**@param** *Object* `args` command arguments (optional)<br>
**@returns** *Q.Promise*
#### Cmd.reject
Return reject of actions results promise.<br>
Use in .act() for return with error.<br>
**@param** *Object* `reason` reject reason<br>
You can customize toString() method and exitCode property
of reason object.<br>
**@returns** *Q.promise* rejected promise
#### Cmd.end
Finish chain for current subcommand and return parent command instance.<br>
**@returns** *COA.Cmd* `parent` command
### Opt
Option is a named entity. Options may have short and long keys for use from command line.<br>
**@namespace**<br>
**@class** Presents option
#### Opt.name
Set a canonical option identifier to be used anywhere in the API.<br>
**@param** *String* `_name` option name<br>
**@returns** *COA.Opt* `this` instance (for chainability)
#### Opt.title
Set a long description for option to be used anywhere in text messages.<br>
**@param** *String* `_title` option title<br>
**@returns** *COA.Opt* `this` instance (for chainability)
#### Opt.short
Set a short key for option to be used with one hyphen from command line.<br>
**@param** *String* `_short`<br>
**@returns** *COA.Opt* `this` instance (for chainability)
#### Opt.long
Set a short key for option to be used with double hyphens from command line.<br>
**@param** *String* `_long`<br>
**@returns** *COA.Opt* `this` instance (for chainability)
#### Opt.flag
Make an option boolean, i.e. option without value.<br>
**@returns** *COA.Opt* `this` instance (for chainability)
#### Opt.arr
Makes an option accepts multiple values.<br>
Otherwise, the value will be used by the latter passed.<br>
**@returns** *COA.Opt* `this` instance (for chainability)
#### Opt.req
Makes an option req.<br>
**@returns** *COA.Opt* `this` instance (for chainability)
#### Opt.only
Makes an option to act as a command,
i.e. program will exit just after option action.<br>
**@returns** *COA.Opt* `this` instance (for chainability)
#### Opt.val
Set a validation (or value) function for argument.<br>
Value from command line passes through before becoming available from API.<br>
Using for validation and convertion simple types to any values.<br>
**@param** *Function* `_val` validating function,
invoked in the context of option instance
and has one parameter with value from command line<br>
**@returns** *COA.Opt* `this` instance (for chainability)
#### Opt.def
Set a default value for option.
Default value passed through validation function as ordinary value.<br>
**@param** *Object* `_def`<br>
**@returns** *COA.Opt* `this` instance (for chainability)
#### Opt.input
Make option value inputting stream.
It's add useful validation and shortcut for STDIN.
**@returns** *{COA.Opt}* `this` instance (for chainability)
#### Opt.output
Make option value outputing stream.<br>
It's add useful validation and shortcut for STDOUT.<br>
**@returns** *COA.Opt* `this` instance (for chainability)
#### Opt.act
Add action for current option command.
This action is performed if the current option
is present in parsed options (with any value).<br>
**@param** *Function* `act` action function,
invoked in the context of command instance
and has the parameters:<br>
- *Object* `opts` parsed options<br>
- *Array* `args` parsed arguments<br>
- *Object* `res` actions result accumulator<br>
It can return rejected promise by Cmd.reject (in case of error)
or any other value treated as result.<br>
**@returns** *COA.Opt* `this` instance (for chainability)
#### Opt.comp
Set custom additional completion for current option.<br>
**@param** *Function* `fn` completion generation function,
invoked in the context of command instance.
Accepts parameters:<br>
- *Object* `opts` completion options<br>
It can return promise or any other value treated as result.<br>
**@returns** *COA.Opt* `this` instance (for chainability)
#### Opt.end
Finish chain for current option and return parent command instance.<br>
**@returns** *COA.Cmd* `parent` command
### Arg
Argument is a unnamed entity.<br>
From command line arguments passed as list of unnamed values.
#### Arg.name
Set a canonical argument identifier to be used anywhere in text messages.<br>
**@param** *String* `_name` argument name<br>
**@returns** *COA.Arg* `this` instance (for chainability)
#### Arg.title
Set a long description for argument to be used anywhere in text messages.<br>
**@param** *String* `_title` argument title<br>
**@returns** *COA.Arg* `this` instance (for chainability)
#### Arg.arr
Makes an argument accepts multiple values.<br>
Otherwise, the value will be used by the latter passed.<br>
**@returns** *COA.Arg* `this` instance (for chainability)
#### Arg.req
Makes an argument req.<br>
**@returns** *COA.Arg* `this` instance (for chainability)
#### Arg.val
Set a validation (or value) function for argument.<br>
Value from command line passes through before becoming available from API.<br>
Using for validation and convertion simple types to any values.<br>
**@param** *Function* `_val` validating function,
invoked in the context of argument instance
and has one parameter with value from command line<br>
**@returns** *COA.Arg* `this` instance (for chainability)
#### Arg.def
Set a default value for argument.
Default value passed through validation function as ordinary value.<br>
**@param** *Object* `_def`<br>
**@returns** *COA.Arg* `this` instance (for chainability)
#### Arg.output
Make argument value outputing stream.<br>
It's add useful validation and shortcut for STDOUT.<br>
**@returns** *COA.Arg* `this` instance (for chainability)
#### Arg.comp
Set custom additional completion for current argument.<br>
**@param** *Function* `fn` completion generation function,
invoked in the context of command instance.
Accepts parameters:<br>
- *Object* `opts` completion options<br>
It can return promise or any other value treated as result.<br>
**@returns** *COA.Arg* `this` instance (for chainability)
#### Arg.end
Finish chain for current option and return parent command instance.<br>
**@returns** *COA.Cmd* `parent` command

View File

@ -0,0 +1,316 @@
# Command-Option-Argument
[![build status](https://secure.travis-ci.org/veged/coa.png)](http://travis-ci.org/veged/coa)
## Что это?
COA — парсер параметров командной строки, позволяющий извлечь максимум пользы от формального API вашей программы.
Как только вы опишете определение в терминах команд, параметров и аргументов, вы автоматически получите:
* Справку для командной строки
* API для использования программы как модуля в COA-совместимых программах
* Автодополнение для командной строки
### Прочие возможности
* Широкий выбор настроек для параметров и аргументов, включая множественные значения, логические значения и обязательность параметров
* Возможность асинхронного исполнения команд, используя промисы (используется библиотека [Q](https://github.com/kriskowal/q))
* Простота использования существующих команд как подмодулей для новых команд
* Комбинированная валидация и анализ сложных значений
## Примеры
````javascript
require('coa').Cmd() // декларация команды верхнего уровня
.name(process.argv[1]) // имя команды верхнего уровня, берем из имени программы
.title('Жутко полезная утилита для командной строки') // название для использования в справке и сообщениях
.helpful() // добавляем поддержку справки командной строки (-h, --help)
.opt() // добавляем параметр
.name('version') // имя параметра для использования в API
.title('Version') // текст для вывода в сообщениях
.short('v') // короткое имя параметра: -v
.long('version') // длинное имя параметра: --version
.flag() // параметр не требует ввода значения
.act(function(opts) { // действия при вызове аргумента
// результатом является вывод текстового сообщения
return JSON.parse(require('fs').readFileSync(__dirname + '/package.json'))
.version;
})
.end() // завершаем определение параметра и возвращаемся к определению верхнего уровня
.cmd().name('subcommand').apply(require('./subcommand').COA).end() // загрузка подкоманды из модуля
.cmd() // добавляем еще одну подкоманду
.name('othercommand').title('Еще одна полезная подпрограмма').helpful()
.opt()
.name('input').title('input file, required')
.short('i').long('input')
.val(function(v) { // функция-валидатор, также может использоваться для трансформации значений параметров
return require('fs').createReadStream(v) })
.req() // параметр является обязательным
.end() // завершаем определение параметра и возвращаемся к определению команды
.end() // завершаем определение подкоманды и возвращаемся к определению команды верхнего уровня
.run(process.argv.slice(2)); // разбираем process.argv и запускаем
````
````javascript
// subcommand.js
exports.COA = function() {
this
.title('Полезная подпрограмма').helpful()
.opt()
.name('output').title('output file')
.short('o').long('output')
.output() // использовать стандартную настройку для параметра вывода
.end()
};
````
## API
### Cmd
Команда — сущность верхнего уровня. У команды могут быть определены параметры и аргументы.
#### Cmd.api
Возвращает объект, который можно использовать в других программах. Подкоманды являются методами этого объекта.<br>
**@returns** *{Object}*
#### Cmd.name
Определяет канонический идентификатор команды, используемый в вызовах API.<br>
**@param** *String* `_name` имя команды<br>
**@returns** *COA.Cmd* `this` экземпляр команды (для поддержки цепочки методов)
#### Cmd.title
Определяет название команды, используемый в текстовых сообщениях.<br>
**@param** *String* `_title` название команды<br>
**@returns** *COA.Cmd* `this` экземпляр команды (для поддержки цепочки методов)
#### Cmd.cmd
Создает новую подкоманду или добавляет ранее определенную подкоманду к текущей команде.<br>
**@param** *COA.Cmd* `[cmd]` экземпляр ранее определенной подкоманды<br>
**@returns** *COA.Cmd* экземпляр новой или ранее определенной подкоманды
#### Cmd.opt
Создает параметр для текущей команды.<br>
**@returns** *COA.Opt* `new` экземпляр параметра
#### Cmd.arg
Создает аргумент для текущей команды.<br>
**@returns** *COA.Opt* `new` экземпляр аргумента
#### Cmd.act
Добавляет (или создает) действие для текущей команды.<br>
**@param** *Function* `act` функция,
выполняемая в контексте экземпляра текущей команды
и принимающая следующие параметры:<br>
- *Object* `opts` параметры команды<br>
- *Array* `args` аргументы команды<br>
- *Object* `res` объект-аккумулятор результатов<br>
Функция может вернуть проваленный промис из Cmd.reject (в случае ошибки)
или любое другое значение, рассматриваемое как результат.<br>
**@param** *{Boolean}* [force=false] флаг, назначающий немедленное исполнение вместо добавления к списку существующих действий<br>
**@returns** *COA.Cmd* `this` экземпляр команды (для поддержки цепочки методов)
#### Cmd.apply
Исполняет функцию с переданными аргументами в контексте экземпляра текущей команды.<br>
**@param** *Function* `fn`<br>
**@param** *Array* `args`<br>
**@returns** *COA.Cmd* `this` экземпляр команды (для поддержки цепочки методов)
#### Cmd.comp
Назначает кастомную функцию автодополнения для текущей команды.<br>
**@param** *Function* `fn` функция-генератор автодополнения,
исполняемая в контексте текущей команды.
Принимает параметры:<br>
- *Object* `opts` параметры<br>
Может возвращать промис или любое другое значение, рассматриваемое как результат исполнения команды.<br>
**@returns** *COA.Cmd* `this` экземпляр команды (для поддержки цепочки методов)
#### Cmd.helpful
Ставит флаг поддержки справки командной строки, т.е. вызов команды с параметрами -h --help выводит справку по работе с командой.<br>
**@returns** *COA.Cmd* `this` экземпляр команды (для поддержки цепочки методов)
#### Cmd.completable
Добавляет поддержку автодополнения командной строки. Добавляется подкоманда "completion", которая выполняет все необходимые действия.<br>
Может быть добавлен только для главной команды.<br>
**@returns** *COA.Cmd* `this` экземпляр команды (для поддержки цепочки методов)
#### Cmd.usage
Возвращает текст справки по использованию команды для текущего экземпляра.<br>
**@returns** *String* `usage` Текст справки по использованию
#### Cmd.run
Разбирает аргументы из значения, возвращаемого NodeJS process.argv,
и запускает текущую программу, т.е. вызывает process.exit после завершения
всех действий.<br>
**@param** *Array* `argv`<br>
**@returns** *COA.Cmd* `this` экземпляр команды (для поддержки цепочки методов)
#### Cmd.invoke
Исполняет переданную (или текущую) команду с указанными параметрами и аргументами.<br>
**@param** *String|Array* `cmds` подкоманда для исполнения (необязательно)<br>
**@param** *Object* `opts` параметры, передаваемые команде (необязательно)<br>
**@param** *Object* `args` аргументы, передаваемые команде (необязательно)<br>
**@returns** *Q.Promise*
#### Cmd.reject
Проваливает промисы, возращенные в действиях.<br>
Используется в .act() для возврата с ошибкой.<br>
**@param** *Object* `reason` причина провала<br>
Вы можете определить метод toString() и свойство toString()
объекта причины провала.<br>
**@returns** *Q.promise* проваленный промис
#### Cmd.end
Завершает цепочку методов текущей подкоманды и возвращает экземпляр родительской команды.<br>
**@returns** *COA.Cmd* `parent` родительская команда
### Opt
Параметр — именованная сущность. У параметра может быть определено короткое или длинное имя для использования из командной строки.<br>
**@namespace**<br>
**@class** Переданный параметр
#### Opt.name
Определяет канонический идентификатор параметра, используемый в вызовах API.<br>
**@param** *String* `_name` имя параметра<br>
**@returns** *COA.Opt* `this` экземпляр параметра (для поддержки цепочки методов)
#### Opt.title
Определяет описание для параметра, используемое в текстовых сообщениях.<br>
**@param** *String* `_title` название параметра<br>
**@returns** *COA.Opt* `this` экземпляр параметра (для поддержки цепочки методов)
#### Opt.short
Назначает ключ для короткого имени параметра, передаваемого из командной строки с одинарным дефисом (например, `-v`).<br>
**@param** *String* `_short`<br>
**@returns** *COA.Opt* `this` экземпляр параметра (для поддержки цепочки методов)
#### Opt.long
Назначает ключ для длинного имени параметра, передаваемого из командной строки с двойным дефисом (например, `--version`).<br>
**@param** *String* `_long`<br>
**@returns** *COA.Opt* `this` экземпляр параметра (для поддержки цепочки методов)
#### Opt.flag
Помечает параметр как логический, т.е. параметр не имеющий значения.<br>
**@returns** *COA.Opt* `this` экземпляр параметра (для поддержки цепочки методов)
#### Opt.arr
Помечает параметр как принимающий множественные значения.<br>
Иначе будет использовано последнее переданное значение параметра.<br>
**@returns** *COA.Opt* `this` экземпляр параметра (для поддержки цепочки методов)
#### Opt.req
Помечает параметр как обязательный.<br>
**@returns** *COA.Opt* `this` экземпляр параметра (для поддержки цепочки методов)
#### Opt.only
Интерпретирует параметр как команду,
т.е. программа будет завершена сразу после выполнения параметра.<br>
**@returns** *COA.Opt* `this` экземпляр параметра (для поддержки цепочки методов)
#### Opt.val
Назначает функцию валидации (или трансформации значения) для значения параметра.<br>
Значение, полученное из командной строки, передается в функцию-валидатор прежде чем оно станет доступно из API.<br>
Используется для валидации и трансформации введенных данных.<br>
**@param** *Function* `_val` функция валидации,
исполняемая в контексте экземпляра параметра
и принимающая в качестве единственного параметра значение, полученное
из командной строки<br>
**@returns** *COA.Opt* `this` экземпляр параметра (для поддержки цепочки методов)
#### Opt.def
Назначает значение параметра по умолчанию. Это значение также передается
в функцию валидации как обычное значение.<br>
**@param** *Object* `_def`<br>
**@returns** *COA.Opt* `this` экземпляр параметра (для поддержки цепочки методов)
#### Opt.input
Помечает параметр как принимающий ввод пользователя. <br>
Позволяет использовать валидацию для STDIN.<br>
**@returns** *{COA.Opt}* `this` экземпляр параметра (для поддержки цепочки методов)
#### Opt.output
Помечает параметр как вывод.<br>
Позволяет использовать валидацию для STDOUT.<br>
**@returns** *COA.Opt* `this` экземпляр параметра (для поддержки цепочки методов)
#### Opt.act
Добавляет (или создает) действие для текущего параметра команды.
Это действие будет выполнено, если текущий параметр есть
в списке полученных параметров (с любым значением).<br>
**@param** *Function* `act` функция, выполняемая в контексте
экземпляра текущей команды и принимающая следующие параметры:<br>
- *Object* `opts` параметры команды<br>
- *Array* `args` аргументы команды<br>
- *Object* `res` объект-аккумулятор результатов<br>
Функция может вернуть проваленный промис из Cmd.reject (в случае ошибки)
или любое другое значение, рассматриваемое как результат.<br>
**@returns** *COA.Opt* `this` экземпляр параметра (для поддержки цепочки методов)
#### Opt.comp
Назначает кастомную функцию автодополнения для текущей команды.<br>
**@param** *Function* `fn` функция-генератор автодоплнения, исполняемая в
контексте экземпляра команды.
Принимает параметры:<br>
- *Object* `opts` параметры автодополнения<br>
Может возвращать промис или любое другое значение, рассматриваемое как результат исполнения команды.<br>
**@returns** *COA.Opt* `this` экземпляр параметра (для поддержки цепочки методов)
#### Opt.end
Завершает цепочку методов текущего параметра и возвращает экземпляр родительской команды.<br>
**@returns** *COA.Cmd* `parent` родительская команда
### Arg
Аргумент — неименованная сущность.<br>
Аргументы передаются из командной строки как список неименованных значений.
#### Arg.name
Определяет канонический идентификатор аргумента, используемый в вызовах API.<br>
**@param** *String* `_name` имя аргумента<br>
**@returns** *COA.Arg* `this` экземпляр аргумента (для поддержки цепочки методов)
#### Arg.title
Определяет описание для аргумента, используемое в текстовых сообщениях.<br>
**@param** *String* `_title` описание аргумента<br>
**@returns** *COA.Arg* `this` экземпляр аргумента (для поддержки цепочки методов)
#### Arg.arr
Помечает аргумент как принимающий множественные значения.<br>
Иначе будет использовано последнее переданное значение аргумента.<br>
**@returns** *COA.Arg* `this` экземпляр аргумента (для поддержки цепочки методов)
#### Arg.req
Помечает аргумент как обязательный.<br>
**@returns** *COA.Arg* `this` экземпляр аргумента (для поддержки цепочки методов)
#### Arg.val
Назначает функцию валидации (или трансформации значения) для аргумента.<br>
Значение, полученное из командной строки, передается в функцию-валидатор прежде чем оно станет доступно из API.<br>
Используется для валидации и трансформации введенных данных.<br>
**@param** *Function* `_val` функция валидации,
исполняемая в контексте экземпляра аргумента
и принимающая в качестве единственного параметра значение, полученное
из командной строки<br>
**@returns** *COA.Opt* `this` экземпляр аргумента (для поддержки цепочки методов)
#### Arg.def
Назначает дефолтное значение для аргумента. Дефолтное значение передается
в функцию валидации как обычное значение.<br>
**@param** *Object* `_def`<br>
**@returns** *COA.Arg* `this` экземпляр аргумента (для поддержки цепочки методов)
#### Arg.output
Помечает параметр как вывод.<br>
Позволяет назначить валидацию для STDOUT.<br>
**@returns** *COA.Arg* `this` экземпляр аргумента (для поддержки цепочки методов)
#### Arg.comp
Назначает кастомную функцию автодополнения для текущего аргумента.<br>
**@param** *Function* `fn` функция-генератор автодоплнения,
исполняемая в контексте текущей команды.
Принимает параметры:<br>
- *Object* `opts` параметры
Может возвращать промис или любое другое значение, рассматриваемое как результат исполнения команды.<br>
**@returns** *COA.Arg* `this` экземпляр аргумента (для поддержки цепочки методов)
#### Arg.end
Завершает цепочку методов текущего аргумента и возвращает экземпляр родительской команды.<br>
**@returns** *COA.Cmd* `parent` родительская команда

View File

@ -0,0 +1 @@
module.exports = require(process.env.COVER? './lib-cov' : './lib');

View File

@ -0,0 +1,175 @@
// Generated by CoffeeScript 1.6.3
var Arg, Cmd, Color, Opt;
Color = require('./color').Color;
Cmd = require('./cmd').Cmd;
Opt = require('./opt').Opt;
/**
Argument
Unnamed entity. From command line arguments passed as list of unnamed values.
@namespace
@class Presents argument
*/
exports.Arg = Arg = (function() {
/**
@constructs
@param {COA.Cmd} cmd parent command
*/
function Arg(_cmd) {
this._cmd = _cmd;
this._cmd._args.push(this);
}
/**
Set a canonical argument identifier to be used anywhere in text messages.
@param {String} _name argument name
@returns {COA.Arg} this instance (for chainability)
*/
Arg.prototype.name = Opt.prototype.name;
/**
Set a long description for argument to be used anywhere in text messages.
@param {String} _title argument title
@returns {COA.Arg} this instance (for chainability)
*/
Arg.prototype.title = Cmd.prototype.title;
/**
Makes an argument accepts multiple values.
Otherwise, the value will be used by the latter passed.
@returns {COA.Arg} this instance (for chainability)
*/
Arg.prototype.arr = Opt.prototype.arr;
/**
Makes an argument required.
@returns {COA.Arg} this instance (for chainability)
*/
Arg.prototype.req = Opt.prototype.req;
/**
Set a validation (or value) function for argument.
Value from command line passes through before becoming available from API.
Using for validation and convertion simple types to any values.
@param {Function} _val validating function,
invoked in the context of argument instance
and has one parameter with value from command line
@returns {COA.Arg} this instance (for chainability)
*/
Arg.prototype.val = Opt.prototype.val;
/**
Set a default value for argument.
Default value passed through validation function as ordinary value.
@param {Object} _def
@returns {COA.Arg} this instance (for chainability)
*/
Arg.prototype.def = Opt.prototype.def;
/**
Set custom additional completion for current argument.
@param {Function} completion generation function,
invoked in the context of argument instance.
Accepts parameters:
- {Object} opts completion options
It can return promise or any other value treated as result.
@returns {COA.Arg} this instance (for chainability)
*/
Arg.prototype.comp = Cmd.prototype.comp;
/**
Make argument value inputting stream.
It's add useful validation and shortcut for STDIN.
@returns {COA.Arg} this instance (for chainability)
*/
Arg.prototype.input = Opt.prototype.input;
/**
Make argument value outputing stream.
It's add useful validation and shortcut for STDOUT.
@returns {COA.Arg} this instance (for chainability)
*/
Arg.prototype.output = Opt.prototype.output;
Arg.prototype._parse = function(arg, args) {
return this._saveVal(args, arg);
};
Arg.prototype._saveVal = Opt.prototype._saveVal;
Arg.prototype._checkParsed = function(opts, args) {
return !args.hasOwnProperty(this._name);
};
Arg.prototype._usage = function() {
var res;
res = [];
res.push(Color('lpurple', this._name.toUpperCase()), ' : ', this._title);
if (this._req) {
res.push(' ', Color('lred', '(required)'));
}
return res.join('');
};
Arg.prototype._requiredText = function() {
return 'Missing required argument:\n ' + this._usage();
};
/**
Return rejected promise with error code.
Use in .val() for return with error.
@param {Object} reject reason
You can customize toString() method and exitCode property
of reason object.
@returns {Q.promise} rejected promise
*/
Arg.prototype.reject = Cmd.prototype.reject;
/**
Finish chain for current option and return parent command instance.
@returns {COA.Cmd} parent command
*/
Arg.prototype.end = Cmd.prototype.end;
/**
Apply function with arguments in context of arg instance.
@param {Function} fn
@param {Array} args
@returns {COA.Arg} this instance (for chainability)
*/
Arg.prototype.apply = Cmd.prototype.apply;
return Arg;
})();

View File

@ -0,0 +1,605 @@
// Generated by CoffeeScript 1.6.3
var Cmd, Color, PATH, Q, UTIL,
__slice = [].slice;
UTIL = require('util');
PATH = require('path');
Color = require('./color').Color;
Q = require('q');
/**
Command
Top level entity. Commands may have options and arguments.
@namespace
@class Presents command
*/
exports.Cmd = Cmd = (function() {
/**
@constructs
@param {COA.Cmd} [cmd] parent command
*/
function Cmd(cmd) {
if (!(this instanceof Cmd)) {
return new Cmd(cmd);
}
this._parent(cmd);
this._cmds = [];
this._cmdsByName = {};
this._opts = [];
this._optsByKey = {};
this._args = [];
this._ext = false;
}
Cmd.get = function(propertyName, func) {
return Object.defineProperty(this.prototype, propertyName, {
configurable: true,
enumerable: true,
get: func
});
};
/**
Returns object containing all its subcommands as methods
to use from other programs.
@returns {Object}
*/
Cmd.get('api', function() {
var c, _fn,
_this = this;
if (!this._api) {
this._api = function() {
return _this.invoke.apply(_this, arguments);
};
}
_fn = function(c) {
return _this._api[c] = _this._cmdsByName[c].api;
};
for (c in this._cmdsByName) {
_fn(c);
}
return this._api;
});
Cmd.prototype._parent = function(cmd) {
this._cmd = cmd || this;
if (cmd) {
cmd._cmds.push(this);
if (this._name) {
this._cmd._cmdsByName[this._name] = this;
}
}
return this;
};
/**
Set a canonical command identifier to be used anywhere in the API.
@param {String} _name command name
@returns {COA.Cmd} this instance (for chainability)
*/
Cmd.prototype.name = function(_name) {
this._name = _name;
if (this._cmd !== this) {
this._cmd._cmdsByName[_name] = this;
}
return this;
};
/**
Set a long description for command to be used anywhere in text messages.
@param {String} _title command title
@returns {COA.Cmd} this instance (for chainability)
*/
Cmd.prototype.title = function(_title) {
this._title = _title;
return this;
};
/**
Create new or add existing subcommand for current command.
@param {COA.Cmd} [cmd] existing command instance
@returns {COA.Cmd} new subcommand instance
*/
Cmd.prototype.cmd = function(cmd) {
if (cmd) {
return cmd._parent(this);
} else {
return new Cmd(this);
}
};
/**
Create option for current command.
@returns {COA.Opt} new option instance
*/
Cmd.prototype.opt = function() {
return new (require('./opt').Opt)(this);
};
/**
Create argument for current command.
@returns {COA.Opt} new argument instance
*/
Cmd.prototype.arg = function() {
return new (require('./arg').Arg)(this);
};
/**
Add (or set) action for current command.
@param {Function} act action function,
invoked in the context of command instance
and has the parameters:
- {Object} opts parsed options
- {Array} args parsed arguments
- {Object} res actions result accumulator
It can return rejected promise by Cmd.reject (in case of error)
or any other value treated as result.
@param {Boolean} [force=false] flag for set action instead add to existings
@returns {COA.Cmd} this instance (for chainability)
*/
Cmd.prototype.act = function(act, force) {
if (!act) {
return this;
}
if (!force && this._act) {
this._act.push(act);
} else {
this._act = [act];
}
return this;
};
/**
Set custom additional completion for current command.
@param {Function} completion generation function,
invoked in the context of command instance.
Accepts parameters:
- {Object} opts completion options
It can return promise or any other value treated as result.
@returns {COA.Cmd} this instance (for chainability)
*/
Cmd.prototype.comp = function(_comp) {
this._comp = _comp;
return this;
};
/**
Apply function with arguments in context of command instance.
@param {Function} fn
@param {Array} args
@returns {COA.Cmd} this instance (for chainability)
*/
Cmd.prototype.apply = function() {
var args, fn;
fn = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
fn.apply(this, args);
return this;
};
/**
Make command "helpful", i.e. add -h --help flags for print usage.
@returns {COA.Cmd} this instance (for chainability)
*/
Cmd.prototype.helpful = function() {
return this.opt().name('help').title('Help').short('h').long('help').flag().only().act(function() {
return this.usage();
}).end();
};
/**
Adds shell completion to command, adds "completion" subcommand,
that makes all the magic.
Must be called only on root command.
@returns {COA.Cmd} this instance (for chainability)
*/
Cmd.prototype.completable = function() {
return this.cmd().name('completion').apply(require('./completion')).end();
};
/**
Allow command to be extendable by external node.js modules.
@param {String} [pattern] Pattern of node.js module to find subcommands at.
@returns {COA.Cmd} this instance (for chainability)
*/
Cmd.prototype.extendable = function(pattern) {
this._ext = pattern || true;
return this;
};
Cmd.prototype._exit = function(msg, code) {
return process.once('exit', function() {
if (msg) {
console.error(msg);
}
return process.exit(code || 0);
});
};
/**
Build full usage text for current command instance.
@returns {String} usage text
*/
Cmd.prototype.usage = function() {
var res;
res = [];
if (this._title) {
res.push(this._fullTitle());
}
res.push('', 'Usage:');
if (this._cmds.length) {
res.push(['', '', Color('lred', this._fullName()), Color('lblue', 'COMMAND'), Color('lgreen', '[OPTIONS]'), Color('lpurple', '[ARGS]')].join(' '));
}
if (this._opts.length + this._args.length) {
res.push(['', '', Color('lred', this._fullName()), Color('lgreen', '[OPTIONS]'), Color('lpurple', '[ARGS]')].join(' '));
}
res.push(this._usages(this._cmds, 'Commands'), this._usages(this._opts, 'Options'), this._usages(this._args, 'Arguments'));
return res.join('\n');
};
Cmd.prototype._usage = function() {
return Color('lblue', this._name) + ' : ' + this._title;
};
Cmd.prototype._usages = function(os, title) {
var o, res, _i, _len;
if (!os.length) {
return;
}
res = ['', title + ':'];
for (_i = 0, _len = os.length; _i < _len; _i++) {
o = os[_i];
res.push(' ' + o._usage());
}
return res.join('\n');
};
Cmd.prototype._fullTitle = function() {
return (this._cmd === this ? '' : this._cmd._fullTitle() + '\n') + this._title;
};
Cmd.prototype._fullName = function() {
return (this._cmd === this ? '' : this._cmd._fullName() + ' ') + PATH.basename(this._name);
};
Cmd.prototype._ejectOpt = function(opts, opt) {
var pos;
if ((pos = opts.indexOf(opt)) >= 0) {
if (opts[pos]._arr) {
return opts[pos];
} else {
return opts.splice(pos, 1)[0];
}
}
};
Cmd.prototype._checkRequired = function(opts, args) {
var all, i;
if (!(this._opts.filter(function(o) {
return o._only && o._name in opts;
})).length) {
all = this._opts.concat(this._args);
while (i = all.shift()) {
if (i._req && i._checkParsed(opts, args)) {
return this.reject(i._requiredText());
}
}
}
};
Cmd.prototype._parseCmd = function(argv, unparsed) {
var c, cmd, cmdDesc, e, i, optSeen, pkg;
if (unparsed == null) {
unparsed = [];
}
argv = argv.concat();
optSeen = false;
while (i = argv.shift()) {
if (!i.indexOf('-')) {
optSeen = true;
}
if (!optSeen && /^\w[\w-_]*$/.test(i)) {
cmd = this._cmdsByName[i];
if (!cmd && this._ext) {
if (typeof this._ext === 'string') {
if (~this._ext.indexOf('%s')) {
pkg = UTIL.format(this._ext, i);
} else {
pkg = this._ext + i;
}
} else if (this._ext === true) {
pkg = i;
c = this;
while (true) {
pkg = c._name + '-' + pkg;
if (c._cmd === c) {
break;
}
c = c._cmd;
}
}
try {
cmdDesc = require(pkg);
} catch (_error) {
e = _error;
}
if (cmdDesc) {
if (typeof cmdDesc === 'function') {
this.cmd().name(i).apply(cmdDesc).end();
} else if (typeof cmdDesc === 'object') {
this.cmd(cmdDesc);
cmdDesc.name(i);
} else {
throw new Error('Error: Unsupported command declaration type, ' + 'should be function or COA.Cmd() object');
}
cmd = this._cmdsByName[i];
}
}
if (cmd) {
return cmd._parseCmd(argv, unparsed);
}
}
unparsed.push(i);
}
return {
cmd: this,
argv: unparsed
};
};
Cmd.prototype._parseOptsAndArgs = function(argv) {
var a, arg, args, i, m, nonParsedArgs, nonParsedOpts, opt, opts, res;
opts = {};
args = {};
nonParsedOpts = this._opts.concat();
nonParsedArgs = this._args.concat();
while (i = argv.shift()) {
if (i !== '--' && !i.indexOf('-')) {
if (m = i.match(/^(--\w[\w-_]*)=(.*)$/)) {
i = m[1];
if (!this._optsByKey[i]._flag) {
argv.unshift(m[2]);
}
}
if (opt = this._ejectOpt(nonParsedOpts, this._optsByKey[i])) {
if (Q.isRejected(res = opt._parse(argv, opts))) {
return res;
}
} else {
return this.reject("Unknown option: " + i);
}
} else {
if (i === '--') {
i = argv.splice(0);
}
i = Array.isArray(i) ? i : [i];
while (a = i.shift()) {
if (arg = nonParsedArgs.shift()) {
if (arg._arr) {
nonParsedArgs.unshift(arg);
}
if (Q.isRejected(res = arg._parse(a, args))) {
return res;
}
} else {
return this.reject("Unknown argument: " + a);
}
}
}
}
return {
opts: this._setDefaults(opts, nonParsedOpts),
args: this._setDefaults(args, nonParsedArgs)
};
};
Cmd.prototype._setDefaults = function(params, desc) {
var i, _i, _len;
for (_i = 0, _len = desc.length; _i < _len; _i++) {
i = desc[_i];
if (!(i._name in params) && '_def' in i) {
i._saveVal(params, i._def);
}
}
return params;
};
Cmd.prototype._processParams = function(params, desc) {
var i, n, notExists, res, v, vals, _i, _j, _len, _len1;
notExists = [];
for (_i = 0, _len = desc.length; _i < _len; _i++) {
i = desc[_i];
n = i._name;
if (!(n in params)) {
notExists.push(i);
continue;
}
vals = params[n];
delete params[n];
if (!Array.isArray(vals)) {
vals = [vals];
}
for (_j = 0, _len1 = vals.length; _j < _len1; _j++) {
v = vals[_j];
if (Q.isRejected(res = i._saveVal(params, v))) {
return res;
}
}
}
return this._setDefaults(params, notExists);
};
Cmd.prototype._parseArr = function(argv) {
return Q.when(this._parseCmd(argv), function(p) {
return Q.when(p.cmd._parseOptsAndArgs(p.argv), function(r) {
return {
cmd: p.cmd,
opts: r.opts,
args: r.args
};
});
});
};
Cmd.prototype._do = function(input) {
var _this = this;
return Q.when(input, function(input) {
var cmd;
cmd = input.cmd;
return [_this._checkRequired].concat(cmd._act || []).reduce(function(res, act) {
return Q.when(res, function(res) {
return act.call(cmd, input.opts, input.args, res);
});
}, void 0);
});
};
/**
Parse arguments from simple format like NodeJS process.argv
and run ahead current program, i.e. call process.exit when all actions done.
@param {Array} argv
@returns {COA.Cmd} this instance (for chainability)
*/
Cmd.prototype.run = function(argv) {
var cb,
_this = this;
if (argv == null) {
argv = process.argv.slice(2);
}
cb = function(code) {
return function(res) {
var _ref, _ref1;
if (res) {
return _this._exit((_ref = res.stack) != null ? _ref : res.toString(), (_ref1 = res.exitCode) != null ? _ref1 : code);
} else {
return _this._exit();
}
};
};
Q.when(this["do"](argv), cb(0), cb(1)).done();
return this;
};
/**
Convenient function to run command from tests.
@param {Array} argv
@returns {Q.Promise}
*/
Cmd.prototype["do"] = function(argv) {
return this._do(this._parseArr(argv || []));
};
/**
Invoke specified (or current) command using provided
options and arguments.
@param {String|Array} cmds subcommand to invoke (optional)
@param {Object} opts command options (optional)
@param {Object} args command arguments (optional)
@returns {Q.Promise}
*/
Cmd.prototype.invoke = function(cmds, opts, args) {
var _this = this;
if (cmds == null) {
cmds = [];
}
if (opts == null) {
opts = {};
}
if (args == null) {
args = {};
}
if (typeof cmds === 'string') {
cmds = cmds.split(' ');
}
if (arguments.length < 3) {
if (!Array.isArray(cmds)) {
args = opts;
opts = cmds;
cmds = [];
}
}
return Q.when(this._parseCmd(cmds), function(p) {
if (p.argv.length) {
return _this.reject("Unknown command: " + cmds.join(' '));
}
return Q.all([_this._processParams(opts, _this._opts), _this._processParams(args, _this._args)]).spread(function(opts, args) {
return _this._do({
cmd: p.cmd,
opts: opts,
args: args
}).fail(function(res) {
if (res && res.exitCode === 0) {
return res.toString();
} else {
return _this.reject(res);
}
});
});
});
};
/**
Return reject of actions results promise with error code.
Use in .act() for return with error.
@param {Object} reject reason
You can customize toString() method and exitCode property
of reason object.
@returns {Q.promise} rejected promise
*/
Cmd.prototype.reject = function(reason) {
return Q.reject(reason);
};
/**
Finish chain for current subcommand and return parent command instance.
@returns {COA.Cmd} parent command
*/
Cmd.prototype.end = function() {
return this._cmd;
};
return Cmd;
})();

View File

@ -0,0 +1,25 @@
// Generated by CoffeeScript 1.6.3
var colors;
colors = {
black: '30',
dgray: '1;30',
red: '31',
lred: '1;31',
green: '32',
lgreen: '1;32',
brown: '33',
yellow: '1;33',
blue: '34',
lblue: '1;34',
purple: '35',
lpurple: '1;35',
cyan: '36',
lcyan: '1;36',
lgray: '37',
white: '1;37'
};
exports.Color = function(c, str) {
return ['\x1B[', colors[c], 'm', str, '\x1B[m'].join('');
};

View File

@ -0,0 +1,134 @@
// Generated by CoffeeScript 1.6.3
/**
Most of the code adopted from the npm package shell completion code.
See https://github.com/isaacs/npm/blob/master/lib/completion.js
*/
var Q, complete, dumpScript, escape, getOpts, unescape;
Q = require('q');
escape = require('./shell').escape;
unescape = require('./shell').unescape;
module.exports = function() {
return this.title('Shell completion').helpful().arg().name('raw').title('Completion words').arr().end().act(function(opts, args) {
var argv, cmd, e, _ref;
if (process.platform === 'win32') {
e = new Error('shell completion not supported on windows');
e.code = 'ENOTSUP';
e.errno = require('constants').ENOTSUP;
return this.reject(e);
}
if ((process.env.COMP_CWORD == null) || (process.env.COMP_LINE == null) || (process.env.COMP_POINT == null)) {
return dumpScript(this._cmd._name);
}
console.error('COMP_LINE: %s', process.env.COMP_LINE);
console.error('COMP_CWORD: %s', process.env.COMP_CWORD);
console.error('COMP_POINT: %s', process.env.COMP_POINT);
console.error('args: %j', args.raw);
opts = getOpts(args.raw);
_ref = this._cmd._parseCmd(opts.partialWords), cmd = _ref.cmd, argv = _ref.argv;
return Q.when(complete(cmd, opts), function(compls) {
console.error('filtered: %j', compls);
return console.log(compls.map(escape).join('\n'));
});
});
};
dumpScript = function(name) {
var defer, fs, path;
fs = require('fs');
path = require('path');
defer = Q.defer();
fs.readFile(path.resolve(__dirname, 'completion.sh'), 'utf8', function(err, d) {
var onError;
if (err) {
return defer.reject(err);
}
d = d.replace(/{{cmd}}/g, path.basename(name)).replace(/^\#\!.*?\n/, '');
onError = function(err) {
if (err.errno === require('constants').EPIPE) {
process.stdout.removeListener('error', onError);
return defer.resolve();
} else {
return defer.reject(err);
}
};
process.stdout.on('error', onError);
return process.stdout.write(d, function() {
return defer.resolve();
});
});
return defer.promise;
};
getOpts = function(argv) {
var i, line, partialLine, partialWord, partialWords, point, w, word, words;
line = process.env.COMP_LINE;
w = +process.env.COMP_CWORD;
point = +process.env.COMP_POINT;
words = argv.map(unescape);
word = words[w];
partialLine = line.substr(0, point);
partialWords = words.slice(0, w);
partialWord = argv[w] || '';
i = partialWord.length;
while (partialWord.substr(0, i) !== partialLine.substr(-1 * i) && i > 0) {
i--;
}
partialWord = unescape(partialWord.substr(0, i));
if (partialWord) {
partialWords.push(partialWord);
}
return {
line: line,
w: w,
point: point,
words: words,
word: word,
partialLine: partialLine,
partialWords: partialWords,
partialWord: partialWord
};
};
complete = function(cmd, opts) {
var compls, m, o, opt, optPrefix, optWord;
compls = [];
if (opts.partialWord.indexOf('-')) {
compls = Object.keys(cmd._cmdsByName);
} else {
if (m = opts.partialWord.match(/^(--\w[\w-_]*)=(.*)$/)) {
optWord = m[1];
optPrefix = optWord + '=';
} else {
compls = Object.keys(cmd._optsByKey);
}
}
if (!(o = opts.partialWords[opts.w - 1]).indexOf('-')) {
optWord = o;
}
if (optWord && (opt = cmd._optsByKey[optWord])) {
if (!opt._flag && opt._comp) {
compls = Q.join(compls, Q.when(opt._comp(opts), function(c, o) {
return c.concat(o.map(function(v) {
return (optPrefix || '') + v;
}));
}));
}
}
if (cmd._comp) {
compls = Q.join(compls, Q.when(cmd._comp(opts)), function(c, o) {
return c.concat(o);
});
}
return Q.when(compls, function(compls) {
console.error('partialWord: %s', opts.partialWord);
console.error('compls: %j', compls);
return compls.filter(function(c) {
return c.indexOf(opts.partialWord) === 0;
});
});
};

View File

@ -0,0 +1,43 @@
#!/usr/bin/env bash
###-begin-{{cmd}}-completion-###
#
# {{cmd}} command completion script
#
# Installation: {{cmd}} completion >> ~/.bashrc (or ~/.zshrc)
# Or, maybe: {{cmd}} completion > /usr/local/etc/bash_completion.d/{{cmd}}
#
COMP_WORDBREAKS=${COMP_WORDBREAKS/=/}
COMP_WORDBREAKS=${COMP_WORDBREAKS/@/}
export COMP_WORDBREAKS
if complete &>/dev/null; then
_{{cmd}}_completion () {
local si="$IFS"
IFS=$'\n' COMPREPLY=($(COMP_CWORD="$COMP_CWORD" \
COMP_LINE="$COMP_LINE" \
COMP_POINT="$COMP_POINT" \
{{cmd}} completion -- "${COMP_WORDS[@]}" \
2>/dev/null)) || return $?
IFS="$si"
}
complete -F _{{cmd}}_completion {{cmd}}
elif compctl &>/dev/null; then
_{{cmd}}_completion () {
local cword line point words si
read -Ac words
read -cn cword
let cword-=1
read -l line
read -ln point
si="$IFS"
IFS=$'\n' reply=($(COMP_CWORD="$cword" \
COMP_LINE="$line" \
COMP_POINT="$point" \
{{cmd}} completion -- "${words[@]}" \
2>/dev/null)) || return $?
IFS="$si"
}
compctl -K _{{cmd}}_completion {{cmd}}
fi
###-end-{{cmd}}-completion-###

View File

@ -0,0 +1,10 @@
// Generated by CoffeeScript 1.6.3
exports.Cmd = require('./cmd').Cmd;
exports.Opt = require('./cmd').Opt;
exports.Arg = require('./cmd').Arg;
exports.shell = require('./shell');
exports.require = require;

View File

@ -0,0 +1,338 @@
// Generated by CoffeeScript 1.6.3
var Cmd, Color, Opt, Q, fs;
fs = require('fs');
Q = require('q');
Color = require('./color').Color;
Cmd = require('./cmd').Cmd;
/**
Option
Named entity. Options may have short and long keys for use from command line.
@namespace
@class Presents option
*/
exports.Opt = Opt = (function() {
/**
@constructs
@param {COA.Cmd} cmd parent command
*/
function Opt(_cmd) {
this._cmd = _cmd;
this._cmd._opts.push(this);
}
/**
Set a canonical option identifier to be used anywhere in the API.
@param {String} _name option name
@returns {COA.Opt} this instance (for chainability)
*/
Opt.prototype.name = function(_name) {
this._name = _name;
return this;
};
/**
Set a long description for option to be used anywhere in text messages.
@param {String} _title option title
@returns {COA.Opt} this instance (for chainability)
*/
Opt.prototype.title = Cmd.prototype.title;
/**
Set a short key for option to be used with one hyphen from command line.
@param {String} _short
@returns {COA.Opt} this instance (for chainability)
*/
Opt.prototype.short = function(_short) {
this._short = _short;
return this._cmd._optsByKey['-' + _short] = this;
};
/**
Set a short key for option to be used with double hyphens from command line.
@param {String} _long
@returns {COA.Opt} this instance (for chainability)
*/
Opt.prototype.long = function(_long) {
this._long = _long;
return this._cmd._optsByKey['--' + _long] = this;
};
/**
Make an option boolean, i.e. option without value.
@returns {COA.Opt} this instance (for chainability)
*/
Opt.prototype.flag = function() {
this._flag = true;
return this;
};
/**
Makes an option accepts multiple values.
Otherwise, the value will be used by the latter passed.
@returns {COA.Opt} this instance (for chainability)
*/
Opt.prototype.arr = function() {
this._arr = true;
return this;
};
/**
Makes an option required.
@returns {COA.Opt} this instance (for chainability)
*/
Opt.prototype.req = function() {
this._req = true;
return this;
};
/**
Makes an option to act as a command,
i.e. program will exit just after option action.
@returns {COA.Opt} this instance (for chainability)
*/
Opt.prototype.only = function() {
this._only = true;
return this;
};
/**
Set a validation (or value) function for option.
Value from command line passes through before becoming available from API.
Using for validation and convertion simple types to any values.
@param {Function} _val validating function,
invoked in the context of option instance
and has one parameter with value from command line
@returns {COA.Opt} this instance (for chainability)
*/
Opt.prototype.val = function(_val) {
this._val = _val;
return this;
};
/**
Set a default value for option.
Default value passed through validation function as ordinary value.
@param {Object} _def
@returns {COA.Opt} this instance (for chainability)
*/
Opt.prototype.def = function(_def) {
this._def = _def;
return this;
};
/**
Make option value inputting stream.
It's add useful validation and shortcut for STDIN.
@returns {COA.Opt} this instance (for chainability)
*/
Opt.prototype.input = function() {
process.stdin.pause();
return this.def(process.stdin).val(function(v) {
var s;
if (typeof v === 'string') {
if (v === '-') {
return process.stdin;
} else {
s = fs.createReadStream(v, {
encoding: 'utf8'
});
s.pause();
return s;
}
} else {
return v;
}
});
};
/**
Make option value outputing stream.
It's add useful validation and shortcut for STDOUT.
@returns {COA.Opt} this instance (for chainability)
*/
Opt.prototype.output = function() {
return this.def(process.stdout).val(function(v) {
if (typeof v === 'string') {
if (v === '-') {
return process.stdout;
} else {
return fs.createWriteStream(v, {
encoding: 'utf8'
});
}
} else {
return v;
}
});
};
/**
Add action for current option command.
This action is performed if the current option
is present in parsed options (with any value).
@param {Function} act action function,
invoked in the context of command instance
and has the parameters:
- {Object} opts parsed options
- {Array} args parsed arguments
- {Object} res actions result accumulator
It can return rejected promise by Cmd.reject (in case of error)
or any other value treated as result.
@returns {COA.Opt} this instance (for chainability)
*/
Opt.prototype.act = function(act) {
var name, opt;
opt = this;
name = this._name;
this._cmd.act(function(opts) {
var res,
_this = this;
if (name in opts) {
res = act.apply(this, arguments);
if (opt._only) {
return Q.when(res, function(res) {
return _this.reject({
toString: function() {
return res.toString();
},
exitCode: 0
});
});
} else {
return res;
}
}
});
return this;
};
/**
Set custom additional completion for current option.
@param {Function} completion generation function,
invoked in the context of option instance.
Accepts parameters:
- {Object} opts completion options
It can return promise or any other value treated as result.
@returns {COA.Opt} this instance (for chainability)
*/
Opt.prototype.comp = Cmd.prototype.comp;
Opt.prototype._saveVal = function(opts, val) {
var _name;
if (this._val) {
val = this._val(val);
}
if (this._arr) {
(opts[_name = this._name] || (opts[_name] = [])).push(val);
} else {
opts[this._name] = val;
}
return val;
};
Opt.prototype._parse = function(argv, opts) {
return this._saveVal(opts, this._flag ? true : argv.shift());
};
Opt.prototype._checkParsed = function(opts, args) {
return !opts.hasOwnProperty(this._name);
};
Opt.prototype._usage = function() {
var nameStr, res;
res = [];
nameStr = this._name.toUpperCase();
if (this._short) {
res.push('-', Color('lgreen', this._short));
if (!this._flag) {
res.push(' ' + nameStr);
}
res.push(', ');
}
if (this._long) {
res.push('--', Color('green', this._long));
if (!this._flag) {
res.push('=' + nameStr);
}
}
res.push(' : ', this._title);
if (this._req) {
res.push(' ', Color('lred', '(required)'));
}
return res.join('');
};
Opt.prototype._requiredText = function() {
return 'Missing required option:\n ' + this._usage();
};
/**
Return rejected promise with error code.
Use in .val() for return with error.
@param {Object} reject reason
You can customize toString() method and exitCode property
of reason object.
@returns {Q.promise} rejected promise
*/
Opt.prototype.reject = Cmd.prototype.reject;
/**
Finish chain for current option and return parent command instance.
@returns {COA.Cmd} parent command
*/
Opt.prototype.end = Cmd.prototype.end;
/**
Apply function with arguments in context of option instance.
@param {Function} fn
@param {Array} args
@returns {COA.Opt} this instance (for chainability)
*/
Opt.prototype.apply = Cmd.prototype.apply;
return Opt;
})();

View File

@ -0,0 +1,14 @@
// Generated by CoffeeScript 1.6.3
exports.unescape = function(w) {
w = w.charAt(0) === '"' ? w.replace(/^"|([^\\])"$/g, '$1') : w.replace(/\\ /g, ' ');
return w.replace(/\\("|'|\$|`|\\)/g, '$1');
};
exports.escape = function(w) {
w = w.replace(/(["'$`\\])/g, '\\$1');
if (w.match(/\s+/)) {
return '"' + w + '"';
} else {
return w;
}
};

View File

@ -0,0 +1,786 @@
## 1.4.1
- Address an issue that prevented Q from being used as a `<script>` for
Firefox add-ons. Q can now be used in any environment that provides `window`
or `self` globals, favoring `window` since add-ons have an an immutable
`self` that is distinct from `window`.
## 1.4.0
- Add `noConflict` support for use in `<script>` (@jahnjw).
## 1.3.0
- Add tracking for unhandled and handled rejections in Node.js (@benjamingr).
## 1.2.1
- Fix Node.js environment detection for modern Browserify (@kahnjw).
## 1.2.0
- Added Q.any(promisesArray) method (@vergara).
Returns a promise fulfilled with the value of the first resolved promise in
promisesArray. If all promises in promisesArray are rejected, it returns
a rejected promise.
## 1.1.2
- Removed extraneous files from the npm package by using the "files"
whitelist in package.json instead of the .npmignore blacklist.
(@anton-rudeshko)
## 1.1.1
- Fix a pair of regressions in bootstrapping, one which precluded
WebWorker support, and another that precluded support in
``<script>`` usage outright. #607
## 1.1.0
- Adds support for enabling long stack traces in node.js by setting
environment variable `Q_DEBUG=1`.
- Introduces the `tap` method to promises, which will see a value
pass through without alteration.
- Use instanceof to recognize own promise instances as opposed to
thenables.
- Construct timeout errors with `code === ETIMEDOUT` (Kornel Lesiński)
- More descriminant CommonJS module environment detection.
- Dropped continuous integration for Node.js 0.6 and 0.8 because of
changes to npm that preclude the use of new `^` version predicate
operator in any transitive dependency.
- Users can now override `Q.nextTick`.
## 1.0.1
- Adds support for `Q.Promise`, which implements common usage of the
ES6 `Promise` constructor and its methods. `Promise` does not have
a valid promise constructor and a proper implementation awaits
version 2 of Q.
- Removes the console stopgap for a promise inspector. This no longer
works with any degree of reliability.
- Fixes support for content security policies that forbid eval. Now
using the `StopIteration` global to distinguish SpiderMonkey
generators from ES6 generators, assuming that they will never
coexist.
## 1.0.0
:cake: This is all but a re-release of version 0.9, which has settled
into a gentle maintenance mode and rightly deserves an official 1.0.
An ambitious 2.0 release is already around the corner, but 0.9/1.0
have been distributed far and wide and demand long term support.
- Q will now attempt to post a debug message in browsers regardless
of whether window.Touch is defined. Chrome at least now has this
property regardless of whether touch is supported by the underlying
hardware.
- Remove deprecation warning from `promise.valueOf`. The function is
called by the browser in various ways so there is no way to
distinguish usage that should be migrated from usage that cannot be
altered.
## 0.9.7
- :warning: `q.min.js` is no longer checked-in. It is however still
created by Grunt and NPM.
- Fixes a bug that inhibited `Q.async` with implementations of the new
ES6 generators.
- Fixes a bug with `nextTick` affecting Safari 6.0.5 the first time a
page loads when an `iframe` is involved.
- Introduces `passByCopy`, `join`, and `race`.
- Shows stack traces or error messages on the console, instead of
`Error` objects.
- Elimintates wrapper methods for improved performance.
- `Q.all` now propagates progress notifications of the form you might
expect of ES6 iterations, `{value, index}` where the `value` is the
progress notification from the promise at `index`.
## 0.9.6
- Fixes a bug in recognizing the difference between compatible Q
promises, and Q promises from before the implementation of "inspect".
The latter are now coerced.
- Fixes an infinite asynchronous coercion cycle introduced by former
solution, in two independently sufficient ways. 1.) All promises
returned by makePromise now implement "inspect", albeit a default
that reports that the promise has an "unknown" state. 2.) The
implementation of "then/when" is now in "then" instead of "when", so
that the responsibility to "coerce" the given promise rests solely in
the "when" method and the "then" method may assume that "this" is a
promise of the right type.
- Refactors `nextTick` to use an unrolled microtask within Q regardless
of how new ticks a requested. #316 @rkatic
## 0.9.5
- Introduces `inspect` for getting the state of a promise as
`{state: "fulfilled" | "rejected" | "pending", value | reason}`.
- Introduces `allSettled` which produces an array of promises states
for the input promises once they have all "settled". This is in
accordance with a discussion on Promises/A+ that "settled" refers to
a promise that is "fulfilled" or "rejected". "resolved" refers to a
deferred promise that has been "resolved" to another promise,
"sealing its fate" to the fate of the successor promise.
- Long stack traces are now off by default. Set `Q.longStackSupport`
to true to enable long stack traces.
- Long stack traces can now follow the entire asynchronous history of a
promise, not just a single jump.
- Introduces `spawn` for an immediately invoked asychronous generator.
@jlongster
- Support for *experimental* synonyms `mapply`, `mcall`, `nmapply`,
`nmcall` for method invocation.
## 0.9.4
- `isPromise` and `isPromiseAlike` now always returns a boolean
(even for falsy values). #284 @lfac-pt
- Support for ES6 Generators in `async` #288 @andywingo
- Clear duplicate promise rejections from dispatch methods #238 @SLaks
- Unhandled rejection API #296 @domenic
`stopUnhandledRejectionTracking`, `getUnhandledReasons`,
`resetUnhandledRejections`.
## 0.9.3
- Add the ability to give `Q.timeout`'s errors a custom error message. #270
@jgrenon
- Fix Q's call-stack busting behavior in Node.js 0.10, by switching from
`process.nextTick` to `setImmediate`. #254 #259
- Fix Q's behavior when used with the Mocha test runner in the browser, since
Mocha introduces a fake `process` global without a `nextTick` property. #267
- Fix some, but not all, cases wherein Q would give false positives in its
unhandled rejection detection (#252). A fix for other cases (#238) is
hopefully coming soon.
- Made `Q.promise` throw early if given a non-function.
## 0.9.2
- Pass through progress notifications when using `timeout`. #229 @omares
- Pass through progress notifications when using `delay`.
- Fix `nbind` to actually bind the `thisArg`. #232 @davidpadbury
## 0.9.1
- Made the AMD detection compatible with the RequireJS optimizer's `namespace`
option. #225 @terinjokes
- Fix side effects from `valueOf`, and thus from `isFulfilled`, `isRejected`,
and `isPending`. #226 @benjamn
## 0.9.0
This release removes many layers of deprecated methods and brings Q closer to
alignment with Mark Millers TC39 [strawman][] for concurrency. At the same
time, it fixes many bugs and adds a few features around error handling. Finally,
it comes with an updated and comprehensive [API Reference][].
[strawman]: http://wiki.ecmascript.org/doku.php?id=strawman:concurrency
[API Reference]: https://github.com/kriskowal/q/wiki/API-Reference
### API Cleanup
The following deprecated or undocumented methods have been removed.
Their replacements are listed here:
<table>
<thead>
<tr>
<th>0.8.x method</th>
<th>0.9 replacement</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Q.ref</code></td>
<td><code>Q</code></td>
</tr>
<tr>
<td><code>call</code>, <code>apply</code>, <code>bind</code> (*)</td>
<td><code>fcall</code>/<code>invoke</code>, <code>fapply</code>/<code>post</code>, <code>fbind</code></td>
</tr>
<tr>
<td><code>ncall</code>, <code>napply</code> (*)</td>
<td><code>nfcall</code>/<code>ninvoke</code>, <code>nfapply</code>/<code>npost</code></td>
</tr>
<tr>
<td><code>end</code></td>
<td><code>done</code></td>
</tr>
<tr>
<td><code>put</code></td>
<td><code>set</code></td>
</tr>
<tr>
<td><code>node</code></td>
<td><code>nbind</code></td>
</tr>
<tr>
<td><code>nend</code></td>
<td><code>nodeify</code></td>
</tr>
<tr>
<td><code>isResolved</code></td>
<td><code>isPending</code></td>
</tr>
<tr>
<td><code>deferred.node</code></td>
<td><code>deferred.makeNodeResolver</code></td>
</tr>
<tr>
<td><code>Method</code>, <code>sender</code></td>
<td><code>dispatcher</code></td>
</tr>
<tr>
<td><code>send</code></td>
<td><code>dispatch</code></td>
</tr>
<tr>
<td><code>view</code>, <code>viewInfo</code></td>
<td>(none)</td>
</tr>
</tbody>
</table>
(*) Use of ``thisp`` is discouraged. For calling methods, use ``post`` or
``invoke``.
### Alignment with the Concurrency Strawman
- Q now exports a `Q(value)` function, an alias for `resolve`.
`Q.call`, `Q.apply`, and `Q.bind` were removed to make room for the
same methods on the function prototype.
- `invoke` has been aliased to `send` in all its forms.
- `post` with no method name acts like `fapply`.
### Error Handling
- Long stack traces can be turned off by setting `Q.stackJumpLimit` to zero.
In the future, this property will be used to fine tune how many stack jumps
are retained in long stack traces; for now, anything nonzero is treated as
one (since Q only tracks one stack jump at the moment, see #144). #168
- In Node.js, if there are unhandled rejections when the process exits, they
are output to the console. #115
### Other
- `delete` and `set` (née `put`) no longer have a fulfillment value.
- Q promises are no longer frozen, which
[helps with performance](http://code.google.com/p/v8/issues/detail?id=1858).
- `thenReject` is now included, as a counterpart to `thenResolve`.
- The included browser `nextTick` shim is now faster. #195 @rkatic.
### Bug Fixes
- Q now works in Internet Explorer 10. #186 @ForbesLindesay
- `fbind` no longer hard-binds the returned function's `this` to `undefined`.
#202
- `Q.reject` no longer leaks memory. #148
- `npost` with no arguments now works. #207
- `allResolved` now works with non-Q promises ("thenables"). #179
- `keys` behavior is now correct even in browsers without native
`Object.keys`. #192 @rkatic
- `isRejected` and the `exception` property now work correctly if the
rejection reason is falsy. #198
### Internals and Advanced
- The internal interface for a promise now uses
`dispatchPromise(resolve, op, operands)` instead of `sendPromise(op,
resolve, ...operands)`, which reduces the cases where Q needs to do
argument slicing.
- The internal protocol uses different operands. "put" is now "set".
"del" is now "delete". "view" and "viewInfo" have been removed.
- `Q.fulfill` has been added. It is distinct from `Q.resolve` in that
it does not pass promises through, nor coerces promises from other
systems. The promise becomes the fulfillment value. This is only
recommended for use when trying to fulfill a promise with an object that has
a `then` function that is at the same time not a promise.
## 0.8.12
- Treat foreign promises as unresolved in `Q.isFulfilled`; this lets `Q.all`
work on arrays containing foreign promises. #154
- Fix minor incompliances with the [Promises/A+ spec][] and [test suite][]. #157
#158
[Promises/A+ spec]: http://promises-aplus.github.com/promises-spec/
[test suite]: https://github.com/promises-aplus/promises-tests
## 0.8.11
- Added ``nfcall``, ``nfapply``, and ``nfbind`` as ``thisp``-less versions of
``ncall``, ``napply``, and ``nbind``. The latter are now deprecated. #142
- Long stack traces no longer cause linearly-growing memory usage when chaining
promises together. #111
- Inspecting ``error.stack`` in a rejection handler will now give a long stack
trace. #103
- Fixed ``Q.timeout`` to clear its timeout handle when the promise is rejected;
previously, it kept the event loop alive until the timeout period expired.
#145 @dfilatov
- Added `q/queue` module, which exports an infinite promise queue
constructor.
## 0.8.10
- Added ``done`` as a replacement for ``end``, taking the usual fulfillment,
rejection, and progress handlers. It's essentially equivalent to
``then(f, r, p).end()``.
- Added ``Q.onerror``, a settable error trap that you can use to get full stack
traces for uncaught errors. #94
- Added ``thenResolve`` as a shortcut for returning a constant value once a
promise is fulfilled. #108 @ForbesLindesay
- Various tweaks to progress notification, including propagation and
transformation of progress values and only forwarding a single progress
object.
- Renamed ``nend`` to ``nodeify``. It no longer returns an always-fulfilled
promise when a Node callback is passed.
- ``deferred.resolve`` and ``deferred.reject`` no longer (sometimes) return
``deferred.promise``.
- Fixed stack traces getting mangled if they hit ``end`` twice. #116 #121 @ef4
- Fixed ``ninvoke`` and ``npost`` to work on promises for objects with Node
methods. #134
- Fixed accidental coercion of objects with nontrivial ``valueOf`` methods,
like ``Date``s, by the promise's ``valueOf`` method. #135
- Fixed ``spread`` not calling the passed rejection handler if given a rejected
promise.
## 0.8.9
- Added ``nend``
- Added preliminary progress notification support, via
``promise.then(onFulfilled, onRejected, onProgress)``,
``promise.progress(onProgress)``, and ``deferred.notify(...progressData)``.
- Made ``put`` and ``del`` return the object acted upon for easier chaining.
#84
- Fixed coercion cycles with cooperating promises. #106
## 0.8.7
- Support [Montage Require](http://github.com/kriskowal/mr)
## 0.8.6
- Fixed ``npost`` and ``ninvoke`` to pass the correct ``thisp``. #74
- Fixed various cases involving unorthodox rejection reasons. #73 #90
@ef4
- Fixed double-resolving of misbehaved custom promises. #75
- Sped up ``Q.all`` for arrays contain already-resolved promises or scalar
values. @ForbesLindesay
- Made stack trace filtering work when concatenating assets. #93 @ef4
- Added warnings for deprecated methods. @ForbesLindesay
- Added ``.npmignore`` file so that dependent packages get a slimmer
``node_modules`` directory.
## 0.8.5
- Added preliminary support for long traces (@domenic)
- Added ``fapply``, ``fcall``, ``fbind`` for non-thisp
promised function calls.
- Added ``return`` for async generators, where generators
are implemented.
- Rejected promises now have an "exception" property. If an object
isRejected(object), then object.valueOf().exception will
be the wrapped error.
- Added Jasmine specifications
- Support Internet Explorers 79 (with multiple bug fixes @domenic)
- Support Firefox 12
- Support Safari 5.1.5
- Support Chrome 18
## 0.8.4
- WARNING: ``promise.timeout`` is now rejected with an ``Error`` object
and the message now includes the duration of the timeout in
miliseconds. This doesn't constitute (in my opinion) a
backward-incompatibility since it is a change of an undocumented and
unspecified public behavior, but if you happened to depend on the
exception being a string, you will need to revise your code.
- Added ``deferred.makeNodeResolver()`` to replace the more cryptic
``deferred.node()`` method.
- Added experimental ``Q.promise(maker(resolve, reject))`` to make a
promise inside a callback, such that thrown exceptions in the
callback are converted and the resolver and rejecter are arguments.
This is a shorthand for making a deferred directly and inspired by
@gozalas stream constructor pattern and the Microsoft Windows Metro
Promise constructor interface.
- Added experimental ``Q.begin()`` that is intended to kick off chains
of ``.then`` so that each of these can be reordered without having to
edit the new and former first step.
## 0.8.3
- Added ``isFulfilled``, ``isRejected``, and ``isResolved``
to the promise prototype.
- Added ``allResolved`` for waiting for every promise to either be
fulfilled or rejected, without propagating an error. @utvara #53
- Added ``Q.bind`` as a method to transform functions that
return and throw into promise-returning functions. See
[an example](https://gist.github.com/1782808). @domenic
- Renamed ``node`` export to ``nbind``, and added ``napply`` to
complete the set. ``node`` remains as deprecated. @domenic #58
- Renamed ``Method`` export to ``sender``. ``Method``
remains as deprecated and will be removed in the next
major version since I expect it has very little usage.
- Added browser console message indicating a live list of
unhandled errors.
- Added support for ``msSetImmediate`` (IE10) or ``setImmediate``
(available via [polyfill](https://github.com/NobleJS/setImmediate))
as a browser-side ``nextTick`` implementation. #44 #50 #59
- Stopped using the event-queue dependency, which was in place for
Narwhal support: now directly using ``process.nextTick``.
- WARNING: EXPERIMENTAL: added ``finally`` alias for ``fin``, ``catch``
alias for ``fail``, ``try`` alias for ``call``, and ``delete`` alias
for ``del``. These properties are enquoted in the library for
cross-browser compatibility, but may be used as property names in
modern engines.
## 0.8.2
- Deprecated ``ref`` in favor of ``resolve`` as recommended by
@domenic.
- Update event-queue dependency.
## 0.8.1
- Fixed Opera bug. #35 @cadorn
- Fixed ``Q.all([])`` #32 @domenic
## 0.8.0
- WARNING: ``enqueue`` removed. Use ``nextTick`` instead.
This is more consistent with NodeJS and (subjectively)
more explicit and intuitive.
- WARNING: ``def`` removed. Use ``master`` instead. The
term ``def`` was too confusing to new users.
- WARNING: ``spy`` removed in favor of ``fin``.
- WARNING: ``wait`` removed. Do ``all(args).get(0)`` instead.
- WARNING: ``join`` removed. Do ``all(args).spread(callback)`` instead.
- WARNING: Removed the ``Q`` function module.exports alias
for ``Q.ref``. It conflicts with ``Q.apply`` in weird
ways, making it uncallable.
- Revised ``delay`` so that it accepts both ``(value,
timeout)`` and ``(timeout)`` variations based on
arguments length.
- Added ``ref().spread(cb(...args))``, a variant of
``then`` that spreads an array across multiple arguments.
Useful with ``all()``.
- Added ``defer().node()`` Node callback generator. The
callback accepts ``(error, value)`` or ``(error,
...values)``. For multiple value arguments, the
fulfillment value is an array, useful in conjunction with
``spread``.
- Added ``node`` and ``ncall``, both with the signature
``(fun, thisp_opt, ...args)``. The former is a decorator
and the latter calls immediately. ``node`` optional
binds and partially applies. ``ncall`` can bind and pass
arguments.
## 0.7.2
- Fixed thenable promise assimilation.
## 0.7.1
- Stopped shimming ``Array.prototype.reduce``. The
enumerable property has bad side-effects. Libraries that
depend on this (for example, QQ) will need to be revised.
## 0.7.0 - BACKWARD INCOMPATIBILITY
- WARNING: Removed ``report`` and ``asap``
- WARNING: The ``callback`` argument of the ``fin``
function no longer receives any arguments. Thus, it can
be used to call functions that should not receive
arguments on resolution. Use ``when``, ``then``, or
``fail`` if you need a value.
- IMPORTANT: Fixed a bug in the use of ``MessageChannel``
for ``nextTick``.
- Renamed ``enqueue`` to ``nextTick``.
- Added experimental ``view`` and ``viewInfo`` for creating
views of promises either when or before they're
fulfilled.
- Shims are now externally applied so subsequent scripts or
dependees can use them.
- Improved minification results.
- Improved readability.
## 0.6.0 - BACKWARD INCOMPATIBILITY
- WARNING: In practice, the implementation of ``spy`` and
the name ``fin`` were useful. I've removed the old
``fin`` implementation and renamed/aliased ``spy``.
- The "q" module now exports its ``ref`` function as a "Q"
constructor, with module systems that support exports
assignment including NodeJS, RequireJS, and when used as
a ``<script>`` tag. Notably, strictly compliant CommonJS
does not support this, but UncommonJS does.
- Added ``async`` decorator for generators that use yield
to "trampoline" promises. In engines that support
generators (SpiderMonkey), this will greatly reduce the
need for nested callbacks.
- Made ``when`` chainable.
- Made ``all`` chainable.
## 0.5.3
- Added ``all`` and refactored ``join`` and ``wait`` to use
it. All of these will now reject at the earliest
rejection.
## 0.5.2
- Minor improvement to ``spy``; now waits for resolution of
callback promise.
## 0.5.1
- Made most Q API methods chainable on promise objects, and
turned the previous promise-methods of ``join``,
``wait``, and ``report`` into Q API methods.
- Added ``apply`` and ``call`` to the Q API, and ``apply``
as a promise handler.
- Added ``fail``, ``fin``, and ``spy`` to Q and the promise
prototype for convenience when observing rejection,
fulfillment and rejection, or just observing without
affecting the resolution.
- Renamed ``def`` (although ``def`` remains shimmed until
the next major release) to ``master``.
- Switched to using ``MessageChannel`` for next tick task
enqueue in browsers that support it.
## 0.5.0 - MINOR BACKWARD INCOMPATIBILITY
- Exceptions are no longer reported when consumed.
- Removed ``error`` from the API. Since exceptions are
getting consumed, throwing them in an errback causes the
exception to silently disappear. Use ``end``.
- Added ``end`` as both an API method and a promise-chain
ending method. It causes propagated rejections to be
thrown, which allows Node to write stack traces and
emit ``uncaughtException`` events, and browsers to
likewise emit ``onerror`` and log to the console.
- Added ``join`` and ``wait`` as promise chain functions,
so you can wait for variadic promises, returning your own
promise back, or join variadic promises, resolving with a
callback that receives variadic fulfillment values.
## 0.4.4
- ``end`` no longer returns a promise. It is the end of the
promise chain.
- Stopped reporting thrown exceptions in ``when`` callbacks
and errbacks. These must be explicitly reported through
``.end()``, ``.then(null, Q.error)``, or some other
mechanism.
- Added ``report`` as an API method, which can be used as
an errback to report and propagate an error.
- Added ``report`` as a promise-chain method, so an error
can be reported if it passes such a gate.
## 0.4.3
- Fixed ``<script>`` support that regressed with 0.4.2
because of "use strict" in the module system
multi-plexer.
## 0.4.2
- Added support for RequireJS (jburke)
## 0.4.1
- Added an "end" method to the promise prototype,
as a shorthand for waiting for the promise to
be resolved gracefully, and failing to do so,
to dump an error message.
## 0.4.0 - BACKWARD INCOMPATIBLE*
- *Removed the utility modules. NPM and Node no longer
expose any module except the main module. These have
been moved and merged into the "qq" package.
- *In a non-CommonJS browser, q.js can be used as a script.
It now creates a Q global variable.
- Fixed thenable assimilation.
- Fixed some issues with asap, when it resolves to
undefined, or throws an exception.
## 0.3.0 - BACKWARD-INCOMPATIBLE
- The `post` method has been reverted to its original
signature, as provided in Tyler Close's `ref_send` API.
That is, `post` accepts two arguments, the second of
which is an arbitrary object, but usually invocation
arguments as an `Array`. To provide variadic arguments
to `post`, there is a new `invoke` function that posts
the variadic arguments to the value given in the first
argument.
- The `defined` method has been moved from `q` to `q/util`
since it gets no use in practice but is still
theoretically useful.
- The `Promise` constructor has been renamed to
`makePromise` to be consistent with the convention that
functions that do not require the `new` keyword to be
used as constructors have camelCase names.
- The `isResolved` function has been renamed to
`isFulfilled`. There is a new `isResolved` function that
indicates whether a value is not a promise or, if it is a
promise, whether it has been either fulfilled or
rejected. The code has been revised to reflect this
nuance in terminology.
## 0.2.10
- Added `join` to `"q/util"` for variadically joining
multiple promises.
## 0.2.9
- The future-compatible `invoke` method has been added,
to replace `post`, since `post` will become backward-
incompatible in the next major release.
- Exceptions thrown in the callbacks of a `when` call are
now emitted to Node's `"uncaughtException"` `process`
event in addition to being returned as a rejection reason.
## 0.2.8
- Exceptions thrown in the callbacks of a `when` call
are now consumed, warned, and transformed into
rejections of the promise returned by `when`.
## 0.2.7
- Fixed a minor bug in thenable assimilation, regressed
because of the change in the forwarding protocol.
- Fixed behavior of "q/util" `deep` method on dates and
other primitives. Github issue #11.
## 0.2.6
- Thenables (objects with a "then" method) are accepted
and provided, bringing this implementation of Q
into conformance with Promises/A, B, and D.
- Added `makePromise`, to replace the `Promise` function
eventually.
- Rejections are now also duck-typed. A rejection is a
promise with a valueOf method that returns a rejection
descriptor. A rejection descriptor has a
"promiseRejected" property equal to "true" and a
"reason" property corresponding to the rejection reason.
- Altered the `makePromise` API such that the `fallback`
method no longer receives a superfluous `resolved` method
after the `operator`. The fallback method is responsible
only for returning a resolution. This breaks an
undocumented API, so third-party API's depending on the
previous undocumented behavior may break.
## 0.2.5
- Changed promises into a duck-type such that multiple
instances of the Q module can exchange promise objects.
A promise is now defined as "an object that implements the
`promiseSend(op, resolved, ...)` method and `valueOf`".
- Exceptions in promises are now captured and returned
as rejections.
## 0.2.4
- Fixed bug in `ref` that prevented `del` messages from
being received (gozala)
- Fixed a conflict with FireFox 4; constructor property
is now read-only.
## 0.2.3
- Added `keys` message to promises and to the promise API.
## 0.2.2
- Added boilerplate to `q/queue` and `q/util`.
- Fixed missing dependency to `q/queue`.
## 0.2.1
- The `resolve` and `reject` methods of `defer` objects now
return the resolution promise for convenience.
- Added `q/util`, which provides `step`, `delay`, `shallow`,
`deep`, and three reduction orders.
- Added `q/queue` module for a promise `Queue`.
- Added `q-comm` to the list of compatible libraries.
- Deprecated `defined` from `q`, with intent to move it to
`q/util`.
## 0.2.0 - BACKWARD INCOMPATIBLE
- Changed post(ref, name, args) to variadic
post(ref, name, ...args). BACKWARD INCOMPATIBLE
- Added a def(value) method to annotate an object as being
necessarily a local value that cannot be serialized, such
that inter-process/worker/vat promise communication
libraries will send messages to it, but never send it
back.
- Added a send(value, op, ...args) method to the public API, for
forwarding messages to a value or promise in a future turn.
## 0.1.9
- Added isRejected() for testing whether a value is a rejected
promise. isResolved() retains the behavior of stating
that rejected promises are not resolved.
## 0.1.8
- Fixed isResolved(null) and isResolved(undefined) [issue #9]
- Fixed a problem with the Object.create shim
## 0.1.7
- shimmed ES5 Object.create in addition to Object.freeze
for compatibility on non-ES5 engines (gozala)
## 0.1.6
- Q.isResolved added
- promise.valueOf() now returns the value of resolved
and near values
- asap retried
- promises are frozen when possible
## 0.1.5
- fixed dependency list for Teleport (gozala)
- all unit tests now pass (gozala)
## 0.1.4
- added support for Teleport as an engine (gozala)
- simplified and updated methods for getting internal
print and enqueue functions universally (gozala)
## 0.1.3
- fixed erroneous link to the q module in package.json
## 0.1.2
- restructured for overlay style package compatibility
## 0.1.0
- removed asap because it was broken, probably down to the
philosophy.
## 0.0.3
- removed q-util
- fixed asap so it returns a value if completed
## 0.0.2
- added q-util
## 0.0.1
- initial version

View File

@ -0,0 +1,18 @@
Copyright 20092014 Kristopher Michael Kowal. All rights reserved.
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.

View File

@ -0,0 +1,881 @@
[![Build Status](https://secure.travis-ci.org/kriskowal/q.png?branch=master)](http://travis-ci.org/kriskowal/q)
<a href="http://promises-aplus.github.com/promises-spec">
<img src="http://kriskowal.github.io/q/q.png"
align="right" alt="Q logo" />
</a>
*This is Q version 1, from the `v1` branch in Git. This documentation applies to
the latest of both the version 1 and version 0.9 release trains. These releases
are stable. There will be no further releases of 0.9 after 0.9.7 which is nearly
equivalent to version 1.0.0. All further releases of `q@~1.0` will be backward
compatible. The version 2 release train introduces significant and
backward-incompatible changes and is experimental at this time.*
If a function cannot return a value or throw an exception without
blocking, it can return a promise instead. A promise is an object
that represents the return value or the thrown exception that the
function may eventually provide. A promise can also be used as a
proxy for a [remote object][Q-Connection] to overcome latency.
[Q-Connection]: https://github.com/kriskowal/q-connection
On the first pass, promises can mitigate the “[Pyramid of
Doom][POD]”: the situation where code marches to the right faster
than it marches forward.
[POD]: http://calculist.org/blog/2011/12/14/why-coroutines-wont-work-on-the-web/
```javascript
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// Do something with value4
});
});
});
});
```
With a promise library, you can flatten the pyramid.
```javascript
Q.fcall(promisedStep1)
.then(promisedStep2)
.then(promisedStep3)
.then(promisedStep4)
.then(function (value4) {
// Do something with value4
})
.catch(function (error) {
// Handle any error from all above steps
})
.done();
```
With this approach, you also get implicit error propagation, just like `try`,
`catch`, and `finally`. An error in `promisedStep1` will flow all the way to
the `catch` function, where its caught and handled. (Here `promisedStepN` is
a version of `stepN` that returns a promise.)
The callback approach is called an “inversion of control”.
A function that accepts a callback instead of a return value
is saying, “Dont call me, Ill call you.”. Promises
[un-invert][IOC] the inversion, cleanly separating the input
arguments from control flow arguments. This simplifies the
use and creation of APIs, particularly variadic,
rest and spread arguments.
[IOC]: http://www.slideshare.net/domenicdenicola/callbacks-promises-and-coroutines-oh-my-the-evolution-of-asynchronicity-in-javascript
## Getting Started
The Q module can be loaded as:
- A ``<script>`` tag (creating a ``Q`` global variable): ~2.5 KB minified and
gzipped.
- A Node.js and CommonJS module, available in [npm](https://npmjs.org/) as
the [q](https://npmjs.org/package/q) package
- An AMD module
- A [component](https://github.com/component/component) as ``microjs/q``
- Using [bower](http://bower.io/) as `q#1.0.1`
- Using [NuGet](http://nuget.org/) as [Q](https://nuget.org/packages/q)
Q can exchange promises with jQuery, Dojo, When.js, WinJS, and more.
## Resources
Our [wiki][] contains a number of useful resources, including:
- A method-by-method [Q API reference][reference].
- A growing [examples gallery][examples], showing how Q can be used to make
everything better. From XHR to database access to accessing the Flickr API,
Q is there for you.
- There are many libraries that produce and consume Q promises for everything
from file system/database access or RPC to templating. For a list of some of
the more popular ones, see [Libraries][].
- If you want materials that introduce the promise concept generally, and the
below tutorial isn't doing it for you, check out our collection of
[presentations, blog posts, and podcasts][resources].
- A guide for those [coming from jQuery's `$.Deferred`][jquery].
We'd also love to have you join the Q-Continuum [mailing list][].
[wiki]: https://github.com/kriskowal/q/wiki
[reference]: https://github.com/kriskowal/q/wiki/API-Reference
[examples]: https://github.com/kriskowal/q/wiki/Examples-Gallery
[Libraries]: https://github.com/kriskowal/q/wiki/Libraries
[resources]: https://github.com/kriskowal/q/wiki/General-Promise-Resources
[jquery]: https://github.com/kriskowal/q/wiki/Coming-from-jQuery
[mailing list]: https://groups.google.com/forum/#!forum/q-continuum
## Tutorial
Promises have a ``then`` method, which you can use to get the eventual
return value (fulfillment) or thrown exception (rejection).
```javascript
promiseMeSomething()
.then(function (value) {
}, function (reason) {
});
```
If ``promiseMeSomething`` returns a promise that gets fulfilled later
with a return value, the first function (the fulfillment handler) will be
called with the value. However, if the ``promiseMeSomething`` function
gets rejected later by a thrown exception, the second function (the
rejection handler) will be called with the exception.
Note that resolution of a promise is always asynchronous: that is, the
fulfillment or rejection handler will always be called in the next turn of the
event loop (i.e. `process.nextTick` in Node). This gives you a nice
guarantee when mentally tracing the flow of your code, namely that
``then`` will always return before either handler is executed.
In this tutorial, we begin with how to consume and work with promises. We'll
talk about how to create them, and thus create functions like
`promiseMeSomething` that return promises, [below](#the-beginning).
### Propagation
The ``then`` method returns a promise, which in this example, Im
assigning to ``outputPromise``.
```javascript
var outputPromise = getInputPromise()
.then(function (input) {
}, function (reason) {
});
```
The ``outputPromise`` variable becomes a new promise for the return
value of either handler. Since a function can only either return a
value or throw an exception, only one handler will ever be called and it
will be responsible for resolving ``outputPromise``.
- If you return a value in a handler, ``outputPromise`` will get
fulfilled.
- If you throw an exception in a handler, ``outputPromise`` will get
rejected.
- If you return a **promise** in a handler, ``outputPromise`` will
“become” that promise. Being able to become a new promise is useful
for managing delays, combining results, or recovering from errors.
If the ``getInputPromise()`` promise gets rejected and you omit the
rejection handler, the **error** will go to ``outputPromise``:
```javascript
var outputPromise = getInputPromise()
.then(function (value) {
});
```
If the input promise gets fulfilled and you omit the fulfillment handler, the
**value** will go to ``outputPromise``:
```javascript
var outputPromise = getInputPromise()
.then(null, function (error) {
});
```
Q promises provide a ``fail`` shorthand for ``then`` when you are only
interested in handling the error:
```javascript
var outputPromise = getInputPromise()
.fail(function (error) {
});
```
If you are writing JavaScript for modern engines only or using
CoffeeScript, you may use `catch` instead of `fail`.
Promises also have a ``fin`` function that is like a ``finally`` clause.
The final handler gets called, with no arguments, when the promise
returned by ``getInputPromise()`` either returns a value or throws an
error. The value returned or error thrown by ``getInputPromise()``
passes directly to ``outputPromise`` unless the final handler fails, and
may be delayed if the final handler returns a promise.
```javascript
var outputPromise = getInputPromise()
.fin(function () {
// close files, database connections, stop servers, conclude tests
});
```
- If the handler returns a value, the value is ignored
- If the handler throws an error, the error passes to ``outputPromise``
- If the handler returns a promise, ``outputPromise`` gets postponed. The
eventual value or error has the same effect as an immediate return
value or thrown error: a value would be ignored, an error would be
forwarded.
If you are writing JavaScript for modern engines only or using
CoffeeScript, you may use `finally` instead of `fin`.
### Chaining
There are two ways to chain promises. You can chain promises either
inside or outside handlers. The next two examples are equivalent.
```javascript
return getUsername()
.then(function (username) {
return getUser(username)
.then(function (user) {
// if we get here without an error,
// the value returned here
// or the exception thrown here
// resolves the promise returned
// by the first line
})
});
```
```javascript
return getUsername()
.then(function (username) {
return getUser(username);
})
.then(function (user) {
// if we get here without an error,
// the value returned here
// or the exception thrown here
// resolves the promise returned
// by the first line
});
```
The only difference is nesting. Its useful to nest handlers if you
need to capture multiple input values in your closure.
```javascript
function authenticate() {
return getUsername()
.then(function (username) {
return getUser(username);
})
// chained because we will not need the user name in the next event
.then(function (user) {
return getPassword()
// nested because we need both user and password next
.then(function (password) {
if (user.passwordHash !== hash(password)) {
throw new Error("Can't authenticate");
}
});
});
}
```
### Combination
You can turn an array of promises into a promise for the whole,
fulfilled array using ``all``.
```javascript
return Q.all([
eventualAdd(2, 2),
eventualAdd(10, 20)
]);
```
If you have a promise for an array, you can use ``spread`` as a
replacement for ``then``. The ``spread`` function “spreads” the
values over the arguments of the fulfillment handler. The rejection handler
will get called at the first sign of failure. That is, whichever of
the received promises fails first gets handled by the rejection handler.
```javascript
function eventualAdd(a, b) {
return Q.spread([a, b], function (a, b) {
return a + b;
})
}
```
But ``spread`` calls ``all`` initially, so you can skip it in chains.
```javascript
return getUsername()
.then(function (username) {
return [username, getUser(username)];
})
.spread(function (username, user) {
});
```
The ``all`` function returns a promise for an array of values. When this
promise is fulfilled, the array contains the fulfillment values of the original
promises, in the same order as those promises. If one of the given promises
is rejected, the returned promise is immediately rejected, not waiting for the
rest of the batch. If you want to wait for all of the promises to either be
fulfilled or rejected, you can use ``allSettled``.
```javascript
Q.allSettled(promises)
.then(function (results) {
results.forEach(function (result) {
if (result.state === "fulfilled") {
var value = result.value;
} else {
var reason = result.reason;
}
});
});
```
The ``any`` function accepts an array of promises and returns a promise that is
fulfilled by the first given promise to be fulfilled, or rejected if all of the
given promises are rejected.
```javascript
Q.any(promises)
.then(function (first) {
// Any of the promises was fulfilled.
}, function (error) {
// All of the promises were rejected.
});
```
### Sequences
If you have a number of promise-producing functions that need
to be run sequentially, you can of course do so manually:
```javascript
return foo(initialVal).then(bar).then(baz).then(qux);
```
However, if you want to run a dynamically constructed sequence of
functions, you'll want something like this:
```javascript
var funcs = [foo, bar, baz, qux];
var result = Q(initialVal);
funcs.forEach(function (f) {
result = result.then(f);
});
return result;
```
You can make this slightly more compact using `reduce`:
```javascript
return funcs.reduce(function (soFar, f) {
return soFar.then(f);
}, Q(initialVal));
```
Or, you could use the ultra-compact version:
```javascript
return funcs.reduce(Q.when, Q(initialVal));
```
### Handling Errors
One sometimes-unintuive aspect of promises is that if you throw an
exception in the fulfillment handler, it will not be caught by the error
handler.
```javascript
return foo()
.then(function (value) {
throw new Error("Can't bar.");
}, function (error) {
// We only get here if "foo" fails
});
```
To see why this is, consider the parallel between promises and
``try``/``catch``. We are ``try``-ing to execute ``foo()``: the error
handler represents a ``catch`` for ``foo()``, while the fulfillment handler
represents code that happens *after* the ``try``/``catch`` block.
That code then needs its own ``try``/``catch`` block.
In terms of promises, this means chaining your rejection handler:
```javascript
return foo()
.then(function (value) {
throw new Error("Can't bar.");
})
.fail(function (error) {
// We get here with either foo's error or bar's error
});
```
### Progress Notification
It's possible for promises to report their progress, e.g. for tasks that take a
long time like a file upload. Not all promises will implement progress
notifications, but for those that do, you can consume the progress values using
a third parameter to ``then``:
```javascript
return uploadFile()
.then(function () {
// Success uploading the file
}, function (err) {
// There was an error, and we get the reason for error
}, function (progress) {
// We get notified of the upload's progress as it is executed
});
```
Like `fail`, Q also provides a shorthand for progress callbacks
called `progress`:
```javascript
return uploadFile().progress(function (progress) {
// We get notified of the upload's progress
});
```
### The End
When you get to the end of a chain of promises, you should either
return the last promise or end the chain. Since handlers catch
errors, its an unfortunate pattern that the exceptions can go
unobserved.
So, either return it,
```javascript
return foo()
.then(function () {
return "bar";
});
```
Or, end it.
```javascript
foo()
.then(function () {
return "bar";
})
.done();
```
Ending a promise chain makes sure that, if an error doesnt get
handled before the end, it will get rethrown and reported.
This is a stopgap. We are exploring ways to make unhandled errors
visible without any explicit handling.
### The Beginning
Everything above assumes you get a promise from somewhere else. This
is the common case. Every once in a while, you will need to create a
promise from scratch.
#### Using ``Q.fcall``
You can create a promise from a value using ``Q.fcall``. This returns a
promise for 10.
```javascript
return Q.fcall(function () {
return 10;
});
```
You can also use ``fcall`` to get a promise for an exception.
```javascript
return Q.fcall(function () {
throw new Error("Can't do it");
});
```
As the name implies, ``fcall`` can call functions, or even promised
functions. This uses the ``eventualAdd`` function above to add two
numbers.
```javascript
return Q.fcall(eventualAdd, 2, 2);
```
#### Using Deferreds
If you have to interface with asynchronous functions that are callback-based
instead of promise-based, Q provides a few shortcuts (like ``Q.nfcall`` and
friends). But much of the time, the solution will be to use *deferreds*.
```javascript
var deferred = Q.defer();
FS.readFile("foo.txt", "utf-8", function (error, text) {
if (error) {
deferred.reject(new Error(error));
} else {
deferred.resolve(text);
}
});
return deferred.promise;
```
Note that a deferred can be resolved with a value or a promise. The
``reject`` function is a shorthand for resolving with a rejected
promise.
```javascript
// this:
deferred.reject(new Error("Can't do it"));
// is shorthand for:
var rejection = Q.fcall(function () {
throw new Error("Can't do it");
});
deferred.resolve(rejection);
```
This is a simplified implementation of ``Q.delay``.
```javascript
function delay(ms) {
var deferred = Q.defer();
setTimeout(deferred.resolve, ms);
return deferred.promise;
}
```
This is a simplified implementation of ``Q.timeout``
```javascript
function timeout(promise, ms) {
var deferred = Q.defer();
Q.when(promise, deferred.resolve);
delay(ms).then(function () {
deferred.reject(new Error("Timed out"));
});
return deferred.promise;
}
```
Finally, you can send a progress notification to the promise with
``deferred.notify``.
For illustration, this is a wrapper for XML HTTP requests in the browser. Note
that a more [thorough][XHR] implementation would be in order in practice.
[XHR]: https://github.com/montagejs/mr/blob/71e8df99bb4f0584985accd6f2801ef3015b9763/browser.js#L29-L73
```javascript
function requestOkText(url) {
var request = new XMLHttpRequest();
var deferred = Q.defer();
request.open("GET", url, true);
request.onload = onload;
request.onerror = onerror;
request.onprogress = onprogress;
request.send();
function onload() {
if (request.status === 200) {
deferred.resolve(request.responseText);
} else {
deferred.reject(new Error("Status code was " + request.status));
}
}
function onerror() {
deferred.reject(new Error("Can't XHR " + JSON.stringify(url)));
}
function onprogress(event) {
deferred.notify(event.loaded / event.total);
}
return deferred.promise;
}
```
Below is an example of how to use this ``requestOkText`` function:
```javascript
requestOkText("http://localhost:3000")
.then(function (responseText) {
// If the HTTP response returns 200 OK, log the response text.
console.log(responseText);
}, function (error) {
// If there's an error or a non-200 status code, log the error.
console.error(error);
}, function (progress) {
// Log the progress as it comes in.
console.log("Request progress: " + Math.round(progress * 100) + "%");
});
```
#### Using `Q.Promise`
This is an alternative promise-creation API that has the same power as
the deferred concept, but without introducing another conceptual entity.
Rewriting the `requestOkText` example above using `Q.Promise`:
```javascript
function requestOkText(url) {
return Q.Promise(function(resolve, reject, notify) {
var request = new XMLHttpRequest();
request.open("GET", url, true);
request.onload = onload;
request.onerror = onerror;
request.onprogress = onprogress;
request.send();
function onload() {
if (request.status === 200) {
resolve(request.responseText);
} else {
reject(new Error("Status code was " + request.status));
}
}
function onerror() {
reject(new Error("Can't XHR " + JSON.stringify(url)));
}
function onprogress(event) {
notify(event.loaded / event.total);
}
});
}
```
If `requestOkText` were to throw an exception, the returned promise would be
rejected with that thrown exception as the rejection reason.
### The Middle
If you are using a function that may return a promise, but just might
return a value if it doesnt need to defer, you can use the “static”
methods of the Q library.
The ``when`` function is the static equivalent for ``then``.
```javascript
return Q.when(valueOrPromise, function (value) {
}, function (error) {
});
```
All of the other methods on a promise have static analogs with the
same name.
The following are equivalent:
```javascript
return Q.all([a, b]);
```
```javascript
return Q.fcall(function () {
return [a, b];
})
.all();
```
When working with promises provided by other libraries, you should
convert it to a Q promise. Not all promise libraries make the same
guarantees as Q and certainly dont provide all of the same methods.
Most libraries only provide a partially functional ``then`` method.
This thankfully is all we need to turn them into vibrant Q promises.
```javascript
return Q($.ajax(...))
.then(function () {
});
```
If there is any chance that the promise you receive is not a Q promise
as provided by your library, you should wrap it using a Q function.
You can even use ``Q.invoke`` as a shorthand.
```javascript
return Q.invoke($, 'ajax', ...)
.then(function () {
});
```
### Over the Wire
A promise can serve as a proxy for another object, even a remote
object. There are methods that allow you to optimistically manipulate
properties or call functions. All of these interactions return
promises, so they can be chained.
```
direct manipulation using a promise as a proxy
-------------------------- -------------------------------
value.foo promise.get("foo")
value.foo = value promise.put("foo", value)
delete value.foo promise.del("foo")
value.foo(...args) promise.post("foo", [args])
value.foo(...args) promise.invoke("foo", ...args)
value(...args) promise.fapply([args])
value(...args) promise.fcall(...args)
```
If the promise is a proxy for a remote object, you can shave
round-trips by using these functions instead of ``then``. To take
advantage of promises for remote objects, check out [Q-Connection][].
[Q-Connection]: https://github.com/kriskowal/q-connection
Even in the case of non-remote objects, these methods can be used as
shorthand for particularly-simple fulfillment handlers. For example, you
can replace
```javascript
return Q.fcall(function () {
return [{ foo: "bar" }, { foo: "baz" }];
})
.then(function (value) {
return value[0].foo;
});
```
with
```javascript
return Q.fcall(function () {
return [{ foo: "bar" }, { foo: "baz" }];
})
.get(0)
.get("foo");
```
### Adapting Node
If you're working with functions that make use of the Node.js callback pattern,
where callbacks are in the form of `function(err, result)`, Q provides a few
useful utility functions for converting between them. The most straightforward
are probably `Q.nfcall` and `Q.nfapply` ("Node function call/apply") for calling
Node.js-style functions and getting back a promise:
```javascript
return Q.nfcall(FS.readFile, "foo.txt", "utf-8");
return Q.nfapply(FS.readFile, ["foo.txt", "utf-8"]);
```
If you are working with methods, instead of simple functions, you can easily
run in to the usual problems where passing a method to another function—like
`Q.nfcall`—"un-binds" the method from its owner. To avoid this, you can either
use `Function.prototype.bind` or some nice shortcut methods we provide:
```javascript
return Q.ninvoke(redisClient, "get", "user:1:id");
return Q.npost(redisClient, "get", ["user:1:id"]);
```
You can also create reusable wrappers with `Q.denodeify` or `Q.nbind`:
```javascript
var readFile = Q.denodeify(FS.readFile);
return readFile("foo.txt", "utf-8");
var redisClientGet = Q.nbind(redisClient.get, redisClient);
return redisClientGet("user:1:id");
```
Finally, if you're working with raw deferred objects, there is a
`makeNodeResolver` method on deferreds that can be handy:
```javascript
var deferred = Q.defer();
FS.readFile("foo.txt", "utf-8", deferred.makeNodeResolver());
return deferred.promise;
```
### Long Stack Traces
Q comes with optional support for “long stack traces,” wherein the `stack`
property of `Error` rejection reasons is rewritten to be traced along
asynchronous jumps instead of stopping at the most recent one. As an example:
```js
function theDepthsOfMyProgram() {
Q.delay(100).done(function explode() {
throw new Error("boo!");
});
}
theDepthsOfMyProgram();
```
usually would give a rather unhelpful stack trace looking something like
```
Error: boo!
at explode (/path/to/test.js:3:11)
at _fulfilled (/path/to/test.js:q:54)
at resolvedValue.promiseDispatch.done (/path/to/q.js:823:30)
at makePromise.promise.promiseDispatch (/path/to/q.js:496:13)
at pending (/path/to/q.js:397:39)
at process.startup.processNextTick.process._tickCallback (node.js:244:9)
```
But, if you turn this feature on by setting
```js
Q.longStackSupport = true;
```
then the above code gives a nice stack trace to the tune of
```
Error: boo!
at explode (/path/to/test.js:3:11)
From previous event:
at theDepthsOfMyProgram (/path/to/test.js:2:16)
at Object.<anonymous> (/path/to/test.js:7:1)
```
Note how you can see the function that triggered the async operation in the
stack trace! This is very helpful for debugging, as otherwise you end up getting
only the first line, plus a bunch of Q internals, with no sign of where the
operation started.
In node.js, this feature can also be enabled through the Q_DEBUG environment
variable:
```
Q_DEBUG=1 node server.js
```
This will enable long stack support in every instance of Q.
This feature does come with somewhat-serious performance and memory overhead,
however. If you're working with lots of promises, or trying to scale a server
to many users, you should probably keep it off. But in development, go for it!
## Tests
You can view the results of the Q test suite [in your browser][tests]!
[tests]: https://rawgithub.com/kriskowal/q/v1/spec/q-spec.html
## License
Copyright 20092015 Kristopher Michael Kowal and contributors
MIT License (enclosed)

View File

@ -0,0 +1,120 @@
{
"name": "q",
"version": "1.4.1",
"description": "A library for promises (CommonJS/Promises/A,B,D)",
"homepage": "https://github.com/kriskowal/q",
"author": {
"name": "Kris Kowal",
"email": "kris@cixar.com",
"url": "https://github.com/kriskowal"
},
"keywords": [
"q",
"promise",
"promises",
"promises-a",
"promises-aplus",
"deferred",
"future",
"async",
"flow control",
"fluent",
"browser",
"node"
],
"contributors": [
{
"name": "Kris Kowal",
"email": "kris@cixar.com",
"url": "https://github.com/kriskowal"
},
{
"name": "Irakli Gozalishvili",
"email": "rfobic@gmail.com",
"url": "http://jeditoolkit.com"
},
{
"name": "Domenic Denicola",
"email": "domenic@domenicdenicola.com",
"url": "http://domenicdenicola.com"
}
],
"bugs": {
"url": "http://github.com/kriskowal/q/issues"
},
"license": {
"type": "MIT",
"url": "http://github.com/kriskowal/q/raw/master/LICENSE"
},
"main": "q.js",
"files": [
"LICENSE",
"q.js",
"queue.js"
],
"repository": {
"type": "git",
"url": "git://github.com/kriskowal/q.git"
},
"engines": {
"node": ">=0.6.0",
"teleport": ">=0.2.0"
},
"dependencies": {},
"devDependencies": {
"cover": "*",
"grunt": "~0.4.1",
"grunt-cli": "~0.1.9",
"grunt-contrib-uglify": "~0.9.1",
"jasmine-node": "1.11.0",
"jshint": "~2.1.9",
"matcha": "~0.2.0",
"opener": "*",
"promises-aplus-tests": "1.x"
},
"scripts": {
"test": "jasmine-node spec && promises-aplus-tests spec/aplus-adapter",
"test-browser": "opener spec/q-spec.html",
"benchmark": "matcha",
"lint": "jshint q.js",
"cover": "cover run jasmine-node spec && cover report html && opener cover_html/index.html",
"minify": "grunt",
"prepublish": "grunt"
},
"overlay": {
"teleport": {
"dependencies": {
"system": ">=0.0.4"
}
}
},
"directories": {
"test": "./spec"
},
"gitHead": "d373079d3620152e3d60e82f27265a09ee0e81bd",
"_id": "q@1.4.1",
"_shasum": "55705bcd93c5f3673530c2c2cbc0c2b3addc286e",
"_from": "q@>=1.1.2 <2.0.0",
"_npmVersion": "2.8.3",
"_nodeVersion": "1.8.1",
"_npmUser": {
"name": "kriskowal",
"email": "kris.kowal@cixar.com"
},
"maintainers": [
{
"name": "kriskowal",
"email": "kris.kowal@cixar.com"
},
{
"name": "domenic",
"email": "domenic@domenicdenicola.com"
}
],
"dist": {
"shasum": "55705bcd93c5f3673530c2c2cbc0c2b3addc286e",
"tarball": "http://registry.npmjs.org/q/-/q-1.4.1.tgz"
},
"_resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz",
"readme": "ERROR: No README data found!"
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,35 @@
var Q = require("./q");
module.exports = Queue;
function Queue() {
var ends = Q.defer();
var closed = Q.defer();
return {
put: function (value) {
var next = Q.defer();
ends.resolve({
head: value,
tail: next.promise
});
ends.resolve = next.resolve;
},
get: function () {
var result = ends.promise.get("head");
ends.promise = ends.promise.get("tail");
return result.fail(function (error) {
closed.resolve(error);
throw error;
});
},
closed: closed.promise,
close: function (error) {
error = error || new Error("Can't get value from closed queue");
var end = {head: Q.reject(error)};
end.tail = end;
ends.resolve(end);
return closed.promise;
}
};
}

View File

@ -0,0 +1,76 @@
{
"name": "coa",
"description": "Command-Option-Argument: Yet another parser for command line options.",
"version": "1.0.1",
"homepage": "http://github.com/veged/coa",
"author": {
"name": "Sergey Berezhnoy",
"email": "veged@ya.ru",
"url": "http://github.com/veged"
},
"maintainers": [
{
"name": "veged",
"email": "veged@mail.ru"
},
{
"name": "arikon",
"email": "peimei@ya.ru"
}
],
"contributors": [
{
"name": "Sergey Belov",
"email": "peimei@ya.ru",
"url": "http://github.com/arikon"
}
],
"repository": {
"type": "git",
"url": "git://github.com/veged/coa.git"
},
"directories": {
"lib": "./lib"
},
"dependencies": {
"q": "^1.1.2"
},
"devDependencies": {
"coffee-script": "~1.6.3",
"istanbul": "~0.1.40",
"mocha-istanbul": "*",
"mocha": "~1.21.4",
"chai": "~1.7.2"
},
"scripts": {
"test": "make test",
"coverage": "make coverage"
},
"engines": {
"node": ">= 0.8.0"
},
"licenses": [
{
"type": "MIT"
}
],
"optionalDependencies": {},
"gitHead": "ec694e82e7fb2c79fc114c9b23625a90c57e81fe",
"bugs": {
"url": "https://github.com/veged/coa/issues"
},
"_id": "coa@1.0.1",
"_shasum": "7f959346cfc8719e3f7233cd6852854a7c67d8a3",
"_from": "coa@>=1.0.1 <1.1.0",
"_npmVersion": "2.0.0-alpha-5",
"_npmUser": {
"name": "veged",
"email": "veged@ya.ru"
},
"dist": {
"shasum": "7f959346cfc8719e3f7233cd6852854a7c67d8a3",
"tarball": "http://registry.npmjs.org/coa/-/coa-1.0.1.tgz"
},
"_resolved": "https://registry.npmjs.org/coa/-/coa-1.0.1.tgz",
"readme": "ERROR: No README data found!"
}

View File

@ -0,0 +1,130 @@
Color = require('./color').Color
Cmd = require('./cmd').Cmd
Opt = require('./opt').Opt
###*
Argument
Unnamed entity. From command line arguments passed as list of unnamed values.
@namespace
@class Presents argument
###
exports.Arg = class Arg
###*
@constructs
@param {COA.Cmd} cmd parent command
###
constructor: (@_cmd) -> @_cmd._args.push @
###*
Set a canonical argument identifier to be used anywhere in text messages.
@param {String} _name argument name
@returns {COA.Arg} this instance (for chainability)
###
name: Opt::name
###*
Set a long description for argument to be used anywhere in text messages.
@param {String} _title argument title
@returns {COA.Arg} this instance (for chainability)
###
title: Cmd::title
###*
Makes an argument accepts multiple values.
Otherwise, the value will be used by the latter passed.
@returns {COA.Arg} this instance (for chainability)
###
arr: Opt::arr
###*
Makes an argument required.
@returns {COA.Arg} this instance (for chainability)
###
req: Opt::req
###*
Set a validation (or value) function for argument.
Value from command line passes through before becoming available from API.
Using for validation and convertion simple types to any values.
@param {Function} _val validating function,
invoked in the context of argument instance
and has one parameter with value from command line
@returns {COA.Arg} this instance (for chainability)
###
val: Opt::val
###*
Set a default value for argument.
Default value passed through validation function as ordinary value.
@param {Object} _def
@returns {COA.Arg} this instance (for chainability)
###
def: Opt::def
###*
Set custom additional completion for current argument.
@param {Function} completion generation function,
invoked in the context of argument instance.
Accepts parameters:
- {Object} opts completion options
It can return promise or any other value treated as result.
@returns {COA.Arg} this instance (for chainability)
###
comp: Cmd::comp
###*
Make argument value inputting stream.
It's add useful validation and shortcut for STDIN.
@returns {COA.Arg} this instance (for chainability)
###
input: Opt::input
###*
Make argument value outputing stream.
It's add useful validation and shortcut for STDOUT.
@returns {COA.Arg} this instance (for chainability)
###
output: Opt::output
_parse: (arg, args) ->
@_saveVal(args, arg)
_saveVal: Opt::_saveVal
_checkParsed: (opts, args) -> not args.hasOwnProperty(@_name)
_usage: ->
res = []
res.push Color('lpurple', @_name.toUpperCase()), ' : ', @_title
if @_req then res.push ' ', Color('lred', '(required)')
res.join ''
_requiredText: -> 'Missing required argument:\n ' + @_usage()
###*
Return rejected promise with error code.
Use in .val() for return with error.
@param {Object} reject reason
You can customize toString() method and exitCode property
of reason object.
@returns {Q.promise} rejected promise
###
reject: Cmd::reject
###*
Finish chain for current option and return parent command instance.
@returns {COA.Cmd} parent command
###
end: Cmd::end
###*
Apply function with arguments in context of arg instance.
@param {Function} fn
@param {Array} args
@returns {COA.Arg} this instance (for chainability)
###
apply: Cmd::apply

View File

@ -0,0 +1,456 @@
UTIL = require 'util'
PATH = require 'path'
Color = require('./color').Color
Q = require('q')
#inspect = require('eyes').inspector { maxLength: 99999, stream: process.stderr }
###*
Command
Top level entity. Commands may have options and arguments.
@namespace
@class Presents command
###
exports.Cmd = class Cmd
###*
@constructs
@param {COA.Cmd} [cmd] parent command
###
constructor: (cmd) ->
if this not instanceof Cmd
return new Cmd cmd
@_parent cmd
@_cmds = []
@_cmdsByName = {}
@_opts = []
@_optsByKey = {}
@_args = []
@_ext = false
@get: (propertyName, func) ->
Object.defineProperty @::, propertyName,
configurable: true
enumerable: true
get: func
###*
Returns object containing all its subcommands as methods
to use from other programs.
@returns {Object}
###
@get 'api', () ->
if not @_api
@_api = => @invoke.apply @, arguments
for c of @_cmdsByName
do (c) =>
@_api[c] = @_cmdsByName[c].api
@_api
_parent: (cmd) ->
@_cmd = cmd or this
if cmd
cmd._cmds.push @
if @_name then @_cmd._cmdsByName[@_name] = @
@
###*
Set a canonical command identifier to be used anywhere in the API.
@param {String} _name command name
@returns {COA.Cmd} this instance (for chainability)
###
name: (@_name) ->
if @_cmd isnt @ then @_cmd._cmdsByName[_name] = @
@
###*
Set a long description for command to be used anywhere in text messages.
@param {String} _title command title
@returns {COA.Cmd} this instance (for chainability)
###
title: (@_title) -> @
###*
Create new or add existing subcommand for current command.
@param {COA.Cmd} [cmd] existing command instance
@returns {COA.Cmd} new subcommand instance
###
cmd: (cmd) ->
if cmd then cmd._parent @
else new Cmd @
###*
Create option for current command.
@returns {COA.Opt} new option instance
###
opt: -> new (require('./opt').Opt) @
###*
Create argument for current command.
@returns {COA.Opt} new argument instance
###
arg: -> new (require('./arg').Arg) @
###*
Add (or set) action for current command.
@param {Function} act action function,
invoked in the context of command instance
and has the parameters:
- {Object} opts parsed options
- {Array} args parsed arguments
- {Object} res actions result accumulator
It can return rejected promise by Cmd.reject (in case of error)
or any other value treated as result.
@param {Boolean} [force=false] flag for set action instead add to existings
@returns {COA.Cmd} this instance (for chainability)
###
act: (act, force) ->
return @ unless act
if not force and @_act
@_act.push act
else
@_act = [act]
@
###*
Set custom additional completion for current command.
@param {Function} completion generation function,
invoked in the context of command instance.
Accepts parameters:
- {Object} opts completion options
It can return promise or any other value treated as result.
@returns {COA.Cmd} this instance (for chainability)
###
comp: (@_comp) -> @
###*
Apply function with arguments in context of command instance.
@param {Function} fn
@param {Array} args
@returns {COA.Cmd} this instance (for chainability)
###
apply: (fn, args...) ->
fn.apply this, args
@
###*
Make command "helpful", i.e. add -h --help flags for print usage.
@returns {COA.Cmd} this instance (for chainability)
###
helpful: ->
@opt()
.name('help').title('Help')
.short('h').long('help')
.flag()
.only()
.act ->
return @usage()
.end()
###*
Adds shell completion to command, adds "completion" subcommand,
that makes all the magic.
Must be called only on root command.
@returns {COA.Cmd} this instance (for chainability)
###
completable: ->
@cmd()
.name('completion')
.apply(require './completion')
.end()
###*
Allow command to be extendable by external node.js modules.
@param {String} [pattern] Pattern of node.js module to find subcommands at.
@returns {COA.Cmd} this instance (for chainability)
###
extendable: (pattern) ->
@_ext = pattern or true
@
_exit: (msg, code) ->
process.once 'exit', ->
if msg then console.error msg
process.exit code or 0
###*
Build full usage text for current command instance.
@returns {String} usage text
###
usage: ->
res = []
if @_title then res.push @_fullTitle()
res.push('', 'Usage:')
if @_cmds.length then res.push(['', '',
Color('lred', @_fullName()),
Color('lblue', 'COMMAND'),
Color('lgreen', '[OPTIONS]'),
Color('lpurple', '[ARGS]')].join ' ')
if @_opts.length + @_args.length then res.push(['', '',
Color('lred', @_fullName()),
Color('lgreen', '[OPTIONS]'),
Color('lpurple', '[ARGS]')].join ' ')
res.push(
@_usages(@_cmds, 'Commands'),
@_usages(@_opts, 'Options'),
@_usages(@_args, 'Arguments'))
res.join '\n'
_usage: ->
Color('lblue', @_name) + ' : ' + @_title
_usages: (os, title) ->
unless os.length then return
res = ['', title + ':']
for o in os
res.push ' ' + o._usage()
res.join '\n'
_fullTitle: ->
(if @_cmd is this then '' else @_cmd._fullTitle() + '\n') + @_title
_fullName: ->
(if this._cmd is this then '' else @_cmd._fullName() + ' ') + PATH.basename(@_name)
_ejectOpt: (opts, opt) ->
if (pos = opts.indexOf(opt)) >= 0
if opts[pos]._arr
opts[pos]
else
opts.splice(pos, 1)[0]
_checkRequired: (opts, args) ->
if not (@_opts.filter (o) -> o._only and o._name of opts).length
all = @_opts.concat @_args
while i = all.shift()
if i._req and i._checkParsed opts, args
return @reject i._requiredText()
_parseCmd: (argv, unparsed = []) ->
argv = argv.concat()
optSeen = false
while i = argv.shift()
if not i.indexOf '-'
optSeen = true
if not optSeen and /^\w[\w-_]*$/.test(i)
cmd = @_cmdsByName[i]
if not cmd and @_ext
# construct package name to require
if typeof @_ext is 'string'
if ~@_ext.indexOf('%s')
# use formatted string
pkg = UTIL.format(@_ext, i)
else
# just append subcommand name to the prefix
pkg = @_ext + i
else if @_ext is true
# use default scheme: <command>-<subcommand>-<subcommand> and so on
pkg = i
c = @
loop
pkg = c._name + '-' + pkg
if c._cmd is c then break
c = c._cmd
try
cmdDesc = require(pkg)
catch e
if cmdDesc
if typeof cmdDesc == 'function'
# set create subcommand, set its name and apply imported function
@cmd()
.name(i)
.apply(cmdDesc)
.end()
else if typeof cmdDesc == 'object'
# register subcommand
@cmd(cmdDesc)
# set command name
cmdDesc.name(i)
else
throw new Error 'Error: Unsupported command declaration type, ' +
'should be function or COA.Cmd() object'
cmd = @_cmdsByName[i]
if cmd
return cmd._parseCmd argv, unparsed
unparsed.push i
{ cmd: @, argv: unparsed }
_parseOptsAndArgs: (argv) ->
opts = {}
args = {}
nonParsedOpts = @_opts.concat()
nonParsedArgs = @_args.concat()
while i = argv.shift()
# opt
if i isnt '--' and not i.indexOf '-'
if m = i.match /^(--\w[\w-_]*)=(.*)$/
i = m[1]
# suppress 'unknown argument' error for flag options with values
if not @_optsByKey[i]._flag
argv.unshift m[2]
if opt = @_ejectOpt nonParsedOpts, @_optsByKey[i]
if Q.isRejected(res = opt._parse argv, opts)
return res
else
return @reject "Unknown option: #{ i }"
# arg
else
if i is '--'
i = argv.splice(0)
i = if Array.isArray(i) then i else [i]
while a = i.shift()
if arg = nonParsedArgs.shift()
if arg._arr then nonParsedArgs.unshift arg
if Q.isRejected(res = arg._parse a, args)
return res
else
return @reject "Unknown argument: #{ a }"
# set defaults
{
opts: @_setDefaults(opts, nonParsedOpts),
args: @_setDefaults(args, nonParsedArgs)
}
_setDefaults: (params, desc) ->
for i in desc
if i._name not of params and '_def' of i
i._saveVal params, i._def
params
_processParams: (params, desc) ->
notExists = []
for i in desc
n = i._name
if n not of params
notExists.push i
continue
vals = params[n]
delete params[n]
if not Array.isArray vals
vals = [vals]
for v in vals
if Q.isRejected(res = i._saveVal(params, v))
return res
# set defaults
@_setDefaults params, notExists
_parseArr: (argv) ->
Q.when @_parseCmd(argv), (p) ->
Q.when p.cmd._parseOptsAndArgs(p.argv), (r) ->
{ cmd: p.cmd, opts: r.opts, args: r.args }
_do: (input) ->
Q.when input, (input) =>
cmd = input.cmd
[@_checkRequired].concat(cmd._act or []).reduce(
(res, act) ->
Q.when res, (res) ->
act.call(
cmd
input.opts
input.args
res)
undefined
)
###*
Parse arguments from simple format like NodeJS process.argv
and run ahead current program, i.e. call process.exit when all actions done.
@param {Array} argv
@returns {COA.Cmd} this instance (for chainability)
###
run: (argv = process.argv.slice(2)) ->
cb = (code) => (res) =>
if res
@_exit res.stack ? res.toString(), res.exitCode ? code
else
@_exit()
Q.when(@do(argv), cb(0), cb(1)).done()
@
###*
Convenient function to run command from tests.
@param {Array} argv
@returns {Q.Promise}
###
do: (argv) ->
@_do(@_parseArr argv || [])
###*
Invoke specified (or current) command using provided
options and arguments.
@param {String|Array} cmds subcommand to invoke (optional)
@param {Object} opts command options (optional)
@param {Object} args command arguments (optional)
@returns {Q.Promise}
###
invoke: (cmds = [], opts = {}, args = {}) ->
if typeof cmds == 'string'
cmds = cmds.split(' ')
if arguments.length < 3
if not Array.isArray cmds
args = opts
opts = cmds
cmds = []
Q.when @_parseCmd(cmds), (p) =>
if p.argv.length
return @reject "Unknown command: " + cmds.join ' '
Q.all([@_processParams(opts, @_opts), @_processParams(args, @_args)])
.spread (opts, args) =>
@_do({ cmd: p.cmd, opts: opts, args: args })
# catch fails from .only() options
.fail (res) =>
if res and res.exitCode is 0
res.toString()
else
@reject(res)
###*
Return reject of actions results promise with error code.
Use in .act() for return with error.
@param {Object} reject reason
You can customize toString() method and exitCode property
of reason object.
@returns {Q.promise} rejected promise
###
reject: (reason) -> Q.reject(reason)
###*
Finish chain for current subcommand and return parent command instance.
@returns {COA.Cmd} parent command
###
end: -> @_cmd

View File

@ -0,0 +1,25 @@
colors =
black: '30'
dgray: '1;30'
red: '31'
lred: '1;31'
green: '32'
lgreen: '1;32'
brown: '33'
yellow: '1;33'
blue: '34'
lblue: '1;34'
purple: '35'
lpurple: '1;35'
cyan: '36'
lcyan: '1;36'
lgray: '37'
white: '1;37'
exports.Color = (c, str) ->
# Use \x1B instead of \033 because of CoffeeScript 1.3.x compilation error
[
'\x1B[', colors[c], 'm'
str
'\x1B[m'
].join ''

View File

@ -0,0 +1,156 @@
###*
Most of the code adopted from the npm package shell completion code.
See https://github.com/isaacs/npm/blob/master/lib/completion.js
###
Q = require 'q'
escape = require('./shell').escape
unescape = require('./shell').unescape
module.exports = ->
@title('Shell completion')
.helpful()
.arg()
.name('raw')
.title('Completion words')
.arr()
.end()
.act (opts, args) ->
if process.platform == 'win32'
e = new Error 'shell completion not supported on windows'
e.code = 'ENOTSUP'
e.errno = require('constants').ENOTSUP
return @reject(e)
# if the COMP_* isn't in the env, then just dump the script
if !process.env.COMP_CWORD? or !process.env.COMP_LINE? or !process.env.COMP_POINT?
return dumpScript(@_cmd._name)
console.error 'COMP_LINE: %s', process.env.COMP_LINE
console.error 'COMP_CWORD: %s', process.env.COMP_CWORD
console.error 'COMP_POINT: %s', process.env.COMP_POINT
console.error 'args: %j', args.raw
# completion opts
opts = getOpts args.raw
# cmd
{ cmd, argv } = @_cmd._parseCmd opts.partialWords
Q.when complete(cmd, opts), (compls) ->
console.error 'filtered: %j', compls
console.log compls.map(escape).join('\n')
dumpScript = (name) ->
fs = require 'fs'
path = require 'path'
defer = Q.defer()
fs.readFile path.resolve(__dirname, 'completion.sh'), 'utf8', (err, d) ->
if err then return defer.reject err
d = d.replace(/{{cmd}}/g, path.basename name).replace(/^\#\!.*?\n/, '')
onError = (err) ->
# Darwin is a real dick sometimes.
#
# This is necessary because the "source" or "." program in
# bash on OS X closes its file argument before reading
# from it, meaning that you get exactly 1 write, which will
# work most of the time, and will always raise an EPIPE.
#
# Really, one should not be tossing away EPIPE errors, or any
# errors, so casually. But, without this, `. <(cmd completion)`
# can never ever work on OS X.
if err.errno == require('constants').EPIPE
process.stdout.removeListener 'error', onError
defer.resolve()
else
defer.reject(err)
process.stdout.on 'error', onError
process.stdout.write d, -> defer.resolve()
defer.promise
getOpts = (argv) ->
# get the partial line and partial word, if the point isn't at the end
# ie, tabbing at: cmd foo b|ar
line = process.env.COMP_LINE
w = +process.env.COMP_CWORD
point = +process.env.COMP_POINT
words = argv.map unescape
word = words[w]
partialLine = line.substr 0, point
partialWords = words.slice 0, w
# figure out where in that last word the point is
partialWord = argv[w] or ''
i = partialWord.length
while partialWord.substr(0, i) isnt partialLine.substr(-1 * i) and i > 0
i--
partialWord = unescape partialWord.substr 0, i
if partialWord then partialWords.push partialWord
{
line: line
w: w
point: point
words: words
word: word
partialLine: partialLine
partialWords: partialWords
partialWord: partialWord
}
complete = (cmd, opts) ->
compls = []
# complete on cmds
if opts.partialWord.indexOf('-')
compls = Object.keys(cmd._cmdsByName)
# Complete on required opts without '-' in last partial word
# (if required not already specified)
#
# Commented out because of uselessness:
# -b, --block suggest results in '-' on cmd line;
# next completion suggest all options, because of '-'
#.concat Object.keys(cmd._optsByKey).filter (v) -> cmd._optsByKey[v]._req
else
# complete on opt values: --opt=| case
if m = opts.partialWord.match /^(--\w[\w-_]*)=(.*)$/
optWord = m[1]
optPrefix = optWord + '='
else
# complete on opts
# don't complete on opts in case of --opt=val completion
# TODO: don't complete on opts in case of unknown arg after commands
# TODO: complete only on opts with arr() or not already used
# TODO: complete only on full opts?
compls = Object.keys cmd._optsByKey
# complete on opt values: next arg case
if not (o = opts.partialWords[opts.w - 1]).indexOf '-'
optWord = o
# complete on opt values: completion
if optWord and opt = cmd._optsByKey[optWord]
if not opt._flag and opt._comp
compls = Q.join compls, Q.when opt._comp(opts), (c, o) ->
c.concat o.map (v) -> (optPrefix or '') + v
# TODO: complete on args values (context aware, custom completion?)
# custom completion on cmds
if cmd._comp
compls = Q.join compls, Q.when(cmd._comp(opts)), (c, o) ->
c.concat o
# TODO: context aware custom completion on cmds, opts and args
# (can depend on already entered values, especially options)
Q.when compls, (compls) ->
console.error 'partialWord: %s', opts.partialWord
console.error 'compls: %j', compls
compls.filter (c) -> c.indexOf(opts.partialWord) is 0

View File

@ -0,0 +1,5 @@
exports.Cmd = require('./cmd').Cmd
exports.Opt = require('./cmd').Opt
exports.Arg = require('./cmd').Arg
exports.shell = require('./shell')
exports.require = require;

View File

@ -0,0 +1,243 @@
fs = require 'fs'
Q = require 'q'
Color = require('./color').Color
Cmd = require('./cmd').Cmd
###*
Option
Named entity. Options may have short and long keys for use from command line.
@namespace
@class Presents option
###
exports.Opt = class Opt
###*
@constructs
@param {COA.Cmd} cmd parent command
###
constructor: (@_cmd) -> @_cmd._opts.push @
###*
Set a canonical option identifier to be used anywhere in the API.
@param {String} _name option name
@returns {COA.Opt} this instance (for chainability)
###
name: (@_name) -> @
###*
Set a long description for option to be used anywhere in text messages.
@param {String} _title option title
@returns {COA.Opt} this instance (for chainability)
###
title: Cmd::title
###*
Set a short key for option to be used with one hyphen from command line.
@param {String} _short
@returns {COA.Opt} this instance (for chainability)
###
short: (@_short) -> @_cmd._optsByKey['-' + _short] = @
###*
Set a short key for option to be used with double hyphens from command line.
@param {String} _long
@returns {COA.Opt} this instance (for chainability)
###
long: (@_long) -> @_cmd._optsByKey['--' + _long] = @
###*
Make an option boolean, i.e. option without value.
@returns {COA.Opt} this instance (for chainability)
###
flag: () ->
@_flag = true
@
###*
Makes an option accepts multiple values.
Otherwise, the value will be used by the latter passed.
@returns {COA.Opt} this instance (for chainability)
###
arr: ->
@_arr = true
@
###*
Makes an option required.
@returns {COA.Opt} this instance (for chainability)
###
req: ->
@_req = true
@
###*
Makes an option to act as a command,
i.e. program will exit just after option action.
@returns {COA.Opt} this instance (for chainability)
###
only: ->
@_only = true
@
###*
Set a validation (or value) function for option.
Value from command line passes through before becoming available from API.
Using for validation and convertion simple types to any values.
@param {Function} _val validating function,
invoked in the context of option instance
and has one parameter with value from command line
@returns {COA.Opt} this instance (for chainability)
###
val: (@_val) -> @
###*
Set a default value for option.
Default value passed through validation function as ordinary value.
@param {Object} _def
@returns {COA.Opt} this instance (for chainability)
###
def: (@_def) -> @
###*
Make option value inputting stream.
It's add useful validation and shortcut for STDIN.
@returns {COA.Opt} this instance (for chainability)
###
input: ->
# XXX: hack to workaround a bug in node 0.6.x,
# see https://github.com/joyent/node/issues/2130
process.stdin.pause();
@
.def(process.stdin)
.val (v) ->
if typeof v is 'string'
if v is '-'
process.stdin
else
s = fs.createReadStream v, { encoding: 'utf8' }
s.pause()
s
else v
###*
Make option value outputing stream.
It's add useful validation and shortcut for STDOUT.
@returns {COA.Opt} this instance (for chainability)
###
output: ->
@
.def(process.stdout)
.val (v) ->
if typeof v is 'string'
if v is '-'
process.stdout
else
fs.createWriteStream v, { encoding: 'utf8' }
else v
###*
Add action for current option command.
This action is performed if the current option
is present in parsed options (with any value).
@param {Function} act action function,
invoked in the context of command instance
and has the parameters:
- {Object} opts parsed options
- {Array} args parsed arguments
- {Object} res actions result accumulator
It can return rejected promise by Cmd.reject (in case of error)
or any other value treated as result.
@returns {COA.Opt} this instance (for chainability)
###
act: (act) ->
opt = @
name = @_name
@_cmd.act (opts) ->
if name of opts
res = act.apply @, arguments
if opt._only
Q.when res, (res) =>
@reject {
toString: -> res.toString()
exitCode: 0
}
else
res
@
###*
Set custom additional completion for current option.
@param {Function} completion generation function,
invoked in the context of option instance.
Accepts parameters:
- {Object} opts completion options
It can return promise or any other value treated as result.
@returns {COA.Opt} this instance (for chainability)
###
comp: Cmd::comp
_saveVal: (opts, val) ->
if @_val then val = @_val val
if @_arr
(opts[@_name] or= []).push val
else
opts[@_name] = val
val
_parse: (argv, opts) ->
@_saveVal(
opts,
if @_flag
true
else
argv.shift()
)
_checkParsed: (opts, args) -> not opts.hasOwnProperty @_name
_usage: ->
res = []
nameStr = @_name.toUpperCase()
if @_short
res.push '-', Color 'lgreen', @_short
unless @_flag then res.push ' ' + nameStr
res.push ', '
if @_long
res.push '--', Color 'green', @_long
unless @_flag then res.push '=' + nameStr
res.push ' : ', @_title
if @_req then res.push ' ', Color('lred', '(required)')
res.join ''
_requiredText: -> 'Missing required option:\n ' + @_usage()
###*
Return rejected promise with error code.
Use in .val() for return with error.
@param {Object} reject reason
You can customize toString() method and exitCode property
of reason object.
@returns {Q.promise} rejected promise
###
reject: Cmd::reject
###*
Finish chain for current option and return parent command instance.
@returns {COA.Cmd} parent command
###
end: Cmd::end
###*
Apply function with arguments in context of option instance.
@param {Function} fn
@param {Array} args
@returns {COA.Opt} this instance (for chainability)
###
apply: Cmd::apply

View File

@ -0,0 +1,10 @@
exports.unescape = (w) ->
w = if w.charAt(0) is '"'
w.replace(/^"|([^\\])"$/g, '$1')
else
w.replace(/\\ /g, ' ')
w.replace(/\\("|'|\$|`|\\)/g, '$1')
exports.escape = (w) ->
w = w.replace(/(["'$`\\])/g,'\\$1')
if w.match(/\s+/) then '"' + w + '"' else w

View File

@ -0,0 +1,496 @@
var assert = require('chai').assert,
COA = require('..');
/**
* Mocha BDD interface.
*/
/** @name describe @function */
/** @name it @function */
/** @name before @function */
/** @name after @function */
/** @name beforeEach @function */
/** @name afterEach @function */
describe('Opt', function() {
describe('Unknown option', function() {
var cmd = COA.Cmd();
it('should fail', function() {
return cmd.do(['-a'])
.then(assert.fail, emptyFn);
});
});
describe('Short options', function() {
var cmd = COA.Cmd()
.opt()
.name('a')
.short('a')
.end()
.opt()
.name('b')
.short('b')
.end()
.act(function(opts) {
return opts;
});
it('should return passed values', function() {
return cmd.do(['-a', 'a', '-b', 'b'])
.then(function(res) {
assert.deepEqual(res, { a: 'a', b: 'b' });
});
});
});
describe('Long options', function() {
var cmd = COA.Cmd()
.opt()
.name('long1')
.long('long1')
.end()
.opt()
.name('long2')
.long('long2')
.end()
.act(function(opts) {
return opts;
});
it('should return passed values', function() {
return cmd.do(['--long1', 'long value', '--long2=another long value'])
.then(function(res) {
assert.deepEqual(res, { long1: 'long value', long2: 'another long value' });
});
});
});
describe('Array option', function() {
var cmd = COA.Cmd()
.opt()
.name('a')
.short('a')
.arr()
.end()
.act(function(opts) {
return opts;
});
it('should return array of passed values', function() {
return cmd.do(['-a', '1', '-a', '2'])
.then(function(res) {
assert.deepEqual(res, { a: ['1', '2'] });
});
});
});
describe('Required option', function() {
var cmd = COA.Cmd()
.opt()
.name('a')
.short('a')
.req()
.end()
.act(function(opts) {
return opts;
});
it('should fail if not specified', function() {
return cmd.do()
.then(assert.fail, emptyFn);
});
it('should return passed value if specified', function() {
return cmd.do(['-a', 'test'])
.then(function(opts) {
assert.equal(opts.a, 'test');
});
});
});
describe('Option with default value', function() {
var cmd = COA.Cmd()
.opt()
.name('a')
.short('a')
.def('aaa')
.end()
.act(function(opts) {
return opts;
});
it('should return default value if not specified', function() {
return cmd.do()
.then(function(opts) {
assert.equal(opts.a, 'aaa');
});
});
it('should return passed value if specified', function() {
return cmd.do(['-a', 'test'])
.then(function(opts) {
assert.equal(opts.a, 'test');
});
});
});
describe('Validated / transformed option', function() {
var cmd = COA.Cmd()
.opt()
.name('a')
.short('a')
.val(function(v) {
if (v === 'invalid') return this.reject('fail');
return { value: v };
})
.end()
.act(function(opts) {
return opts;
});
it('should fail if custom checks suppose to do so', function() {
return cmd.do(['-a', 'invalid'])
.then(assert.fail, emptyFn);
});
it('should return transformed value', function() {
return cmd.do(['-a', 'test'])
.then(function(opts) {
assert.deepEqual(opts.a, { value: 'test' });
});
});
});
describe('Only option (--version case)', function() {
var ver = require('../package.json').version,
cmd = COA.Cmd()
.opt()
.name('version')
.long('version')
.flag()
.only()
.act(function() {
return ver;
})
.end()
.opt()
.name('req')
.short('r')
.req()
.end();
it('should process the only() option', function() {
return cmd.do(['--version'])
.then(assert.fail, function(res) {
assert.equal(res, ver);
});
});
});
it('input()');
it('output()');
});
describe('Arg', function() {
describe('Unknown arg', function() {
var cmd = COA.Cmd();
it('should fail', function() {
return cmd.do(['test'])
.then(assert.fail, emptyFn);
});
});
describe('Unknown arg after known', function() {
var cmd = COA.Cmd()
.arg()
.name('a')
.end();
it('should fail', function() {
return cmd.do(['test', 'unknown'])
.then(assert.fail, emptyFn);
});
});
describe('Array arg', function() {
var cmd = COA.Cmd()
.arg()
.name('a')
.arr()
.end()
.act(function(opts, args) {
return args;
});
it('should return array of passed values', function() {
return cmd.do(['value 1', 'value 2'])
.then(function(args) {
assert.deepEqual(args, { a: ['value 1', 'value 2'] });
});
});
});
describe('Required arg', function() {
var cmd = COA.Cmd()
.arg()
.name('a')
.req()
.end()
.act(function(opts, args) {
return args;
});
it('should fail if not specified', function() {
return cmd.do()
.then(assert.fail, emptyFn);
});
it('should return passed value if specified', function() {
return cmd.do(['value'])
.then(function(args) {
assert.equal(args.a, 'value');
});
});
});
describe('Args after options', function() {
var cmd = COA.Cmd()
.opt()
.name('opt')
.long('opt')
.end()
.arg()
.name('arg1')
.end()
.arg()
.name('arg2')
.arr()
.end()
.act(function(opts, args) {
return { opts: opts, args: args };
});
it('should return passed values', function() {
return cmd.do(['--opt', 'value', 'value', 'value 1', 'value 2'])
.then(function(o) {
assert.deepEqual(o, {
opts: { opt: 'value' },
args: {
arg1: 'value',
arg2: ['value 1', 'value 2']
}
});
});
});
});
describe('Raw args', function() {
var cmd = COA.Cmd()
.arg()
.name('raw')
.arr()
.end()
.act(function(opts, args) {
return args;
});
it('should return passed arg values', function() {
return cmd.do(['--', 'raw', 'arg', 'values'])
.then(function(args) {
assert.deepEqual(args, { raw: ['raw', 'arg', 'values'] });
});
});
});
});
describe('Cmd', function() {
var doTest = function(o) {
assert.deepEqual(o, {
opts: { opt: 'value' },
args: {
arg1: 'value',
arg2: ['value 1', 'value 2']
}
});
},
invokeOpts = { opt: 'value' },
invokeArgs = {
arg1: 'value',
arg2: ['value 1', 'value 2']
};
describe('Subcommand', function() {
var cmd = COA.Cmd()
.cmd()
.name('command')
.opt()
.name('opt')
.long('opt')
.end()
.arg()
.name('arg1')
.end()
.arg()
.name('arg2')
.arr()
.end()
.act(function(opts, args) {
return { opts: opts, args: args };
})
.end();
describe('when specified on command line', function() {
it('should be invoked and accept passed opts and args', function() {
return cmd.do(['command', '--opt', 'value', 'value', 'value 1', 'value 2'])
.then(doTest);
});
});
describe('when invoked using api', function() {
it('should be invoked and accept passed opts and args', function() {
return cmd.api.command(invokeOpts, invokeArgs)
.then(doTest);
});
});
describe('when invoked using invoke()', function() {
it('should be invoked and accept passed opts and args', function() {
return cmd.invoke('command', invokeOpts, invokeArgs)
.then(doTest);
});
});
describe('when unexisting command invoked using invoke()', function() {
it('should fail', function() {
return cmd.invoke('unexistent')
.then(assert.fail, emptyFn);
});
});
});
describe('External subcommand', function() {
describe('default scheme: cmd.extendable()', function() {
describe('when described as a function', function() {
var cmd = COA.Cmd()
.name('coa')
.extendable();
it('should be invoked and accept passed opts and args', function() {
return cmd.do(['test', '--opt', 'value', 'value', 'value 1', 'value 2'])
.then(doTest);
});
});
describe('when described as an COA.Cmd() object', function() {
var cmd = COA.Cmd()
.name('coa')
.extendable();
it('should be invoked and accept passed opts and args', function() {
return cmd.do(['test-obj', '--opt', 'value', 'value', 'value 1', 'value 2'])
.then(doTest);
});
});
describe('2nd level subcommand', function() {
var cmd = COA.Cmd()
.name('coa')
.cmd()
.name('test')
.extendable()
.end();
it('should be invoked and accept passed opts and args', function() {
return cmd.do(['test', 'obj', '--opt', 'value', 'value', 'value 1', 'value 2'])
.then(doTest);
});
});
});
describe("common prefix: cmd.extendable('coa-')", function() {
describe('when described as a function', function() {
var cmd = COA.Cmd()
.name('coa')
.extendable('coa-');
it('should be invoked and accept passed opts and args', function() {
return cmd.do(['test', '--opt', 'value', 'value', 'value 1', 'value 2'])
.then(doTest);
});
});
});
describe("format string: cmd.extendable('coa-%s')", function() {
describe('when described as a function', function() {
var cmd = COA.Cmd()
.name('coa')
.extendable('coa-%s');
it('should be invoked and accept passed opts and args', function() {
return cmd.do(['test', '--opt', 'value', 'value', 'value 1', 'value 2'])
.then(doTest);
});
});
});
});
it('helpful(), name(), title()');
});
function emptyFn() {
// empty function
}

View File

@ -0,0 +1,2 @@
--reporter spec
--timeout 20

View File

@ -0,0 +1,60 @@
var assert = require('chai').assert,
shell = require('..').shell;
/**
* Mocha BDD interface.
*/
/** @name describe @function */
/** @name it @function */
/** @name before @function */
/** @name after @function */
/** @name beforeEach @function */
/** @name afterEach @function */
describe('shell', function() {
describe('escape()', function() {
var escape = shell.escape;
it('Should wrap values with spaces in double quotes', function() {
assert.equal(escape('asd abc'), '"asd abc"');
});
it('Should escape double quote "', function() {
assert.equal(escape('"asd'), '\\"asd');
});
it("Should escape single quote '", function() {
assert.equal(escape("'asd"), "\\'asd");
});
it('Should escape backslash \\', function() {
assert.equal(escape('\\asd'), '\\\\asd');
});
it('Should escape dollar $', function() {
assert.equal(escape('$asd'), '\\$asd');
});
it('Should escape backtick `', function() {
assert.equal(escape('`asd'), '\\`asd');
});
});
describe('unescape()', function() {
var unescape = shell.unescape;
it('Should strip double quotes at the both ends', function() {
assert.equal(unescape('"asd"'), 'asd');
});
it('Should not strip escaped double quotes at the both ends', function() {
assert.equal(unescape('\\"asd\\"'), '"asd"');
});
});
});

View File

@ -0,0 +1,9 @@
require('..').Cmd()
.name('bla')
.title('Bla bla bla')
.helpful()
.invoke({ help: true })
.then(function(res) {
console.log(res);
})
.done(); // Q.done()

View File

@ -0,0 +1,6 @@
var argv = process.argv.slice(2);
require('..').Cmd()
.name('bla')
.title('Bla bla bla')
.helpful()
.run(argv.length? argv : ['-h']);

View File

@ -0,0 +1,23 @@
Original Library
- Copyright (c) Marak Squires
Additional Functionality
- Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.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.

View File

@ -0,0 +1,178 @@
# colors.js [![Build Status](https://travis-ci.org/Marak/colors.js.svg?branch=master)](https://travis-ci.org/Marak/colors.js)
## get color and style in your node.js console
![Demo](https://raw.githubusercontent.com/Marak/colors.js/master/screenshots/colors.png)
## Installation
npm install colors
## colors and styles!
### text colors
- black
- red
- green
- yellow
- blue
- magenta
- cyan
- white
- gray
- grey
### background colors
- bgBlack
- bgRed
- bgGreen
- bgYellow
- bgBlue
- bgMagenta
- bgCyan
- bgWhite
### styles
- reset
- bold
- dim
- italic
- underline
- inverse
- hidden
- strikethrough
### extras
- rainbow
- zebra
- america
- trap
- random
## Usage
By popular demand, `colors` now ships with two types of usages!
The super nifty way
```js
var colors = require('colors');
console.log('hello'.green); // outputs green text
console.log('i like cake and pies'.underline.red) // outputs red underlined text
console.log('inverse the color'.inverse); // inverses the color
console.log('OMG Rainbows!'.rainbow); // rainbow
console.log('Run the trap'.trap); // Drops the bass
```
or a slightly less nifty way which doesn't extend `String.prototype`
```js
var colors = require('colors/safe');
console.log(colors.green('hello')); // outputs green text
console.log(colors.red.underline('i like cake and pies')) // outputs red underlined text
console.log(colors.inverse('inverse the color')); // inverses the color
console.log(colors.rainbow('OMG Rainbows!')); // rainbow
console.log(colors.trap('Run the trap')); // Drops the bass
```
I prefer the first way. Some people seem to be afraid of extending `String.prototype` and prefer the second way.
If you are writing good code you will never have an issue with the first approach. If you really don't want to touch `String.prototype`, the second usage will not touch `String` native object.
## Disabling Colors
To disable colors you can pass the following arguments in the command line to your application:
```bash
node myapp.js --no-color
```
## Console.log [string substitution](http://nodejs.org/docs/latest/api/console.html#console_console_log_data)
```js
var name = 'Marak';
console.log(colors.green('Hello %s'), name);
// outputs -> 'Hello Marak'
```
## Custom themes
### Using standard API
```js
var colors = require('colors');
colors.setTheme({
silly: 'rainbow',
input: 'grey',
verbose: 'cyan',
prompt: 'grey',
info: 'green',
data: 'grey',
help: 'cyan',
warn: 'yellow',
debug: 'blue',
error: 'red'
});
// outputs red text
console.log("this is an error".error);
// outputs yellow text
console.log("this is a warning".warn);
```
### Using string safe API
```js
var colors = require('colors/safe');
// set single property
var error = colors.red;
error('this is red');
// set theme
colors.setTheme({
silly: 'rainbow',
input: 'grey',
verbose: 'cyan',
prompt: 'grey',
info: 'green',
data: 'grey',
help: 'cyan',
warn: 'yellow',
debug: 'blue',
error: 'red'
});
// outputs red text
console.log(colors.error("this is an error"));
// outputs yellow text
console.log(colors.warn("this is a warning"));
```
You can also combine them:
```javascript
var colors = require('colors');
colors.setTheme({
custom: ['red', 'underline']
});
console.log('test'.custom);
```
*Protip: There is a secret undocumented style in `colors`. If you find the style you can summon him.*

View File

@ -0,0 +1,74 @@
var colors = require('../lib/index');
console.log("First some yellow text".yellow);
console.log("Underline that text".yellow.underline);
console.log("Make it bold and red".red.bold);
console.log(("Double Raindows All Day Long").rainbow)
console.log("Drop the bass".trap)
console.log("DROP THE RAINBOW BASS".trap.rainbow)
console.log('Chains are also cool.'.bold.italic.underline.red); // styles not widely supported
console.log('So '.green + 'are'.underline + ' ' + 'inverse'.inverse + ' styles! '.yellow.bold); // styles not widely supported
console.log("Zebras are so fun!".zebra);
//
// Remark: .strikethrough may not work with Mac OS Terminal App
//
console.log("This is " + "not".strikethrough + " fun.");
console.log('Background color attack!'.black.bgWhite)
console.log('Use random styles on everything!'.random)
console.log('America, Heck Yeah!'.america)
console.log('Setting themes is useful')
//
// Custom themes
//
console.log('Generic logging theme as JSON'.green.bold.underline);
// Load theme with JSON literal
colors.setTheme({
silly: 'rainbow',
input: 'grey',
verbose: 'cyan',
prompt: 'grey',
info: 'green',
data: 'grey',
help: 'cyan',
warn: 'yellow',
debug: 'blue',
error: 'red'
});
// outputs red text
console.log("this is an error".error);
// outputs yellow text
console.log("this is a warning".warn);
// outputs grey text
console.log("this is an input".input);
console.log('Generic logging theme as file'.green.bold.underline);
// Load a theme from file
colors.setTheme(__dirname + '/../themes/generic-logging.js');
// outputs red text
console.log("this is an error".error);
// outputs yellow text
console.log("this is a warning".warn);
// outputs grey text
console.log("this is an input".input);
//console.log("Don't summon".zalgo)

View File

@ -0,0 +1,76 @@
var colors = require('../safe');
console.log(colors.yellow("First some yellow text"));
console.log(colors.yellow.underline("Underline that text"));
console.log(colors.red.bold("Make it bold and red"));
console.log(colors.rainbow("Double Raindows All Day Long"))
console.log(colors.trap("Drop the bass"))
console.log(colors.rainbow(colors.trap("DROP THE RAINBOW BASS")));
console.log(colors.bold.italic.underline.red('Chains are also cool.')); // styles not widely supported
console.log(colors.green('So ') + colors.underline('are') + ' ' + colors.inverse('inverse') + colors.yellow.bold(' styles! ')); // styles not widely supported
console.log(colors.zebra("Zebras are so fun!"));
console.log("This is " + colors.strikethrough("not") + " fun.");
console.log(colors.black.bgWhite('Background color attack!'));
console.log(colors.random('Use random styles on everything!'))
console.log(colors.america('America, Heck Yeah!'));
console.log('Setting themes is useful')
//
// Custom themes
//
//console.log('Generic logging theme as JSON'.green.bold.underline);
// Load theme with JSON literal
colors.setTheme({
silly: 'rainbow',
input: 'grey',
verbose: 'cyan',
prompt: 'grey',
info: 'green',
data: 'grey',
help: 'cyan',
warn: 'yellow',
debug: 'blue',
error: 'red'
});
// outputs red text
console.log(colors.error("this is an error"));
// outputs yellow text
console.log(colors.warn("this is a warning"));
// outputs grey text
console.log(colors.input("this is an input"));
// console.log('Generic logging theme as file'.green.bold.underline);
// Load a theme from file
colors.setTheme(__dirname + '/../themes/generic-logging.js');
// outputs red text
console.log(colors.error("this is an error"));
// outputs yellow text
console.log(colors.warn("this is a warning"));
// outputs grey text
console.log(colors.input("this is an input"));
// console.log(colors.zalgo("Don't summon him"))

View File

@ -0,0 +1,187 @@
/*
The MIT License (MIT)
Original Library
- Copyright (c) Marak Squires
Additional functionality
- Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.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.
*/
var colors = {};
module['exports'] = colors;
colors.themes = {};
var ansiStyles = colors.styles = require('./styles');
var defineProps = Object.defineProperties;
colors.supportsColor = require('./system/supports-colors');
if (typeof colors.enabled === "undefined") {
colors.enabled = colors.supportsColor;
}
colors.stripColors = colors.strip = function(str){
return ("" + str).replace(/\x1B\[\d+m/g, '');
};
var stylize = colors.stylize = function stylize (str, style) {
if (!colors.enabled) {
return str+'';
}
return ansiStyles[style].open + str + ansiStyles[style].close;
}
var matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g;
var escapeStringRegexp = function (str) {
if (typeof str !== 'string') {
throw new TypeError('Expected a string');
}
return str.replace(matchOperatorsRe, '\\$&');
}
function build(_styles) {
var builder = function builder() {
return applyStyle.apply(builder, arguments);
};
builder._styles = _styles;
// __proto__ is used because we must return a function, but there is
// no way to create a function with a different prototype.
builder.__proto__ = proto;
return builder;
}
var styles = (function () {
var ret = {};
ansiStyles.grey = ansiStyles.gray;
Object.keys(ansiStyles).forEach(function (key) {
ansiStyles[key].closeRe = new RegExp(escapeStringRegexp(ansiStyles[key].close), 'g');
ret[key] = {
get: function () {
return build(this._styles.concat(key));
}
};
});
return ret;
})();
var proto = defineProps(function colors() {}, styles);
function applyStyle() {
var args = arguments;
var argsLen = args.length;
var str = argsLen !== 0 && String(arguments[0]);
if (argsLen > 1) {
for (var a = 1; a < argsLen; a++) {
str += ' ' + args[a];
}
}
if (!colors.enabled || !str) {
return str;
}
var nestedStyles = this._styles;
var i = nestedStyles.length;
while (i--) {
var code = ansiStyles[nestedStyles[i]];
str = code.open + str.replace(code.closeRe, code.open) + code.close;
}
return str;
}
function applyTheme (theme) {
for (var style in theme) {
(function(style){
colors[style] = function(str){
if (typeof theme[style] === 'object'){
var out = str;
for (var i in theme[style]){
out = colors[theme[style][i]](out);
}
return out;
}
return colors[theme[style]](str);
};
})(style)
}
}
colors.setTheme = function (theme) {
if (typeof theme === 'string') {
try {
colors.themes[theme] = require(theme);
applyTheme(colors.themes[theme]);
return colors.themes[theme];
} catch (err) {
console.log(err);
return err;
}
} else {
applyTheme(theme);
}
};
function init() {
var ret = {};
Object.keys(styles).forEach(function (name) {
ret[name] = {
get: function () {
return build([name]);
}
};
});
return ret;
}
var sequencer = function sequencer (map, str) {
var exploded = str.split(""), i = 0;
exploded = exploded.map(map);
return exploded.join("");
};
// custom formatter methods
colors.trap = require('./custom/trap');
colors.zalgo = require('./custom/zalgo');
// maps
colors.maps = {};
colors.maps.america = require('./maps/america');
colors.maps.zebra = require('./maps/zebra');
colors.maps.rainbow = require('./maps/rainbow');
colors.maps.random = require('./maps/random')
for (var map in colors.maps) {
(function(map){
colors[map] = function (str) {
return sequencer(colors.maps[map], str);
}
})(map)
}
defineProps(colors, init());

View File

@ -0,0 +1,45 @@
module['exports'] = function runTheTrap (text, options) {
var result = "";
text = text || "Run the trap, drop the bass";
text = text.split('');
var trap = {
a: ["\u0040", "\u0104", "\u023a", "\u0245", "\u0394", "\u039b", "\u0414"],
b: ["\u00df", "\u0181", "\u0243", "\u026e", "\u03b2", "\u0e3f"],
c: ["\u00a9", "\u023b", "\u03fe"],
d: ["\u00d0", "\u018a", "\u0500" , "\u0501" ,"\u0502", "\u0503"],
e: ["\u00cb", "\u0115", "\u018e", "\u0258", "\u03a3", "\u03be", "\u04bc", "\u0a6c"],
f: ["\u04fa"],
g: ["\u0262"],
h: ["\u0126", "\u0195", "\u04a2", "\u04ba", "\u04c7", "\u050a"],
i: ["\u0f0f"],
j: ["\u0134"],
k: ["\u0138", "\u04a0", "\u04c3", "\u051e"],
l: ["\u0139"],
m: ["\u028d", "\u04cd", "\u04ce", "\u0520", "\u0521", "\u0d69"],
n: ["\u00d1", "\u014b", "\u019d", "\u0376", "\u03a0", "\u048a"],
o: ["\u00d8", "\u00f5", "\u00f8", "\u01fe", "\u0298", "\u047a", "\u05dd", "\u06dd", "\u0e4f"],
p: ["\u01f7", "\u048e"],
q: ["\u09cd"],
r: ["\u00ae", "\u01a6", "\u0210", "\u024c", "\u0280", "\u042f"],
s: ["\u00a7", "\u03de", "\u03df", "\u03e8"],
t: ["\u0141", "\u0166", "\u0373"],
u: ["\u01b1", "\u054d"],
v: ["\u05d8"],
w: ["\u0428", "\u0460", "\u047c", "\u0d70"],
x: ["\u04b2", "\u04fe", "\u04fc", "\u04fd"],
y: ["\u00a5", "\u04b0", "\u04cb"],
z: ["\u01b5", "\u0240"]
}
text.forEach(function(c){
c = c.toLowerCase();
var chars = trap[c] || [" "];
var rand = Math.floor(Math.random() * chars.length);
if (typeof trap[c] !== "undefined") {
result += trap[c][rand];
} else {
result += c;
}
});
return result;
}

View File

@ -0,0 +1,104 @@
// please no
module['exports'] = function zalgo(text, options) {
text = text || " he is here ";
var soul = {
"up" : [
'̍', '̎', '̄', '̅',
'̿', '̑', '̆', '̐',
'͒', '͗', '͑', '̇',
'̈', '̊', '͂', '̓',
'̈', '͊', '͋', '͌',
'̃', '̂', '̌', '͐',
'̀', '́', '̋', '̏',
'̒', '̓', '̔', '̽',
'̉', 'ͣ', 'ͤ', 'ͥ',
'ͦ', 'ͧ', 'ͨ', 'ͩ',
'ͪ', 'ͫ', 'ͬ', 'ͭ',
'ͮ', 'ͯ', '̾', '͛',
'͆', '̚'
],
"down" : [
'̖', '̗', '̘', '̙',
'̜', '̝', '̞', '̟',
'̠', '̤', '̥', '̦',
'̩', '̪', '̫', '̬',
'̭', '̮', '̯', '̰',
'̱', '̲', '̳', '̹',
'̺', '̻', '̼', 'ͅ',
'͇', '͈', '͉', '͍',
'͎', '͓', '͔', '͕',
'͖', '͙', '͚', '̣'
],
"mid" : [
'̕', '̛', '̀', '́',
'͘', '̡', '̢', '̧',
'̨', '̴', '̵', '̶',
'͜', '͝', '͞',
'͟', '͠', '͢', '̸',
'̷', '͡', ' ҉'
]
},
all = [].concat(soul.up, soul.down, soul.mid),
zalgo = {};
function randomNumber(range) {
var r = Math.floor(Math.random() * range);
return r;
}
function is_char(character) {
var bool = false;
all.filter(function (i) {
bool = (i === character);
});
return bool;
}
function heComes(text, options) {
var result = '', counts, l;
options = options || {};
options["up"] = typeof options["up"] !== 'undefined' ? options["up"] : true;
options["mid"] = typeof options["mid"] !== 'undefined' ? options["mid"] : true;
options["down"] = typeof options["down"] !== 'undefined' ? options["down"] : true;
options["size"] = typeof options["size"] !== 'undefined' ? options["size"] : "maxi";
text = text.split('');
for (l in text) {
if (is_char(l)) {
continue;
}
result = result + text[l];
counts = {"up" : 0, "down" : 0, "mid" : 0};
switch (options.size) {
case 'mini':
counts.up = randomNumber(8);
counts.mid = randomNumber(2);
counts.down = randomNumber(8);
break;
case 'maxi':
counts.up = randomNumber(16) + 3;
counts.mid = randomNumber(4) + 1;
counts.down = randomNumber(64) + 3;
break;
default:
counts.up = randomNumber(8) + 1;
counts.mid = randomNumber(6) / 2;
counts.down = randomNumber(8) + 1;
break;
}
var arr = ["up", "mid", "down"];
for (var d in arr) {
var index = arr[d];
for (var i = 0 ; i <= counts[index]; i++) {
if (options[index]) {
result = result + soul[index][randomNumber(soul[index].length)];
}
}
}
}
return result;
}
// don't summon him
return heComes(text, options);
}

View File

@ -0,0 +1,113 @@
var colors = require('./colors');
module['exports'] = function () {
//
// Extends prototype of native string object to allow for "foo".red syntax
//
var addProperty = function (color, func) {
String.prototype.__defineGetter__(color, func);
};
var sequencer = function sequencer (map, str) {
return function () {
var exploded = this.split(""), i = 0;
exploded = exploded.map(map);
return exploded.join("");
}
};
addProperty('strip', function () {
return colors.strip(this);
});
addProperty('stripColors', function () {
return colors.strip(this);
});
addProperty("trap", function(){
return colors.trap(this);
});
addProperty("zalgo", function(){
return colors.zalgo(this);
});
addProperty("zebra", function(){
return colors.zebra(this);
});
addProperty("rainbow", function(){
return colors.rainbow(this);
});
addProperty("random", function(){
return colors.random(this);
});
addProperty("america", function(){
return colors.america(this);
});
//
// Iterate through all default styles and colors
//
var x = Object.keys(colors.styles);
x.forEach(function (style) {
addProperty(style, function () {
return colors.stylize(this, style);
});
});
function applyTheme(theme) {
//
// Remark: This is a list of methods that exist
// on String that you should not overwrite.
//
var stringPrototypeBlacklist = [
'__defineGetter__', '__defineSetter__', '__lookupGetter__', '__lookupSetter__', 'charAt', 'constructor',
'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'valueOf', 'charCodeAt',
'indexOf', 'lastIndexof', 'length', 'localeCompare', 'match', 'replace', 'search', 'slice', 'split', 'substring',
'toLocaleLowerCase', 'toLocaleUpperCase', 'toLowerCase', 'toUpperCase', 'trim', 'trimLeft', 'trimRight'
];
Object.keys(theme).forEach(function (prop) {
if (stringPrototypeBlacklist.indexOf(prop) !== -1) {
console.log('warn: '.red + ('String.prototype' + prop).magenta + ' is probably something you don\'t want to override. Ignoring style name');
}
else {
if (typeof(theme[prop]) === 'string') {
colors[prop] = colors[theme[prop]];
addProperty(prop, function () {
return colors[theme[prop]](this);
});
}
else {
addProperty(prop, function () {
var ret = this;
for (var t = 0; t < theme[prop].length; t++) {
ret = colors[theme[prop][t]](ret);
}
return ret;
});
}
}
});
}
colors.setTheme = function (theme) {
if (typeof theme === 'string') {
try {
colors.themes[theme] = require(theme);
applyTheme(colors.themes[theme]);
return colors.themes[theme];
} catch (err) {
console.log(err);
return err;
}
} else {
applyTheme(theme);
}
};
};

View File

@ -0,0 +1,12 @@
var colors = require('./colors');
module['exports'] = colors;
// Remark: By default, colors will add style properties to String.prototype
//
// If you don't wish to extend String.prototype you can do this instead and native String will not be touched
//
// var colors = require('colors/safe);
// colors.red("foo")
//
//
require('./extendStringPrototype')();

View File

@ -0,0 +1,12 @@
var colors = require('../colors');
module['exports'] = (function() {
return function (letter, i, exploded) {
if(letter === " ") return letter;
switch(i%3) {
case 0: return colors.red(letter);
case 1: return colors.white(letter)
case 2: return colors.blue(letter)
}
}
})();

View File

@ -0,0 +1,13 @@
var colors = require('../colors');
module['exports'] = (function () {
var rainbowColors = ['red', 'yellow', 'green', 'blue', 'magenta']; //RoY G BiV
return function (letter, i, exploded) {
if (letter === " ") {
return letter;
} else {
return colors[rainbowColors[i++ % rainbowColors.length]](letter);
}
};
})();

View File

@ -0,0 +1,8 @@
var colors = require('../colors');
module['exports'] = (function () {
var available = ['underline', 'inverse', 'grey', 'yellow', 'red', 'green', 'blue', 'white', 'cyan', 'magenta'];
return function(letter, i, exploded) {
return letter === " " ? letter : colors[available[Math.round(Math.random() * (available.length - 1))]](letter);
};
})();

View File

@ -0,0 +1,5 @@
var colors = require('../colors');
module['exports'] = function (letter, i, exploded) {
return i % 2 === 0 ? letter : colors.inverse(letter);
};

View File

@ -0,0 +1,77 @@
/*
The MIT License (MIT)
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.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.
*/
var styles = {};
module['exports'] = styles;
var codes = {
reset: [0, 0],
bold: [1, 22],
dim: [2, 22],
italic: [3, 23],
underline: [4, 24],
inverse: [7, 27],
hidden: [8, 28],
strikethrough: [9, 29],
black: [30, 39],
red: [31, 39],
green: [32, 39],
yellow: [33, 39],
blue: [34, 39],
magenta: [35, 39],
cyan: [36, 39],
white: [37, 39],
gray: [90, 39],
grey: [90, 39],
bgBlack: [40, 49],
bgRed: [41, 49],
bgGreen: [42, 49],
bgYellow: [43, 49],
bgBlue: [44, 49],
bgMagenta: [45, 49],
bgCyan: [46, 49],
bgWhite: [47, 49],
// legacy styles for colors pre v1.0.0
blackBG: [40, 49],
redBG: [41, 49],
greenBG: [42, 49],
yellowBG: [43, 49],
blueBG: [44, 49],
magentaBG: [45, 49],
cyanBG: [46, 49],
whiteBG: [47, 49]
};
Object.keys(codes).forEach(function (key) {
var val = codes[key];
var style = styles[key] = [];
style.open = '\u001b[' + val[0] + 'm';
style.close = '\u001b[' + val[1] + 'm';
});

View File

@ -0,0 +1,61 @@
/*
The MIT License (MIT)
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.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.
*/
var argv = process.argv;
module.exports = (function () {
if (argv.indexOf('--no-color') !== -1 ||
argv.indexOf('--color=false') !== -1) {
return false;
}
if (argv.indexOf('--color') !== -1 ||
argv.indexOf('--color=true') !== -1 ||
argv.indexOf('--color=always') !== -1) {
return true;
}
if (process.stdout && !process.stdout.isTTY) {
return false;
}
if (process.platform === 'win32') {
return true;
}
if ('COLORTERM' in process.env) {
return true;
}
if (process.env.TERM === 'dumb') {
return false;
}
if (/^screen|^xterm|^vt100|color|ansi|cygwin|linux/i.test(process.env.TERM)) {
return true;
}
return false;
})();

View File

@ -0,0 +1,59 @@
{
"name": "colors",
"description": "get colors in your node.js console",
"version": "1.1.2",
"author": {
"name": "Marak Squires"
},
"homepage": "https://github.com/Marak/colors.js",
"bugs": {
"url": "https://github.com/Marak/colors.js/issues"
},
"keywords": [
"ansi",
"terminal",
"colors"
],
"repository": {
"type": "git",
"url": "http://github.com/Marak/colors.js.git"
},
"license": "MIT",
"scripts": {
"test": "node tests/basic-test.js && node tests/safe-test.js"
},
"engines": {
"node": ">=0.1.90"
},
"main": "lib",
"files": [
"examples",
"lib",
"LICENSE",
"safe.js",
"themes"
],
"gitHead": "8bf2ad9fa695dcb30b7e9fd83691b139fd6655c4",
"_id": "colors@1.1.2",
"_shasum": "168a4701756b6a7f51a12ce0c97bfa28c084ed63",
"_from": "colors@>=1.1.2 <1.2.0",
"_npmVersion": "2.1.8",
"_nodeVersion": "0.11.13",
"_npmUser": {
"name": "marak",
"email": "marak.squires@gmail.com"
},
"maintainers": [
{
"name": "marak",
"email": "marak.squires@gmail.com"
}
],
"dist": {
"shasum": "168a4701756b6a7f51a12ce0c97bfa28c084ed63",
"tarball": "http://registry.npmjs.org/colors/-/colors-1.1.2.tgz"
},
"directories": {},
"_resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
"readme": "ERROR: No README data found!"
}

View File

@ -0,0 +1,9 @@
//
// Remark: Requiring this file will use the "safe" colors API which will not touch String.prototype
//
// var colors = require('colors/safe);
// colors.red("foo")
//
//
var colors = require('./lib/colors');
module['exports'] = colors;

View File

@ -0,0 +1,12 @@
module['exports'] = {
silly: 'rainbow',
input: 'grey',
verbose: 'cyan',
prompt: 'grey',
info: 'green',
data: 'grey',
help: 'cyan',
warn: 'yellow',
debug: 'blue',
error: 'red'
};

View File

@ -0,0 +1,299 @@
3.3.1 / 2015-05-13
------------------
- Added `.sortKeys` dumper option, thanks to @rjmunro.
- Fixed astral characters support, #191.
3.3.0 / 2015-04-26
------------------
- Significantly improved long strings formatting in dumper, thanks to @isaacs.
- Strip BOM if exists.
3.2.7 / 2015-02-19
------------------
- Maintenance release.
- Updated dependencies.
- HISTORY.md -> CHANGELOG.md
3.2.6 / 2015-02-07
------------------
- Fixed encoding of UTF-16 surrogate pairs. (e.g. "\U0001F431" CAT FACE).
- Fixed demo dates dump (#113, thanks to @Hypercubed).
3.2.5 / 2014-12-28
------------------
- Fixed resolving of all built-in types on empty nodes.
- Fixed invalid warning on empty lines within quoted scalars and flow collections.
- Fixed bug: Tag on an empty node didn't resolve in some cases.
3.2.4 / 2014-12-19
------------------
- Fixed resolving of !!null tag on an empty node.
3.2.3 / 2014-11-08
------------------
- Implemented dumping of objects with circular and cross references.
- Partially fixed aliasing of constructed objects. (see issue #141 for details)
3.2.2 / 2014-09-07
------------------
- Fixed infinite loop on unindented block scalars.
- Rewritten base64 encode/decode in binary type, to keep code licence clear.
3.2.1 / 2014-08-24
------------------
- Nothig new. Just fix npm publish error.
3.2.0 / 2014-08-24
------------------
- Added input piping support to CLI.
- Fixed typo, that could cause hand on initial indent (#139).
3.1.0 / 2014-07-07
------------------
- 1.5x-2x speed boost.
- Removed deprecated `require('xxx.yml')` support.
- Significant code cleanup and refactoring.
- Internal API changed. If you used custom types - see updated examples.
Others are not affected.
- Even if the input string has no trailing line break character,
it will be parsed as if it has one.
- Added benchmark scripts.
- Moved bower files to /dist folder
- Bugfixes.
3.0.2 / 2014-02-27
------------------
- Fixed bug: "constructor" string parsed as `null`.
3.0.1 / 2013-12-22
------------------
- Fixed parsing of literal scalars. (issue #108)
- Prevented adding unnecessary spaces in object dumps. (issue #68)
- Fixed dumping of objects with very long (> 1024 in length) keys.
3.0.0 / 2013-12-16
------------------
- Refactored code. Changed API for custom types.
- Removed output colors in CLI, dump json by default.
- Removed big dependencies from browser version (esprima, buffer)
- load `esprima` manually, if !!js/function needed
- !!bin now returns Array in browser
- AMD support.
- Don't quote dumped strings because of `-` & `?` (if not first char).
- __Deprecated__ loading yaml files via `require()`, as not recommended
behaviour for node.
2.1.3 / 2013-10-16
------------------
- Fix wrong loading of empty block scalars.
2.1.2 / 2013-10-07
------------------
- Fix unwanted line breaks in folded scalars.
2.1.1 / 2013-10-02
------------------
- Dumper now respects deprecated booleans syntax from YAML 1.0/1.1
- Fixed reader bug in JSON-like sequences/mappings.
2.1.0 / 2013-06-05
------------------
- Add standard YAML schemas: Failsafe (`FAILSAFE_SCHEMA`),
JSON (`JSON_SCHEMA`) and Core (`CORE_SCHEMA`).
- Rename `DEFAULT_SCHEMA` to `DEFAULT_FULL_SCHEMA`
and `SAFE_SCHEMA` to `DEFAULT_SAFE_SCHEMA`.
- Bug fix: export `NIL` constant from the public interface.
- Add `skipInvalid` dumper option.
- Use `safeLoad` for `require` extension.
2.0.5 / 2013-04-26
------------------
- Close security issue in !!js/function constructor.
Big thanks to @nealpoole for security audit.
2.0.4 / 2013-04-08
------------------
- Updated .npmignore to reduce package size
2.0.3 / 2013-02-26
------------------
- Fixed dumping of empty arrays ans objects. ([] and {} instead of null)
2.0.2 / 2013-02-15
------------------
- Fixed input validation: tabs are printable characters.
2.0.1 / 2013-02-09
------------------
- Fixed error, when options not passed to function cass
2.0.0 / 2013-02-09
------------------
- Full rewrite. New architecture. Fast one-stage parsing.
- Changed custom types API.
- Added YAML dumper.
1.0.3 / 2012-11-05
------------------
- Fixed utf-8 files loading.
1.0.2 / 2012-08-02
------------------
- Pull out hand-written shims. Use ES5-Shims for old browsers support. See #44.
- Fix timstamps incorectly parsed in local time when no time part specified.
1.0.1 / 2012-07-07
------------------
- Fixes `TypeError: 'undefined' is not an object` under Safari. Thanks Phuong.
- Fix timestamps incorrectly parsed in local time. Thanks @caolan. Closes #46.
1.0.0 / 2012-07-01
------------------
- `y`, `yes`, `n`, `no`, `on`, `off` are not converted to Booleans anymore.
Fixes #42.
- `require(filename)` now returns a single document and throws an Error if
file contains more than one document.
- CLI was merged back from js-yaml.bin
0.3.7 / 2012-02-28
------------------
- Fix export of `addConstructor()`. Closes #39.
0.3.6 / 2012-02-22
------------------
- Removed AMD parts - too buggy to use. Need help to rewrite from scratch
- Removed YUI compressor warning (renamed `double` variable). Closes #40.
0.3.5 / 2012-01-10
------------------
- Workagound for .npmignore fuckup under windows. Thanks to airportyh.
0.3.4 / 2011-12-24
------------------
- Fixes str[] for oldIEs support.
- Adds better has change support for browserified demo.
- improves compact output of Error. Closes #33.
0.3.3 / 2011-12-20
------------------
- jsyaml executable moved to separate module.
- adds `compact` stringification of Errors.
0.3.2 / 2011-12-16
------------------
- Fixes ug with block style scalars. Closes #26.
- All sources are passing JSLint now.
- Fixes bug in Safari. Closes #28.
- Fixes bug in Opers. Closes #29.
- Improves browser support. Closes #20.
- Added jsyaml executable.
- Added !!js/function support. Closes #12.
0.3.1 / 2011-11-18
------------------
- Added AMD support for browserified version.
- Wrapped browserified js-yaml into closure.
- Fixed the resolvement of non-specific tags. Closes #17.
- Added permalinks for online demo YAML snippets. Now we have YPaste service, lol.
- Added !!js/regexp and !!js/undefined types. Partially solves #12.
- Fixed !!set mapping.
- Fixed month parse in dates. Closes #19.
0.3.0 / 2011-11-09
------------------
- Removed JS.Class dependency. Closes #3.
- Added browserified version. Closes #13.
- Added live demo of browserified version.
- Ported some of the PyYAML tests. See #14.
- Fixed timestamp bug when fraction was given.
0.2.2 / 2011-11-06
------------------
- Fixed crash on docs without ---. Closes #8.
- Fixed miltiline string parse
- Fixed tests/comments for using array as key
0.2.1 / 2011-11-02
------------------
- Fixed short file read (<4k). Closes #9.
0.2.0 / 2011-11-02
------------------
- First public release

View File

@ -0,0 +1,21 @@
(The MIT License)
Copyright (C) 2011-2015 by Vitaly Puzrin
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.

View File

@ -0,0 +1,290 @@
JS-YAML - YAML 1.2 parser and serializer for JavaScript
=======================================================
[![Build Status](https://travis-ci.org/nodeca/js-yaml.svg?branch=master)](https://travis-ci.org/nodeca/js-yaml)
[![NPM version](https://img.shields.io/npm/v/js-yaml.svg)](https://www.npmjs.org/package/js-yaml)
[Online Demo](http://nodeca.github.com/js-yaml/)
This is an implementation of [YAML](http://yaml.org/), a human friendly data
serialization language. Started as [PyYAML](http://pyyaml.org/) port, it was
completely rewritten from scratch. Now it's very fast, and supports 1.2 spec.
Installation
------------
### YAML module for node.js
```
npm install js-yaml
```
### CLI executable
If you want to inspect your YAML files from CLI, install js-yaml globally:
```
npm install -g js-yaml
```
#### Usage
```
usage: js-yaml [-h] [-v] [-c] [-t] file
Positional arguments:
file File with YAML document(s)
Optional arguments:
-h, --help Show this help message and exit.
-v, --version Show program's version number and exit.
-c, --compact Display errors in compact mode
-t, --trace Show stack trace on error
```
### Bundled YAML library for browsers
``` html
<!-- esprima required only for !!js/function -->
<script src="esprima.js"></script>
<script src="js-yaml.min.js"></script>
<script type="text/javascript">
var doc = jsyaml.load('greeting: hello\nname: world');
</script>
```
Browser support was done mostly for online demo. If you find any errors - feel
free to send pull requests with fixes. Also note, that IE and other old browsers
needs [es5-shims](https://github.com/kriskowal/es5-shim) to operate.
Notes:
1. We have no resourses to support browserified version. Don't expect it to be
well tested. Don't expect fast fixes if something goes wrong there.
2. `!!js/function` in browser bundle will not work by default. If you really need
it - load `esprima` parser first (via amd or directly).
3. `!!bin` in browser will return `Array`, because browsers do not support
node.js `Buffer` and adding Buffer shims is completely useless on practice.
API
---
Here we cover the most 'useful' methods. If you need advanced details (creating
your own tags), see [wiki](https://github.com/nodeca/js-yaml/wiki) and
[examples](https://github.com/nodeca/js-yaml/tree/master/examples) for more
info.
``` javascript
yaml = require('js-yaml');
fs = require('fs');
// Get document, or throw exception on error
try {
var doc = yaml.safeLoad(fs.readFileSync('/home/ixti/example.yml', 'utf8'));
console.log(doc);
} catch (e) {
console.log(e);
}
```
### safeLoad (string [ , options ])
**Recommended loading way.** Parses `string` as single YAML document. Returns a JavaScript
object or throws `YAMLException` on error. By default, does not support regexps,
functions and undefined. This method is safe for untrusted data.
options:
- `filename` _(default: null)_ - string to be used as a file path in
error/warning messages.
- `onWarning` _(default: null)_ - function to call on warning messages.
Loader will throw on warnings if this function is not provided.
- `schema` _(default: `DEFAULT_SAFE_SCHEMA`)_ - specifies a schema to use.
- `FAILSAFE_SCHEMA` - only strings, arrays and plain objects:
http://www.yaml.org/spec/1.2/spec.html#id2802346
- `JSON_SCHEMA` - all JSON-supported types:
http://www.yaml.org/spec/1.2/spec.html#id2803231
- `CORE_SCHEMA` - same as `JSON_SCHEMA`:
http://www.yaml.org/spec/1.2/spec.html#id2804923
- `DEFAULT_SAFE_SCHEMA` - all supported YAML types, without unsafe ones
(`!!js/undefined`, `!!js/regexp` and `!!js/function`):
http://yaml.org/type/
- `DEFAULT_FULL_SCHEMA` - all supported YAML types.
NOTE: This function **does not** understand multi-document sources, it throws
exception on those.
NOTE: JS-YAML **does not** support schema-specific tag resolution restrictions.
So, JSON schema is not as strict as defined in the YAML specification.
It allows numbers in any notaion, use `Null` and `NULL` as `null`, etc.
Core schema also has no such restrictions. It allows binary notation for integers.
### load (string [ , options ])
**Use with care with untrusted sources**. The same as `safeLoad()` but uses
`DEFAULT_FULL_SCHEMA` by default - adds some JavaScript-specific types:
`!!js/function`, `!!js/regexp` and `!!js/undefined`. For untrusted sources you
must additionally validate object structure, to avoid injections:
``` javascript
var untrusted_code = '"toString": !<tag:yaml.org,2002:js/function> "function (){very_evil_thing();}"';
// I'm just converting that string, what could possibly go wrong?
require('js-yaml').load(untrusted_code) + ''
```
### safeLoadAll (string, iterator [ , options ])
Same as `safeLoad()`, but understands multi-document sources and apply
`iterator` to each document.
``` javascript
var yaml = require('js-yaml');
yaml.safeLoadAll(data, function (doc) {
console.log(doc);
});
```
### loadAll (string, iterator [ , options ])
Same as `safeLoadAll()` but uses `DEFAULT_FULL_SCHEMA` by default.
### safeDump (object [ , options ])
Serializes `object` as YAML document. Uses `DEFAULT_SAFE_SCHEMA`, so it will
throw exception if you try to dump regexps or functions. However, you can
disable exceptions by `skipInvalid` option.
options:
- `indent` _(default: 2)_ - indentation width to use (in spaces).
- `skipInvalid` _(default: false)_ - do not throw on invalid types (like function
in the safe schema) and skip pairs and single values with such types.
- `flowLevel` (default: -1) - specifies level of nesting, when to switch from
block to flow style for collections. -1 means block style everwhere
- `styles` - "tag" => "style" map. Each tag may have own set of styles.
- `schema` _(default: `DEFAULT_SAFE_SCHEMA`)_ specifies a schema to use.
- `sortKeys` _(default: `false`)_ - if `true`, sort keys when dumping YAML. If a
function, use the function to sort the keys.
styles:
``` none
!!null
"canonical" => "~"
!!int
"binary" => "0b1", "0b101010", "0b1110001111010"
"octal" => "01", "052", "016172"
"decimal" => "1", "42", "7290"
"hexadecimal" => "0x1", "0x2A", "0x1C7A"
!!null, !!bool, !!float
"lowercase" => "null", "true", "false", ".nan", '.inf'
"uppercase" => "NULL", "TRUE", "FALSE", ".NAN", '.INF'
"camelcase" => "Null", "True", "False", ".NaN", '.Inf'
```
By default, !!int uses `decimal`, and !!null, !!bool, !!float use `lowercase`.
### dump (object [ , options ])
Same as `safeDump()` but without limits (uses `DEFAULT_FULL_SCHEMA` by default).
Supported YAML types
--------------------
The list of standard YAML tags and corresponding JavaScipt types. See also
[YAML tag discussion](http://pyyaml.org/wiki/YAMLTagDiscussion) and
[YAML types repository](http://yaml.org/type/).
```
!!null '' # null
!!bool 'yes' # bool
!!int '3...' # number
!!float '3.14...' # number
!!binary '...base64...' # buffer
!!timestamp 'YYYY-...' # date
!!omap [ ... ] # array of key-value pairs
!!pairs [ ... ] # array or array pairs
!!set { ... } # array of objects with given keys and null values
!!str '...' # string
!!seq [ ... ] # array
!!map { ... } # object
```
**JavaScript-specific tags**
```
!!js/regexp /pattern/gim # RegExp
!!js/undefined '' # Undefined
!!js/function 'function () {...}' # Function
```
Caveats
-------
Note, that you use arrays or objects as key in JS-YAML. JS do not allows objects
or array as keys, and stringifies (by calling .toString method) them at the
moment of adding them.
``` yaml
---
? [ foo, bar ]
: - baz
? { foo: bar }
: - baz
- baz
```
``` javascript
{ "foo,bar": ["baz"], "[object Object]": ["baz", "baz"] }
```
Also, reading of properties on implicit block mapping keys is not supported yet.
So, the following YAML document cannot be loaded.
``` yaml
&anchor foo:
foo: bar
*anchor: duplicate key
baz: bat
*anchor: duplicate key
```
Breaking changes in 2.x.x -> 3.x.x
----------------------------------
If your have not used __custom__ tags or loader classes and not loaded yaml
files via `require()` - no changes needed. Just upgrade library.
In other case, you should:
1. Replace all occurences of `require('xxxx.yml')` by `fs.readFileSync()` +
`yaml.safeLoad()`.
2. rewrite your custom tags constructors and custom loader
classes, to conform new API. See
[examples](https://github.com/nodeca/js-yaml/tree/master/examples) and
[wiki](https://github.com/nodeca/js-yaml/wiki) for details.
License
-------
View the [LICENSE](https://github.com/nodeca/js-yaml/blob/master/LICENSE) file
(MIT).

View File

@ -0,0 +1,142 @@
#!/usr/bin/env node
'use strict';
/*eslint-disable no-console*/
// stdlib
var fs = require('fs');
// 3rd-party
var argparse = require('argparse');
// internal
var yaml = require('..');
////////////////////////////////////////////////////////////////////////////////
var cli = new argparse.ArgumentParser({
prog: 'js-yaml',
version: require('../package.json').version,
addHelp: true
});
cli.addArgument([ '-c', '--compact' ], {
help: 'Display errors in compact mode',
action: 'storeTrue'
});
// deprecated (not needed after we removed output colors)
// option suppressed, but not completely removed for compatibility
cli.addArgument([ '-j', '--to-json' ], {
help: argparse.Const.SUPPRESS,
dest: 'json',
action: 'storeTrue'
});
cli.addArgument([ '-t', '--trace' ], {
help: 'Show stack trace on error',
action: 'storeTrue'
});
cli.addArgument([ 'file' ], {
help: 'File to read, utf-8 encoded without BOM',
nargs: '?',
defaultValue: '-'
});
////////////////////////////////////////////////////////////////////////////////
var options = cli.parseArgs();
////////////////////////////////////////////////////////////////////////////////
function readFile(filename, encoding, callback) {
if (options.file === '-') {
// read from stdin
var chunks = [];
process.stdin.on('data', function (chunk) {
chunks.push(chunk);
});
process.stdin.on('end', function () {
return callback(null, Buffer.concat(chunks).toString(encoding));
});
} else {
fs.readFile(filename, encoding, callback);
}
}
readFile(options.file, 'utf8', function (error, input) {
var output, isYaml;
if (error) {
if (error.code === 'ENOENT') {
console.error('File not found: ' + options.file);
process.exit(2);
}
console.error(
options.trace && error.stack ||
error.message ||
String(error));
process.exit(1);
}
try {
output = JSON.parse(input);
isYaml = false;
} catch (error) {
if (error instanceof SyntaxError) {
try {
output = [];
yaml.loadAll(input, function (doc) { output.push(doc); }, {});
isYaml = true;
if (0 === output.length) {
output = null;
} else if (1 === output.length) {
output = output[0];
}
} catch (error) {
if (options.trace && error.stack) {
console.error(error.stack);
} else {
console.error(error.toString(options.compact));
}
process.exit(1);
}
} else {
console.error(
options.trace && error.stack ||
error.message ||
String(error));
process.exit(1);
}
}
if (isYaml) {
console.log(JSON.stringify(output, null, ' '));
} else {
console.log(yaml.dump(output));
}
process.exit(0);
});

View File

@ -0,0 +1,23 @@
{
"name": "js-yaml",
"main": "dist/js-yaml.js",
"homepage": "https://github.com/nodeca/js-yaml",
"authors": [ "Dervus Grim <dervus.grim@gmail.com>",
"Vitaly Puzrin <vitaly@rcdesign.ru>",
"Aleksey V Zapparov <ixti@member.fsf.org>",
"Martin Grenfell <martin.grenfell@gmail.com>" ],
"description": "YAML 1.2 parser and serializer",
"keywords": ["yaml", "parser", "serializer", "pyyaml"],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"benchmark",
"bower_components",
"test",
"Makefile",
"index*",
"package.json"
]
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,103 @@
'use strict';
/*eslint-disable no-console*/
var fs = require('fs');
var path = require('path');
var util = require('util');
var yaml = require('../lib/js-yaml');
// Let's define a couple of classes.
function Point(x, y, z) {
this.klass = 'Point';
this.x = x;
this.y = y;
this.z = z;
}
function Space(height, width, points) {
if (points) {
if (!points.every(function (point) { return point instanceof Point; })) {
throw new Error('A non-Point inside a points array!');
}
}
this.klass = 'Space';
this.height = height;
this.width = width;
this.points = points;
}
// Then define YAML types to load and dump our Point/Space objects.
var PointYamlType = new yaml.Type('!point', {
// Loader must parse sequence nodes only for this type (i.e. arrays in JS terminology).
// Other available kinds are 'scalar' (string) and 'mapping' (object).
// http://www.yaml.org/spec/1.2/spec.html#kind//
kind: 'sequence',
// Loader must check if the input object is suitable for this type.
resolve: function (data) {
// `data` may be either:
// - Null in case of an "empty node" (http://www.yaml.org/spec/1.2/spec.html#id2786563)
// - Array since we specified `kind` to 'sequence'
return data !== null && data.length === 3;
},
// If a node is resolved, use it to create a Point instance.
construct: function (data) {
return new Point(data[0], data[1], data[2]);
},
// Dumper must process instances of Point by rules of this YAML type.
instanceOf: Point,
// Dumper must represent Point objects as three-element sequence in YAML.
represent: function (point) {
return [ point.x, point.y, point.z ];
}
});
var SpaceYamlType = new yaml.Type('!space', {
kind: 'mapping',
construct: function (data) {
data = data || {}; // in case of empty node
return new Space(data.height || 0, data.width || 0, data.points || []);
},
instanceOf: Space
// `represent` is omitted here. So, Space objects will be dumped as is.
// That is regular mapping with three key-value pairs but with !space tag.
});
// After our types are defined, it's time to join them into a schema.
var SPACE_SCHEMA = yaml.Schema.create([ SpaceYamlType, PointYamlType ]);
// do not execute the following if file is required (http://stackoverflow.com/a/6398335)
if (require.main === module) {
// And read a document using that schema.
fs.readFile(path.join(__dirname, 'custom_types.yml'), 'utf8', function (error, data) {
var loaded;
if (!error) {
loaded = yaml.load(data, { schema: SPACE_SCHEMA });
console.log(util.inspect(loaded, false, 20, true));
} else {
console.error(error.stack || error.message || String(error));
}
});
}
// There are some exports to play with this example interactively.
module.exports.Point = Point;
module.exports.Space = Space;
module.exports.PointYamlType = PointYamlType;
module.exports.SpaceYamlType = SpaceYamlType;
module.exports.SPACE_SCHEMA = SPACE_SCHEMA;

View File

@ -0,0 +1,18 @@
subject: Custom types in JS-YAML
spaces:
- !space
height: 1000
width: 1000
points:
- !point [ 10, 43, 23 ]
- !point [ 165, 0, 50 ]
- !point [ 100, 100, 100 ]
- !space
height: 64
width: 128
points:
- !point [ 12, 43, 0 ]
- !point [ 1, 4, 90 ]
- !space # An empty space

View File

@ -0,0 +1,32 @@
'use strict';
/*eslint-disable no-console*/
var yaml = require('../lib/js-yaml');
var object = require('./dumper.json');
console.log(yaml.dump(object, {
flowLevel: 3,
styles: {
'!!int' : 'hexadecimal',
'!!null' : 'camelcase'
}
}));
// Output:
//==============================================================================
// name: Wizzard
// level: 0x11
// sanity: Null
// inventory:
// - name: Hat
// features: [magic, pointed]
// traits: {}
// - name: Staff
// features: []
// traits: {damage: 0xA}
// - name: Cloak
// features: [old]
// traits: {defence: 0x0, comfort: 0x3}

View File

@ -0,0 +1,22 @@
{
"name" : "Wizzard",
"level" : 17,
"sanity" : null,
"inventory" : [
{
"name" : "Hat",
"features" : [ "magic", "pointed" ],
"traits" : {}
},
{
"name" : "Staff",
"features" : [],
"traits" : { "damage" : 10 }
},
{
"name" : "Cloak",
"features" : [ "old" ],
"traits" : { "defence" : 0, "comfort" : 3 }
}
]
}

Some files were not shown because too many files have changed in this diff Show More