Comparing version 0.3.0 to 1.0.0
"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": { |
252
README.md
@@ -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 @@ |
258
src/index.js
@@ -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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
0
15405
308
241
1