get-options
Advanced tools
Comparing version 1.1.3 to 1.2.0
@@ -10,2 +10,18 @@ Change Log | ||
[v1.2.0] | ||
------------------------------------------------------------------------ | ||
**February 27th, 2019** | ||
Introduced several features to simplify control of option processing. | ||
* __Added:__ Ability to extract option-lists from strings | ||
* __Added:__ Setting to [disable mixed-order][6] option/argument lists | ||
* __Added:__ Setting to [throw an error][7] for unrecognised options | ||
* __Added:__ Support for [terminating][8] options using a double-dash | ||
* __Fixed:__ Options array being modified by reference | ||
[6]: ./docs/advanced-settings.md#nomixedorder | ||
[7]: ./docs/advanced-settings.md#noundefined | ||
[8]: ./docs/advanced-settings.md#terminator | ||
[v1.1.3] | ||
@@ -71,2 +87,4 @@ ------------------------------------------------------------------------ | ||
[Referenced links]:_____________________________________________________ | ||
[Staged]: https://github.com/Alhadis/GetOptions/compare/v1.2.0...HEAD | ||
[v1.2.0]: https://github.com/Alhadis/GetOptions/releases/tag/v1.2.0 | ||
[v1.1.3]: https://github.com/Alhadis/GetOptions/releases/tag/v1.1.3 | ||
@@ -73,0 +91,0 @@ [v1.1.2]: https://github.com/Alhadis/GetOptions/releases/tag/v1.1.2 |
// Import the function like | ||
// import getOpts = require('get-options'); | ||
// import getOpts = require("get-options"); | ||
// If you need the types you can import them like normal | ||
// import { Options } from 'get-options'; | ||
// import { Options } from "get-options"; | ||
@@ -23,3 +23,6 @@ /** | ||
noBundling?: boolean; | ||
noMixedOrder?: boolean; | ||
noUndefined?: boolean; | ||
ignoreEquals?: boolean; | ||
terminator?: string | RegExp; | ||
duplicates?: | ||
@@ -26,0 +29,0 @@ | "use-first" |
143
index.js
@@ -162,2 +162,24 @@ "use strict"; | ||
/** | ||
* Test a string against a list of patterns. | ||
* | ||
* @param {String} input | ||
* @param {String[]|RegExp[]} patterns | ||
* @return {Boolean} | ||
* @internal | ||
*/ | ||
function match(input, patterns = []){ | ||
if(!patterns || 0 === patterns.length) | ||
return false; | ||
input = String(input); | ||
patterns = arrayify(patterns).filter(Boolean); | ||
for(const pattern of patterns) | ||
if((pattern === input && "string" === typeof pattern) | ||
|| (pattern instanceof RegExp) && pattern.test(input)) | ||
return true; | ||
return false; | ||
} | ||
/** | ||
* Filter duplicate strings from an array. | ||
@@ -178,2 +200,63 @@ * | ||
/** | ||
* Parse a string as a whitespace-delimited list of options, | ||
* preserving quoted and escaped characters. | ||
* | ||
* @example unstringify("--foo --bar") => ["--foo", "--bar"]; | ||
* @example unstringify('--foo "bar baz"') => ["--foo", '"bar baz"']; | ||
* @param {String} input | ||
* @return {Object} | ||
* @internal | ||
*/ | ||
function unstringify(input){ | ||
input = String(input || ""); | ||
const tokens = []; | ||
const {length} = input; | ||
let quoteChar = ""; // Quote-type enclosing current region | ||
let tokenData = ""; // Characters currently being collected | ||
let isEscaped = false; // Flag identifying an escape sequence | ||
for(let i = 0; i < length; ++i){ | ||
const char = input[i]; | ||
// Previous character was a backslash | ||
if(isEscaped){ | ||
tokenData += char; | ||
isEscaped = false; | ||
continue; | ||
} | ||
// Whitespace: terminate token unless quoted | ||
if(!quoteChar && /[ \t\n]/.test(char)){ | ||
tokenData && tokens.push(tokenData); | ||
tokenData = ""; | ||
continue; | ||
} | ||
// Backslash: escape next character | ||
if("\\" === char){ | ||
isEscaped = true; | ||
// Swallow backslash if it escapes a metacharacter | ||
const next = input[i + 1]; | ||
if(quoteChar && (quoteChar === next || "\\" === next) | ||
|| !quoteChar && /[- \t\n\\'"`]/.test(next)) | ||
continue; | ||
} | ||
// Quote marks | ||
else if((!quoteChar || char === quoteChar) && /['"`]/.test(char)){ | ||
quoteChar = quoteChar === char ? "" : char; | ||
continue; | ||
} | ||
tokenData += char; | ||
} | ||
if(tokenData) | ||
tokens.push(tokenData); | ||
return tokens; | ||
} | ||
/** | ||
* Parse input using "best guess" logic. Called when no optdef is passed. | ||
@@ -269,3 +352,3 @@ * | ||
* | ||
* @param {Array} input | ||
* @param {String|Array} input | ||
* @param {String|Object} [optdef=null] | ||
@@ -280,2 +363,11 @@ * @param {Object} [config={}] | ||
// Avoid modifying original array | ||
if(Array.isArray(input)) | ||
input = [...input].map(String); | ||
// If called with a string, break it apart into an array | ||
else if("string" === typeof input) | ||
input = unstringify(input); | ||
// Take a different approach if optdefs aren't specified | ||
@@ -300,2 +392,5 @@ if(null === optdef || "" === optdef || false === optdef) | ||
noBundling, | ||
noMixedOrder, | ||
noUndefined, | ||
terminator, | ||
ignoreEquals, | ||
@@ -437,8 +532,3 @@ duplicates = "use-last", | ||
// Ascertain if this option's being duplicated | ||
if(result.options[ names[0] ]) | ||
value = resolveDuplicate(option, value); | ||
option.names.forEach(name => { | ||
for(let name of names){ | ||
@@ -448,4 +538,8 @@ // Decide whether to camelCase this option name | ||
// Ascertain if this option's being duplicated | ||
if(result.options[name]) | ||
resolveDuplicate(option, name, value); | ||
result.options[name] = value; | ||
}); | ||
} | ||
} | ||
@@ -585,10 +679,39 @@ } | ||
else{ | ||
const isTerminator = match(arg, terminator); | ||
const keepRest = () => result.argv.push(...input.slice(i + 1)); | ||
// A previous option is still collecting arguments | ||
if(currentOption && currentOption.canCollect) | ||
if(currentOption && currentOption.canCollect && !isTerminator) | ||
currentOption.values.push(arg); | ||
// Not associated with an option; push this value onto the argv array | ||
// Not associated with an option | ||
else{ | ||
currentOption && wrapItUp(); | ||
// Terminate option parsing? | ||
if(isTerminator){ | ||
keepRest(); | ||
break; | ||
} | ||
// Raise an exception if unrecognised switches are considered an error | ||
if(noUndefined && /^-./.test(arg)){ | ||
let error = noUndefined; | ||
// Prepare an error object to be thrown in the user's direction | ||
switch(typeof noUndefined){ | ||
case "function": error = error(arg); break; | ||
case "boolean": error = 'Unknown option: "%s"'; // Fall-through | ||
case "string": error = new TypeError(error.replace("%s", arg)); | ||
} | ||
throw error; | ||
} | ||
result.argv.push(arg); | ||
// Finish processing if mixed-order is disabled | ||
if(noMixedOrder){ | ||
keepRest(); | ||
break; | ||
} | ||
} | ||
@@ -595,0 +718,0 @@ } |
@@ -1,2 +0,2 @@ | ||
Copyright (c) 2015-2018, John Gardner | ||
Copyright (c) 2015-2019, John Gardner | ||
@@ -3,0 +3,0 @@ Permission to use, copy, modify, and/or distribute this software for any |
{ | ||
"name": "get-options", | ||
"version": "1.1.3", | ||
"version": "v1.2.0", | ||
"description": "JavaScript's answer to getopts. Simple, obvious, and direct.", | ||
@@ -13,11 +13,13 @@ "keywords": ["CLI", "getopt", "getopts", "options", "argv", "command-line", "configuration", "config"], | ||
"devDependencies": { | ||
"@alhadis/eslint-config": "^1.0.0", | ||
"eslint": "^5.6.1", | ||
"tslint": "^5.11.0", | ||
"@alhadis/eslint-config": "^1.2.0", | ||
"eslint": "^5.14.1", | ||
"tslint": "^5.13.0", | ||
"typescript": ">=2.1.0", | ||
"mocha": "*", | ||
"chai": "*" | ||
"mocha": "^6.0.1", | ||
"chai": "^4.2.0" | ||
}, | ||
"eslintConfig": { | ||
"extends": "@alhadis/eslint-config" | ||
"extends": "@alhadis/eslint-config", | ||
"globals": {"assert": true}, | ||
"rules": {"prefer-object-spread": 0} | ||
}, | ||
@@ -24,0 +26,0 @@ "scripts": { |
30429
609