Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

parsec

Package Overview
Dependencies
Maintainers
1
Versions
26
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

parsec - npm Package Compare versions

Comparing version 0.3.0 to 1.0.0

example/index.js

690

dist/index.js
"use strict";
var _createClass = require("babel-runtime/helpers/create-class")["default"];
var _toConsumableArray = require("babel-runtime/helpers/to-consumable-array")["default"];
var _classCallCheck = require("babel-runtime/helpers/class-call-check")["default"];
var _slicedToArray = require("babel-runtime/helpers/sliced-to-array")["default"];
var _regeneratorRuntime = require("babel-runtime/regenerator")["default"];
var _getIterator = require("babel-runtime/core-js/get-iterator")["default"];
var _Object$keys = require("babel-runtime/core-js/object/keys")["default"];
Object.defineProperty(exports, "__esModule", {

@@ -19,634 +9,78 @@ value: true

var Parsec = (function () {
function Parsec(_ref) {
var _ref$operandsKey = _ref.operandsKey;
var operandsKey = _ref$operandsKey === undefined ? "_" : _ref$operandsKey;
var _ref$noFlags = _ref.noFlags;
var noFlags = _ref$noFlags === undefined ? true : _ref$noFlags;
var _ref$sliceIndex = _ref.sliceIndex;
var sliceIndex = _ref$sliceIndex === undefined ? 2 : _ref$sliceIndex;
var _ref$strictMode = _ref.strictMode;
var strictMode = _ref$strictMode === undefined ? false : _ref$strictMode;
_classCallCheck(this, Parsec);
/**
@type {String} Key name for array of operands. _ by default.
*/
this.operandsKey = operandsKey;
/**
@type {Boolean} Given an option A, create another option no-A==!A.
*/
this.noFlags = noFlags;
/**
@type {Boolean} Slice index for arguments array.
*/
this.sliceIndex = sliceIndex;
/**
@type {Boolean} Throw a runtime error if unknown flags are found.
*/
this.strictMode = strictMode;
/**
@private
@type Flag the end of options was found.
*/
this._endOfOpts = false;
exports["default"] = function () {
for (var _len = arguments.length, aliases = Array(_len), _key = 0; _key < _len; _key++) {
aliases[_key] = arguments[_key];
}
/**
Create mappings for cli options.
@param {[String]} keys to map to cli args
@param {Object} options
*/
return parse(this || process.argv.slice(2), aliases.map(function (alias) {
return typeof alias === "string" ? [alias, alias[0]] : alias;
}));
};
_createClass(Parsec, [{
key: "entries",
function find(aliases, fun) {
return aliases.some(function (alias) {
return fun(alias = alias.slice(), typeof alias[alias.length - 1] === "object" && alias.pop()["default"]);
});
}
/**
Iterator for entries
@param {Array} args
@param {String} operandsKey
@return {{ key, value }}
*/
value: _regeneratorRuntime.mark(function entries(args) {
var isBool, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, _step$value, curr, next, value;
function parse(argv, aliases) {
var map = {},
stack = [],
keys = aliases.length > 0 ? aliases.reduce(function (p, n) {
return p.concat(n);
}).filter(function (a) {
return typeof a !== "object";
}) : null;
var unknown = typeof aliases[aliases.length - 1] === "function" ? aliases.pop() : Function;
return _regeneratorRuntime.wrap(function entries$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
isBool = function isBool(obj) {
return typeof obj === "boolean" || obj === "true" || obj === "false";
};
argv.some(function (token, index) {
if (token === "--") return add("_", argv.slice(index + 1)) || true;
_iteratorNormalCompletion = true;
_didIteratorError = false;
_iteratorError = undefined;
context$2$0.prev = 4;
_iterator = _getIterator(this.tokens(args));
case 6:
if (_iteratorNormalCompletion = (_step = _iterator.next()).done) {
context$2$0.next = 24;
break;
}
_step$value = _step.value;
curr = _step$value.curr;
next = _step$value.next;
value = _step$value.value;
if (!curr.isBare) {
context$2$0.next = 18;
break;
}
if (!this.isEndOfOpts(curr.token)) {
context$2$0.next = 14;
break;
}
return context$2$0.abrupt("continue", 21);
case 14:
context$2$0.next = 16;
return { key: this.operandsKey, value: curr.token };
case 16:
context$2$0.next = 21;
break;
case 18:
if (this.noFlags && isBool(value) && curr.isNoFlag) {
curr.token = curr.noKey;
value = value === "false" ? true : value === "true" ? false : !value;
}
context$2$0.next = 21;
return { key: curr.token, value: value };
case 21:
_iteratorNormalCompletion = true;
context$2$0.next = 6;
break;
case 24:
context$2$0.next = 30;
break;
case 26:
context$2$0.prev = 26;
context$2$0.t0 = context$2$0["catch"](4);
_didIteratorError = true;
_iteratorError = context$2$0.t0;
case 30:
context$2$0.prev = 30;
context$2$0.prev = 31;
if (!_iteratorNormalCompletion && _iterator["return"]) {
_iterator["return"]();
}
case 33:
context$2$0.prev = 33;
if (!_didIteratorError) {
context$2$0.next = 36;
break;
}
throw _iteratorError;
case 36:
return context$2$0.finish(33);
case 37:
return context$2$0.finish(30);
case 38:
case "end":
return context$2$0.stop();
}
}, entries, this, [[4, 26, 30, 38], [31,, 33, 37]]);
})
/**
Token iterator for options.
@param {Array} args
@return {{ curr, next, value }}
*/
}, {
key: "tokens",
value: _regeneratorRuntime.mark(function tokens(args) {
var _iteratorNormalCompletion2, _didIteratorError2, _iteratorError2, _iterator2, _step2, _step2$value, prev, curr, next;
return _regeneratorRuntime.wrap(function tokens$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
_iteratorNormalCompletion2 = true;
_didIteratorError2 = false;
_iteratorError2 = undefined;
context$2$0.prev = 3;
_iterator2 = _getIterator(this.tuples(this.map(args)));
case 5:
if (_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done) {
context$2$0.next = 31;
break;
}
_step2$value = _step2.value;
prev = _step2$value.prev;
curr = _step2$value.curr;
next = _step2$value.next;
if (!(curr === undefined)) {
context$2$0.next = 12;
break;
}
return context$2$0.abrupt("continue", 28);
case 12:
if (!curr.isLong) {
context$2$0.next = 17;
break;
}
context$2$0.next = 15;
return {
prev: prev, curr: curr,
value: curr.value !== undefined ? curr.value : next !== undefined && next.isBare ? next.token : true
};
case 15:
context$2$0.next = 28;
break;
case 17:
if (!curr.isShort) {
context$2$0.next = 21;
break;
}
return context$2$0.delegateYield(this.shorts(this.tuples(curr.tokens, next)), "t0", 19);
case 19:
context$2$0.next = 28;
break;
case 21:
if (!(prev !== undefined)) {
context$2$0.next = 26;
break;
}
if (!(prev.isLong && prev.value === undefined)) {
context$2$0.next = 24;
break;
}
return context$2$0.abrupt("continue", 28);
case 24:
if (!(prev.isShort && isNaN(prev.tokens.pop()))) {
context$2$0.next = 26;
break;
}
return context$2$0.abrupt("continue", 28);
case 26:
context$2$0.next = 28;
return { prev: prev, curr: curr };
case 28:
_iteratorNormalCompletion2 = true;
context$2$0.next = 5;
break;
case 31:
context$2$0.next = 37;
break;
case 33:
context$2$0.prev = 33;
context$2$0.t1 = context$2$0["catch"](3);
_didIteratorError2 = true;
_iteratorError2 = context$2$0.t1;
case 37:
context$2$0.prev = 37;
context$2$0.prev = 38;
if (!_iteratorNormalCompletion2 && _iterator2["return"]) {
_iterator2["return"]();
}
case 40:
context$2$0.prev = 40;
if (!_didIteratorError2) {
context$2$0.next = 43;
break;
}
throw _iteratorError2;
case 43:
return context$2$0.finish(40);
case 44:
return context$2$0.finish(37);
case 45:
case "end":
return context$2$0.stop();
}
}, tokens, this, [[3, 33, 37, 45], [38,, 40, 44]]);
})
/**
Token sub-iterator for short options.
@param {Token} *iterator
@return {{ curr: {Token}, value }}
*/
}, {
key: "shorts",
value: _regeneratorRuntime.mark(function shorts(iterator) {
var _iteratorNormalCompletion3, _didIteratorError3, _iteratorError3, _iterator3, _step3, _step3$value, curr, next, last;
return _regeneratorRuntime.wrap(function shorts$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
_iteratorNormalCompletion3 = true;
_didIteratorError3 = false;
_iteratorError3 = undefined;
context$2$0.prev = 3;
_iterator3 = _getIterator(iterator);
case 5:
if (_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done) {
context$2$0.next = 17;
break;
}
_step3$value = _step3.value;
curr = _step3$value.curr;
next = _step3$value.next;
last = _step3$value.last;
if (isNaN(curr)) {
context$2$0.next = 12;
break;
}
return context$2$0.abrupt("continue", 14);
case 12:
context$2$0.next = 14;
return {
curr: { token: curr },
value: next === undefined && last !== undefined && last.isBare && !this.isEndOfOpts(last.token) ? last.token : !isNaN(next) ? next : true
};
case 14:
_iteratorNormalCompletion3 = true;
context$2$0.next = 5;
break;
case 17:
context$2$0.next = 23;
break;
case 19:
context$2$0.prev = 19;
context$2$0.t0 = context$2$0["catch"](3);
_didIteratorError3 = true;
_iteratorError3 = context$2$0.t0;
case 23:
context$2$0.prev = 23;
context$2$0.prev = 24;
if (!_iteratorNormalCompletion3 && _iterator3["return"]) {
_iterator3["return"]();
}
case 26:
context$2$0.prev = 26;
if (!_didIteratorError3) {
context$2$0.next = 29;
break;
}
throw _iteratorError3;
case 29:
return context$2$0.finish(26);
case 30:
return context$2$0.finish(23);
case 31:
case "end":
return context$2$0.stop();
}
}, shorts, this, [[3, 19, 23, 31], [24,, 26, 30]]);
})
/**
Map CLI arguments to custom Token objects.
Short Options { isShort, tokens }
Long Options { isLong, key, value, token }
Operands { token, isBare }
@param {Array} args
@return {[Token]}
*/
}, {
key: "map",
value: function map(args) {
var _this = this;
return args.map(function (token) {
if (!_this._endOfOpts) {
_this._endOfOpts = _this.isEndOfOpts(token);
if (/^-[a-z]/i.test(token)) {
var TOKENS = /([+-]?(\.?\d+\d+)?)|([a-z])/ig;
return {
isShort: true,
tokens: token.slice(1).split(TOKENS).filter(function (_) {
return _;
})
};
}
if (/^--[a-z]/i.test(token)) {
return (function (_ref2) {
var _ref22 = _slicedToArray(_ref2, 2);
var key = _ref22[0];
var value = _ref22[1];
return {
isLong: true,
key: key, value: value, token: key.slice(2),
isNoFlag: /^--no-([^-\n]{1}.*$)/.test(key),
noKey: key.replace(/^--no-([^-\n]{1}.*$)/, "$1")
};
})((function (pair) {
return [pair.shift(), pair[0]];
})(token.split("=")));
}
}
return { token: token, isBare: token !== undefined };
if (/^[a-z/"'@#$`~.]|^[+-]?[0-9]\d*(\.\d+)?$/i.test(token)) {
add.apply(undefined, _toConsumableArray(stack.length === 0 ? ["_", [token]] : [stack.pop(), token]));
} else if (/^-[a-z]/i.test(token)) {
var _index = (token = token.slice(1)).search(/[\d\W]/i);
var split = ~_index ? token.slice(0, _index) : token;
if (~_index) {
add(split[split.length - 1], token.slice(_index));
} else {
stack.push(split[split.length - 1]);
}
split.split("").slice(0, -1).forEach(function (key) {
return add(key);
});
}
/**
Returns an iterator that yields objects of the form
`{ prev, curr, next }` for each item in a list.
@param {Array | String} List to iterate.
@param {Object} [last] last value (optional)
@param {Object} [prev] first prev value (optional)
@param {Object} [curr] first curr value (optional)
@return { prev, curr, next } for each item in a list
*/
}, {
key: "tuples",
value: _regeneratorRuntime.mark(function tuples(list, last, prev, curr) {
var _iteratorNormalCompletion4, _didIteratorError4, _iteratorError4, _iterator4, _step4, next;
return _regeneratorRuntime.wrap(function tuples$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
_iteratorNormalCompletion4 = true;
_didIteratorError4 = false;
_iteratorError4 = undefined;
context$2$0.prev = 3;
_iterator4 = _getIterator(list);
case 5:
if (_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done) {
context$2$0.next = 15;
break;
}
next = _step4.value;
if (!(curr !== undefined)) {
context$2$0.next = 10;
break;
}
context$2$0.next = 10;
return { prev: prev, curr: curr, next: next };
case 10:
prev = curr;
curr = next;
case 12:
_iteratorNormalCompletion4 = true;
context$2$0.next = 5;
break;
case 15:
context$2$0.next = 21;
break;
case 17:
context$2$0.prev = 17;
context$2$0.t0 = context$2$0["catch"](3);
_didIteratorError4 = true;
_iteratorError4 = context$2$0.t0;
case 21:
context$2$0.prev = 21;
context$2$0.prev = 22;
if (!_iteratorNormalCompletion4 && _iterator4["return"]) {
_iterator4["return"]();
}
case 24:
context$2$0.prev = 24;
if (!_didIteratorError4) {
context$2$0.next = 27;
break;
}
throw _iteratorError4;
case 27:
return context$2$0.finish(24);
case 28:
return context$2$0.finish(21);
case 29:
context$2$0.next = 31;
return { prev: prev, curr: curr, last: last };
case 31:
case "end":
return context$2$0.stop();
}
}, tuples, this, [[3, 17, 21, 29], [22,, 24, 28]]);
})
}, {
key: "isEndOfOpts",
value: function isEndOfOpts(token) {
return /^--[-]*$/.test(token);
}
}], [{
key: "options",
value: function options(keys, opts) {
return options.call({ $: [], options: options }, keys, opts);
function options(keys, opts) {
this.$.push({ options: opts, keys: Array.isArray(keys) ? keys : [keys, keys.charAt(0)]
});
this.parse = parse.bind(this);
return this;
} else if (/^--[a-z]/i.test(token)) {
if (~(token = token.slice(2)).indexOf("=")) {
add.apply(undefined, _toConsumableArray(token.split("=")));
} else if (/^no-.+/.test(token)) {
add(token.match(/^no-(.+)/)[1], false);
} else {
stack.push(token);
}
}
} else throw new RangeError("invalid option " + token);
});
stack.forEach(function (key) {
return add(key);
});
find(aliases, function (alias, value) {
return alias.forEach(function (a) {
return map[a] = map[a] === undefined ? value : map[a];
});
});
/**
Parse an array of arguments like process.argv
@param {[String]} args
@param {Object} options
*/
}, {
key: "parse",
value: (function (_parse) {
function parse(_x, _x2) {
return _parse.apply(this, arguments);
}
return map;
parse.toString = function () {
return _parse.toString();
};
function add(key) {
var value = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1];
return parse;
})(function (args, options) {
return parse.call({}, args, options);
})
}]);
map[key] = map[key] === undefined ? value : Array.isArray(map[key]) ? map[key].concat(value) : [map[key]].concat(value);
return Parsec;
})();
exports["default"] = Parsec;
function parse(args) {
var _this2 = this;
var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
delete this.options;
delete this.parse;
var p = new Parsec(options);
var _iteratorNormalCompletion5 = true;
var _didIteratorError5 = false;
var _iteratorError5 = undefined;
try {
for (var _iterator5 = _getIterator(p.entries(args.slice(p.sliceIndex))), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
var _step5$value = _step5.value;
var key = _step5$value.key;
var value = _step5$value.value;
this[key] = this[key] === undefined ? value : !contains(this[key], value) ? [].concat(this[key], value) : this[key];
}
} catch (err) {
_didIteratorError5 = true;
_iteratorError5 = err;
} finally {
try {
if (!_iteratorNormalCompletion5 && _iterator5["return"]) {
_iterator5["return"]();
}
} finally {
if (_didIteratorError5) {
throw _iteratorError5;
}
}
}
function contains(key, value) {
return key === value || Array.isArray(key) && ~key.indexOf(value);
}
if (this.$) {
this.$.forEach(function (_ref3) {
var keys = _ref3.keys;
var options = _ref3.options;
keys = Array.isArray(keys) ? keys : [keys, keys.charAt(0)];
var value = _this2[keys.filter(function (key) {
return _this2[key] !== undefined;
})];
keys.forEach((function (key) {
this[key] = value !== undefined ? value : options !== undefined ? options["default"] : value;
}).bind(_this2));
find(aliases, function (alias, _value) {
if (! ~keys.indexOf(key)) return !unknown(key);
if (~alias.indexOf(key)) alias.forEach(function (key) {
return map[key] = typeof value === typeof _value ? value : value === true ? _value : value;
});
});
if (options.strictMode) {
(function () {
var validKeys = _this2.$.reduce(function (prev, next) {
return prev.concat(next.keys);
}, []).concat("_");
_Object$keys(_this2).filter(function (key) {
return key !== "$";
}).forEach(function (key) {
if (! ~validKeys.indexOf(key)) {
throw { code: "INVALID_OPTION", key: key };
}
});
})();
}
}
return this;
}
module.exports = exports["default"];
{
"name": "parsec",
"version": "0.3.0",
"description": "Generator-based CLI parser.",
"version": "1.0.0",
"description": "",
"main": "dist/index.js",
"directories": {
"test": "test"
},
"scripts": {
"build": "babel --optional runtime src/ -d ./dist",
"setup": "npm i && npm run test",
"build": "babel --optional runtime src -d dist",
"test": "npm run build && npm run tape | tspec",
"tape": "tape test/*.js",
"setup": "npm i",
"deploy": "npm run build && npm run test && git push origin master && npm publish"
"deploy": "npm run test && git push origin master && npm publish",
"tape": "node --harmony --harmony_arrow_functions ./node_modules/tape/bin/tape test/*.js"
},

@@ -16,0 +13,0 @@ "repository": {

@@ -1,2 +0,2 @@

> Generator-based CLI parser.
> CLI parser

@@ -13,3 +13,3 @@ [![][parsec-badge]][parsec]

<a href="https://github.com/bucaran/parsec/blob/master/README.md">
<img width="75%" src="https://cloud.githubusercontent.com/assets/8317250/7609248/1735d894-f9ac-11e4-9f80-eb0483533355.png">
<img width="65%" src="https://cloud.githubusercontent.com/assets/8317250/7609248/1735d894-f9ac-11e4-9f80-eb0483533355.png">
</a>

@@ -33,3 +33,3 @@ </p>

# Install
## Install

@@ -40,170 +40,158 @@ ```sh

# Synopsis
## Synopsis
_Parsec_ is a generator-based CLI options parser written in ES6.
_Parsec_ is a [CLI parser](https://en.wikipedia.org/wiki/Command-line_interface#Arguments) in 60 LOC.
```js
// $ ./app.js --msg=hi
Parsec
.options("msg")
.options("verbose", { default: false })
.parse(process.argv)
// node ./index.js -abc --secret=42
parse()
```
```json
{
"message": "hi",
"m": "hi",
"v": true,
"verbose": true
"a": true,
"b": true,
"c": true,
"secret": 42
}
```
# Usage
Customize:
```js
Parsec
.options("option string")
.options([aliases], { default: "value" })
.parse(argv, {
sliceIndex: 2,
operandsKey: "_",
noFlags: true,
strictMode: false
})
// node ./index.js -s42
parse("secret", ["verbose", "V", { default: true }])
```
The first letter of an option string is used as a short alias by default.
```js
// $ ./app.js -tla
Parsec
.options("three")
.options("letter")
.options("abbreviation")
.parse(process.argv)
```
```json
{
"t": true,
"l": true,
"a": true,
"three": true,
"letter": true,
"abbreviation": true
"s": 42,
"secret": 42,
"V": true,
"verbose": true
}
```
Use an array to specify multiple aliases, and default values via `{ default: value }`
## Features
```js
// $ ./app.js -G
Parsec
.options(["G", "g", "great"])
.options(["r", "R"], { default: 8 })
.parse(process.argv)
```
+ Simple
+ Based in the [UNIX Utility Conventions](http://pubs.opengroup.org/onlinepubs/7908799/xbd/utilconv.html)
+ Custom aliases
* Default shorthands
+ Default values / types
+ Handle `--no-*` options
+ Handle unknown options
```json
{
"G":true,
"g":true,
"great":true,
"r":8,
"R":8
}
```
## Usage
Options with `--no-` prefix before are parsed out of the box.
> Parse [`process.argv`](https://nodejs.org/docs/latest/api/process.html#process_process_argv) by default.
> Use `{ noFlags: false }` to disable this behavior.
```js
// $ ./app.js --no-woman-no-cry --no-wonder=false
Parsec.parse(process.argv)
import parse from "parsec"
parse(alias1, alias2, ...)
```
```json
{
"woman-no-cry": false,
"wonder": true
}
```
_Parsec_ automatically slices arguments starting from index `2`. To specify a different slice index:
```js
Parsec.parse(["--how", "--nice"], { sliceIndex: 0 })
```
+ Custom aliases
## API
```js
// node ./index.js --bar
parse(["foo", "bar", "baz"])
```
### `Parsec.options (aliases, { default: value })`
```json
{
"foo": true,
"bar": true,
"baz": true
}
```
+ #### `aliases`
+ Example aliases
+ Use a string to define a single long alias. The first character of the string will be used as the short alias by default.
```js
"foo" // → ["f", "foo"]
["F", "f", "foo"]
["foo", { default: "./" }]
["baz", { default: true }]
```
+ To specify a custom list of aliases pass an array of strings.
+ Default shorthands
+ Use the optional, `{ default: value }` to specify a default value for the option.
```js
// node ./index.js -fb
parse("foo", "bar")
```
```json
{
"f": true,
"foo": true,
"b": true,
"bar": true,
}
```
### `Parsec.parse (argv, options)`
+ Default values and types
+ #### `argv`
```js
// node ./index.js --file
parse(["f", "file", { default: "." }])
```
```json
{
"f": ".",
"file": "."
}
```
Array with cli arguments. Usually [`process.argv`](https://nodejs.org/docs/latest/api/process.html#process_process_argv).
+ Handle `--no-flags`
```js
// node ./index.js --no-foo --no-bar=baz
parse()
```
```json
{
"foo": false,
"no-bar": "baz"
}
```
+ #### `options`
+ Handle unknown options
+ `operandsKey` Key name for array of operands. _ by default.
```js
// node ./index.js --bar
parse("foo", (option) => {
throw new RangeError(`unknown option ${option}`) // bar
})
```
+ `noFlags` Given an option A, create another option no-A==!A.
+ Bare operands and arguments after `--` are added to `._`. To override:
```js
Parsec.parse(["--no-love=false", "--no-war", "--no-no", "ok"],
{ sliceIndex: 0 })
```
```json
{
"love": true,
"war": false,
"no-no": "ok"
}
```
```js
parse(["_", "alias"])
```
+ `sliceIndex` Slice index for arguments array.
+ Bind `parse` to a different source of arguments:
+ `strictMode` Throw a runtime error if unknown flags are found.
```js
parse.call(["--foo", "--bar"], [alias1, alias2, ...])
```
## Example
If your _app_ receives the arguments `./app --foo bar -t now -xzy`:
```js
Parsec.parse(process.argv)
// ./node index.js -f bar -bp
parse()
```
will produce the following object
```json
{
"foo": "bar",
"t": "now",
"x": true,
"z": true,
"y": true
"f": "bar",
"b": true,
"p": "./"
}
```
To customize how the options shall be parsed, use `Parsec.options`:
with custom aliases:
```js
Parsec
.options("foo", { default: "hoge" })
.options("time", { default: 24 })
.options("xoxo")
.options("yoyo")
.options("zorg")
.parse(process.argv)
parse("foo", "bar", ["path", { default: "./" }])
```

@@ -213,13 +201,9 @@

{
"f": "bar",
"foo": "bar",
"t": "now",
"time": "now",
"x": true,
"xoxo": true,
"z": true,
"zorg": true,
"y": true,
"yoyo": true
}
"f": "bar",
"foo": "bar",
"b": true,
"bar": true,
"p": "./",
"path": "./"
}
```

@@ -229,10 +213,9 @@

Why? I wanted a CLI parser with a better _feature:bloat_ ratio and a modern algorithm relying in generators instead of the traditional strategies found in [`minimist`](https://github.com/substack/minimist) and [`nopt`](https://github.com/npm/nopt) with a simple API to customize options and a small code base.
I also found the following projects useful and inspiring while writing Parsec:
If _Parsec_ didn't meet your requirements, consider:
+ [`minimist`](https://github.com/substack/minimist)
+ [`nopt`](https://github.com/npm/nopt)
+ [`commander`](https://github.com/tj/commander.js)
+ [`yargs`](https://github.com/bcoe/yargs)
# Hacking

@@ -243,4 +226,3 @@

cd parsec
npm install
npm run build
npm run setup
```

@@ -247,0 +229,0 @@

@@ -1,218 +0,62 @@

export default class Parsec {
constructor ({ operandsKey = "_", noFlags = true, sliceIndex = 2, strictMode = false }) {
/**
@type {String} Key name for array of operands. _ by default.
*/
this.operandsKey = operandsKey
/**
@type {Boolean} Given an option A, create another option no-A==!A.
*/
this.noFlags = noFlags
/**
@type {Boolean} Slice index for arguments array.
*/
this.sliceIndex = sliceIndex
/**
@type {Boolean} Throw a runtime error if unknown flags are found.
*/
this.strictMode = strictMode
/**
@private
@type Flag the end of options was found.
*/
this._endOfOpts = false
}
/**
Create mappings for cli options.
@param {[String]} keys to map to cli args
@param {Object} options
*/
static options (keys, opts) {
return options.call({ $: [], options }, keys, opts)
function options (keys, opts) {
this.$.push({ options: opts, keys: Array.isArray(keys)
? keys : [keys, keys.charAt(0)]
})
this.parse = parse.bind(this)
return this
}
}
/**
Parse an array of arguments like process.argv
@param {[String]} args
@param {Object} options
*/
static parse (args, options) {
return parse.call({}, args, options)
}
/**
Iterator for entries
@param {Array} args
@param {String} operandsKey
@return {{ key, value }}
*/
*entries (args) {
let isBool = (obj) =>
typeof obj === "boolean" || obj === "true" || obj === "false"
export default function (...aliases) {
return parse(this || process.argv.slice(2), aliases.map((alias) =>
typeof alias === "string" ? [alias, alias[0]] : alias))
}
for (let { curr, next, value } of this.tokens(args)) {
if (curr.isBare) {
if (this.isEndOfOpts(curr.token)) continue
yield { key: this.operandsKey, value: curr.token }
function find (aliases, fun) {
return aliases.some((alias) => fun(alias = alias.slice(),
typeof alias[alias.length - 1] === "object" && alias.pop().default))
}
function parse (argv, aliases) {
const map = {}, stack = [], keys = aliases.length > 0
? aliases.reduce((p, n) =>
p.concat(n)).filter((a) => typeof a !== "object") : null
const unknown = typeof aliases[aliases.length - 1] === "function"
? aliases.pop() : Function
argv.some((token, index) => {
if (token === "--") return add("_", argv.slice(index + 1)) || true
if (/^[a-z/"'@#$`~.]|^[+-]?[0-9]\d*(\.\d+)?$/i.test(token)) {
add(...stack.length === 0 ? ["_", [token]] : [stack.pop(), token])
} else if (/^-[a-z]/i.test(token)) {
const index = (token = token.slice(1)).search(/[\d\W]/i)
const split = ~index ? token.slice(0, index) : token
if (~index) {
add(split[split.length - 1], token.slice(index))
} else {
if (this.noFlags && isBool(value) && curr.isNoFlag) {
curr.token = curr.noKey
value = (value === "false")
? true
: (value === "true")
? false
: !value
}
yield { key: curr.token, value }
stack.push(split[split.length - 1])
}
}
}
/**
Token iterator for options.
@param {Array} args
@return {{ curr, next, value }}
*/
*tokens (args) {
for (let { prev, curr, next } of this.tuples(this.map(args))) {
if (curr === undefined) continue
if (curr.isLong) {
yield {
prev, curr,
value: (curr.value !== undefined)
? curr.value
: next !== undefined && next.isBare
? next.token
: true
}
} else if (curr.isShort) {
yield* this.shorts(this.tuples(curr.tokens, next))
split.split("").slice(0, -1).forEach((key) => add(key))
} else if (/^--[a-z]/i.test(token)) {
if (~(token = token.slice(2)).indexOf("=")) {
add(...token.split("="))
} else if (/^no-.+/.test(token)) {
add(token.match(/^no-(.+)/)[1], false)
} else {
if (prev !== undefined) {
if (prev.isLong && prev.value === undefined) continue
if (prev.isShort && isNaN(prev.tokens.pop())) continue
}
yield { prev, curr }
stack.push(token)
}
}
}
/**
Token sub-iterator for short options.
@param {Token} *iterator
@return {{ curr: {Token}, value }}
*/
*shorts (iterator) {
for (let { curr, next, last } of iterator) {
if (!isNaN(curr)) continue
yield {
curr: { token: curr },
value: (next === undefined && last !== undefined
&& last.isBare && !this.isEndOfOpts(last.token))
? last.token
: (!isNaN(next))
? next
: true
}
}
}
/**
Map CLI arguments to custom Token objects.
Short Options { isShort, tokens }
Long Options { isLong, key, value, token }
Operands { token, isBare }
@param {Array} args
@return {[Token]}
*/
map (args) {
return args.map((token) => {
if (!this._endOfOpts) {
this._endOfOpts = this.isEndOfOpts(token)
} else throw new RangeError(`invalid option ${token}`)
})
stack.forEach((key) => add(key))
find(aliases, (alias, value) => alias.forEach((a) =>
map[a] = map[a] === undefined ? value : map[a]))
if (/^-[a-z]/i.test(token)) {
const TOKENS = /([+-]?(\.?\d+\d+)?)|([a-z])/ig
return {
isShort: true,
tokens: token.slice(1).split(TOKENS).filter(_=>_)
}
}
if (/^--[a-z]/i.test(token)) {
return (([key, value]) => ({
isLong: true,
key, value, token: key.slice(2),
isNoFlag: /^--no-([^-\n]{1}.*$)/.test(key),
noKey: key.replace(/^--no-([^-\n]{1}.*$)/, "$1")
}))((pair => [pair.shift(), pair[0]])(token.split("=")))
}
}
return map
return { token, isBare: token !== undefined }
})
}
/**
Returns an iterator that yields objects of the form
`{ prev, curr, next }` for each item in a list.
@param {Array | String} List to iterate.
@param {Object} [last] last value (optional)
@param {Object} [prev] first prev value (optional)
@param {Object} [curr] first curr value (optional)
@return { prev, curr, next } for each item in a list
*/
*tuples (list, last, prev, curr) {
for (let next of list) {
if (curr !== undefined) yield { prev, curr, next }
prev = curr
curr = next
}
yield { prev, curr, last }
}
isEndOfOpts (token) {
return /^--[-]*$/.test(token)
}
}
function add (key, value = true) {
map[key] = map[key] === undefined
? value : Array.isArray(map[key])
? map[key].concat(value) : [map[key]].concat(value)
function parse (args, options = {}) {
delete this.options
delete this.parse
const p = new Parsec(options)
for (let { key, value } of p.entries(args.slice(p.sliceIndex))) {
this[key] = this[key] === undefined
? value
: !contains(this[key], value)
? [].concat(this[key], value)
: this[key]
}
function contains (key, value) {
return key === value || Array.isArray(key) && ~key.indexOf(value)
}
if (this.$) {
this.$.forEach(({ keys, options }) => {
keys = Array.isArray(keys) ? keys : [keys, keys.charAt(0)]
const value = this[keys.filter((key) => this[key] !== undefined)]
keys.forEach(function (key) {
this[key] = (value !== undefined)
? value
: (options !== undefined)
? options.default
: value
}.bind(this))
find(aliases, (alias, _value) => {
if (!~keys.indexOf(key)) return !unknown(key)
if (~alias.indexOf(key)) alias.forEach((key) =>
map[key] = typeof value === typeof _value
? value : value === true ? _value : value)
})
if (options.strictMode) {
const validKeys = this.$.reduce((prev, next) =>
prev.concat(next.keys), []).concat("_")
Object.keys(this)
.filter((key) => key !== "$")
.forEach((key) => {
if (!~validKeys.indexOf(key)) {
throw { code: "INVALID_OPTION", key }
}
})
}
}
return this
}

@@ -1,206 +0,207 @@

var Parsec = require("../dist/")
var test = require("tape")
const OPTS = { sliceIndex: 0 }
const parse = require("../dist/")
const test = require("tape")
test("Defaults", function (t) {
t.ok(Parsec !== undefined, "Parsec")
t.ok(Parsec.options !== undefined, "Parsec.options")
t.ok(Parsec.parse !== undefined, "Parsec.parse")
t.ok(Parsec.options("").parse !== undefined, "Parsec...parse")
test("end of opts", (t) => {
t.deepEqual(parse.call(["--", "--foo", "bar"]), {
_: ["--foo", "bar"]
}, "double dash")
// -T850
var opts = Parsec
.options(["T", "terminator"], { default: 800 })
.options("model", { default: 101 })
.options("name", { default: "Schwa" })
.parse(["-T850"], OPTS)
t.deepEqual(parse.call(["-a", "--", "--foo", "bar"]), {
a: true,
_: ["--foo", "bar"]
}, "single and double dash")
t.equal(opts.name, "Schwa")
t.equal(opts.n, "Schwa")
t.equal(opts.model, 101)
t.equal(opts.m, 101)
t.equal(opts.terminator, "850")
t.equal(opts.T, "850")
t.deepEqual(parse.call(["--foo=bar", "baz", "--", "foo", "--foo"]), {
foo: "bar",
_: ["baz", "foo", "--foo"]
}, "double, bare and double dash")
// -tla
opts = Parsec
.options("three")
.options("letter")
.options("abbreviation")
.parse(["-tla"], OPTS)
t.end()
})
t.equal(opts.three, true)
t.equal(opts.t, true)
t.equal(opts.letter, true)
t.equal(opts.l, true)
t.equal(opts.abbreviation, true)
t.equal(opts.a, true)
test("bares", (t) => {
t.deepEqual(parse.call(["beer"]), {
_: ["beer"]
}, "only bare")
// -S true -l false
opts = Parsec
.options("arms", { default: 2 })
.options(["S", "H", "senseOfHumor"], { default: false })
.options(["L", "l", "luck"], { default: true })
.parse(["-S", true, "-l", false], OPTS)
t.deepEqual(parse.call(["beer", "--foo"]), {
foo: true,
_: ["beer"]
}, "bare first")
t.equal(opts.senseOfHumor, true)
t.equal(opts.S, true)
t.equal(opts.H, true)
t.deepEqual(parse.call(["bar", "beer", "--foo"]), {
foo: true,
_: ["bar", "beer"]
}, "bare and bare")
t.equal(opts.a, 2)
t.equal(opts.arms, 2)
t.deepEqual(parse.call(["-ab42", "beer", "--foo", "--bar"]), {
a: true,
b: "42",
foo: true,
bar: true,
_: ["beer"]
}, "bare end of opts")
t.equal(opts.L, false)
t.equal(opts.l, false)
t.equal(opts.luck, false)
t.end()
})
// -v -f ["Myfile", "myfile"]
opts = Parsec
.options("base", { default: "./" })
.options("logger", { default: "./log/logger" })
.options("file", { default: ["Xfile", "xfile"] })
.options("verbose", { default: false })
.options(["V", "version"], { default: false })
.parse(["-v", "-f", ["Myfile", "myfile"]], OPTS)
test("singles", (t) => {
t.deepEqual(parse.call(["-foo42"]), {
f: true,
o: ["42", true],
}, "only single")
t.equal(opts.base, "./")
t.equal(opts.b, "./")
t.equal(opts.logger, "./log/logger")
t.equal(opts.l, "./log/logger")
t.deepEqual(opts.file, ["Myfile", "myfile"])
t.deepEqual(opts.f, ["Myfile", "myfile"])
t.equal(opts.verbose, true)
t.equal(opts.v, true)
t.equal(opts.V, false)
t.equal(opts.version, false)
t.deepEqual(parse.call(["-abc", "-xyz"]), {
a: true,
b: true,
c: true,
x: true,
y: true,
z: true
}, "single and single")
t.end()
})
t.deepEqual(parse.call(["-abc", "bar"]), {
a: true,
b: true,
c: "bar",
}, "single and bare")
test("No-Flags", function (t) {
var opts = Parsec.parse([
"--love", "--no-war", "--no-fun",
"--no-sex=false", "--no-rest", true,
"--no-mom=false", "--no-2", "--no-1",
"--no-way=way"
], OPTS)
t.deepEqual(parse.call(["-a", "bar"]), {
a: "bar"
}, "single and value")
t.equal(opts.love, true)
t.equal(opts.war, false)
t.equal(opts.fun, false)
t.equal(opts.sex, true)
t.equal(opts.rest, false)
t.equal(opts.mom, true)
t.equal(opts["1"], false)
t.equal(opts["2"], false)
t.equal(opts["no-way"], "way")
t.deepEqual(parse.call(["-abc./", "bar"]), {
a: true,
b: true,
c: "./",
_: ["bar"]
}, "single w/ value and bare")
t.deepEqual(parse.call(["-abc", "--foo"]), {
a: true,
b: true,
c: true,
foo: true
}, "single and double")
t.end()
})
test("Short Options", function (t) {
var opts = Parsec.parse([
"-abcde", -0.5, "-qt3", "-xyz-.09", "-Z", null ], OPTS)
test("doubles", (t) => {
t.deepEqual(parse.call(["--foo"]), {
foo: true
}, "double")
t.equal(opts.a, true)
t.equal(opts.b, true)
t.equal(opts.c, true)
t.equal(opts.d, true)
t.equal(opts.e, -0.5)
t.equal(opts.q, true)
t.equal(parseInt(opts.t), 3)
t.equal(opts.x, true)
t.equal(opts.y, true)
t.equal(parseFloat(opts.z), -0.09)
t.equal(opts.Z, null)
t.deepEqual(parse.call(["--foo=bar"]), {
foo: "bar"
}, "double w/ value")
t.end()
})
t.deepEqual(parse.call(["--foo=bar", "--bar=foo"]), {
foo: "bar",
bar: "foo"
}, "double w/ value group")
test("Long Options", function (t) {
var opts = Parsec.parse([
"--lang", "JavaScript", "--age=25", "--no-future=false"], OPTS)
t.deepEqual(parse.call(["--foo=bar", "beer"]), {
foo: "bar",
_: ["beer"]
}, "double w/ value and bare")
t.equal(opts.lang, "JavaScript")
t.equal(opts.age, "25")
t.equal(opts.future, true)
t.deepEqual(parse.call(["--foo", "--bar"]), {
foo: true,
bar: true
}, "double double")
t.deepEqual(parse.call(["--foo-bar-baz"]), {
"foo-bar-baz": true
}, "double w/ inner dashes")
t.deepEqual(parse.call(["--foo", "-abc"]), {
foo: true,
a: true,
b: true,
c: true
}, "double and single")
t.end()
})
test("Single Cases", function (t) {
test("aliases", (t) => {
t.deepEqual(parse.call(["-f"], "foo"), {
f: true,
foo: true
}, "single w/ alias")
t.equal(Parsec.parse(["--long"], OPTS).long, true)
t.equal(Parsec.parse(["--long=value"], OPTS).long, "value")
t.equal(Parsec.parse(["--no-long"], OPTS).long, false)
t.equal(Parsec.parse(["--long", 1], OPTS).long, 1)
t.equal(Parsec.parse(["--long", "--nope"], OPTS).long, true)
t.deepEqual(parse.call(["-f", "bar"], "foo"), {
f: "bar",
foo: "bar"
}, "single and value w/ alias")
t.equal(Parsec.parse(["A"], OPTS)._, "A")
t.deepEqual(Parsec.parse(["A", "B"], OPTS)._, ["A", "B"])
t.deepEqual(Parsec.parse(["A", "B", 5, null, true, false], OPTS)._,
["A", "B", 5, null, true, false])
t.deepEqual(parse.call(["--foo"], "foo"), {
f: true,
foo: true
}, "double w/ alias")
t.end()
})
t.deepEqual(parse.call(["foo"], "foo"), {
f: false,
foo: false,
_: ["foo"]
}, "bare w/ alias")
test("Operands", function (t) {
var opts = Parsec.parse(
[1,2,3, "-a", 100, 4,
"--nope", 200, 5, "-b", "Hello", "Six",
"--js=yes", [7, 8], "--ok", -1, 9, "TEN"], OPTS)
t.deepEqual(parse.call(["-xf"], "foo", "bar"), {
x: true,
f: true,
foo: true,
b: false,
bar: false
}, "missing alias for option")
t.deepEqual(opts._, [1,2,3,4,5, "Six", 7, 8, 9, "TEN"])
t.end()
})
t.deepEqual(parse.call(["-xf"], "foo", ["bar"]), {
x: true,
f: true,
foo: true,
bar: false
}, "missing option w/o short alias")
test("Slice Index", function (t) {
var opts = Parsec.parse(
[0,1,2,3,4,5,6,7,8,9], { sliceIndex: 5 })
t.deepEqual(parse.call(["-x"], ["bar", "beer"]), {
x: true,
bar: false,
beer: false
}, "missing option w/ multiple aliases")
t.deepEqual(opts._, [5,6,7,8,9])
t.deepEqual(parse.call(["-x"], ["bar", "beer", { default: "." }]), {
x: true,
bar: ".",
beer: "."
}, "missing option w/ default values")
t.end()
})
t.deepEqual(parse.call(["-f"], ["f", { default: "." }]), {
f: "."
}, "option w/ missing value and default type")
test("Edge Cases", function (t) {
t.deepEqual(Parsec.parse(
[
"O1",
"-abc",null,
"O2"
], OPTS)._, ["O1", "O2"])
t.deepEqual(parse.call(["-f"], ["f", { default: false }]), {
f: true
}, "option w/ alias and type match")
var opts = Parsec.parse(
[
"--foo", "bar", "--no-hoge",
"-Z1985", "-Z", null, "-c",
null, false
], OPTS)
t.deepEqual(parse.call(["--foo"], ["_", "operands"]), {
foo: true,
_: false,
operands: false
}, "operands alias")
t.equal(opts.foo, "bar")
t.equal(opts.hoge, false)
t.deepEqual(opts.Z, ["1985", null])
t.equal(opts._, false)
t.end()
})
test("End of Options", function (t) {
t.deepEqual(Parsec.parse(
[
"--long=true",
"-abc","--",
"-nope", "--neither", "--no-way", "-a1", "ABC"
], OPTS)._, ["-nope", "--neither", "--no-way", "-a1", "ABC"])
test("no flags", (t) => {
t.deepEqual(parse.call(["--no-foo"]), {
foo: false
}, "single no-flag")
t.deepEqual(Parsec.parse(
[
"--", "--we", "--are", "--watching", "-you"
], OPTS)._, ["--we", "--are", "--watching", "-you"])
t.deepEqual(parse.call(["--no-foo=bar"]), {
"no-foo": "bar",
}, "invalid no-flag")
t.deepEqual(Parsec.parse(["-a","--"], OPTS).a, true,
"Double dash -- is not a value")
t.deepEqual(Parsec.parse(["-a","---"], OPTS).a, true,
"Tripple dash --- is not a value")
t.deepEqual(parse.call(["--no-foo", "bar"]), {
foo: false,
_: ["bar"]
}, "invalid no-flag")

@@ -210,24 +211,10 @@ t.end()

test("Throws an exception if invalid options are found using strictMode", function (t) {
function parse (args) {
return Parsec
.options("bread")
.options("Butter")
.options("jam")
.parse(args, {
sliceIndex: 0,
strictMode: true
})
}
try { parse(["-bBjx"]) } catch (e) {
t.equal(e.code, "INVALID_OPTION", "invalid last key")
}
try { parse(["-xbBj"]) } catch (e) {
t.equal(e.code, "INVALID_OPTION", "invalid first key")
}
try { parse(["-bBxj"]) } catch (e) {
t.equal(e.code, "INVALID_OPTION", "invalid key somewhere")
}
test("invalid options", (t) => {
parse.call(["-a", "--bar"], "a", ["b", "bar"], _ => t.ok(false))
t.ok(true, "ignore callback if all options are good")
parse.call(["-a"], "b", "c", (unknown) => {
t.equal(unknown, "a", "invoke callback with bad options")
})
t.end()
})

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc