From 52c3a2e057a0383a1e58b130b61c928e06f72b9d Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 15 May 2022 10:22:37 +1200 Subject: [PATCH 01/64] Proof of concept --- index.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/index.js b/index.js index e82c32d..bd602f5 100644 --- a/index.js +++ b/index.js @@ -202,11 +202,14 @@ const parseArgs = (config = { __proto__: null }) => { values: { __proto__: null }, positionals: [] }; + const ast = []; let remainingArgs = ArrayPrototypeSlice(args); + let originalArgs = ArrayPrototypeSlice(args); // Before splitting groups while (remainingArgs.length > 0) { const arg = ArrayPrototypeShift(remainingArgs); const nextArg = remainingArgs[0]; + const originalArg = ArrayPrototypeShift(originalArgs); // Check if `arg` is an options terminator. // Guideline 10 in https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html @@ -220,6 +223,9 @@ const parseArgs = (config = { __proto__: null }) => { result.positionals, remainingArgs ); + ast.push({ symbol: 'option-terminator' }); + remainingArgs.forEach((arg) => + ast.push({ symbol: 'positional', value: arg })); break; // Finished processing args, leave while loop. } @@ -237,12 +243,16 @@ const parseArgs = (config = { __proto__: null }) => { checkOptionUsage(longOption, optionValue, options, arg, strict, allowPositionals); storeOption(longOption, optionValue, options, result.values); + ast.push({ symbol: 'option', optionName: longOption, + usage: arg, value: optionValue ?? null, originalArg, + valueSource: optionValue != null ? 'arg' : 'none' }); continue; } if (isShortOptionGroup(arg, options)) { // Expand -fXzy to -f -X -z -y const expanded = []; + const expandedOriginal = []; for (let index = 1; index < arg.length; index++) { const shortOption = StringPrototypeCharAt(arg, index); const longOption = findLongOptionForShort(shortOption, options); @@ -256,8 +266,10 @@ const parseArgs = (config = { __proto__: null }) => { ArrayPrototypePush(expanded, `-${StringPrototypeSlice(arg, index)}`); break; // finished short group } + ArrayPrototypePush(expandedOriginal, originalArg); } remainingArgs = ArrayPrototypeConcat(expanded, remainingArgs); + originalArgs = ArrayPrototypeConcat(expandedOriginal, originalArgs); continue; } @@ -268,6 +280,9 @@ const parseArgs = (config = { __proto__: null }) => { const optionValue = StringPrototypeSlice(arg, 2); checkOptionUsage(longOption, optionValue, options, `-${shortOption}`, strict, allowPositionals); storeOption(longOption, optionValue, options, result.values); + ast.push({ symbol: 'option', optionName: longOption, + usage: `-${shortOption}`, value: optionValue, originalArg, + valueSource: 'embedded' }); continue; } @@ -284,6 +299,9 @@ const parseArgs = (config = { __proto__: null }) => { checkOptionUsage(longOption, optionValue, options, arg, strict, allowPositionals); storeOption(longOption, optionValue, options, result.values); + ast.push({ symbol: 'option', optionName: longOption, + value: optionValue ?? null, usage: arg, originalArg, + valueSource: optionValue != null ? 'arg' : 'none' }); continue; } @@ -294,6 +312,9 @@ const parseArgs = (config = { __proto__: null }) => { const optionValue = StringPrototypeSlice(arg, index + 1); checkOptionUsage(longOption, optionValue, options, `--${longOption}`, strict, allowPositionals); storeOption(longOption, optionValue, options, result.values); + ast.push({ symbol: 'option', optionName: longOption, + value: optionValue, usage: `--${longOption}`, originalArg, + valueSource: 'embedded' }); continue; } @@ -303,8 +324,10 @@ const parseArgs = (config = { __proto__: null }) => { } ArrayPrototypePush(result.positionals, arg); + ast.push({ symbol: 'positional', value: arg }); } + result.ast = ast; return result; }; From 5ec71b17ea4a47787df4cd10e7fa0fbf8b40ebf7 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 15 May 2022 11:52:10 +1200 Subject: [PATCH 02/64] Return originalArgs and indices --- index.js | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/index.js b/index.js index bd602f5..a233c4f 100644 --- a/index.js +++ b/index.js @@ -200,16 +200,21 @@ const parseArgs = (config = { __proto__: null }) => { const result = { values: { __proto__: null }, - positionals: [] + positionals: [], }; const ast = []; + result.originalArgs = ArrayPrototypeSlice(args); + let argIndex = -1; + let groupCount = 0; let remainingArgs = ArrayPrototypeSlice(args); - let originalArgs = ArrayPrototypeSlice(args); // Before splitting groups while (remainingArgs.length > 0) { const arg = ArrayPrototypeShift(remainingArgs); const nextArg = remainingArgs[0]; - const originalArg = ArrayPrototypeShift(originalArgs); + if (groupCount > 0) + groupCount--; + else + argIndex++; // Check if `arg` is an options terminator. // Guideline 10 in https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html @@ -223,9 +228,9 @@ const parseArgs = (config = { __proto__: null }) => { result.positionals, remainingArgs ); - ast.push({ symbol: 'option-terminator' }); + ast.push({ symbol: 'option-terminator', argIndex }); remainingArgs.forEach((arg) => - ast.push({ symbol: 'positional', value: arg })); + ast.push({ symbol: 'positional', value: arg, argIndex: ++argIndex })); break; // Finished processing args, leave while loop. } @@ -234,25 +239,26 @@ const parseArgs = (config = { __proto__: null }) => { const shortOption = StringPrototypeCharAt(arg, 1); const longOption = findLongOptionForShort(shortOption, options); let optionValue; + let valueIndex; if (optionsGetOwn(options, longOption, 'type') === 'string' && isOptionValue(nextArg)) { // e.g. '-f', 'bar' optionValue = ArrayPrototypeShift(remainingArgs); + valueIndex = argIndex + 1; checkOptionLikeValue(longOption, optionValue, arg, strict); } checkOptionUsage(longOption, optionValue, options, arg, strict, allowPositionals); storeOption(longOption, optionValue, options, result.values); ast.push({ symbol: 'option', optionName: longOption, - usage: arg, value: optionValue ?? null, originalArg, - valueSource: optionValue != null ? 'arg' : 'none' }); + short: shortOption, argIndex, + value: optionValue, valueIndex }); continue; } if (isShortOptionGroup(arg, options)) { // Expand -fXzy to -f -X -z -y const expanded = []; - const expandedOriginal = []; for (let index = 1; index < arg.length; index++) { const shortOption = StringPrototypeCharAt(arg, index); const longOption = findLongOptionForShort(shortOption, options); @@ -266,10 +272,9 @@ const parseArgs = (config = { __proto__: null }) => { ArrayPrototypePush(expanded, `-${StringPrototypeSlice(arg, index)}`); break; // finished short group } - ArrayPrototypePush(expandedOriginal, originalArg); } remainingArgs = ArrayPrototypeConcat(expanded, remainingArgs); - originalArgs = ArrayPrototypeConcat(expandedOriginal, originalArgs); + groupCount = expanded.length; continue; } @@ -281,8 +286,8 @@ const parseArgs = (config = { __proto__: null }) => { checkOptionUsage(longOption, optionValue, options, `-${shortOption}`, strict, allowPositionals); storeOption(longOption, optionValue, options, result.values); ast.push({ symbol: 'option', optionName: longOption, - usage: `-${shortOption}`, value: optionValue, originalArg, - valueSource: 'embedded' }); + short: shortOption, argIndex, + value: optionValue, valueIndex: argIndex }); continue; } @@ -290,18 +295,20 @@ const parseArgs = (config = { __proto__: null }) => { // e.g. '--foo' const longOption = StringPrototypeSlice(arg, 2); let optionValue; + let valueIndex; if (optionsGetOwn(options, longOption, 'type') === 'string' && isOptionValue(nextArg)) { // e.g. '--foo', 'bar' optionValue = ArrayPrototypeShift(remainingArgs); + valueIndex = argIndex + 1; checkOptionLikeValue(longOption, optionValue, arg, strict); } checkOptionUsage(longOption, optionValue, options, arg, strict, allowPositionals); storeOption(longOption, optionValue, options, result.values); ast.push({ symbol: 'option', optionName: longOption, - value: optionValue ?? null, usage: arg, originalArg, - valueSource: optionValue != null ? 'arg' : 'none' }); + short: null, argIndex, + value: optionValue, valueIndex }); continue; } @@ -313,8 +320,8 @@ const parseArgs = (config = { __proto__: null }) => { checkOptionUsage(longOption, optionValue, options, `--${longOption}`, strict, allowPositionals); storeOption(longOption, optionValue, options, result.values); ast.push({ symbol: 'option', optionName: longOption, - value: optionValue, usage: `--${longOption}`, originalArg, - valueSource: 'embedded' }); + short: null, argIndex, + value: optionValue, valueIndex: argIndex }); continue; } @@ -324,7 +331,7 @@ const parseArgs = (config = { __proto__: null }) => { } ArrayPrototypePush(result.positionals, arg); - ast.push({ symbol: 'positional', value: arg }); + ast.push({ symbol: 'positional', value: arg, argIndex }); } result.ast = ast; From f7111870b0b2585ace9e8f0ac2bb7add1d48d4ca Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 24 May 2022 20:04:06 +1200 Subject: [PATCH 03/64] Change short in AST to boolean --- index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index a233c4f..51e8ae2 100644 --- a/index.js +++ b/index.js @@ -251,7 +251,7 @@ const parseArgs = (config = { __proto__: null }) => { arg, strict, allowPositionals); storeOption(longOption, optionValue, options, result.values); ast.push({ symbol: 'option', optionName: longOption, - short: shortOption, argIndex, + short: true, argIndex, value: optionValue, valueIndex }); continue; } @@ -286,7 +286,7 @@ const parseArgs = (config = { __proto__: null }) => { checkOptionUsage(longOption, optionValue, options, `-${shortOption}`, strict, allowPositionals); storeOption(longOption, optionValue, options, result.values); ast.push({ symbol: 'option', optionName: longOption, - short: shortOption, argIndex, + short: true, argIndex, value: optionValue, valueIndex: argIndex }); continue; } @@ -307,7 +307,7 @@ const parseArgs = (config = { __proto__: null }) => { arg, strict, allowPositionals); storeOption(longOption, optionValue, options, result.values); ast.push({ symbol: 'option', optionName: longOption, - short: null, argIndex, + short: false, argIndex, value: optionValue, valueIndex }); continue; } @@ -320,7 +320,7 @@ const parseArgs = (config = { __proto__: null }) => { checkOptionUsage(longOption, optionValue, options, `--${longOption}`, strict, allowPositionals); storeOption(longOption, optionValue, options, result.values); ast.push({ symbol: 'option', optionName: longOption, - short: null, argIndex, + short: false, argIndex, value: optionValue, valueIndex: argIndex }); continue; } From cce90bbe2da584477f1c16afd6653c092c48df68 Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 24 May 2022 20:08:24 +1200 Subject: [PATCH 04/64] Store inlineValue rather than valueIndex --- index.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/index.js b/index.js index 51e8ae2..bb3a7ed 100644 --- a/index.js +++ b/index.js @@ -239,12 +239,12 @@ const parseArgs = (config = { __proto__: null }) => { const shortOption = StringPrototypeCharAt(arg, 1); const longOption = findLongOptionForShort(shortOption, options); let optionValue; - let valueIndex; + let inlineValue; if (optionsGetOwn(options, longOption, 'type') === 'string' && isOptionValue(nextArg)) { // e.g. '-f', 'bar' optionValue = ArrayPrototypeShift(remainingArgs); - valueIndex = argIndex + 1; + inlineValue = false; checkOptionLikeValue(longOption, optionValue, arg, strict); } checkOptionUsage(longOption, optionValue, options, @@ -252,7 +252,7 @@ const parseArgs = (config = { __proto__: null }) => { storeOption(longOption, optionValue, options, result.values); ast.push({ symbol: 'option', optionName: longOption, short: true, argIndex, - value: optionValue, valueIndex }); + value: optionValue, inlineValue }); continue; } @@ -287,7 +287,7 @@ const parseArgs = (config = { __proto__: null }) => { storeOption(longOption, optionValue, options, result.values); ast.push({ symbol: 'option', optionName: longOption, short: true, argIndex, - value: optionValue, valueIndex: argIndex }); + value: optionValue, inlineValue: true }); continue; } @@ -295,12 +295,12 @@ const parseArgs = (config = { __proto__: null }) => { // e.g. '--foo' const longOption = StringPrototypeSlice(arg, 2); let optionValue; - let valueIndex; + let inlineValue; if (optionsGetOwn(options, longOption, 'type') === 'string' && isOptionValue(nextArg)) { // e.g. '--foo', 'bar' optionValue = ArrayPrototypeShift(remainingArgs); - valueIndex = argIndex + 1; + inlineValue = false; checkOptionLikeValue(longOption, optionValue, arg, strict); } checkOptionUsage(longOption, optionValue, options, @@ -308,7 +308,7 @@ const parseArgs = (config = { __proto__: null }) => { storeOption(longOption, optionValue, options, result.values); ast.push({ symbol: 'option', optionName: longOption, short: false, argIndex, - value: optionValue, valueIndex }); + value: optionValue, inlineValue }); continue; } @@ -321,7 +321,7 @@ const parseArgs = (config = { __proto__: null }) => { storeOption(longOption, optionValue, options, result.values); ast.push({ symbol: 'option', optionName: longOption, short: false, argIndex, - value: optionValue, valueIndex: argIndex }); + value: optionValue, inlineValue: true }); continue; } From e4bc5fec1bd573f6ebb8efa980972a9ad1b1f340 Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 24 May 2022 20:09:29 +1200 Subject: [PATCH 05/64] Rename symbol property in AST to kind --- index.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index bb3a7ed..55f8a52 100644 --- a/index.js +++ b/index.js @@ -228,9 +228,9 @@ const parseArgs = (config = { __proto__: null }) => { result.positionals, remainingArgs ); - ast.push({ symbol: 'option-terminator', argIndex }); + ast.push({ kind: 'option-terminator', argIndex }); remainingArgs.forEach((arg) => - ast.push({ symbol: 'positional', value: arg, argIndex: ++argIndex })); + ast.push({ kind: 'positional', value: arg, argIndex: ++argIndex })); break; // Finished processing args, leave while loop. } @@ -250,7 +250,7 @@ const parseArgs = (config = { __proto__: null }) => { checkOptionUsage(longOption, optionValue, options, arg, strict, allowPositionals); storeOption(longOption, optionValue, options, result.values); - ast.push({ symbol: 'option', optionName: longOption, + ast.push({ kind: 'option', optionName: longOption, short: true, argIndex, value: optionValue, inlineValue }); continue; @@ -285,7 +285,7 @@ const parseArgs = (config = { __proto__: null }) => { const optionValue = StringPrototypeSlice(arg, 2); checkOptionUsage(longOption, optionValue, options, `-${shortOption}`, strict, allowPositionals); storeOption(longOption, optionValue, options, result.values); - ast.push({ symbol: 'option', optionName: longOption, + ast.push({ kind: 'option', optionName: longOption, short: true, argIndex, value: optionValue, inlineValue: true }); continue; @@ -306,7 +306,7 @@ const parseArgs = (config = { __proto__: null }) => { checkOptionUsage(longOption, optionValue, options, arg, strict, allowPositionals); storeOption(longOption, optionValue, options, result.values); - ast.push({ symbol: 'option', optionName: longOption, + ast.push({ kind: 'option', optionName: longOption, short: false, argIndex, value: optionValue, inlineValue }); continue; @@ -319,7 +319,7 @@ const parseArgs = (config = { __proto__: null }) => { const optionValue = StringPrototypeSlice(arg, index + 1); checkOptionUsage(longOption, optionValue, options, `--${longOption}`, strict, allowPositionals); storeOption(longOption, optionValue, options, result.values); - ast.push({ symbol: 'option', optionName: longOption, + ast.push({ kind: 'option', optionName: longOption, short: false, argIndex, value: optionValue, inlineValue: true }); continue; @@ -331,7 +331,7 @@ const parseArgs = (config = { __proto__: null }) => { } ArrayPrototypePush(result.positionals, arg); - ast.push({ symbol: 'positional', value: arg, argIndex }); + ast.push({ kind: 'positional', value: arg, argIndex }); } result.ast = ast; From 87624a1732d5be045cefa46465e73dc7a3681504 Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 24 May 2022 20:16:50 +1200 Subject: [PATCH 06/64] Rename returned ast property to parseElements --- index.js | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/index.js b/index.js index 55f8a52..5bde3e2 100644 --- a/index.js +++ b/index.js @@ -202,7 +202,7 @@ const parseArgs = (config = { __proto__: null }) => { values: { __proto__: null }, positionals: [], }; - const ast = []; + const elements = []; result.originalArgs = ArrayPrototypeSlice(args); let argIndex = -1; let groupCount = 0; @@ -228,9 +228,10 @@ const parseArgs = (config = { __proto__: null }) => { result.positionals, remainingArgs ); - ast.push({ kind: 'option-terminator', argIndex }); + elements.push({ kind: 'option-terminator', argIndex }); remainingArgs.forEach((arg) => - ast.push({ kind: 'positional', value: arg, argIndex: ++argIndex })); + elements.push({ kind: 'positional', + value: arg, argIndex: ++argIndex })); break; // Finished processing args, leave while loop. } @@ -250,9 +251,9 @@ const parseArgs = (config = { __proto__: null }) => { checkOptionUsage(longOption, optionValue, options, arg, strict, allowPositionals); storeOption(longOption, optionValue, options, result.values); - ast.push({ kind: 'option', optionName: longOption, - short: true, argIndex, - value: optionValue, inlineValue }); + elements.push({ kind: 'option', optionName: longOption, + short: true, argIndex, + value: optionValue, inlineValue }); continue; } @@ -285,9 +286,9 @@ const parseArgs = (config = { __proto__: null }) => { const optionValue = StringPrototypeSlice(arg, 2); checkOptionUsage(longOption, optionValue, options, `-${shortOption}`, strict, allowPositionals); storeOption(longOption, optionValue, options, result.values); - ast.push({ kind: 'option', optionName: longOption, - short: true, argIndex, - value: optionValue, inlineValue: true }); + elements.push({ kind: 'option', optionName: longOption, + short: true, argIndex, + value: optionValue, inlineValue: true }); continue; } @@ -306,9 +307,9 @@ const parseArgs = (config = { __proto__: null }) => { checkOptionUsage(longOption, optionValue, options, arg, strict, allowPositionals); storeOption(longOption, optionValue, options, result.values); - ast.push({ kind: 'option', optionName: longOption, - short: false, argIndex, - value: optionValue, inlineValue }); + elements.push({ kind: 'option', optionName: longOption, + short: false, argIndex, + value: optionValue, inlineValue }); continue; } @@ -319,9 +320,9 @@ const parseArgs = (config = { __proto__: null }) => { const optionValue = StringPrototypeSlice(arg, index + 1); checkOptionUsage(longOption, optionValue, options, `--${longOption}`, strict, allowPositionals); storeOption(longOption, optionValue, options, result.values); - ast.push({ kind: 'option', optionName: longOption, - short: false, argIndex, - value: optionValue, inlineValue: true }); + elements.push({ kind: 'option', optionName: longOption, + short: false, argIndex, + value: optionValue, inlineValue: true }); continue; } @@ -331,10 +332,10 @@ const parseArgs = (config = { __proto__: null }) => { } ArrayPrototypePush(result.positionals, arg); - ast.push({ kind: 'positional', value: arg, argIndex }); + elements.push({ kind: 'positional', value: arg, argIndex }); } - result.ast = ast; + result.parseElements = elements; return result; }; From 292563975598565d459fe4f18eceae52273eeaa7 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 29 May 2022 16:18:26 +1200 Subject: [PATCH 07/64] Refactor to use parseElements to test for errors and store values --- index.js | 166 +++++++++++++++++++++++++++---------------------------- 1 file changed, 80 insertions(+), 86 deletions(-) diff --git a/index.js b/index.js index 5bde3e2..e497ede 100644 --- a/index.js +++ b/index.js @@ -81,19 +81,20 @@ function getMainArgs() { /** * In strict mode, throw for possible usage errors like --foo --bar * - * @param {string} longOption - long option name e.g. 'foo' - * @param {string|undefined} optionValue - value from user args - * @param {string} shortOrLong - option used, with dashes e.g. `-l` or `--long` - * @param {boolean} strict - show errors, from parseArgs({ strict }) + * @param {object} config - from config passed to parseArgs + * @param {object} element- array item from parseElements returned by parseArgs */ -function checkOptionLikeValue(longOption, optionValue, shortOrLong, strict) { - if (strict && isOptionLikeValue(optionValue)) { +function checkOptionLikeValue(config, element) { + if (config.strict && (element.inlineValue === false) && + isOptionLikeValue(element.value)) { // Only show short example if user used short option. - const example = (shortOrLong.length === 2) ? - `'--${longOption}=-XYZ' or '${shortOrLong}-XYZ'` : - `'--${longOption}=-XYZ'`; - const errorMessage = `Option '${shortOrLong}' argument is ambiguous. -Did you forget to specify the option argument for '${shortOrLong}'? + const short = optionsGetOwn(config.options, element.optionName, 'short'); + const example = (element.isShort ?? short) ? + `'--${element.optionName}=-XYZ' or '-${short}-XYZ'` : + `'--${element.optionName}=-XYZ'`; + const arg = config.args[element.argIndex]; + const errorMessage = `Option '${arg}' argument is ambiguous. +Did you forget to specify the option argument for '${arg}'? To specify an option argument starting with a dash use ${example}.`; throw new ERR_PARSE_ARGS_INVALID_OPTION_VALUE(errorMessage); } @@ -102,62 +103,60 @@ To specify an option argument starting with a dash use ${example}.`; /** * In strict mode, throw for usage errors. * - * @param {string} longOption - long option name e.g. 'foo' - * @param {string|undefined} optionValue - value from user args - * @param {object} options - option configs, from parseArgs({ options }) - * @param {string} shortOrLong - option used, with dashes e.g. `-l` or `--long` - * @param {boolean} strict - show errors, from parseArgs({ strict }) + * @param {object} config - from config passed to parseArgs + * @param {object} element- array item from parseElements returned by parseArgs */ -function checkOptionUsage(longOption, optionValue, options, - shortOrLong, strict, allowPositionals) { - // Strict and options are used from local context. - if (!strict) return; +function checkOptionUsage(config, element) { + if (!config.strict) return; - if (!ObjectHasOwn(options, longOption)) { - throw new ERR_PARSE_ARGS_UNKNOWN_OPTION(shortOrLong, allowPositionals); + if (!ObjectHasOwn(config.options, element.optionName)) { + const optionUsed = element.isShort ? `-${element.optionName}` : `--${element.optionName}`; + // eslint-disable-next-line max-len + throw new ERR_PARSE_ARGS_UNKNOWN_OPTION(optionUsed, config.allowPositionals); } - const short = optionsGetOwn(options, longOption, 'short'); - const shortAndLong = short ? `-${short}, --${longOption}` : `--${longOption}`; - const type = optionsGetOwn(options, longOption, 'type'); - if (type === 'string' && typeof optionValue !== 'string') { + const short = optionsGetOwn(config.options, element.optionName, 'short'); + const shortAndLong = `${short ? `-${short}, ` : ''}--${element.optionName}`; + const type = optionsGetOwn(config.options, element.optionName, 'type'); + if (type === 'string' && typeof element.value !== 'string') { throw new ERR_PARSE_ARGS_INVALID_OPTION_VALUE(`Option '${shortAndLong} ' argument missing`); } // (Idiomatic test for undefined||null, expecting undefined.) - if (type === 'boolean' && optionValue != null) { + if (type === 'boolean' && element.value != null) { throw new ERR_PARSE_ARGS_INVALID_OPTION_VALUE(`Option '${shortAndLong}' does not take an argument`); } } + /** * Store the option value in `values`. * - * @param {string} longOption - long option name e.g. 'foo' + * @param {string} optionName - long option name e.g. 'foo' * @param {string|undefined} optionValue - value from user args * @param {object} options - option configs, from parseArgs({ options }) * @param {object} values - option values returned in `values` by parseArgs */ -function storeOption(longOption, optionValue, options, values) { - if (longOption === '__proto__') { +function storeOption(optionName, optionValue, options, values) { + if (optionName === '__proto__') { return; // No. Just no. } // We store based on the option value rather than option type, // preserving the users intent for author to deal with. const newValue = optionValue ?? true; - if (optionsGetOwn(options, longOption, 'multiple')) { + if (optionsGetOwn(options, optionName, 'multiple')) { // Always store value in array, including for boolean. - // values[longOption] starts out not present, + // values[optionName] starts out not present, // first value is added as new array [newValue], // subsequent values are pushed to existing array. // (note: values has null prototype, so simpler usage) - if (values[longOption]) { - ArrayPrototypePush(values[longOption], newValue); + if (values[optionName]) { + ArrayPrototypePush(values[optionName], newValue); } else { - values[longOption] = [newValue]; + values[optionName] = [newValue]; } } else { - values[longOption] = newValue; + values[optionName] = newValue; } } @@ -166,6 +165,7 @@ const parseArgs = (config = { __proto__: null }) => { const strict = objectGetOwn(config, 'strict') ?? true; const allowPositionals = objectGetOwn(config, 'allowPositionals') ?? !strict; const options = objectGetOwn(config, 'options') ?? { __proto__: null }; + const parseConfig = { args, strict, options, allowPositionals }; // Validate input configuration. validateArray(args, 'args'); @@ -201,9 +201,9 @@ const parseArgs = (config = { __proto__: null }) => { const result = { values: { __proto__: null }, positionals: [], + // parseElements: [], }; - const elements = []; - result.originalArgs = ArrayPrototypeSlice(args); + const elements = []; // result.parseElements; let argIndex = -1; let groupCount = 0; @@ -219,10 +219,6 @@ const parseArgs = (config = { __proto__: null }) => { // Check if `arg` is an options terminator. // Guideline 10 in https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html if (arg === '--') { - if (!allowPositionals && remainingArgs.length > 0) { - throw new ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL(nextArg); - } - // Everything after a bare '--' is considered a positional argument. result.positionals = ArrayPrototypeConcat( result.positionals, @@ -238,22 +234,18 @@ const parseArgs = (config = { __proto__: null }) => { if (isLoneShortOption(arg)) { // e.g. '-f' const shortOption = StringPrototypeCharAt(arg, 1); - const longOption = findLongOptionForShort(shortOption, options); - let optionValue; + const optionName = findLongOptionForShort(shortOption, options); + let value; let inlineValue; - if (optionsGetOwn(options, longOption, 'type') === 'string' && + if (optionsGetOwn(options, optionName, 'type') === 'string' && isOptionValue(nextArg)) { // e.g. '-f', 'bar' - optionValue = ArrayPrototypeShift(remainingArgs); + value = ArrayPrototypeShift(remainingArgs); inlineValue = false; - checkOptionLikeValue(longOption, optionValue, arg, strict); } - checkOptionUsage(longOption, optionValue, options, - arg, strict, allowPositionals); - storeOption(longOption, optionValue, options, result.values); - elements.push({ kind: 'option', optionName: longOption, - short: true, argIndex, - value: optionValue, inlineValue }); + elements.push({ kind: 'option', optionName, + isShort: true, argIndex, + value, inlineValue }); continue; } @@ -262,8 +254,8 @@ const parseArgs = (config = { __proto__: null }) => { const expanded = []; for (let index = 1; index < arg.length; index++) { const shortOption = StringPrototypeCharAt(arg, index); - const longOption = findLongOptionForShort(shortOption, options); - if (optionsGetOwn(options, longOption, 'type') !== 'string' || + const optionName = findLongOptionForShort(shortOption, options); + if (optionsGetOwn(options, optionName, 'type') !== 'string' || index === arg.length - 1) { // Boolean option, or last short in group. Well formed. ArrayPrototypePush(expanded, `-${shortOption}`); @@ -282,60 +274,62 @@ const parseArgs = (config = { __proto__: null }) => { if (isShortOptionAndValue(arg, options)) { // e.g. -fFILE const shortOption = StringPrototypeCharAt(arg, 1); - const longOption = findLongOptionForShort(shortOption, options); - const optionValue = StringPrototypeSlice(arg, 2); - checkOptionUsage(longOption, optionValue, options, `-${shortOption}`, strict, allowPositionals); - storeOption(longOption, optionValue, options, result.values); - elements.push({ kind: 'option', optionName: longOption, - short: true, argIndex, - value: optionValue, inlineValue: true }); + const optionName = findLongOptionForShort(shortOption, options); + const value = StringPrototypeSlice(arg, 2); + elements.push({ kind: 'option', optionName, + isShort: true, argIndex, + value, inlineValue: true }); continue; } if (isLoneLongOption(arg)) { // e.g. '--foo' - const longOption = StringPrototypeSlice(arg, 2); - let optionValue; + const optionName = StringPrototypeSlice(arg, 2); + let value; let inlineValue; - if (optionsGetOwn(options, longOption, 'type') === 'string' && + if (optionsGetOwn(options, optionName, 'type') === 'string' && isOptionValue(nextArg)) { // e.g. '--foo', 'bar' - optionValue = ArrayPrototypeShift(remainingArgs); + value = ArrayPrototypeShift(remainingArgs); inlineValue = false; - checkOptionLikeValue(longOption, optionValue, arg, strict); } - checkOptionUsage(longOption, optionValue, options, - arg, strict, allowPositionals); - storeOption(longOption, optionValue, options, result.values); - elements.push({ kind: 'option', optionName: longOption, - short: false, argIndex, - value: optionValue, inlineValue }); + elements.push({ kind: 'option', optionName, + isShort: false, argIndex, + value, inlineValue }); continue; } if (isLongOptionAndValue(arg)) { // e.g. --foo=bar const index = StringPrototypeIndexOf(arg, '='); - const longOption = StringPrototypeSlice(arg, 2, index); - const optionValue = StringPrototypeSlice(arg, index + 1); - checkOptionUsage(longOption, optionValue, options, `--${longOption}`, strict, allowPositionals); - storeOption(longOption, optionValue, options, result.values); - elements.push({ kind: 'option', optionName: longOption, - short: false, argIndex, - value: optionValue, inlineValue: true }); + const optionName = StringPrototypeSlice(arg, 2, index); + const value = StringPrototypeSlice(arg, index + 1); + elements.push({ kind: 'option', optionName, + isShort: false, argIndex, + value, inlineValue: true }); continue; } - // Anything left is a positional - if (!allowPositionals) { - throw new ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL(arg); - } - ArrayPrototypePush(result.positionals, arg); elements.push({ kind: 'positional', value: arg, argIndex }); } - result.parseElements = elements; + elements.forEach((element) => { + switch (element.kind) { + case 'option-terminator': + break; + case 'positional': + if (!allowPositionals) + throw new ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL(element.value); + break; + case 'option': + checkOptionUsage(parseConfig, element); + checkOptionLikeValue(parseConfig, element); + storeOption(element.optionName, element.value, options, result.values); + break; + } + }); + return result; }; From 1897dacaa14e84e8c4a0bcbe3baca21617bae9bb Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 29 May 2022 19:24:24 +1200 Subject: [PATCH 08/64] Build positionals from parseElements --- index.js | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/index.js b/index.js index 5e7ca36..05456f9 100644 --- a/index.js +++ b/index.js @@ -4,7 +4,6 @@ const { ArrayPrototypeForEach, ArrayPrototypeIncludes, ArrayPrototypePush, - ArrayPrototypePushApply, ArrayPrototypeShift, ArrayPrototypeSlice, ArrayPrototypeUnshiftApply, @@ -181,12 +180,7 @@ const parseArgs = (config = { __proto__: null }) => { } ); - const result = { - values: { __proto__: null }, - positionals: [], - // parseElements: [], - }; - const elements = []; // result.parseElements; + const elements = []; let argIndex = -1; let groupCount = 0; @@ -203,10 +197,6 @@ const parseArgs = (config = { __proto__: null }) => { // Guideline 10 in https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html if (arg === '--') { // Everything after a bare '--' is considered a positional argument. - ArrayPrototypePushApply( - result.positionals, - remainingArgs - ); elements.push({ kind: 'option-terminator', argIndex }); remainingArgs.forEach((arg) => elements.push({ kind: 'positional', @@ -293,17 +283,23 @@ const parseArgs = (config = { __proto__: null }) => { continue; } - ArrayPrototypePush(result.positionals, arg); elements.push({ kind: 'positional', value: arg, argIndex }); } + const result = { + values: { __proto__: null }, + positionals: [], + // parseElements = elements + }; elements.forEach((element) => { switch (element.kind) { case 'option-terminator': break; case 'positional': - if (!allowPositionals) + if (!allowPositionals) { throw new ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL(element.value); + } + ArrayPrototypePush(result.positionals, element.value); break; case 'option': checkOptionUsage(parseConfig, element); From 45d12e73f5e864a2a53ef15c3e3e442e1777d0f1 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 29 May 2022 19:41:38 +1200 Subject: [PATCH 09/64] Replace .push with primordial --- index.js | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/index.js b/index.js index 05456f9..8e984b0 100644 --- a/index.js +++ b/index.js @@ -197,10 +197,11 @@ const parseArgs = (config = { __proto__: null }) => { // Guideline 10 in https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html if (arg === '--') { // Everything after a bare '--' is considered a positional argument. - elements.push({ kind: 'option-terminator', argIndex }); + ArrayPrototypePush(elements, { kind: 'option-terminator', argIndex }); remainingArgs.forEach((arg) => - elements.push({ kind: 'positional', - value: arg, argIndex: ++argIndex })); + ArrayPrototypePush( + elements, + { kind: 'positional', value: arg, argIndex: ++argIndex })); break; // Finished processing args, leave while loop. } @@ -216,9 +217,10 @@ const parseArgs = (config = { __proto__: null }) => { value = ArrayPrototypeShift(remainingArgs); inlineValue = false; } - elements.push({ kind: 'option', optionName, - isShort: true, argIndex, - value, inlineValue }); + ArrayPrototypePush( + elements, + { kind: 'option', optionName, isShort: true, argIndex, + value, inlineValue }); continue; } @@ -249,9 +251,10 @@ const parseArgs = (config = { __proto__: null }) => { const shortOption = StringPrototypeCharAt(arg, 1); const optionName = findLongOptionForShort(shortOption, options); const value = StringPrototypeSlice(arg, 2); - elements.push({ kind: 'option', optionName, - isShort: true, argIndex, - value, inlineValue: true }); + ArrayPrototypePush( + elements, + { kind: 'option', optionName, isShort: true, argIndex, + value, inlineValue: true }); continue; } @@ -266,9 +269,10 @@ const parseArgs = (config = { __proto__: null }) => { value = ArrayPrototypeShift(remainingArgs); inlineValue = false; } - elements.push({ kind: 'option', optionName, - isShort: false, argIndex, - value, inlineValue }); + ArrayPrototypePush( + elements, + { kind: 'option', optionName, isShort: false, argIndex, + value, inlineValue }); continue; } @@ -277,13 +281,14 @@ const parseArgs = (config = { __proto__: null }) => { const index = StringPrototypeIndexOf(arg, '='); const optionName = StringPrototypeSlice(arg, 2, index); const value = StringPrototypeSlice(arg, index + 1); - elements.push({ kind: 'option', optionName, - isShort: false, argIndex, - value, inlineValue: true }); + ArrayPrototypePush( + elements, + { kind: 'option', optionName, isShort: false, argIndex, + value, inlineValue: true }); continue; } - elements.push({ kind: 'positional', value: arg, argIndex }); + ArrayPrototypePush(elements, { kind: 'positional', value: arg, argIndex }); } const result = { From 1d19fb21dc471c269aeafd1b62c8f8d6df06f74a Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 29 May 2022 20:20:54 +1200 Subject: [PATCH 10/64] forEach replaced with primordial --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 8e984b0..610dd1f 100644 --- a/index.js +++ b/index.js @@ -198,7 +198,7 @@ const parseArgs = (config = { __proto__: null }) => { if (arg === '--') { // Everything after a bare '--' is considered a positional argument. ArrayPrototypePush(elements, { kind: 'option-terminator', argIndex }); - remainingArgs.forEach((arg) => + ArrayPrototypeForEach(remainingArgs, (arg) => ArrayPrototypePush( elements, { kind: 'positional', value: arg, argIndex: ++argIndex })); @@ -296,7 +296,7 @@ const parseArgs = (config = { __proto__: null }) => { positionals: [], // parseElements = elements }; - elements.forEach((element) => { + ArrayPrototypeForEach(elements, (element) => { switch (element.kind) { case 'option-terminator': break; From 9d1267d0d426ba31965d795364ea437f127dcfc4 Mon Sep 17 00:00:00 2001 From: John Gee Date: Mon, 30 May 2022 20:33:53 +1200 Subject: [PATCH 11/64] Swap isShort for optionUsed --- index.js | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/index.js b/index.js index 610dd1f..925efdc 100644 --- a/index.js +++ b/index.js @@ -12,6 +12,7 @@ const { StringPrototypeCharAt, StringPrototypeIndexOf, StringPrototypeSlice, + StringPrototypeStartsWith, } = require('./primordials'); const { @@ -70,10 +71,9 @@ function checkOptionLikeValue(config, element) { if (config.strict && (element.inlineValue === false) && isOptionLikeValue(element.value)) { // Only show short example if user used short option. - const short = optionsGetOwn(config.options, element.optionName, 'short'); - const example = (element.isShort ?? short) ? - `'--${element.optionName}=-XYZ' or '-${short}-XYZ'` : - `'--${element.optionName}=-XYZ'`; + const example = StringPrototypeStartsWith(element.optionUsed, '--') ? + `'${element.optionUsed}=-XYZ'` : + `'--${element.optionName}=-XYZ' or '${element.optionUsed}-XYZ'`; const arg = config.args[element.argIndex]; const errorMessage = `Option '${arg}' argument is ambiguous. Did you forget to specify the option argument for '${arg}'? @@ -92,9 +92,8 @@ function checkOptionUsage(config, element) { if (!config.strict) return; if (!ObjectHasOwn(config.options, element.optionName)) { - const optionUsed = element.isShort ? `-${element.optionName}` : `--${element.optionName}`; - // eslint-disable-next-line max-len - throw new ERR_PARSE_ARGS_UNKNOWN_OPTION(optionUsed, config.allowPositionals); + throw new ERR_PARSE_ARGS_UNKNOWN_OPTION( + element.optionUsed, config.allowPositionals); } const short = optionsGetOwn(config.options, element.optionName, 'short'); @@ -219,8 +218,8 @@ const parseArgs = (config = { __proto__: null }) => { } ArrayPrototypePush( elements, - { kind: 'option', optionName, isShort: true, argIndex, - value, inlineValue }); + { kind: 'option', optionName, optionUsed: arg, + argIndex, value, inlineValue }); continue; } @@ -253,8 +252,8 @@ const parseArgs = (config = { __proto__: null }) => { const value = StringPrototypeSlice(arg, 2); ArrayPrototypePush( elements, - { kind: 'option', optionName, isShort: true, argIndex, - value, inlineValue: true }); + { kind: 'option', optionName, optionUsed: `-${shortOption}`, + argIndex, value, inlineValue: true }); continue; } @@ -271,8 +270,8 @@ const parseArgs = (config = { __proto__: null }) => { } ArrayPrototypePush( elements, - { kind: 'option', optionName, isShort: false, argIndex, - value, inlineValue }); + { kind: 'option', optionName, optionUsed: arg, + argIndex, value, inlineValue }); continue; } @@ -283,8 +282,8 @@ const parseArgs = (config = { __proto__: null }) => { const value = StringPrototypeSlice(arg, index + 1); ArrayPrototypePush( elements, - { kind: 'option', optionName, isShort: false, argIndex, - value, inlineValue: true }); + { kind: 'option', optionName, optionUsed: `--${optionName}`, + argIndex, value, inlineValue: true }); continue; } From c9c4d4c4bfbbd97c3944d442d9bab72613add789 Mon Sep 17 00:00:00 2001 From: John Gee Date: Mon, 30 May 2022 21:08:26 +1200 Subject: [PATCH 12/64] Rework checkOptionLikeValue --- index.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 925efdc..2fbaa36 100644 --- a/index.js +++ b/index.js @@ -68,15 +68,14 @@ function getMainArgs() { * @param {object} element- array item from parseElements returned by parseArgs */ function checkOptionLikeValue(config, element) { - if (config.strict && (element.inlineValue === false) && - isOptionLikeValue(element.value)) { + if (config.strict && (element.kind === 'option') && + (element.inlineValue === false) && isOptionLikeValue(element.value)) { // Only show short example if user used short option. const example = StringPrototypeStartsWith(element.optionUsed, '--') ? `'${element.optionUsed}=-XYZ'` : `'--${element.optionName}=-XYZ' or '${element.optionUsed}-XYZ'`; - const arg = config.args[element.argIndex]; - const errorMessage = `Option '${arg}' argument is ambiguous. -Did you forget to specify the option argument for '${arg}'? + const errorMessage = `Option '${element.optionUsed}' argument is ambiguous. +Did you forget to specify the option argument for '${element.optionUsed}'? To specify an option argument starting with a dash use ${example}.`; throw new ERR_PARSE_ARGS_INVALID_OPTION_VALUE(errorMessage); } From ed4168d3a26846fb70cf8ddde63b207e074e349f Mon Sep 17 00:00:00 2001 From: John Gee Date: Mon, 30 May 2022 22:39:08 +1200 Subject: [PATCH 13/64] Consistent property order and renames --- index.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/index.js b/index.js index 2fbaa36..9ff61d4 100644 --- a/index.js +++ b/index.js @@ -179,7 +179,7 @@ const parseArgs = (config = { __proto__: null }) => { ); const elements = []; - let argIndex = -1; + let index = -1; let groupCount = 0; const remainingArgs = ArrayPrototypeSlice(args); @@ -189,17 +189,17 @@ const parseArgs = (config = { __proto__: null }) => { if (groupCount > 0) groupCount--; else - argIndex++; + index++; // Check if `arg` is an options terminator. // Guideline 10 in https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html if (arg === '--') { // Everything after a bare '--' is considered a positional argument. - ArrayPrototypePush(elements, { kind: 'option-terminator', argIndex }); + ArrayPrototypePush(elements, { kind: 'option-terminator', index }); ArrayPrototypeForEach(remainingArgs, (arg) => ArrayPrototypePush( elements, - { kind: 'positional', value: arg, argIndex: ++argIndex })); + { kind: 'positional', index: ++index, value: arg })); break; // Finished processing args, leave while loop. } @@ -218,7 +218,7 @@ const parseArgs = (config = { __proto__: null }) => { ArrayPrototypePush( elements, { kind: 'option', optionName, optionUsed: arg, - argIndex, value, inlineValue }); + index, value, inlineValue }); continue; } @@ -252,7 +252,7 @@ const parseArgs = (config = { __proto__: null }) => { ArrayPrototypePush( elements, { kind: 'option', optionName, optionUsed: `-${shortOption}`, - argIndex, value, inlineValue: true }); + index, value, inlineValue: true }); continue; } @@ -270,7 +270,7 @@ const parseArgs = (config = { __proto__: null }) => { ArrayPrototypePush( elements, { kind: 'option', optionName, optionUsed: arg, - argIndex, value, inlineValue }); + index, value, inlineValue }); continue; } @@ -282,17 +282,17 @@ const parseArgs = (config = { __proto__: null }) => { ArrayPrototypePush( elements, { kind: 'option', optionName, optionUsed: `--${optionName}`, - argIndex, value, inlineValue: true }); + index, value, inlineValue: true }); continue; } - ArrayPrototypePush(elements, { kind: 'positional', value: arg, argIndex }); + ArrayPrototypePush(elements, { kind: 'positional', index, value: arg }); } const result = { values: { __proto__: null }, positionals: [], - // parseElements = elements + tokens: elements, }; ArrayPrototypeForEach(elements, (element) => { switch (element.kind) { From 16a9f5165de366b5fc85fa362e804b81c36ad9de Mon Sep 17 00:00:00 2001 From: John Gee Date: Mon, 30 May 2022 22:44:55 +1200 Subject: [PATCH 14/64] Comment out new returned property until naming and tests ready --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 9ff61d4..b575d38 100644 --- a/index.js +++ b/index.js @@ -292,7 +292,7 @@ const parseArgs = (config = { __proto__: null }) => { const result = { values: { __proto__: null }, positionals: [], - tokens: elements, + // tokens: elements, }; ArrayPrototypeForEach(elements, (element) => { switch (element.kind) { From 915e6c3f64194a2149b15ad39dc9bfb05c353101 Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 31 May 2022 18:21:26 +1200 Subject: [PATCH 15/64] Refactor tokenize into own function --- index.js | 123 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 70 insertions(+), 53 deletions(-) diff --git a/index.js b/index.js index b575d38..9621073 100644 --- a/index.js +++ b/index.js @@ -140,45 +140,17 @@ function storeOption(optionName, optionValue, options, values) { } } -const parseArgs = (config = { __proto__: null }) => { - const args = objectGetOwn(config, 'args') ?? getMainArgs(); - const strict = objectGetOwn(config, 'strict') ?? true; - const allowPositionals = objectGetOwn(config, 'allowPositionals') ?? !strict; - const options = objectGetOwn(config, 'options') ?? { __proto__: null }; - const parseConfig = { args, strict, options, allowPositionals }; - - // Validate input configuration. - validateArray(args, 'args'); - validateBoolean(strict, 'strict'); - validateBoolean(allowPositionals, 'allowPositionals'); - validateObject(options, 'options'); - ArrayPrototypeForEach( - ObjectEntries(options), - ({ 0: longOption, 1: optionConfig }) => { - validateObject(optionConfig, `options.${longOption}`); - - // type is required - validateUnion(objectGetOwn(optionConfig, 'type'), `options.${longOption}.type`, ['string', 'boolean']); - - if (ObjectHasOwn(optionConfig, 'short')) { - const shortOption = optionConfig.short; - validateString(shortOption, `options.${longOption}.short`); - if (shortOption.length !== 1) { - throw new ERR_INVALID_ARG_VALUE( - `options.${longOption}.short`, - shortOption, - 'must be a single character' - ); - } - } - - if (ObjectHasOwn(optionConfig, 'multiple')) { - validateBoolean(optionConfig.multiple, `options.${longOption}.multiple`); - } - } - ); - - const elements = []; +/** + * Process args and turn into identified tokens: + * - option (along with value, if any) + * - positional + * - option-terminator + * + * @param {string[]} args, from parseArgs({ args }) or mainArgs + * @param {object} options - option configs, from parseArgs({ options }) + */ +function argsToTokens(args, options) { + const tokens = []; let index = -1; let groupCount = 0; @@ -195,10 +167,10 @@ const parseArgs = (config = { __proto__: null }) => { // Guideline 10 in https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html if (arg === '--') { // Everything after a bare '--' is considered a positional argument. - ArrayPrototypePush(elements, { kind: 'option-terminator', index }); + ArrayPrototypePush(tokens, { kind: 'option-terminator', index }); ArrayPrototypeForEach(remainingArgs, (arg) => ArrayPrototypePush( - elements, + tokens, { kind: 'positional', index: ++index, value: arg })); break; // Finished processing args, leave while loop. } @@ -216,7 +188,7 @@ const parseArgs = (config = { __proto__: null }) => { inlineValue = false; } ArrayPrototypePush( - elements, + tokens, { kind: 'option', optionName, optionUsed: arg, index, value, inlineValue }); continue; @@ -250,7 +222,7 @@ const parseArgs = (config = { __proto__: null }) => { const optionName = findLongOptionForShort(shortOption, options); const value = StringPrototypeSlice(arg, 2); ArrayPrototypePush( - elements, + tokens, { kind: 'option', optionName, optionUsed: `-${shortOption}`, index, value, inlineValue: true }); continue; @@ -268,7 +240,7 @@ const parseArgs = (config = { __proto__: null }) => { inlineValue = false; } ArrayPrototypePush( - elements, + tokens, { kind: 'option', optionName, optionUsed: arg, index, value, inlineValue }); continue; @@ -280,34 +252,79 @@ const parseArgs = (config = { __proto__: null }) => { const optionName = StringPrototypeSlice(arg, 2, index); const value = StringPrototypeSlice(arg, index + 1); ArrayPrototypePush( - elements, + tokens, { kind: 'option', optionName, optionUsed: `--${optionName}`, index, value, inlineValue: true }); continue; } - ArrayPrototypePush(elements, { kind: 'positional', index, value: arg }); + ArrayPrototypePush(tokens, { kind: 'positional', index, value: arg }); } + return tokens; +} + +const parseArgs = (config = { __proto__: null }) => { + const args = objectGetOwn(config, 'args') ?? getMainArgs(); + const strict = objectGetOwn(config, 'strict') ?? true; + const allowPositionals = objectGetOwn(config, 'allowPositionals') ?? !strict; + const options = objectGetOwn(config, 'options') ?? { __proto__: null }; + // Bundle these up for passing to strict-mode checks. + const parseConfig = { args, strict, options, allowPositionals }; + + // Validate input configuration. + validateArray(args, 'args'); + validateBoolean(strict, 'strict'); + validateBoolean(allowPositionals, 'allowPositionals'); + validateObject(options, 'options'); + ArrayPrototypeForEach( + ObjectEntries(options), + ({ 0: longOption, 1: optionConfig }) => { + validateObject(optionConfig, `options.${longOption}`); + + // type is required + validateUnion(objectGetOwn(optionConfig, 'type'), `options.${longOption}.type`, ['string', 'boolean']); + + if (ObjectHasOwn(optionConfig, 'short')) { + const shortOption = optionConfig.short; + validateString(shortOption, `options.${longOption}.short`); + if (shortOption.length !== 1) { + throw new ERR_INVALID_ARG_VALUE( + `options.${longOption}.short`, + shortOption, + 'must be a single character' + ); + } + } + + if (ObjectHasOwn(optionConfig, 'multiple')) { + validateBoolean(optionConfig.multiple, `options.${longOption}.multiple`); + } + } + ); + + // Phase 1: identify tokens + const tokens = argsToTokens(args, options); + // Phase 2: process tokens into parsed option values and positionals const result = { values: { __proto__: null }, positionals: [], // tokens: elements, }; - ArrayPrototypeForEach(elements, (element) => { - switch (element.kind) { + ArrayPrototypeForEach(tokens, (token) => { + switch (token.kind) { case 'option-terminator': break; case 'positional': if (!allowPositionals) { - throw new ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL(element.value); + throw new ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL(token.value); } - ArrayPrototypePush(result.positionals, element.value); + ArrayPrototypePush(result.positionals, token.value); break; case 'option': - checkOptionUsage(parseConfig, element); - checkOptionLikeValue(parseConfig, element); - storeOption(element.optionName, element.value, options, result.values); + checkOptionUsage(parseConfig, token); + checkOptionLikeValue(parseConfig, token); + storeOption(token.optionName, token.value, options, result.values); break; } }); From 641197ee6e2a2630e3ee756ea719a5628bcd7da2 Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 31 May 2022 21:40:40 +1200 Subject: [PATCH 16/64] First test pass, get tests working by ignoring tokens in results --- index.js | 2 +- test/dash.js | 4 +- test/index.js | 76 ++++++++++++------------ test/prototype-pollution.js | 4 +- test/short-option-combined-with-value.js | 14 ++--- test/short-option-groups.js | 8 +-- 6 files changed, 55 insertions(+), 53 deletions(-) diff --git a/index.js b/index.js index 9621073..57b33ec 100644 --- a/index.js +++ b/index.js @@ -309,7 +309,7 @@ const parseArgs = (config = { __proto__: null }) => { const result = { values: { __proto__: null }, positionals: [], - // tokens: elements, + tokens, }; ArrayPrototypeForEach(tokens, (token) => { switch (token.kind) { diff --git a/test/dash.js b/test/dash.js index 0ab661b..4ae413c 100644 --- a/test/dash.js +++ b/test/dash.js @@ -17,7 +17,7 @@ test("dash: when args include '-' used as positional then result has '-' in posi const result = parseArgs({ allowPositionals: true, args }); - t.deepEqual(result, expected); + t.deepEqual({ values: result.values, positionals: result.positionals }, expected); t.end(); }); @@ -29,6 +29,6 @@ test("dash: when args include '-' used as space-separated option value then resu const result = parseArgs({ args, options }); - t.deepEqual(result, expected); + t.deepEqual({ values: result.values, positionals: result.positionals }, expected); t.end(); }); diff --git a/test/index.js b/test/index.js index f936d30..7fab76d 100644 --- a/test/index.js +++ b/test/index.js @@ -11,14 +11,14 @@ test('when short option used as flag then stored as flag', () => { const args = ['-f']; const expected = { values: { __proto__: null, f: true }, positionals: [] }; const result = parseArgs({ strict: false, args }); - assert.deepStrictEqual(result, expected); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); }); test('when short option used as flag before positional then stored as flag and positional (and not value)', () => { const args = ['-f', 'bar']; const expected = { values: { __proto__: null, f: true }, positionals: [ 'bar' ] }; const result = parseArgs({ strict: false, args }); - assert.deepStrictEqual(result, expected); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); }); test('when short option `type: "string"` used with value then stored as value', () => { @@ -26,7 +26,7 @@ test('when short option `type: "string"` used with value then stored as value', const options = { f: { type: 'string' } }; const expected = { values: { __proto__: null, f: 'bar' }, positionals: [] }; const result = parseArgs({ args, options }); - assert.deepStrictEqual(result, expected); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); }); test('when short option listed in short used as flag then long option stored as flag', () => { @@ -34,7 +34,7 @@ test('when short option listed in short used as flag then long option stored as const options = { foo: { short: 'f', type: 'boolean' } }; const expected = { values: { __proto__: null, foo: true }, positionals: [] }; const result = parseArgs({ args, options }); - assert.deepStrictEqual(result, expected); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); }); test('when short option listed in short and long listed in `type: "string"` and ' + @@ -43,7 +43,7 @@ test('when short option listed in short and long listed in `type: "string"` and const options = { foo: { short: 'f', type: 'string' } }; const expected = { values: { __proto__: null, foo: 'bar' }, positionals: [] }; const result = parseArgs({ args, options }); - assert.deepStrictEqual(result, expected); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); }); test('when short option `type: "string"` used without value then stored as flag', () => { @@ -51,7 +51,7 @@ test('when short option `type: "string"` used without value then stored as flag' const options = { f: { type: 'string' } }; const expected = { values: { __proto__: null, f: true }, positionals: [] }; const result = parseArgs({ strict: false, args, options }); - assert.deepStrictEqual(result, expected); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); }); test('short option group behaves like multiple short options', () => { @@ -59,7 +59,7 @@ test('short option group behaves like multiple short options', () => { const options = { }; const expected = { values: { __proto__: null, r: true, f: true }, positionals: [] }; const result = parseArgs({ strict: false, args, options }); - assert.deepStrictEqual(result, expected); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); }); test('short option group does not consume subsequent positional', () => { @@ -67,7 +67,7 @@ test('short option group does not consume subsequent positional', () => { const options = { }; const expected = { values: { __proto__: null, r: true, f: true }, positionals: ['foo'] }; const result = parseArgs({ strict: false, args, options }); - assert.deepStrictEqual(result, expected); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); }); // See: Guideline 5 https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html @@ -76,7 +76,7 @@ test('if terminal of short-option group configured `type: "string"`, subsequent const options = { f: { type: 'string' } }; const expected = { values: { __proto__: null, r: true, v: true, f: 'foo' }, positionals: [] }; const result = parseArgs({ strict: false, args, options }); - assert.deepStrictEqual(result, expected); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); }); test('handles short-option groups in conjunction with long-options', () => { @@ -84,7 +84,7 @@ test('handles short-option groups in conjunction with long-options', () => { const options = { foo: { type: 'string' } }; const expected = { values: { __proto__: null, r: true, f: true, foo: 'foo' }, positionals: [] }; const result = parseArgs({ strict: false, args, options }); - assert.deepStrictEqual(result, expected); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); }); test('handles short-option groups with "short" alias configured', () => { @@ -92,28 +92,28 @@ test('handles short-option groups with "short" alias configured', () => { const options = { remove: { short: 'r', type: 'boolean' } }; const expected = { values: { __proto__: null, remove: true, f: true }, positionals: [] }; const result = parseArgs({ strict: false, args, options }); - assert.deepStrictEqual(result, expected); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); }); test('Everything after a bare `--` is considered a positional argument', () => { const args = ['--', 'barepositionals', 'mopositionals']; const expected = { values: { __proto__: null }, positionals: ['barepositionals', 'mopositionals'] }; const result = parseArgs({ allowPositionals: true, args }); - assert.deepStrictEqual(result, expected, Error('testing bare positionals')); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); }); test('args are true', () => { const args = ['--foo', '--bar']; const expected = { values: { __proto__: null, foo: true, bar: true }, positionals: [] }; const result = parseArgs({ strict: false, args }); - assert.deepStrictEqual(result, expected, Error('args are true')); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); }); test('arg is true and positional is identified', () => { const args = ['--foo=a', '--foo', 'b']; const expected = { values: { __proto__: null, foo: true }, positionals: ['b'] }; const result = parseArgs({ strict: false, args }); - assert.deepStrictEqual(result, expected, Error('arg is true and positional is identified')); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); }); test('args equals are passed `type: "string"`', () => { @@ -121,14 +121,14 @@ test('args equals are passed `type: "string"`', () => { const options = { so: { type: 'string' } }; const expected = { values: { __proto__: null, so: 'wat' }, positionals: [] }; const result = parseArgs({ args, options }); - assert.deepStrictEqual(result, expected, Error('arg value is passed')); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); }); test('when args include single dash then result stores dash as positional', () => { const args = ['-']; const expected = { values: { __proto__: null }, positionals: ['-'] }; const result = parseArgs({ allowPositionals: true, args }); - assert.deepStrictEqual(result, expected); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); }); test('zero config args equals are parsed as if `type: "string"`', () => { @@ -136,7 +136,7 @@ test('zero config args equals are parsed as if `type: "string"`', () => { const options = { }; const expected = { values: { __proto__: null, so: 'wat' }, positionals: [] }; const result = parseArgs({ strict: false, args, options }); - assert.deepStrictEqual(result, expected, Error('arg value is passed')); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); }); test('same arg is passed twice `type: "string"` and last value is recorded', () => { @@ -144,7 +144,7 @@ test('same arg is passed twice `type: "string"` and last value is recorded', () const options = { foo: { type: 'string' } }; const expected = { values: { __proto__: null, foo: 'b' }, positionals: [] }; const result = parseArgs({ args, options }); - assert.deepStrictEqual(result, expected, Error('last arg value is passed')); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); }); test('args equals pass string including more equals', () => { @@ -152,7 +152,7 @@ test('args equals pass string including more equals', () => { const options = { so: { type: 'string' } }; const expected = { values: { __proto__: null, so: 'wat=bing' }, positionals: [] }; const result = parseArgs({ args, options }); - assert.deepStrictEqual(result, expected, Error('arg value is passed')); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); }); test('first arg passed for `type: "string"` and "multiple" is in array', () => { @@ -160,7 +160,7 @@ test('first arg passed for `type: "string"` and "multiple" is in array', () => { const options = { foo: { type: 'string', multiple: true } }; const expected = { values: { __proto__: null, foo: ['a'] }, positionals: [] }; const result = parseArgs({ args, options }); - assert.deepStrictEqual(result, expected, Error('first multiple in array')); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); }); test('args are passed `type: "string"` and "multiple"', () => { @@ -173,7 +173,7 @@ test('args are passed `type: "string"` and "multiple"', () => { }; const expected = { values: { __proto__: null, foo: ['a', 'b'] }, positionals: [] }; const result = parseArgs({ args, options }); - assert.deepStrictEqual(result, expected, Error('both arg values are passed')); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); }); test('when expecting `multiple:true` boolean option and option used multiple times then result includes array of ' + @@ -187,7 +187,7 @@ test('when expecting `multiple:true` boolean option and option used multiple tim }; const expected = { values: { __proto__: null, foo: [true, true] }, positionals: [] }; const result = parseArgs({ args, options }); - assert.deepStrictEqual(result, expected); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); }); test('order of option and positional does not matter (per README)', () => { @@ -195,13 +195,15 @@ test('order of option and positional does not matter (per README)', () => { const args2 = ['baz', '--foo=bar']; const options = { foo: { type: 'string' } }; const expected = { values: { __proto__: null, foo: 'bar' }, positionals: ['baz'] }; + let result = parseArgs({ allowPositionals: true, args: args1, options }); assert.deepStrictEqual( - parseArgs({ allowPositionals: true, args: args1, options }), + { values: result.values, positionals: result.positionals }, expected, Error('option then positional') ); + result = parseArgs({ allowPositionals: true, args: args2, options }); assert.deepStrictEqual( - parseArgs({ allowPositionals: true, args: args2, options }), + { values: result.values, positionals: result.positionals }, expected, Error('positional then option') ); @@ -216,7 +218,7 @@ test('correct default args when use node -p', () => { const expected = { values: { __proto__: null, foo: true }, positionals: [] }; - assert.deepStrictEqual(result, expected); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); process.argv = holdArgv; process.execArgv = holdExecArgv; }); @@ -230,7 +232,7 @@ test('correct default args when use node --print', () => { const expected = { values: { __proto__: null, foo: true }, positionals: [] }; - assert.deepStrictEqual(result, expected); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); process.argv = holdArgv; process.execArgv = holdExecArgv; }); @@ -244,7 +246,7 @@ test('correct default args when use node -e', () => { const expected = { values: { __proto__: null, foo: true }, positionals: [] }; - assert.deepStrictEqual(result, expected); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); process.argv = holdArgv; process.execArgv = holdExecArgv; }); @@ -257,7 +259,7 @@ test('correct default args when use node --eval', () => { const result = parseArgs({ strict: false }); const expected = { values: { __proto__: null, foo: true }, positionals: [] }; - assert.deepStrictEqual(result, expected); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); process.argv = holdArgv; process.execArgv = holdExecArgv; }); @@ -271,7 +273,7 @@ test('correct default args when normal arguments', () => { const expected = { values: { __proto__: null, foo: true }, positionals: [] }; - assert.deepStrictEqual(result, expected); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); process.argv = holdArgv; process.execArgv = holdExecArgv; }); @@ -285,7 +287,7 @@ test('excess leading dashes on options are retained', () => { positionals: [] }; const result = parseArgs({ strict: false, args, options }); - assert.deepStrictEqual(result, expected, Error('excess option dashes are retained')); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); }); test('positional arguments are allowed by default in strict:false', () => { @@ -296,7 +298,7 @@ test('positional arguments are allowed by default in strict:false', () => { positionals: ['foo'] }; const result = parseArgs({ strict: false, args, options }); - assert.deepStrictEqual(result, expected); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); }); test('positional arguments may be explicitly disallowed in strict:false', () => { @@ -396,7 +398,7 @@ test('-- by itself is not a positional', () => { const result = parseArgs({ args, options }); const expected = { values: { __proto__: null, foo: true }, positionals: [] }; - assert.deepStrictEqual(result, expected); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); }); test('string option used as boolean', () => { @@ -434,7 +436,7 @@ test('null prototype: when --toString then values.toString is true', () => { const expectedResult = { values: { __proto__: null, toString: true }, positionals: [] }; const result = parseArgs({ args, options }); - assert.deepStrictEqual(result, expectedResult); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expectedResult); }); const candidateGreedyOptions = [ @@ -454,7 +456,7 @@ candidateGreedyOptions.forEach((value) => { const expectedResult = { values: { __proto__: null, with: value }, positionals: [] }; const result = parseArgs({ args, options, strict: false }); - assert.deepStrictEqual(result, expectedResult); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expectedResult); }); test(`greedy: when long option with value '${value}' then eaten`, () => { @@ -463,7 +465,7 @@ candidateGreedyOptions.forEach((value) => { const expectedResult = { values: { __proto__: null, with: value }, positionals: [] }; const result = parseArgs({ args, options, strict: false }); - assert.deepStrictEqual(result, expectedResult); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expectedResult); }); }); @@ -473,7 +475,7 @@ test('strict: when candidate option value is plain text then does not throw', () const expectedResult = { values: { __proto__: null, with: 'abc' }, positionals: [] }; const result = parseArgs({ args, options, strict: true }); - assert.deepStrictEqual(result, expectedResult); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expectedResult); }); test("strict: when candidate option value is '-' then does not throw", () => { @@ -482,7 +484,7 @@ test("strict: when candidate option value is '-' then does not throw", () => { const expectedResult = { values: { __proto__: null, with: '-' }, positionals: [] }; const result = parseArgs({ args, options, strict: true }); - assert.deepStrictEqual(result, expectedResult); + assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expectedResult); }); test("strict: when candidate option value is '--' then throws", () => { diff --git a/test/prototype-pollution.js b/test/prototype-pollution.js index a8a5d2f..d44fda2 100644 --- a/test/prototype-pollution.js +++ b/test/prototype-pollution.js @@ -27,7 +27,7 @@ test('should not allow __proto__ key to be set on object', (t) => { const result = parseArgs({ strict: false, args }); - t.deepEqual(result, expected); + t.deepEqual({ values: result.values, positionals: result.positionals }, expected); t.end(); }); @@ -39,7 +39,7 @@ test('when prototype has multiple then ignored', (t) => { const holdDescriptor = setObjectPrototype('multiple', true); const result = parseArgs({ args, options }); restoreObjectPrototype('multiple', holdDescriptor); - t.deepEqual(result, expectedResult); + t.deepEqual({ values: result.values, positionals: result.positionals }, expectedResult); t.end(); }); diff --git a/test/short-option-combined-with-value.js b/test/short-option-combined-with-value.js index ca0ec6a..0294c5b 100644 --- a/test/short-option-combined-with-value.js +++ b/test/short-option-combined-with-value.js @@ -11,7 +11,7 @@ test('when combine string short with plain text then parsed as value', (t) => { const result = parseArgs({ args, options }); - t.deepEqual(result, expected); + t.deepEqual({ values: result.values, positionals: result.positionals }, expected); t.end(); }); @@ -22,7 +22,7 @@ test('when combine low-config string short with plain text then parsed as value' const result = parseArgs({ args, options }); - t.deepEqual(result, expected); + t.deepEqual({ values: result.values, positionals: result.positionals }, expected); t.end(); }); @@ -33,7 +33,7 @@ test('when combine string short with value like short option then parsed as valu const result = parseArgs({ args, options }); - t.deepEqual(result, expected); + t.deepEqual({ values: result.values, positionals: result.positionals }, expected); t.end(); }); @@ -44,7 +44,7 @@ test('when combine string short with value like long option then parsed as value const result = parseArgs({ args, options }); - t.deepEqual(result, expected); + t.deepEqual({ values: result.values, positionals: result.positionals }, expected); t.end(); }); @@ -55,7 +55,7 @@ test('when combine string short with value like negative number then parsed as v const result = parseArgs({ args, options }); - t.deepEqual(result, expected); + t.deepEqual({ values: result.values, positionals: result.positionals }, expected); t.end(); }); @@ -66,7 +66,7 @@ test('when combine string short with value which matches configured flag then pa const expected = { values: { __proto__: null, alpha: 'f' }, positionals: [] }; const result = parseArgs({ args, options }); - t.deepEqual(result, expected); + t.deepEqual({ values: result.values, positionals: result.positionals }, expected); t.end(); }); @@ -77,6 +77,6 @@ test('when combine string short with value including equals then parsed with equ const result = parseArgs({ args, options }); - t.deepEqual(result, expected); + t.deepEqual({ values: result.values, positionals: result.positionals }, expected); t.end(); }); diff --git a/test/short-option-groups.js b/test/short-option-groups.js index c790a7d..317f73f 100644 --- a/test/short-option-groups.js +++ b/test/short-option-groups.js @@ -11,7 +11,7 @@ test('when pass zero-config group of booleans then parsed as booleans', (t) => { const result = parseArgs({ strict: false, args, options }); - t.deepEqual(result, expected); + t.deepEqual({ values: result.values, positionals: result.positionals }, expected); t.end(); }); @@ -22,7 +22,7 @@ test('when pass full-config group of booleans then parsed as booleans', (t) => { const result = parseArgs({ allowPositionals: true, args, options }); - t.deepEqual(result, expected); + t.deepEqual({ values: result.values, positionals: result.positionals }, expected); t.end(); }); @@ -33,7 +33,7 @@ test('when pass group with string option on end then parsed as booleans and stri const result = parseArgs({ args, options }); - t.deepEqual(result, expected); + t.deepEqual({ values: result.values, positionals: result.positionals }, expected); t.end(); }); @@ -44,6 +44,6 @@ test('when pass group with string option in middle and strict:false then parsed const result = parseArgs({ args, options, strict: false }); - t.deepEqual(result, expected); + t.deepEqual({ values: result.values, positionals: result.positionals }, expected); t.end(); }); From 18470bd2eb1036fc622914080c59c8ee7f9be15c Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 31 May 2022 21:46:35 +1200 Subject: [PATCH 17/64] Less magical check now checking kind --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 57b33ec..a090830 100644 --- a/index.js +++ b/index.js @@ -69,7 +69,7 @@ function getMainArgs() { */ function checkOptionLikeValue(config, element) { if (config.strict && (element.kind === 'option') && - (element.inlineValue === false) && isOptionLikeValue(element.value)) { + !element.inlineValue && isOptionLikeValue(element.value)) { // Only show short example if user used short option. const example = StringPrototypeStartsWith(element.optionUsed, '--') ? `'${element.optionUsed}=-XYZ'` : From 38d1d6c7b06a2c6ed6e194da868c9ab90e073321 Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 31 May 2022 23:21:02 +1200 Subject: [PATCH 18/64] Add some token tests --- index.js | 6 +- test/index.js | 245 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 248 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index a090830..6e89541 100644 --- a/index.js +++ b/index.js @@ -248,9 +248,9 @@ function argsToTokens(args, options) { if (isLongOptionAndValue(arg)) { // e.g. --foo=bar - const index = StringPrototypeIndexOf(arg, '='); - const optionName = StringPrototypeSlice(arg, 2, index); - const value = StringPrototypeSlice(arg, index + 1); + const equalIndex = StringPrototypeIndexOf(arg, '='); + const optionName = StringPrototypeSlice(arg, 2, equalIndex); + const value = StringPrototypeSlice(arg, equalIndex + 1); ArrayPrototypePush( tokens, { kind: 'option', optionName, optionUsed: `--${optionName}`, diff --git a/test/index.js b/test/index.js index 7fab76d..9e9112e 100644 --- a/test/index.js +++ b/test/index.js @@ -570,3 +570,248 @@ test('strict: when long option and suspect value then throws with whole expected }, /To specify an option argument starting with a dash use '--with=-XYZ'/ ); }); + +test('tokens: positional', () => { + const args = ['one']; + const expectedTokens = [ + { kind: 'positional', index: 0, value: 'one' }, + ]; + const result = parseArgs({ strict: false, args }); + assert.deepStrictEqual(result.tokens, expectedTokens); +}); + +test('tokens: -- followed by option-like', () => { + const args = ['--', '--foo']; + const expectedTokens = [ + { kind: 'option-terminator', index: 0 }, + { kind: 'positional', index: 1, value: '--foo' }, + ]; + const result = parseArgs({ strict: false, args }); + assert.deepStrictEqual(result.tokens, expectedTokens); +}); + +test('tokens: strict:true boolean short', () => { + const args = ['-f']; + const options = { + file: { short: 'f', type: 'boolean' } + }; + const expectedTokens = [ + { kind: 'option', optionName: 'file', optionUsed: '-f', + index: 0, value: undefined, inlineValue: undefined }, + ]; + const result = parseArgs({ strict: true, args, options }); + assert.deepStrictEqual(result.tokens, expectedTokens); +}); + +test('tokens: strict:true boolean long', () => { + const args = ['--file']; + const options = { + file: { short: 'f', type: 'boolean' } + }; + const expectedTokens = [ + { kind: 'option', optionName: 'file', optionUsed: '--file', + index: 0, value: undefined, inlineValue: undefined }, + ]; + const result = parseArgs({ strict: true, args, options }); + assert.deepStrictEqual(result.tokens, expectedTokens); +}); + +test('tokens: strict:false boolean short', () => { + const args = ['-f']; + const expectedTokens = [ + { kind: 'option', optionName: 'f', optionUsed: '-f', + index: 0, value: undefined, inlineValue: undefined }, + ]; + const result = parseArgs({ strict: false, args }); + assert.deepStrictEqual(result.tokens, expectedTokens); +}); + +test('tokens: strict:false boolean long', () => { + const args = ['--file']; + const expectedTokens = [ + { kind: 'option', optionName: 'file', optionUsed: '--file', + index: 0, value: undefined, inlineValue: undefined }, + ]; + const result = parseArgs({ strict: false, args }); + assert.deepStrictEqual(result.tokens, expectedTokens); +}); + +test('tokens: strict:false boolean option group', () => { + const args = ['-ab']; + const expectedTokens = [ + { kind: 'option', optionName: 'a', optionUsed: '-a', + index: 0, value: undefined, inlineValue: undefined }, + { kind: 'option', optionName: 'b', optionUsed: '-b', + index: 0, value: undefined, inlineValue: undefined }, + ]; + const result = parseArgs({ strict: false, args }); + assert.deepStrictEqual(result.tokens, expectedTokens); +}); + +test('tokens: strict:true string short with value after space', () => { + const args = ['-f', 'bar']; + const options = { + file: { short: 'f', type: 'string' } + }; + const expectedTokens = [ + { kind: 'option', optionName: 'file', optionUsed: '-f', + index: 0, value: 'bar', inlineValue: false }, + ]; + const result = parseArgs({ strict: true, args, options }); + assert.deepStrictEqual(result.tokens, expectedTokens); +}); + +test('tokens: strict:true string short with value inline', () => { + const args = ['-fBAR']; + const options = { + file: { short: 'f', type: 'string' } + }; + const expectedTokens = [ + { kind: 'option', optionName: 'file', optionUsed: '-f', + index: 0, value: 'BAR', inlineValue: true }, + ]; + const result = parseArgs({ strict: true, args, options }); + assert.deepStrictEqual(result.tokens, expectedTokens); +}); + +test('tokens: strict:false string short missing value', () => { + const args = ['-f']; + const options = { + file: { short: 'f', type: 'string' } + }; + const expectedTokens = [ + { kind: 'option', optionName: 'file', optionUsed: '-f', + index: 0, value: undefined, inlineValue: undefined }, + ]; + const result = parseArgs({ strict: false, args, options }); + assert.deepStrictEqual(result.tokens, expectedTokens); +}); + +test('tokens: strict:true string long with value after equals', () => { + const args = ['--file', 'bar']; + const options = { + file: { short: 'f', type: 'string' } + }; + const expectedTokens = [ + { kind: 'option', optionName: 'file', optionUsed: '--file', + index: 0, value: 'bar', inlineValue: false }, + ]; + const result = parseArgs({ strict: true, args, options }); + assert.deepStrictEqual(result.tokens, expectedTokens); +}); + +test('tokens: strict:true string long with value inline', () => { + const args = ['--file=bar']; + const options = { + file: { short: 'f', type: 'string' } + }; + const expectedTokens = [ + { kind: 'option', optionName: 'file', optionUsed: '--file', + index: 0, value: 'bar', inlineValue: true }, + ]; + const result = parseArgs({ strict: true, args, options }); + assert.deepStrictEqual(result.tokens, expectedTokens); +}); + +test('tokens: strict:false string long with value inline', () => { + const args = ['--file=bar']; + const expectedTokens = [ + { kind: 'option', optionName: 'file', optionUsed: '--file', + index: 0, value: 'bar', inlineValue: true }, + ]; + const result = parseArgs({ strict: false, args }); + assert.deepStrictEqual(result.tokens, expectedTokens); +}); + +test('tokens: strict:false string long missing value', () => { + const args = ['--file']; + const options = { + file: { short: 'f', type: 'string' } + }; + const expectedTokens = [ + { kind: 'option', optionName: 'file', optionUsed: '--file', + index: 0, value: undefined, inlineValue: undefined }, + ]; + const result = parseArgs({ strict: false, args, options }); + assert.deepStrictEqual(result.tokens, expectedTokens); +}); + +test('tokens: strict:true complex option group with value after space', () => { + const args = ['-ab', 'c']; + const options = { + alpha: { short: 'a', type: 'boolean' }, + beta: { short: 'b', type: 'string' }, + }; + const expectedTokens = [ + { kind: 'option', optionName: 'alpha', optionUsed: '-a', + index: 0, value: undefined, inlineValue: undefined }, + { kind: 'option', optionName: 'beta', optionUsed: '-b', + index: 0, value: 'c', inlineValue: false }, + ]; + const result = parseArgs({ strict: false, args, options }); + assert.deepStrictEqual(result.tokens, expectedTokens); +}); + +test('tokens: strict:true complex option group with inline value', () => { + const args = ['-abc']; + const options = { + alpha: { short: 'a', type: 'boolean' }, + beta: { short: 'b', type: 'string' }, + }; + const expectedTokens = [ + { kind: 'option', optionName: 'alpha', optionUsed: '-a', + index: 0, value: undefined, inlineValue: undefined }, + { kind: 'option', optionName: 'beta', optionUsed: '-b', + index: 0, value: 'c', inlineValue: true }, + ]; + const result = parseArgs({ strict: false, args, options }); + assert.deepStrictEqual(result.tokens, expectedTokens); +}); + +test('tokens: strict:false variety', () => { + const args = ['-a', '1', '-bc', '2', '--ddd', '--eee=fff', '--', '3']; + const expectedTokens = [ + { kind: 'option', optionName: 'a', optionUsed: '-a', index: 0, value: undefined, inlineValue: undefined }, + { kind: 'positional', index: 1, value: '1' }, + { kind: 'option', optionName: 'b', optionUsed: '-b', index: 2, value: undefined, inlineValue: undefined }, + { kind: 'option', optionName: 'c', optionUsed: '-c', index: 2, value: undefined, inlineValue: undefined }, + { kind: 'positional', index: 3, value: '2' }, + { kind: 'option', optionName: 'ddd', optionUsed: '--ddd', index: 4, value: undefined, inlineValue: undefined }, + { kind: 'option', optionName: 'eee', optionUsed: '--eee', index: 5, value: 'fff', inlineValue: true }, + { kind: 'option-terminator', index: 6 }, + { kind: 'positional', index: 7, value: '3' }, + ]; + const result = parseArgs({ strict: false, args }); + assert.deepStrictEqual(result.tokens, expectedTokens); +}); + +test('tokens: strict:true variety', () => { + const args = ['-a', '1', '-bc', '-dDDD', '-e', 'EEE', '2', + '--fff=FFF', '--ggg', 'GGG', '--hhh', '--', '3']; + const options = { + alpha: { short: 'a', type: 'boolean' }, + beta: { short: 'b', type: 'boolean' }, + cat: { short: 'c', type: 'boolean' }, + delta: { short: 'd', type: 'string' }, + epsilon: { short: 'e', type: 'string' }, + fff: { type: 'string' }, + ggg: { type: 'string' }, + hhh: { type: 'boolean' }, + }; + const expectedTokens = [ + { kind: 'option', optionName: 'alpha', optionUsed: '-a', index: 0, value: undefined, inlineValue: undefined }, + { kind: 'positional', index: 1, value: '1' }, + { kind: 'option', optionName: 'beta', optionUsed: '-b', index: 2, value: undefined, inlineValue: undefined }, + { kind: 'option', optionName: 'cat', optionUsed: '-c', index: 2, value: undefined, inlineValue: undefined }, + { kind: 'option', optionName: 'delta', optionUsed: '-d', index: 3, value: 'DDD', inlineValue: true }, + { kind: 'option', optionName: 'epsilon', optionUsed: '-e', index: 4, value: 'EEE', inlineValue: false }, + { kind: 'positional', index: 5, value: '2' }, + { kind: 'option', optionName: 'fff', optionUsed: '--fff', index: 6, value: 'FFF', inlineValue: true }, + { kind: 'option', optionName: 'ggg', optionUsed: '--ggg', index: 7, value: 'GGG', inlineValue: false }, + { kind: 'option', optionName: 'hhh', optionUsed: '--hhh', index: 8, value: undefined, inlineValue: undefined }, + { kind: 'option-terminator', index: 9 }, + { kind: 'positional', index: 10, value: '3' }, + ]; + const result = parseArgs({ strict: false, args, options }); + assert.deepStrictEqual(result.tokens, expectedTokens); +}); From cfbd436f495a2634436f637caa4e53919f34d24a Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 31 May 2022 23:54:16 +1200 Subject: [PATCH 19/64] Fix index after space-separated option value --- index.js | 2 + test/index.js | 118 +++++++++++++++++++++++++++++++------------------- 2 files changed, 75 insertions(+), 45 deletions(-) diff --git a/index.js b/index.js index 6e89541..e843178 100644 --- a/index.js +++ b/index.js @@ -191,6 +191,7 @@ function argsToTokens(args, options) { tokens, { kind: 'option', optionName, optionUsed: arg, index, value, inlineValue }); + if (value != null) ++index; continue; } @@ -243,6 +244,7 @@ function argsToTokens(args, options) { tokens, { kind: 'option', optionName, optionUsed: arg, index, value, inlineValue }); + if (value != null) ++index; continue; } diff --git a/test/index.js b/test/index.js index 9e9112e..3d55b7d 100644 --- a/test/index.js +++ b/test/index.js @@ -576,8 +576,8 @@ test('tokens: positional', () => { const expectedTokens = [ { kind: 'positional', index: 0, value: 'one' }, ]; - const result = parseArgs({ strict: false, args }); - assert.deepStrictEqual(result.tokens, expectedTokens); + const { tokens } = parseArgs({ strict: false, args }); + assert.deepStrictEqual(tokens, expectedTokens); }); test('tokens: -- followed by option-like', () => { @@ -586,8 +586,8 @@ test('tokens: -- followed by option-like', () => { { kind: 'option-terminator', index: 0 }, { kind: 'positional', index: 1, value: '--foo' }, ]; - const result = parseArgs({ strict: false, args }); - assert.deepStrictEqual(result.tokens, expectedTokens); + const { tokens } = parseArgs({ strict: false, args }); + assert.deepStrictEqual(tokens, expectedTokens); }); test('tokens: strict:true boolean short', () => { @@ -599,8 +599,8 @@ test('tokens: strict:true boolean short', () => { { kind: 'option', optionName: 'file', optionUsed: '-f', index: 0, value: undefined, inlineValue: undefined }, ]; - const result = parseArgs({ strict: true, args, options }); - assert.deepStrictEqual(result.tokens, expectedTokens); + const { tokens } = parseArgs({ strict: true, args, options }); + assert.deepStrictEqual(tokens, expectedTokens); }); test('tokens: strict:true boolean long', () => { @@ -612,8 +612,8 @@ test('tokens: strict:true boolean long', () => { { kind: 'option', optionName: 'file', optionUsed: '--file', index: 0, value: undefined, inlineValue: undefined }, ]; - const result = parseArgs({ strict: true, args, options }); - assert.deepStrictEqual(result.tokens, expectedTokens); + const { tokens } = parseArgs({ strict: true, args, options }); + assert.deepStrictEqual(tokens, expectedTokens); }); test('tokens: strict:false boolean short', () => { @@ -622,8 +622,8 @@ test('tokens: strict:false boolean short', () => { { kind: 'option', optionName: 'f', optionUsed: '-f', index: 0, value: undefined, inlineValue: undefined }, ]; - const result = parseArgs({ strict: false, args }); - assert.deepStrictEqual(result.tokens, expectedTokens); + const { tokens } = parseArgs({ strict: false, args }); + assert.deepStrictEqual(tokens, expectedTokens); }); test('tokens: strict:false boolean long', () => { @@ -632,8 +632,8 @@ test('tokens: strict:false boolean long', () => { { kind: 'option', optionName: 'file', optionUsed: '--file', index: 0, value: undefined, inlineValue: undefined }, ]; - const result = parseArgs({ strict: false, args }); - assert.deepStrictEqual(result.tokens, expectedTokens); + const { tokens } = parseArgs({ strict: false, args }); + assert.deepStrictEqual(tokens, expectedTokens); }); test('tokens: strict:false boolean option group', () => { @@ -644,21 +644,23 @@ test('tokens: strict:false boolean option group', () => { { kind: 'option', optionName: 'b', optionUsed: '-b', index: 0, value: undefined, inlineValue: undefined }, ]; - const result = parseArgs({ strict: false, args }); - assert.deepStrictEqual(result.tokens, expectedTokens); + const { tokens } = parseArgs({ strict: false, args }); + assert.deepStrictEqual(tokens, expectedTokens); }); test('tokens: strict:true string short with value after space', () => { - const args = ['-f', 'bar']; + // Also positional to check index correct after out-of-line. + const args = ['-f', 'bar', 'ppp']; const options = { file: { short: 'f', type: 'string' } }; const expectedTokens = [ { kind: 'option', optionName: 'file', optionUsed: '-f', index: 0, value: 'bar', inlineValue: false }, + { kind: 'positional', index: 2, value: 'ppp' }, ]; - const result = parseArgs({ strict: true, args, options }); - assert.deepStrictEqual(result.tokens, expectedTokens); + const { tokens } = parseArgs({ strict: true, allowPositionals: true, args, options }); + assert.deepStrictEqual(tokens, expectedTokens); }); test('tokens: strict:true string short with value inline', () => { @@ -670,8 +672,8 @@ test('tokens: strict:true string short with value inline', () => { { kind: 'option', optionName: 'file', optionUsed: '-f', index: 0, value: 'BAR', inlineValue: true }, ]; - const result = parseArgs({ strict: true, args, options }); - assert.deepStrictEqual(result.tokens, expectedTokens); + const { tokens } = parseArgs({ strict: true, args, options }); + assert.deepStrictEqual(tokens, expectedTokens); }); test('tokens: strict:false string short missing value', () => { @@ -683,21 +685,23 @@ test('tokens: strict:false string short missing value', () => { { kind: 'option', optionName: 'file', optionUsed: '-f', index: 0, value: undefined, inlineValue: undefined }, ]; - const result = parseArgs({ strict: false, args, options }); - assert.deepStrictEqual(result.tokens, expectedTokens); + const { tokens } = parseArgs({ strict: false, args, options }); + assert.deepStrictEqual(tokens, expectedTokens); }); -test('tokens: strict:true string long with value after equals', () => { - const args = ['--file', 'bar']; +test('tokens: strict:true string long with value after space', () => { + // Also positional to check index correct after out-of-line. + const args = ['--file', 'bar', 'ppp']; const options = { file: { short: 'f', type: 'string' } }; const expectedTokens = [ { kind: 'option', optionName: 'file', optionUsed: '--file', index: 0, value: 'bar', inlineValue: false }, + { kind: 'positional', index: 2, value: 'ppp' }, ]; - const result = parseArgs({ strict: true, args, options }); - assert.deepStrictEqual(result.tokens, expectedTokens); + const { tokens } = parseArgs({ strict: true, allowPositionals: true, args, options }); + assert.deepStrictEqual(tokens, expectedTokens); }); test('tokens: strict:true string long with value inline', () => { @@ -709,8 +713,8 @@ test('tokens: strict:true string long with value inline', () => { { kind: 'option', optionName: 'file', optionUsed: '--file', index: 0, value: 'bar', inlineValue: true }, ]; - const result = parseArgs({ strict: true, args, options }); - assert.deepStrictEqual(result.tokens, expectedTokens); + const { tokens } = parseArgs({ strict: true, args, options }); + assert.deepStrictEqual(tokens, expectedTokens); }); test('tokens: strict:false string long with value inline', () => { @@ -719,8 +723,8 @@ test('tokens: strict:false string long with value inline', () => { { kind: 'option', optionName: 'file', optionUsed: '--file', index: 0, value: 'bar', inlineValue: true }, ]; - const result = parseArgs({ strict: false, args }); - assert.deepStrictEqual(result.tokens, expectedTokens); + const { tokens } = parseArgs({ strict: false, args }); + assert.deepStrictEqual(tokens, expectedTokens); }); test('tokens: strict:false string long missing value', () => { @@ -732,8 +736,8 @@ test('tokens: strict:false string long missing value', () => { { kind: 'option', optionName: 'file', optionUsed: '--file', index: 0, value: undefined, inlineValue: undefined }, ]; - const result = parseArgs({ strict: false, args, options }); - assert.deepStrictEqual(result.tokens, expectedTokens); + const { tokens } = parseArgs({ strict: false, args, options }); + assert.deepStrictEqual(tokens, expectedTokens); }); test('tokens: strict:true complex option group with value after space', () => { @@ -748,8 +752,8 @@ test('tokens: strict:true complex option group with value after space', () => { { kind: 'option', optionName: 'beta', optionUsed: '-b', index: 0, value: 'c', inlineValue: false }, ]; - const result = parseArgs({ strict: false, args, options }); - assert.deepStrictEqual(result.tokens, expectedTokens); + const { tokens } = parseArgs({ strict: true, args, options }); + assert.deepStrictEqual(tokens, expectedTokens); }); test('tokens: strict:true complex option group with inline value', () => { @@ -764,8 +768,8 @@ test('tokens: strict:true complex option group with inline value', () => { { kind: 'option', optionName: 'beta', optionUsed: '-b', index: 0, value: 'c', inlineValue: true }, ]; - const result = parseArgs({ strict: false, args, options }); - assert.deepStrictEqual(result.tokens, expectedTokens); + const { tokens } = parseArgs({ strict: true, args, options }); + assert.deepStrictEqual(tokens, expectedTokens); }); test('tokens: strict:false variety', () => { @@ -781,8 +785,8 @@ test('tokens: strict:false variety', () => { { kind: 'option-terminator', index: 6 }, { kind: 'positional', index: 7, value: '3' }, ]; - const result = parseArgs({ strict: false, args }); - assert.deepStrictEqual(result.tokens, expectedTokens); + const { tokens } = parseArgs({ strict: false, args }); + assert.deepStrictEqual(tokens, expectedTokens); }); test('tokens: strict:true variety', () => { @@ -805,13 +809,37 @@ test('tokens: strict:true variety', () => { { kind: 'option', optionName: 'cat', optionUsed: '-c', index: 2, value: undefined, inlineValue: undefined }, { kind: 'option', optionName: 'delta', optionUsed: '-d', index: 3, value: 'DDD', inlineValue: true }, { kind: 'option', optionName: 'epsilon', optionUsed: '-e', index: 4, value: 'EEE', inlineValue: false }, - { kind: 'positional', index: 5, value: '2' }, - { kind: 'option', optionName: 'fff', optionUsed: '--fff', index: 6, value: 'FFF', inlineValue: true }, - { kind: 'option', optionName: 'ggg', optionUsed: '--ggg', index: 7, value: 'GGG', inlineValue: false }, - { kind: 'option', optionName: 'hhh', optionUsed: '--hhh', index: 8, value: undefined, inlineValue: undefined }, - { kind: 'option-terminator', index: 9 }, - { kind: 'positional', index: 10, value: '3' }, + { kind: 'positional', index: 6, value: '2' }, + { kind: 'option', optionName: 'fff', optionUsed: '--fff', index: 7, value: 'FFF', inlineValue: true }, + { kind: 'option', optionName: 'ggg', optionUsed: '--ggg', index: 8, value: 'GGG', inlineValue: false }, + { kind: 'option', optionName: 'hhh', optionUsed: '--hhh', index: 10, value: undefined, inlineValue: undefined }, + { kind: 'option-terminator', index: 11 }, + { kind: 'positional', index: 12, value: '3' }, ]; - const result = parseArgs({ strict: false, args, options }); - assert.deepStrictEqual(result.tokens, expectedTokens); + const { tokens } = parseArgs({ strict: true, allowPositionals: true, args, options }); + assert.deepStrictEqual(tokens, expectedTokens); +}); + +test('tokens: strict:false with single dashes', () => { + const args = ['--file', '-', '-']; + const options = { + file: { short: 'f', type: 'string' }, + }; + const expectedTokens = [ + { kind: 'option', optionName: 'file', optionUsed: '--file', + index: 0, value: '-', inlineValue: false }, + { kind: 'positional', index: 2, value: '-' }, + ]; + const { tokens } = parseArgs({ strict: false, args, options }); + assert.deepStrictEqual(tokens, expectedTokens); +}); + +test('tokens: strict:false with -- --', () => { + const args = ['--', '--']; + const expectedTokens = [ + { kind: 'option-terminator', index: 0 }, + { kind: 'positional', index: 1, value: '--' }, + ]; + const { tokens } = parseArgs({ strict: false, args }); + assert.deepStrictEqual(tokens, expectedTokens); }); From 397dcf164bd9a0babd6982b75cda8efa9b91db9b Mon Sep 17 00:00:00 2001 From: John Gee Date: Wed, 1 Jun 2022 19:05:11 +1200 Subject: [PATCH 20/64] Make tokens opt-in --- index.js | 5 +- test/dash.js | 4 +- test/index.js | 110 +++++++++++------------ test/prototype-pollution.js | 4 +- test/short-option-combined-with-value.js | 14 +-- test/short-option-groups.js | 8 +- 6 files changed, 74 insertions(+), 71 deletions(-) diff --git a/index.js b/index.js index e843178..87eb16f 100644 --- a/index.js +++ b/index.js @@ -269,6 +269,7 @@ const parseArgs = (config = { __proto__: null }) => { const args = objectGetOwn(config, 'args') ?? getMainArgs(); const strict = objectGetOwn(config, 'strict') ?? true; const allowPositionals = objectGetOwn(config, 'allowPositionals') ?? !strict; + const details = objectGetOwn(config, 'details') ?? false; const options = objectGetOwn(config, 'options') ?? { __proto__: null }; // Bundle these up for passing to strict-mode checks. const parseConfig = { args, strict, options, allowPositionals }; @@ -311,8 +312,10 @@ const parseArgs = (config = { __proto__: null }) => { const result = { values: { __proto__: null }, positionals: [], - tokens, }; + if (details) { + result.tokens = tokens; + } ArrayPrototypeForEach(tokens, (token) => { switch (token.kind) { case 'option-terminator': diff --git a/test/dash.js b/test/dash.js index 4ae413c..0ab661b 100644 --- a/test/dash.js +++ b/test/dash.js @@ -17,7 +17,7 @@ test("dash: when args include '-' used as positional then result has '-' in posi const result = parseArgs({ allowPositionals: true, args }); - t.deepEqual({ values: result.values, positionals: result.positionals }, expected); + t.deepEqual(result, expected); t.end(); }); @@ -29,6 +29,6 @@ test("dash: when args include '-' used as space-separated option value then resu const result = parseArgs({ args, options }); - t.deepEqual({ values: result.values, positionals: result.positionals }, expected); + t.deepEqual(result, expected); t.end(); }); diff --git a/test/index.js b/test/index.js index 3d55b7d..854bf0a 100644 --- a/test/index.js +++ b/test/index.js @@ -11,14 +11,14 @@ test('when short option used as flag then stored as flag', () => { const args = ['-f']; const expected = { values: { __proto__: null, f: true }, positionals: [] }; const result = parseArgs({ strict: false, args }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); }); test('when short option used as flag before positional then stored as flag and positional (and not value)', () => { const args = ['-f', 'bar']; const expected = { values: { __proto__: null, f: true }, positionals: [ 'bar' ] }; const result = parseArgs({ strict: false, args }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); }); test('when short option `type: "string"` used with value then stored as value', () => { @@ -26,7 +26,7 @@ test('when short option `type: "string"` used with value then stored as value', const options = { f: { type: 'string' } }; const expected = { values: { __proto__: null, f: 'bar' }, positionals: [] }; const result = parseArgs({ args, options }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); }); test('when short option listed in short used as flag then long option stored as flag', () => { @@ -34,7 +34,7 @@ test('when short option listed in short used as flag then long option stored as const options = { foo: { short: 'f', type: 'boolean' } }; const expected = { values: { __proto__: null, foo: true }, positionals: [] }; const result = parseArgs({ args, options }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); }); test('when short option listed in short and long listed in `type: "string"` and ' + @@ -43,7 +43,7 @@ test('when short option listed in short and long listed in `type: "string"` and const options = { foo: { short: 'f', type: 'string' } }; const expected = { values: { __proto__: null, foo: 'bar' }, positionals: [] }; const result = parseArgs({ args, options }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); }); test('when short option `type: "string"` used without value then stored as flag', () => { @@ -51,7 +51,7 @@ test('when short option `type: "string"` used without value then stored as flag' const options = { f: { type: 'string' } }; const expected = { values: { __proto__: null, f: true }, positionals: [] }; const result = parseArgs({ strict: false, args, options }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); }); test('short option group behaves like multiple short options', () => { @@ -59,7 +59,7 @@ test('short option group behaves like multiple short options', () => { const options = { }; const expected = { values: { __proto__: null, r: true, f: true }, positionals: [] }; const result = parseArgs({ strict: false, args, options }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); }); test('short option group does not consume subsequent positional', () => { @@ -67,7 +67,7 @@ test('short option group does not consume subsequent positional', () => { const options = { }; const expected = { values: { __proto__: null, r: true, f: true }, positionals: ['foo'] }; const result = parseArgs({ strict: false, args, options }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); }); // See: Guideline 5 https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html @@ -76,7 +76,7 @@ test('if terminal of short-option group configured `type: "string"`, subsequent const options = { f: { type: 'string' } }; const expected = { values: { __proto__: null, r: true, v: true, f: 'foo' }, positionals: [] }; const result = parseArgs({ strict: false, args, options }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); }); test('handles short-option groups in conjunction with long-options', () => { @@ -84,7 +84,7 @@ test('handles short-option groups in conjunction with long-options', () => { const options = { foo: { type: 'string' } }; const expected = { values: { __proto__: null, r: true, f: true, foo: 'foo' }, positionals: [] }; const result = parseArgs({ strict: false, args, options }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); }); test('handles short-option groups with "short" alias configured', () => { @@ -92,28 +92,28 @@ test('handles short-option groups with "short" alias configured', () => { const options = { remove: { short: 'r', type: 'boolean' } }; const expected = { values: { __proto__: null, remove: true, f: true }, positionals: [] }; const result = parseArgs({ strict: false, args, options }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); }); test('Everything after a bare `--` is considered a positional argument', () => { const args = ['--', 'barepositionals', 'mopositionals']; const expected = { values: { __proto__: null }, positionals: ['barepositionals', 'mopositionals'] }; const result = parseArgs({ allowPositionals: true, args }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); }); test('args are true', () => { const args = ['--foo', '--bar']; const expected = { values: { __proto__: null, foo: true, bar: true }, positionals: [] }; const result = parseArgs({ strict: false, args }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); }); test('arg is true and positional is identified', () => { const args = ['--foo=a', '--foo', 'b']; const expected = { values: { __proto__: null, foo: true }, positionals: ['b'] }; const result = parseArgs({ strict: false, args }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); }); test('args equals are passed `type: "string"`', () => { @@ -121,14 +121,14 @@ test('args equals are passed `type: "string"`', () => { const options = { so: { type: 'string' } }; const expected = { values: { __proto__: null, so: 'wat' }, positionals: [] }; const result = parseArgs({ args, options }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); }); test('when args include single dash then result stores dash as positional', () => { const args = ['-']; const expected = { values: { __proto__: null }, positionals: ['-'] }; const result = parseArgs({ allowPositionals: true, args }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); }); test('zero config args equals are parsed as if `type: "string"`', () => { @@ -136,7 +136,7 @@ test('zero config args equals are parsed as if `type: "string"`', () => { const options = { }; const expected = { values: { __proto__: null, so: 'wat' }, positionals: [] }; const result = parseArgs({ strict: false, args, options }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); }); test('same arg is passed twice `type: "string"` and last value is recorded', () => { @@ -144,7 +144,7 @@ test('same arg is passed twice `type: "string"` and last value is recorded', () const options = { foo: { type: 'string' } }; const expected = { values: { __proto__: null, foo: 'b' }, positionals: [] }; const result = parseArgs({ args, options }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); }); test('args equals pass string including more equals', () => { @@ -152,7 +152,7 @@ test('args equals pass string including more equals', () => { const options = { so: { type: 'string' } }; const expected = { values: { __proto__: null, so: 'wat=bing' }, positionals: [] }; const result = parseArgs({ args, options }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); }); test('first arg passed for `type: "string"` and "multiple" is in array', () => { @@ -160,7 +160,7 @@ test('first arg passed for `type: "string"` and "multiple" is in array', () => { const options = { foo: { type: 'string', multiple: true } }; const expected = { values: { __proto__: null, foo: ['a'] }, positionals: [] }; const result = parseArgs({ args, options }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); }); test('args are passed `type: "string"` and "multiple"', () => { @@ -173,7 +173,7 @@ test('args are passed `type: "string"` and "multiple"', () => { }; const expected = { values: { __proto__: null, foo: ['a', 'b'] }, positionals: [] }; const result = parseArgs({ args, options }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); }); test('when expecting `multiple:true` boolean option and option used multiple times then result includes array of ' + @@ -187,7 +187,7 @@ test('when expecting `multiple:true` boolean option and option used multiple tim }; const expected = { values: { __proto__: null, foo: [true, true] }, positionals: [] }; const result = parseArgs({ args, options }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); }); test('order of option and positional does not matter (per README)', () => { @@ -218,7 +218,7 @@ test('correct default args when use node -p', () => { const expected = { values: { __proto__: null, foo: true }, positionals: [] }; - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); process.argv = holdArgv; process.execArgv = holdExecArgv; }); @@ -232,7 +232,7 @@ test('correct default args when use node --print', () => { const expected = { values: { __proto__: null, foo: true }, positionals: [] }; - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); process.argv = holdArgv; process.execArgv = holdExecArgv; }); @@ -246,7 +246,7 @@ test('correct default args when use node -e', () => { const expected = { values: { __proto__: null, foo: true }, positionals: [] }; - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); process.argv = holdArgv; process.execArgv = holdExecArgv; }); @@ -259,7 +259,7 @@ test('correct default args when use node --eval', () => { const result = parseArgs({ strict: false }); const expected = { values: { __proto__: null, foo: true }, positionals: [] }; - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); process.argv = holdArgv; process.execArgv = holdExecArgv; }); @@ -273,7 +273,7 @@ test('correct default args when normal arguments', () => { const expected = { values: { __proto__: null, foo: true }, positionals: [] }; - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); process.argv = holdArgv; process.execArgv = holdExecArgv; }); @@ -287,7 +287,7 @@ test('excess leading dashes on options are retained', () => { positionals: [] }; const result = parseArgs({ strict: false, args, options }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); }); test('positional arguments are allowed by default in strict:false', () => { @@ -298,7 +298,7 @@ test('positional arguments are allowed by default in strict:false', () => { positionals: ['foo'] }; const result = parseArgs({ strict: false, args, options }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); }); test('positional arguments may be explicitly disallowed in strict:false', () => { @@ -398,7 +398,7 @@ test('-- by itself is not a positional', () => { const result = parseArgs({ args, options }); const expected = { values: { __proto__: null, foo: true }, positionals: [] }; - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expected); + assert.deepStrictEqual(result, expected); }); test('string option used as boolean', () => { @@ -436,7 +436,7 @@ test('null prototype: when --toString then values.toString is true', () => { const expectedResult = { values: { __proto__: null, toString: true }, positionals: [] }; const result = parseArgs({ args, options }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expectedResult); + assert.deepStrictEqual(result, expectedResult); }); const candidateGreedyOptions = [ @@ -456,7 +456,7 @@ candidateGreedyOptions.forEach((value) => { const expectedResult = { values: { __proto__: null, with: value }, positionals: [] }; const result = parseArgs({ args, options, strict: false }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expectedResult); + assert.deepStrictEqual(result, expectedResult); }); test(`greedy: when long option with value '${value}' then eaten`, () => { @@ -465,7 +465,7 @@ candidateGreedyOptions.forEach((value) => { const expectedResult = { values: { __proto__: null, with: value }, positionals: [] }; const result = parseArgs({ args, options, strict: false }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expectedResult); + assert.deepStrictEqual(result, expectedResult); }); }); @@ -475,7 +475,7 @@ test('strict: when candidate option value is plain text then does not throw', () const expectedResult = { values: { __proto__: null, with: 'abc' }, positionals: [] }; const result = parseArgs({ args, options, strict: true }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expectedResult); + assert.deepStrictEqual(result, expectedResult); }); test("strict: when candidate option value is '-' then does not throw", () => { @@ -484,7 +484,7 @@ test("strict: when candidate option value is '-' then does not throw", () => { const expectedResult = { values: { __proto__: null, with: '-' }, positionals: [] }; const result = parseArgs({ args, options, strict: true }); - assert.deepStrictEqual({ values: result.values, positionals: result.positionals }, expectedResult); + assert.deepStrictEqual(result, expectedResult); }); test("strict: when candidate option value is '--' then throws", () => { @@ -576,7 +576,7 @@ test('tokens: positional', () => { const expectedTokens = [ { kind: 'positional', index: 0, value: 'one' }, ]; - const { tokens } = parseArgs({ strict: false, args }); + const { tokens } = parseArgs({ strict: false, args, details: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -586,7 +586,7 @@ test('tokens: -- followed by option-like', () => { { kind: 'option-terminator', index: 0 }, { kind: 'positional', index: 1, value: '--foo' }, ]; - const { tokens } = parseArgs({ strict: false, args }); + const { tokens } = parseArgs({ strict: false, args, details: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -599,7 +599,7 @@ test('tokens: strict:true boolean short', () => { { kind: 'option', optionName: 'file', optionUsed: '-f', index: 0, value: undefined, inlineValue: undefined }, ]; - const { tokens } = parseArgs({ strict: true, args, options }); + const { tokens } = parseArgs({ strict: true, args, options, details: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -612,7 +612,7 @@ test('tokens: strict:true boolean long', () => { { kind: 'option', optionName: 'file', optionUsed: '--file', index: 0, value: undefined, inlineValue: undefined }, ]; - const { tokens } = parseArgs({ strict: true, args, options }); + const { tokens } = parseArgs({ strict: true, args, options, details: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -622,7 +622,7 @@ test('tokens: strict:false boolean short', () => { { kind: 'option', optionName: 'f', optionUsed: '-f', index: 0, value: undefined, inlineValue: undefined }, ]; - const { tokens } = parseArgs({ strict: false, args }); + const { tokens } = parseArgs({ strict: false, args, details: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -632,7 +632,7 @@ test('tokens: strict:false boolean long', () => { { kind: 'option', optionName: 'file', optionUsed: '--file', index: 0, value: undefined, inlineValue: undefined }, ]; - const { tokens } = parseArgs({ strict: false, args }); + const { tokens } = parseArgs({ strict: false, args, details: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -644,7 +644,7 @@ test('tokens: strict:false boolean option group', () => { { kind: 'option', optionName: 'b', optionUsed: '-b', index: 0, value: undefined, inlineValue: undefined }, ]; - const { tokens } = parseArgs({ strict: false, args }); + const { tokens } = parseArgs({ strict: false, args, details: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -659,7 +659,7 @@ test('tokens: strict:true string short with value after space', () => { index: 0, value: 'bar', inlineValue: false }, { kind: 'positional', index: 2, value: 'ppp' }, ]; - const { tokens } = parseArgs({ strict: true, allowPositionals: true, args, options }); + const { tokens } = parseArgs({ strict: true, allowPositionals: true, args, options, details: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -672,7 +672,7 @@ test('tokens: strict:true string short with value inline', () => { { kind: 'option', optionName: 'file', optionUsed: '-f', index: 0, value: 'BAR', inlineValue: true }, ]; - const { tokens } = parseArgs({ strict: true, args, options }); + const { tokens } = parseArgs({ strict: true, args, options, details: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -685,7 +685,7 @@ test('tokens: strict:false string short missing value', () => { { kind: 'option', optionName: 'file', optionUsed: '-f', index: 0, value: undefined, inlineValue: undefined }, ]; - const { tokens } = parseArgs({ strict: false, args, options }); + const { tokens } = parseArgs({ strict: false, args, options, details: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -700,7 +700,7 @@ test('tokens: strict:true string long with value after space', () => { index: 0, value: 'bar', inlineValue: false }, { kind: 'positional', index: 2, value: 'ppp' }, ]; - const { tokens } = parseArgs({ strict: true, allowPositionals: true, args, options }); + const { tokens } = parseArgs({ strict: true, allowPositionals: true, args, options, details: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -713,7 +713,7 @@ test('tokens: strict:true string long with value inline', () => { { kind: 'option', optionName: 'file', optionUsed: '--file', index: 0, value: 'bar', inlineValue: true }, ]; - const { tokens } = parseArgs({ strict: true, args, options }); + const { tokens } = parseArgs({ strict: true, args, options, details: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -723,7 +723,7 @@ test('tokens: strict:false string long with value inline', () => { { kind: 'option', optionName: 'file', optionUsed: '--file', index: 0, value: 'bar', inlineValue: true }, ]; - const { tokens } = parseArgs({ strict: false, args }); + const { tokens } = parseArgs({ strict: false, args, details: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -736,7 +736,7 @@ test('tokens: strict:false string long missing value', () => { { kind: 'option', optionName: 'file', optionUsed: '--file', index: 0, value: undefined, inlineValue: undefined }, ]; - const { tokens } = parseArgs({ strict: false, args, options }); + const { tokens } = parseArgs({ strict: false, args, options, details: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -752,7 +752,7 @@ test('tokens: strict:true complex option group with value after space', () => { { kind: 'option', optionName: 'beta', optionUsed: '-b', index: 0, value: 'c', inlineValue: false }, ]; - const { tokens } = parseArgs({ strict: true, args, options }); + const { tokens } = parseArgs({ strict: true, args, options, details: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -768,7 +768,7 @@ test('tokens: strict:true complex option group with inline value', () => { { kind: 'option', optionName: 'beta', optionUsed: '-b', index: 0, value: 'c', inlineValue: true }, ]; - const { tokens } = parseArgs({ strict: true, args, options }); + const { tokens } = parseArgs({ strict: true, args, options, details: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -785,7 +785,7 @@ test('tokens: strict:false variety', () => { { kind: 'option-terminator', index: 6 }, { kind: 'positional', index: 7, value: '3' }, ]; - const { tokens } = parseArgs({ strict: false, args }); + const { tokens } = parseArgs({ strict: false, args, details: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -816,7 +816,7 @@ test('tokens: strict:true variety', () => { { kind: 'option-terminator', index: 11 }, { kind: 'positional', index: 12, value: '3' }, ]; - const { tokens } = parseArgs({ strict: true, allowPositionals: true, args, options }); + const { tokens } = parseArgs({ strict: true, allowPositionals: true, args, options, details: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -830,7 +830,7 @@ test('tokens: strict:false with single dashes', () => { index: 0, value: '-', inlineValue: false }, { kind: 'positional', index: 2, value: '-' }, ]; - const { tokens } = parseArgs({ strict: false, args, options }); + const { tokens } = parseArgs({ strict: false, args, options, details: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -840,6 +840,6 @@ test('tokens: strict:false with -- --', () => { { kind: 'option-terminator', index: 0 }, { kind: 'positional', index: 1, value: '--' }, ]; - const { tokens } = parseArgs({ strict: false, args }); + const { tokens } = parseArgs({ strict: false, args, details: true }); assert.deepStrictEqual(tokens, expectedTokens); }); diff --git a/test/prototype-pollution.js b/test/prototype-pollution.js index d44fda2..a8a5d2f 100644 --- a/test/prototype-pollution.js +++ b/test/prototype-pollution.js @@ -27,7 +27,7 @@ test('should not allow __proto__ key to be set on object', (t) => { const result = parseArgs({ strict: false, args }); - t.deepEqual({ values: result.values, positionals: result.positionals }, expected); + t.deepEqual(result, expected); t.end(); }); @@ -39,7 +39,7 @@ test('when prototype has multiple then ignored', (t) => { const holdDescriptor = setObjectPrototype('multiple', true); const result = parseArgs({ args, options }); restoreObjectPrototype('multiple', holdDescriptor); - t.deepEqual({ values: result.values, positionals: result.positionals }, expectedResult); + t.deepEqual(result, expectedResult); t.end(); }); diff --git a/test/short-option-combined-with-value.js b/test/short-option-combined-with-value.js index 0294c5b..ca0ec6a 100644 --- a/test/short-option-combined-with-value.js +++ b/test/short-option-combined-with-value.js @@ -11,7 +11,7 @@ test('when combine string short with plain text then parsed as value', (t) => { const result = parseArgs({ args, options }); - t.deepEqual({ values: result.values, positionals: result.positionals }, expected); + t.deepEqual(result, expected); t.end(); }); @@ -22,7 +22,7 @@ test('when combine low-config string short with plain text then parsed as value' const result = parseArgs({ args, options }); - t.deepEqual({ values: result.values, positionals: result.positionals }, expected); + t.deepEqual(result, expected); t.end(); }); @@ -33,7 +33,7 @@ test('when combine string short with value like short option then parsed as valu const result = parseArgs({ args, options }); - t.deepEqual({ values: result.values, positionals: result.positionals }, expected); + t.deepEqual(result, expected); t.end(); }); @@ -44,7 +44,7 @@ test('when combine string short with value like long option then parsed as value const result = parseArgs({ args, options }); - t.deepEqual({ values: result.values, positionals: result.positionals }, expected); + t.deepEqual(result, expected); t.end(); }); @@ -55,7 +55,7 @@ test('when combine string short with value like negative number then parsed as v const result = parseArgs({ args, options }); - t.deepEqual({ values: result.values, positionals: result.positionals }, expected); + t.deepEqual(result, expected); t.end(); }); @@ -66,7 +66,7 @@ test('when combine string short with value which matches configured flag then pa const expected = { values: { __proto__: null, alpha: 'f' }, positionals: [] }; const result = parseArgs({ args, options }); - t.deepEqual({ values: result.values, positionals: result.positionals }, expected); + t.deepEqual(result, expected); t.end(); }); @@ -77,6 +77,6 @@ test('when combine string short with value including equals then parsed with equ const result = parseArgs({ args, options }); - t.deepEqual({ values: result.values, positionals: result.positionals }, expected); + t.deepEqual(result, expected); t.end(); }); diff --git a/test/short-option-groups.js b/test/short-option-groups.js index 317f73f..c790a7d 100644 --- a/test/short-option-groups.js +++ b/test/short-option-groups.js @@ -11,7 +11,7 @@ test('when pass zero-config group of booleans then parsed as booleans', (t) => { const result = parseArgs({ strict: false, args, options }); - t.deepEqual({ values: result.values, positionals: result.positionals }, expected); + t.deepEqual(result, expected); t.end(); }); @@ -22,7 +22,7 @@ test('when pass full-config group of booleans then parsed as booleans', (t) => { const result = parseArgs({ allowPositionals: true, args, options }); - t.deepEqual({ values: result.values, positionals: result.positionals }, expected); + t.deepEqual(result, expected); t.end(); }); @@ -33,7 +33,7 @@ test('when pass group with string option on end then parsed as booleans and stri const result = parseArgs({ args, options }); - t.deepEqual({ values: result.values, positionals: result.positionals }, expected); + t.deepEqual(result, expected); t.end(); }); @@ -44,6 +44,6 @@ test('when pass group with string option in middle and strict:false then parsed const result = parseArgs({ args, options, strict: false }); - t.deepEqual({ values: result.values, positionals: result.positionals }, expected); + t.deepEqual(result, expected); t.end(); }); From 938bdb01e2f1271482f29a35df15881911fec0fb Mon Sep 17 00:00:00 2001 From: John Gee Date: Wed, 1 Jun 2022 19:09:14 +1200 Subject: [PATCH 21/64] Simplify more tests with opt-in details --- test/index.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/test/index.js b/test/index.js index 854bf0a..a7fd3ab 100644 --- a/test/index.js +++ b/test/index.js @@ -196,17 +196,9 @@ test('order of option and positional does not matter (per README)', () => { const options = { foo: { type: 'string' } }; const expected = { values: { __proto__: null, foo: 'bar' }, positionals: ['baz'] }; let result = parseArgs({ allowPositionals: true, args: args1, options }); - assert.deepStrictEqual( - { values: result.values, positionals: result.positionals }, - expected, - Error('option then positional') - ); + assert.deepStrictEqual(result, expected, Error('option then positional')); result = parseArgs({ allowPositionals: true, args: args2, options }); - assert.deepStrictEqual( - { values: result.values, positionals: result.positionals }, - expected, - Error('positional then option') - ); + assert.deepStrictEqual(result, expected, Error('positional then option')); }); test('correct default args when use node -p', () => { From c5da345d3055a0bc247e2147ff64dff53c99ec5b Mon Sep 17 00:00:00 2001 From: John Gee Date: Wed, 1 Jun 2022 19:20:48 +1200 Subject: [PATCH 22/64] Rename token.optionName to name, and restore longOption/shortOption pattern --- index.js | 80 +++++++++++++++++++++++++-------------------------- test/index.js | 62 +++++++++++++++++++-------------------- 2 files changed, 71 insertions(+), 71 deletions(-) diff --git a/index.js b/index.js index 87eb16f..37da584 100644 --- a/index.js +++ b/index.js @@ -65,17 +65,17 @@ function getMainArgs() { * In strict mode, throw for possible usage errors like --foo --bar * * @param {object} config - from config passed to parseArgs - * @param {object} element- array item from parseElements returned by parseArgs + * @param {object} token - from tokens as available from parseArgs */ -function checkOptionLikeValue(config, element) { - if (config.strict && (element.kind === 'option') && - !element.inlineValue && isOptionLikeValue(element.value)) { +function checkOptionLikeValue(config, token) { + if (config.strict && + !token.inlineValue && isOptionLikeValue(token.value)) { // Only show short example if user used short option. - const example = StringPrototypeStartsWith(element.optionUsed, '--') ? - `'${element.optionUsed}=-XYZ'` : - `'--${element.optionName}=-XYZ' or '${element.optionUsed}-XYZ'`; - const errorMessage = `Option '${element.optionUsed}' argument is ambiguous. -Did you forget to specify the option argument for '${element.optionUsed}'? + const example = StringPrototypeStartsWith(token.optionUsed, '--') ? + `'${token.optionUsed}=-XYZ'` : + `'--${token.name}=-XYZ' or '${token.optionUsed}-XYZ'`; + const errorMessage = `Option '${token.optionUsed}' argument is ambiguous. +Did you forget to specify the option argument for '${token.optionUsed}'? To specify an option argument starting with a dash use ${example}.`; throw new ERR_PARSE_ARGS_INVALID_OPTION_VALUE(errorMessage); } @@ -85,24 +85,24 @@ To specify an option argument starting with a dash use ${example}.`; * In strict mode, throw for usage errors. * * @param {object} config - from config passed to parseArgs - * @param {object} element- array item from parseElements returned by parseArgs + * @param {object} token - from tokens as available from parseArgs */ -function checkOptionUsage(config, element) { +function checkOptionUsage(config, token) { if (!config.strict) return; - if (!ObjectHasOwn(config.options, element.optionName)) { + if (!ObjectHasOwn(config.options, token.name)) { throw new ERR_PARSE_ARGS_UNKNOWN_OPTION( - element.optionUsed, config.allowPositionals); + token.optionUsed, config.allowPositionals); } - const short = optionsGetOwn(config.options, element.optionName, 'short'); - const shortAndLong = `${short ? `-${short}, ` : ''}--${element.optionName}`; - const type = optionsGetOwn(config.options, element.optionName, 'type'); - if (type === 'string' && typeof element.value !== 'string') { + const short = optionsGetOwn(config.options, token.name, 'short'); + const shortAndLong = `${short ? `-${short}, ` : ''}--${token.name}`; + const type = optionsGetOwn(config.options, token.name, 'type'); + if (type === 'string' && typeof token.value !== 'string') { throw new ERR_PARSE_ARGS_INVALID_OPTION_VALUE(`Option '${shortAndLong} ' argument missing`); } // (Idiomatic test for undefined||null, expecting undefined.) - if (type === 'boolean' && element.value != null) { + if (type === 'boolean' && token.value != null) { throw new ERR_PARSE_ARGS_INVALID_OPTION_VALUE(`Option '${shortAndLong}' does not take an argument`); } } @@ -111,32 +111,32 @@ function checkOptionUsage(config, element) { /** * Store the option value in `values`. * - * @param {string} optionName - long option name e.g. 'foo' + * @param {string} longOption - long option name e.g. 'foo' * @param {string|undefined} optionValue - value from user args * @param {object} options - option configs, from parseArgs({ options }) * @param {object} values - option values returned in `values` by parseArgs */ -function storeOption(optionName, optionValue, options, values) { - if (optionName === '__proto__') { +function storeOption(longOption, optionValue, options, values) { + if (longOption === '__proto__') { return; // No. Just no. } // We store based on the option value rather than option type, // preserving the users intent for author to deal with. const newValue = optionValue ?? true; - if (optionsGetOwn(options, optionName, 'multiple')) { + if (optionsGetOwn(options, longOption, 'multiple')) { // Always store value in array, including for boolean. - // values[optionName] starts out not present, + // values[longOption] starts out not present, // first value is added as new array [newValue], // subsequent values are pushed to existing array. // (note: values has null prototype, so simpler usage) - if (values[optionName]) { - ArrayPrototypePush(values[optionName], newValue); + if (values[longOption]) { + ArrayPrototypePush(values[longOption], newValue); } else { - values[optionName] = [newValue]; + values[longOption] = [newValue]; } } else { - values[optionName] = newValue; + values[longOption] = newValue; } } @@ -178,10 +178,10 @@ function argsToTokens(args, options) { if (isLoneShortOption(arg)) { // e.g. '-f' const shortOption = StringPrototypeCharAt(arg, 1); - const optionName = findLongOptionForShort(shortOption, options); + const longOption = findLongOptionForShort(shortOption, options); let value; let inlineValue; - if (optionsGetOwn(options, optionName, 'type') === 'string' && + if (optionsGetOwn(options, longOption, 'type') === 'string' && isOptionValue(nextArg)) { // e.g. '-f', 'bar' value = ArrayPrototypeShift(remainingArgs); @@ -189,7 +189,7 @@ function argsToTokens(args, options) { } ArrayPrototypePush( tokens, - { kind: 'option', optionName, optionUsed: arg, + { kind: 'option', name: longOption, optionUsed: arg, index, value, inlineValue }); if (value != null) ++index; continue; @@ -200,8 +200,8 @@ function argsToTokens(args, options) { const expanded = []; for (let index = 1; index < arg.length; index++) { const shortOption = StringPrototypeCharAt(arg, index); - const optionName = findLongOptionForShort(shortOption, options); - if (optionsGetOwn(options, optionName, 'type') !== 'string' || + const longOption = findLongOptionForShort(shortOption, options); + if (optionsGetOwn(options, longOption, 'type') !== 'string' || index === arg.length - 1) { // Boolean option, or last short in group. Well formed. ArrayPrototypePush(expanded, `-${shortOption}`); @@ -220,21 +220,21 @@ function argsToTokens(args, options) { if (isShortOptionAndValue(arg, options)) { // e.g. -fFILE const shortOption = StringPrototypeCharAt(arg, 1); - const optionName = findLongOptionForShort(shortOption, options); + const longOption = findLongOptionForShort(shortOption, options); const value = StringPrototypeSlice(arg, 2); ArrayPrototypePush( tokens, - { kind: 'option', optionName, optionUsed: `-${shortOption}`, + { kind: 'option', name: longOption, optionUsed: `-${shortOption}`, index, value, inlineValue: true }); continue; } if (isLoneLongOption(arg)) { // e.g. '--foo' - const optionName = StringPrototypeSlice(arg, 2); + const longOption = StringPrototypeSlice(arg, 2); let value; let inlineValue; - if (optionsGetOwn(options, optionName, 'type') === 'string' && + if (optionsGetOwn(options, longOption, 'type') === 'string' && isOptionValue(nextArg)) { // e.g. '--foo', 'bar' value = ArrayPrototypeShift(remainingArgs); @@ -242,7 +242,7 @@ function argsToTokens(args, options) { } ArrayPrototypePush( tokens, - { kind: 'option', optionName, optionUsed: arg, + { kind: 'option', name: longOption, optionUsed: arg, index, value, inlineValue }); if (value != null) ++index; continue; @@ -251,11 +251,11 @@ function argsToTokens(args, options) { if (isLongOptionAndValue(arg)) { // e.g. --foo=bar const equalIndex = StringPrototypeIndexOf(arg, '='); - const optionName = StringPrototypeSlice(arg, 2, equalIndex); + const longOption = StringPrototypeSlice(arg, 2, equalIndex); const value = StringPrototypeSlice(arg, equalIndex + 1); ArrayPrototypePush( tokens, - { kind: 'option', optionName, optionUsed: `--${optionName}`, + { kind: 'option', name: longOption, optionUsed: `--${longOption}`, index, value, inlineValue: true }); continue; } @@ -329,7 +329,7 @@ const parseArgs = (config = { __proto__: null }) => { case 'option': checkOptionUsage(parseConfig, token); checkOptionLikeValue(parseConfig, token); - storeOption(token.optionName, token.value, options, result.values); + storeOption(token.name, token.value, options, result.values); break; } }); diff --git a/test/index.js b/test/index.js index a7fd3ab..4931371 100644 --- a/test/index.js +++ b/test/index.js @@ -588,7 +588,7 @@ test('tokens: strict:true boolean short', () => { file: { short: 'f', type: 'boolean' } }; const expectedTokens = [ - { kind: 'option', optionName: 'file', optionUsed: '-f', + { kind: 'option', name: 'file', optionUsed: '-f', index: 0, value: undefined, inlineValue: undefined }, ]; const { tokens } = parseArgs({ strict: true, args, options, details: true }); @@ -601,7 +601,7 @@ test('tokens: strict:true boolean long', () => { file: { short: 'f', type: 'boolean' } }; const expectedTokens = [ - { kind: 'option', optionName: 'file', optionUsed: '--file', + { kind: 'option', name: 'file', optionUsed: '--file', index: 0, value: undefined, inlineValue: undefined }, ]; const { tokens } = parseArgs({ strict: true, args, options, details: true }); @@ -611,7 +611,7 @@ test('tokens: strict:true boolean long', () => { test('tokens: strict:false boolean short', () => { const args = ['-f']; const expectedTokens = [ - { kind: 'option', optionName: 'f', optionUsed: '-f', + { kind: 'option', name: 'f', optionUsed: '-f', index: 0, value: undefined, inlineValue: undefined }, ]; const { tokens } = parseArgs({ strict: false, args, details: true }); @@ -621,7 +621,7 @@ test('tokens: strict:false boolean short', () => { test('tokens: strict:false boolean long', () => { const args = ['--file']; const expectedTokens = [ - { kind: 'option', optionName: 'file', optionUsed: '--file', + { kind: 'option', name: 'file', optionUsed: '--file', index: 0, value: undefined, inlineValue: undefined }, ]; const { tokens } = parseArgs({ strict: false, args, details: true }); @@ -631,9 +631,9 @@ test('tokens: strict:false boolean long', () => { test('tokens: strict:false boolean option group', () => { const args = ['-ab']; const expectedTokens = [ - { kind: 'option', optionName: 'a', optionUsed: '-a', + { kind: 'option', name: 'a', optionUsed: '-a', index: 0, value: undefined, inlineValue: undefined }, - { kind: 'option', optionName: 'b', optionUsed: '-b', + { kind: 'option', name: 'b', optionUsed: '-b', index: 0, value: undefined, inlineValue: undefined }, ]; const { tokens } = parseArgs({ strict: false, args, details: true }); @@ -647,7 +647,7 @@ test('tokens: strict:true string short with value after space', () => { file: { short: 'f', type: 'string' } }; const expectedTokens = [ - { kind: 'option', optionName: 'file', optionUsed: '-f', + { kind: 'option', name: 'file', optionUsed: '-f', index: 0, value: 'bar', inlineValue: false }, { kind: 'positional', index: 2, value: 'ppp' }, ]; @@ -661,7 +661,7 @@ test('tokens: strict:true string short with value inline', () => { file: { short: 'f', type: 'string' } }; const expectedTokens = [ - { kind: 'option', optionName: 'file', optionUsed: '-f', + { kind: 'option', name: 'file', optionUsed: '-f', index: 0, value: 'BAR', inlineValue: true }, ]; const { tokens } = parseArgs({ strict: true, args, options, details: true }); @@ -674,7 +674,7 @@ test('tokens: strict:false string short missing value', () => { file: { short: 'f', type: 'string' } }; const expectedTokens = [ - { kind: 'option', optionName: 'file', optionUsed: '-f', + { kind: 'option', name: 'file', optionUsed: '-f', index: 0, value: undefined, inlineValue: undefined }, ]; const { tokens } = parseArgs({ strict: false, args, options, details: true }); @@ -688,7 +688,7 @@ test('tokens: strict:true string long with value after space', () => { file: { short: 'f', type: 'string' } }; const expectedTokens = [ - { kind: 'option', optionName: 'file', optionUsed: '--file', + { kind: 'option', name: 'file', optionUsed: '--file', index: 0, value: 'bar', inlineValue: false }, { kind: 'positional', index: 2, value: 'ppp' }, ]; @@ -702,7 +702,7 @@ test('tokens: strict:true string long with value inline', () => { file: { short: 'f', type: 'string' } }; const expectedTokens = [ - { kind: 'option', optionName: 'file', optionUsed: '--file', + { kind: 'option', name: 'file', optionUsed: '--file', index: 0, value: 'bar', inlineValue: true }, ]; const { tokens } = parseArgs({ strict: true, args, options, details: true }); @@ -712,7 +712,7 @@ test('tokens: strict:true string long with value inline', () => { test('tokens: strict:false string long with value inline', () => { const args = ['--file=bar']; const expectedTokens = [ - { kind: 'option', optionName: 'file', optionUsed: '--file', + { kind: 'option', name: 'file', optionUsed: '--file', index: 0, value: 'bar', inlineValue: true }, ]; const { tokens } = parseArgs({ strict: false, args, details: true }); @@ -725,7 +725,7 @@ test('tokens: strict:false string long missing value', () => { file: { short: 'f', type: 'string' } }; const expectedTokens = [ - { kind: 'option', optionName: 'file', optionUsed: '--file', + { kind: 'option', name: 'file', optionUsed: '--file', index: 0, value: undefined, inlineValue: undefined }, ]; const { tokens } = parseArgs({ strict: false, args, options, details: true }); @@ -739,9 +739,9 @@ test('tokens: strict:true complex option group with value after space', () => { beta: { short: 'b', type: 'string' }, }; const expectedTokens = [ - { kind: 'option', optionName: 'alpha', optionUsed: '-a', + { kind: 'option', name: 'alpha', optionUsed: '-a', index: 0, value: undefined, inlineValue: undefined }, - { kind: 'option', optionName: 'beta', optionUsed: '-b', + { kind: 'option', name: 'beta', optionUsed: '-b', index: 0, value: 'c', inlineValue: false }, ]; const { tokens } = parseArgs({ strict: true, args, options, details: true }); @@ -755,9 +755,9 @@ test('tokens: strict:true complex option group with inline value', () => { beta: { short: 'b', type: 'string' }, }; const expectedTokens = [ - { kind: 'option', optionName: 'alpha', optionUsed: '-a', + { kind: 'option', name: 'alpha', optionUsed: '-a', index: 0, value: undefined, inlineValue: undefined }, - { kind: 'option', optionName: 'beta', optionUsed: '-b', + { kind: 'option', name: 'beta', optionUsed: '-b', index: 0, value: 'c', inlineValue: true }, ]; const { tokens } = parseArgs({ strict: true, args, options, details: true }); @@ -767,13 +767,13 @@ test('tokens: strict:true complex option group with inline value', () => { test('tokens: strict:false variety', () => { const args = ['-a', '1', '-bc', '2', '--ddd', '--eee=fff', '--', '3']; const expectedTokens = [ - { kind: 'option', optionName: 'a', optionUsed: '-a', index: 0, value: undefined, inlineValue: undefined }, + { kind: 'option', name: 'a', optionUsed: '-a', index: 0, value: undefined, inlineValue: undefined }, { kind: 'positional', index: 1, value: '1' }, - { kind: 'option', optionName: 'b', optionUsed: '-b', index: 2, value: undefined, inlineValue: undefined }, - { kind: 'option', optionName: 'c', optionUsed: '-c', index: 2, value: undefined, inlineValue: undefined }, + { kind: 'option', name: 'b', optionUsed: '-b', index: 2, value: undefined, inlineValue: undefined }, + { kind: 'option', name: 'c', optionUsed: '-c', index: 2, value: undefined, inlineValue: undefined }, { kind: 'positional', index: 3, value: '2' }, - { kind: 'option', optionName: 'ddd', optionUsed: '--ddd', index: 4, value: undefined, inlineValue: undefined }, - { kind: 'option', optionName: 'eee', optionUsed: '--eee', index: 5, value: 'fff', inlineValue: true }, + { kind: 'option', name: 'ddd', optionUsed: '--ddd', index: 4, value: undefined, inlineValue: undefined }, + { kind: 'option', name: 'eee', optionUsed: '--eee', index: 5, value: 'fff', inlineValue: true }, { kind: 'option-terminator', index: 6 }, { kind: 'positional', index: 7, value: '3' }, ]; @@ -795,16 +795,16 @@ test('tokens: strict:true variety', () => { hhh: { type: 'boolean' }, }; const expectedTokens = [ - { kind: 'option', optionName: 'alpha', optionUsed: '-a', index: 0, value: undefined, inlineValue: undefined }, + { kind: 'option', name: 'alpha', optionUsed: '-a', index: 0, value: undefined, inlineValue: undefined }, { kind: 'positional', index: 1, value: '1' }, - { kind: 'option', optionName: 'beta', optionUsed: '-b', index: 2, value: undefined, inlineValue: undefined }, - { kind: 'option', optionName: 'cat', optionUsed: '-c', index: 2, value: undefined, inlineValue: undefined }, - { kind: 'option', optionName: 'delta', optionUsed: '-d', index: 3, value: 'DDD', inlineValue: true }, - { kind: 'option', optionName: 'epsilon', optionUsed: '-e', index: 4, value: 'EEE', inlineValue: false }, + { kind: 'option', name: 'beta', optionUsed: '-b', index: 2, value: undefined, inlineValue: undefined }, + { kind: 'option', name: 'cat', optionUsed: '-c', index: 2, value: undefined, inlineValue: undefined }, + { kind: 'option', name: 'delta', optionUsed: '-d', index: 3, value: 'DDD', inlineValue: true }, + { kind: 'option', name: 'epsilon', optionUsed: '-e', index: 4, value: 'EEE', inlineValue: false }, { kind: 'positional', index: 6, value: '2' }, - { kind: 'option', optionName: 'fff', optionUsed: '--fff', index: 7, value: 'FFF', inlineValue: true }, - { kind: 'option', optionName: 'ggg', optionUsed: '--ggg', index: 8, value: 'GGG', inlineValue: false }, - { kind: 'option', optionName: 'hhh', optionUsed: '--hhh', index: 10, value: undefined, inlineValue: undefined }, + { kind: 'option', name: 'fff', optionUsed: '--fff', index: 7, value: 'FFF', inlineValue: true }, + { kind: 'option', name: 'ggg', optionUsed: '--ggg', index: 8, value: 'GGG', inlineValue: false }, + { kind: 'option', name: 'hhh', optionUsed: '--hhh', index: 10, value: undefined, inlineValue: undefined }, { kind: 'option-terminator', index: 11 }, { kind: 'positional', index: 12, value: '3' }, ]; @@ -818,7 +818,7 @@ test('tokens: strict:false with single dashes', () => { file: { short: 'f', type: 'string' }, }; const expectedTokens = [ - { kind: 'option', optionName: 'file', optionUsed: '--file', + { kind: 'option', name: 'file', optionUsed: '--file', index: 0, value: '-', inlineValue: false }, { kind: 'positional', index: 2, value: '-' }, ]; From 4e1ec2d739d1df541641ef25a647d57f1758ccfa Mon Sep 17 00:00:00 2001 From: John Gee Date: Wed, 1 Jun 2022 22:42:50 +1200 Subject: [PATCH 23/64] Add example --- examples/negate.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 examples/negate.js diff --git a/examples/negate.js b/examples/negate.js new file mode 100644 index 0000000..0ab8be0 --- /dev/null +++ b/examples/negate.js @@ -0,0 +1,42 @@ +'use strict'; + +// How might I add my own support for --no-foo? +// (Not supporting multiples.) + +// 1. const { parseArgs } = require('node:util'); // from node +// 2. const { parseArgs } = require('@pkgjs/parseargs'); // from package +const { parseArgs } = require('..'); // in repo + +function myParseArgs(config) { + // Get the tokens for reprocessing. + const detailedConfig = Object.assign({}, config, { details: true }); + const result = parseArgs(detailedConfig); + + result.tokens + .filter((token) => token.kind === 'option') + .forEach((token) => { + if (token.name.startsWith('no-')) { + // Store foo:false for --no-foo + const positiveName = token.name.slice(3); + result.values[positiveName] = false; + delete result.values[token.name]; + } else { + // Resave value so last one wins if both --foo and --no-foo. + result.values[token.name] = (token.value != null) ? token.value : true; + } + }); + + // Remove the tokens if caller did not ask for them. + if (!config.details) { + delete result.tokens; + } + return result; +} + +const { values } = myParseArgs({ strict: false }); +console.log(values); + +// Try the following: +// node negate.js --foo +// node negate.js --foo --no-foo +// node negate.js --foo --no-foo --foo From bd3f5746244a3eec5ffe05dbda9d71aed3d264ae Mon Sep 17 00:00:00 2001 From: John Gee Date: Thu, 2 Jun 2022 19:47:55 +1200 Subject: [PATCH 24/64] Add side-note --- examples/negate.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/negate.js b/examples/negate.js index 0ab8be0..057c1d3 100644 --- a/examples/negate.js +++ b/examples/negate.js @@ -1,7 +1,11 @@ 'use strict'; // How might I add my own support for --no-foo? -// (Not supporting multiples.) +// (Not supporting multiples in this code.) + +// Side note: I included library-style structure here, but now thinking +// that would be better in just one full example. Keep the code +// smaller and more focused in other example files. // 1. const { parseArgs } = require('node:util'); // from node // 2. const { parseArgs } = require('@pkgjs/parseargs'); // from package From b703ee824918d4ef3adcfb65cee32e4470bc6d1d Mon Sep 17 00:00:00 2001 From: John Gee Date: Thu, 2 Jun 2022 20:29:53 +1200 Subject: [PATCH 25/64] Add example of #52, limit long syntax --- examples/limit-long-syntax.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 examples/limit-long-syntax.js diff --git a/examples/limit-long-syntax.js b/examples/limit-long-syntax.js new file mode 100644 index 0000000..433c157 --- /dev/null +++ b/examples/limit-long-syntax.js @@ -0,0 +1,29 @@ +'use strict'; + +// How might I require long options with values use '='? +// So allow `--foo=bar`, and not allow `--foo bar`. + +// 1. const { parseArgs } = require('node:util'); // from node +// 2. const { parseArgs } = require('@pkgjs/parseargs'); // from package +const { parseArgs } = require('..'); // in repo + +const options = { + file: { short: 'f', type: 'string' }, + log: { type: 'string' }, +}; + +const { values, tokens } = parseArgs({ options, details: true }); + +const badToken = tokens.find((token) => token.kind === 'option' && + options[token.name].type === 'string' && + token.optionUsed.startsWith('--') && + !token.inlineValue); +if (badToken) { + throw new Error(`Option value for '${badToken.optionUsed}' must be inline, like '${badToken.optionUsed}=VALUE'`); +} + +console.log(values); + +// Try the following: +// node limited-long-syntax.js -f FILE --log=LOG +// node limited-long-syntax.js --file FILE From 82339f980b261877b0fb9b55e8fbb50751383685 Mon Sep 17 00:00:00 2001 From: John Gee Date: Thu, 2 Jun 2022 21:20:42 +1200 Subject: [PATCH 26/64] Add ordered example --- examples/limit-long-syntax.js | 2 ++ examples/ordered-options.mjs | 36 +++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 examples/ordered-options.mjs diff --git a/examples/limit-long-syntax.js b/examples/limit-long-syntax.js index 433c157..aa4b46d 100644 --- a/examples/limit-long-syntax.js +++ b/examples/limit-long-syntax.js @@ -2,6 +2,8 @@ // How might I require long options with values use '='? // So allow `--foo=bar`, and not allow `--foo bar`. +// +// NB: this is not a common restriction in CLI. // 1. const { parseArgs } = require('node:util'); // from node // 2. const { parseArgs } = require('@pkgjs/parseargs'); // from package diff --git a/examples/ordered-options.mjs b/examples/ordered-options.mjs new file mode 100644 index 0000000..e748f50 --- /dev/null +++ b/examples/ordered-options.mjs @@ -0,0 +1,36 @@ +// Now might I enforce that two flags are specified in a specific order? +// +// NB: POSIX says that option order does not normally matter. + +import { parseArgs } from '../index.js'; + +function findTokenIndex(tokens, target) { + return tokens.findIndex((token) => token.kind === 'option' && + token.name === target + ); +} + +const experimentalName = 'enable-experimental-options'; +const unstableName = 'some-unstable-option'; + +const options = {}; +options[experimentalName] = { type: 'boolean' }; +options[unstableName] = { type: 'boolean' }; + +const { values, tokens } = parseArgs({ options, details: true }); + +const experimentalIndex = findTokenIndex(tokens, experimentalName); +const unstableIndex = findTokenIndex(tokens, unstableName); +if (unstableIndex !== -1 && + ((experimentalIndex === -1) || (unstableIndex < experimentalIndex))) { + throw new Error(`'--${experimentalName}' must be specified before '--${unstableName}'`); +} + +console.log(values); + +/* eslint-disable max-len */ +// Try the following: +// node ordered-options.mjs +// node ordered-options.mjs --some-unstable-option +// node ordered-options.mjs --some-unstable-option --enable-experimental-options +// node ordered-options.mjs --enable-experimental-options --some-unstable-option From fab1dc47d57436d47662e77201dcdb75f4b8a37e Mon Sep 17 00:00:00 2001 From: John Gee Date: Thu, 2 Jun 2022 22:09:56 +1200 Subject: [PATCH 27/64] Add example for no repeated options --- examples/no-repeated-options.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 examples/no-repeated-options.js diff --git a/examples/no-repeated-options.js b/examples/no-repeated-options.js new file mode 100644 index 0000000..bf7d813 --- /dev/null +++ b/examples/no-repeated-options.js @@ -0,0 +1,30 @@ +'use strict'; + +// How might I throw if an option is repeated? + +// 1. const { parseArgs } = require('node:util'); // from node +// 2. const { parseArgs } = require('@pkgjs/parseargs'); // from package +const { parseArgs } = require('..'); // in repo + +const options = { + ding: { type: 'boolean', short: 'd' }, + beep: { type: 'boolean', short: 'b' } +}; +const { values, tokens } = parseArgs({ options, details: true }); + +// Loop over values and find the options that were repeated. +const repeatedTokens = Object.keys(values) + // Make arrays of tokens for each used option name. + .map((name) => tokens.filter((t) => t.kind === 'option' && t.name === name)) + .filter((used) => used.length > 1); +if (repeatedTokens.length > 0) { + const optionsUsed = repeatedTokens[0].map((token) => token.optionUsed); + throw new Error(`option used multiple times: ${optionsUsed.join(', ')}`); +} + +console.log(values); + +// Try the following: +// node no-repeated-options --ding --beep +// node no-repeated-options -b --beep +// node no-repeated-options -ddd From 99165c99fc4faa8097f4a96aa57afa37b25e68a6 Mon Sep 17 00:00:00 2001 From: John Gee Date: Thu, 2 Jun 2022 22:12:05 +1200 Subject: [PATCH 28/64] Comment wording --- examples/negate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/negate.js b/examples/negate.js index 057c1d3..05fb02e 100644 --- a/examples/negate.js +++ b/examples/negate.js @@ -4,7 +4,7 @@ // (Not supporting multiples in this code.) // Side note: I included library-style structure here, but now thinking -// that would be better in just one full example. Keep the code +// that should be in just one full example. Keep the code // smaller and more focused in other example files. // 1. const { parseArgs } = require('node:util'); // from node From 9bd039df40d422303725ec249a19fba4cc7516bd Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 3 Jun 2022 18:42:29 +1200 Subject: [PATCH 29/64] Switch from details to tokens for configuration --- examples/limit-long-syntax.js | 2 +- examples/negate.js | 4 ++-- examples/no-repeated-options.js | 2 +- examples/ordered-options.mjs | 2 +- index.js | 4 ++-- test/index.js | 40 ++++++++++++++++----------------- 6 files changed, 27 insertions(+), 27 deletions(-) diff --git a/examples/limit-long-syntax.js b/examples/limit-long-syntax.js index aa4b46d..f018c3a 100644 --- a/examples/limit-long-syntax.js +++ b/examples/limit-long-syntax.js @@ -14,7 +14,7 @@ const options = { log: { type: 'string' }, }; -const { values, tokens } = parseArgs({ options, details: true }); +const { values, tokens } = parseArgs({ options, tokens: true }); const badToken = tokens.find((token) => token.kind === 'option' && options[token.name].type === 'string' && diff --git a/examples/negate.js b/examples/negate.js index 05fb02e..27efee6 100644 --- a/examples/negate.js +++ b/examples/negate.js @@ -13,7 +13,7 @@ const { parseArgs } = require('..'); // in repo function myParseArgs(config) { // Get the tokens for reprocessing. - const detailedConfig = Object.assign({}, config, { details: true }); + const detailedConfig = Object.assign({}, config, { tokens: true }); const result = parseArgs(detailedConfig); result.tokens @@ -31,7 +31,7 @@ function myParseArgs(config) { }); // Remove the tokens if caller did not ask for them. - if (!config.details) { + if (!config.tokens) { delete result.tokens; } return result; diff --git a/examples/no-repeated-options.js b/examples/no-repeated-options.js index bf7d813..09d149e 100644 --- a/examples/no-repeated-options.js +++ b/examples/no-repeated-options.js @@ -10,7 +10,7 @@ const options = { ding: { type: 'boolean', short: 'd' }, beep: { type: 'boolean', short: 'b' } }; -const { values, tokens } = parseArgs({ options, details: true }); +const { values, tokens } = parseArgs({ options, tokens: true }); // Loop over values and find the options that were repeated. const repeatedTokens = Object.keys(values) diff --git a/examples/ordered-options.mjs b/examples/ordered-options.mjs index e748f50..2071814 100644 --- a/examples/ordered-options.mjs +++ b/examples/ordered-options.mjs @@ -17,7 +17,7 @@ const options = {}; options[experimentalName] = { type: 'boolean' }; options[unstableName] = { type: 'boolean' }; -const { values, tokens } = parseArgs({ options, details: true }); +const { values, tokens } = parseArgs({ options, tokens: true }); const experimentalIndex = findTokenIndex(tokens, experimentalName); const unstableIndex = findTokenIndex(tokens, unstableName); diff --git a/index.js b/index.js index 37da584..516c372 100644 --- a/index.js +++ b/index.js @@ -269,7 +269,7 @@ const parseArgs = (config = { __proto__: null }) => { const args = objectGetOwn(config, 'args') ?? getMainArgs(); const strict = objectGetOwn(config, 'strict') ?? true; const allowPositionals = objectGetOwn(config, 'allowPositionals') ?? !strict; - const details = objectGetOwn(config, 'details') ?? false; + const returnTokens = objectGetOwn(config, 'tokens') ?? false; const options = objectGetOwn(config, 'options') ?? { __proto__: null }; // Bundle these up for passing to strict-mode checks. const parseConfig = { args, strict, options, allowPositionals }; @@ -313,7 +313,7 @@ const parseArgs = (config = { __proto__: null }) => { values: { __proto__: null }, positionals: [], }; - if (details) { + if (returnTokens) { result.tokens = tokens; } ArrayPrototypeForEach(tokens, (token) => { diff --git a/test/index.js b/test/index.js index 4931371..ef82868 100644 --- a/test/index.js +++ b/test/index.js @@ -568,7 +568,7 @@ test('tokens: positional', () => { const expectedTokens = [ { kind: 'positional', index: 0, value: 'one' }, ]; - const { tokens } = parseArgs({ strict: false, args, details: true }); + const { tokens } = parseArgs({ strict: false, args, tokens: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -578,7 +578,7 @@ test('tokens: -- followed by option-like', () => { { kind: 'option-terminator', index: 0 }, { kind: 'positional', index: 1, value: '--foo' }, ]; - const { tokens } = parseArgs({ strict: false, args, details: true }); + const { tokens } = parseArgs({ strict: false, args, tokens: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -591,7 +591,7 @@ test('tokens: strict:true boolean short', () => { { kind: 'option', name: 'file', optionUsed: '-f', index: 0, value: undefined, inlineValue: undefined }, ]; - const { tokens } = parseArgs({ strict: true, args, options, details: true }); + const { tokens } = parseArgs({ strict: true, args, options, tokens: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -604,7 +604,7 @@ test('tokens: strict:true boolean long', () => { { kind: 'option', name: 'file', optionUsed: '--file', index: 0, value: undefined, inlineValue: undefined }, ]; - const { tokens } = parseArgs({ strict: true, args, options, details: true }); + const { tokens } = parseArgs({ strict: true, args, options, tokens: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -614,7 +614,7 @@ test('tokens: strict:false boolean short', () => { { kind: 'option', name: 'f', optionUsed: '-f', index: 0, value: undefined, inlineValue: undefined }, ]; - const { tokens } = parseArgs({ strict: false, args, details: true }); + const { tokens } = parseArgs({ strict: false, args, tokens: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -624,7 +624,7 @@ test('tokens: strict:false boolean long', () => { { kind: 'option', name: 'file', optionUsed: '--file', index: 0, value: undefined, inlineValue: undefined }, ]; - const { tokens } = parseArgs({ strict: false, args, details: true }); + const { tokens } = parseArgs({ strict: false, args, tokens: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -636,7 +636,7 @@ test('tokens: strict:false boolean option group', () => { { kind: 'option', name: 'b', optionUsed: '-b', index: 0, value: undefined, inlineValue: undefined }, ]; - const { tokens } = parseArgs({ strict: false, args, details: true }); + const { tokens } = parseArgs({ strict: false, args, tokens: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -651,7 +651,7 @@ test('tokens: strict:true string short with value after space', () => { index: 0, value: 'bar', inlineValue: false }, { kind: 'positional', index: 2, value: 'ppp' }, ]; - const { tokens } = parseArgs({ strict: true, allowPositionals: true, args, options, details: true }); + const { tokens } = parseArgs({ strict: true, allowPositionals: true, args, options, tokens: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -664,7 +664,7 @@ test('tokens: strict:true string short with value inline', () => { { kind: 'option', name: 'file', optionUsed: '-f', index: 0, value: 'BAR', inlineValue: true }, ]; - const { tokens } = parseArgs({ strict: true, args, options, details: true }); + const { tokens } = parseArgs({ strict: true, args, options, tokens: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -677,7 +677,7 @@ test('tokens: strict:false string short missing value', () => { { kind: 'option', name: 'file', optionUsed: '-f', index: 0, value: undefined, inlineValue: undefined }, ]; - const { tokens } = parseArgs({ strict: false, args, options, details: true }); + const { tokens } = parseArgs({ strict: false, args, options, tokens: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -692,7 +692,7 @@ test('tokens: strict:true string long with value after space', () => { index: 0, value: 'bar', inlineValue: false }, { kind: 'positional', index: 2, value: 'ppp' }, ]; - const { tokens } = parseArgs({ strict: true, allowPositionals: true, args, options, details: true }); + const { tokens } = parseArgs({ strict: true, allowPositionals: true, args, options, tokens: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -705,7 +705,7 @@ test('tokens: strict:true string long with value inline', () => { { kind: 'option', name: 'file', optionUsed: '--file', index: 0, value: 'bar', inlineValue: true }, ]; - const { tokens } = parseArgs({ strict: true, args, options, details: true }); + const { tokens } = parseArgs({ strict: true, args, options, tokens: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -715,7 +715,7 @@ test('tokens: strict:false string long with value inline', () => { { kind: 'option', name: 'file', optionUsed: '--file', index: 0, value: 'bar', inlineValue: true }, ]; - const { tokens } = parseArgs({ strict: false, args, details: true }); + const { tokens } = parseArgs({ strict: false, args, tokens: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -728,7 +728,7 @@ test('tokens: strict:false string long missing value', () => { { kind: 'option', name: 'file', optionUsed: '--file', index: 0, value: undefined, inlineValue: undefined }, ]; - const { tokens } = parseArgs({ strict: false, args, options, details: true }); + const { tokens } = parseArgs({ strict: false, args, options, tokens: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -744,7 +744,7 @@ test('tokens: strict:true complex option group with value after space', () => { { kind: 'option', name: 'beta', optionUsed: '-b', index: 0, value: 'c', inlineValue: false }, ]; - const { tokens } = parseArgs({ strict: true, args, options, details: true }); + const { tokens } = parseArgs({ strict: true, args, options, tokens: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -760,7 +760,7 @@ test('tokens: strict:true complex option group with inline value', () => { { kind: 'option', name: 'beta', optionUsed: '-b', index: 0, value: 'c', inlineValue: true }, ]; - const { tokens } = parseArgs({ strict: true, args, options, details: true }); + const { tokens } = parseArgs({ strict: true, args, options, tokens: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -777,7 +777,7 @@ test('tokens: strict:false variety', () => { { kind: 'option-terminator', index: 6 }, { kind: 'positional', index: 7, value: '3' }, ]; - const { tokens } = parseArgs({ strict: false, args, details: true }); + const { tokens } = parseArgs({ strict: false, args, tokens: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -808,7 +808,7 @@ test('tokens: strict:true variety', () => { { kind: 'option-terminator', index: 11 }, { kind: 'positional', index: 12, value: '3' }, ]; - const { tokens } = parseArgs({ strict: true, allowPositionals: true, args, options, details: true }); + const { tokens } = parseArgs({ strict: true, allowPositionals: true, args, options, tokens: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -822,7 +822,7 @@ test('tokens: strict:false with single dashes', () => { index: 0, value: '-', inlineValue: false }, { kind: 'positional', index: 2, value: '-' }, ]; - const { tokens } = parseArgs({ strict: false, args, options, details: true }); + const { tokens } = parseArgs({ strict: false, args, options, tokens: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -832,6 +832,6 @@ test('tokens: strict:false with -- --', () => { { kind: 'option-terminator', index: 0 }, { kind: 'positional', index: 1, value: '--' }, ]; - const { tokens } = parseArgs({ strict: false, args, details: true }); + const { tokens } = parseArgs({ strict: false, args, tokens: true }); assert.deepStrictEqual(tokens, expectedTokens); }); From 6a3f637b35d57769c175433da4001ec3fa5ce085 Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 3 Jun 2022 18:49:06 +1200 Subject: [PATCH 30/64] Simplify example by removing "library" support --- examples/negate.js | 50 ++++++++++++++++++---------------------------- 1 file changed, 19 insertions(+), 31 deletions(-) diff --git a/examples/negate.js b/examples/negate.js index 27efee6..effe476 100644 --- a/examples/negate.js +++ b/examples/negate.js @@ -1,46 +1,34 @@ 'use strict'; // How might I add my own support for --no-foo? -// (Not supporting multiples in this code.) - -// Side note: I included library-style structure here, but now thinking -// that should be in just one full example. Keep the code -// smaller and more focused in other example files. // 1. const { parseArgs } = require('node:util'); // from node // 2. const { parseArgs } = require('@pkgjs/parseargs'); // from package const { parseArgs } = require('..'); // in repo -function myParseArgs(config) { - // Get the tokens for reprocessing. - const detailedConfig = Object.assign({}, config, { tokens: true }); - const result = parseArgs(detailedConfig); - - result.tokens - .filter((token) => token.kind === 'option') - .forEach((token) => { - if (token.name.startsWith('no-')) { - // Store foo:false for --no-foo - const positiveName = token.name.slice(3); - result.values[positiveName] = false; - delete result.values[token.name]; - } else { - // Resave value so last one wins if both --foo and --no-foo. - result.values[token.name] = (token.value != null) ? token.value : true; - } - }); - - // Remove the tokens if caller did not ask for them. - if (!config.tokens) { - delete result.tokens; - } - return result; -} +const { values, tokens } = parseArgs({ strict: false, tokens: true }); + +// Reprocess the option tokens and overwrite the returned values. +// (NB: not supporting `multiples` in this code.) +tokens + .filter((token) => token.kind === 'option') + .forEach((token) => { + if (token.name.startsWith('no-')) { + // Store foo:false for --no-foo + const positiveName = token.name.slice(3); + values[positiveName] = false; + delete values[token.name]; + } else { + // Resave value so last one wins if both --foo and --no-foo. + values[token.name] = (token.value != null) ? token.value : true; + } + }); -const { values } = myParseArgs({ strict: false }); console.log(values); // Try the following: // node negate.js --foo // node negate.js --foo --no-foo // node negate.js --foo --no-foo --foo +// node negate.js --foo=FOO --no-foo +// node negate.js --no-foo --foo=FOO From 4cfbc293a68fa244d849a0846aeb140ed3954ae4 Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 3 Jun 2022 19:16:43 +1200 Subject: [PATCH 31/64] Remove comments on how well-advised the examples goals are --- examples/limit-long-syntax.js | 2 -- examples/ordered-options.mjs | 2 -- 2 files changed, 4 deletions(-) diff --git a/examples/limit-long-syntax.js b/examples/limit-long-syntax.js index f018c3a..d65e171 100644 --- a/examples/limit-long-syntax.js +++ b/examples/limit-long-syntax.js @@ -2,8 +2,6 @@ // How might I require long options with values use '='? // So allow `--foo=bar`, and not allow `--foo bar`. -// -// NB: this is not a common restriction in CLI. // 1. const { parseArgs } = require('node:util'); // from node // 2. const { parseArgs } = require('@pkgjs/parseargs'); // from package diff --git a/examples/ordered-options.mjs b/examples/ordered-options.mjs index 2071814..1792ea8 100644 --- a/examples/ordered-options.mjs +++ b/examples/ordered-options.mjs @@ -1,6 +1,4 @@ // Now might I enforce that two flags are specified in a specific order? -// -// NB: POSIX says that option order does not normally matter. import { parseArgs } from '../index.js'; From 9a9a74044335e9fd356be2e822dd848b6cba027a Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 3 Jun 2022 20:03:38 +1200 Subject: [PATCH 32/64] Switch from optionUser to rawName --- examples/limit-long-syntax.js | 4 +-- examples/no-repeated-options.js | 2 +- index.js | 20 +++++------ test/index.js | 62 ++++++++++++++++----------------- 4 files changed, 44 insertions(+), 44 deletions(-) diff --git a/examples/limit-long-syntax.js b/examples/limit-long-syntax.js index d65e171..15be052 100644 --- a/examples/limit-long-syntax.js +++ b/examples/limit-long-syntax.js @@ -16,10 +16,10 @@ const { values, tokens } = parseArgs({ options, tokens: true }); const badToken = tokens.find((token) => token.kind === 'option' && options[token.name].type === 'string' && - token.optionUsed.startsWith('--') && + token.rawName.startsWith('--') && !token.inlineValue); if (badToken) { - throw new Error(`Option value for '${badToken.optionUsed}' must be inline, like '${badToken.optionUsed}=VALUE'`); + throw new Error(`Option value for '${badToken.rawName}' must be inline, like '${badToken.rawName}=VALUE'`); } console.log(values); diff --git a/examples/no-repeated-options.js b/examples/no-repeated-options.js index 09d149e..3113577 100644 --- a/examples/no-repeated-options.js +++ b/examples/no-repeated-options.js @@ -18,7 +18,7 @@ const repeatedTokens = Object.keys(values) .map((name) => tokens.filter((t) => t.kind === 'option' && t.name === name)) .filter((used) => used.length > 1); if (repeatedTokens.length > 0) { - const optionsUsed = repeatedTokens[0].map((token) => token.optionUsed); + const optionsUsed = repeatedTokens[0].map((token) => token.rawName); throw new Error(`option used multiple times: ${optionsUsed.join(', ')}`); } diff --git a/index.js b/index.js index 516c372..4982c1c 100644 --- a/index.js +++ b/index.js @@ -71,11 +71,11 @@ function checkOptionLikeValue(config, token) { if (config.strict && !token.inlineValue && isOptionLikeValue(token.value)) { // Only show short example if user used short option. - const example = StringPrototypeStartsWith(token.optionUsed, '--') ? - `'${token.optionUsed}=-XYZ'` : - `'--${token.name}=-XYZ' or '${token.optionUsed}-XYZ'`; - const errorMessage = `Option '${token.optionUsed}' argument is ambiguous. -Did you forget to specify the option argument for '${token.optionUsed}'? + const example = StringPrototypeStartsWith(token.rawName, '--') ? + `'${token.rawName}=-XYZ'` : + `'--${token.name}=-XYZ' or '${token.rawName}-XYZ'`; + const errorMessage = `Option '${token.rawName}' argument is ambiguous. +Did you forget to specify the option argument for '${token.rawName}'? To specify an option argument starting with a dash use ${example}.`; throw new ERR_PARSE_ARGS_INVALID_OPTION_VALUE(errorMessage); } @@ -92,7 +92,7 @@ function checkOptionUsage(config, token) { if (!ObjectHasOwn(config.options, token.name)) { throw new ERR_PARSE_ARGS_UNKNOWN_OPTION( - token.optionUsed, config.allowPositionals); + token.rawName, config.allowPositionals); } const short = optionsGetOwn(config.options, token.name, 'short'); @@ -189,7 +189,7 @@ function argsToTokens(args, options) { } ArrayPrototypePush( tokens, - { kind: 'option', name: longOption, optionUsed: arg, + { kind: 'option', name: longOption, rawName: arg, index, value, inlineValue }); if (value != null) ++index; continue; @@ -224,7 +224,7 @@ function argsToTokens(args, options) { const value = StringPrototypeSlice(arg, 2); ArrayPrototypePush( tokens, - { kind: 'option', name: longOption, optionUsed: `-${shortOption}`, + { kind: 'option', name: longOption, rawName: `-${shortOption}`, index, value, inlineValue: true }); continue; } @@ -242,7 +242,7 @@ function argsToTokens(args, options) { } ArrayPrototypePush( tokens, - { kind: 'option', name: longOption, optionUsed: arg, + { kind: 'option', name: longOption, rawName: arg, index, value, inlineValue }); if (value != null) ++index; continue; @@ -255,7 +255,7 @@ function argsToTokens(args, options) { const value = StringPrototypeSlice(arg, equalIndex + 1); ArrayPrototypePush( tokens, - { kind: 'option', name: longOption, optionUsed: `--${longOption}`, + { kind: 'option', name: longOption, rawName: `--${longOption}`, index, value, inlineValue: true }); continue; } diff --git a/test/index.js b/test/index.js index ef82868..1d50cbd 100644 --- a/test/index.js +++ b/test/index.js @@ -588,7 +588,7 @@ test('tokens: strict:true boolean short', () => { file: { short: 'f', type: 'boolean' } }; const expectedTokens = [ - { kind: 'option', name: 'file', optionUsed: '-f', + { kind: 'option', name: 'file', rawName: '-f', index: 0, value: undefined, inlineValue: undefined }, ]; const { tokens } = parseArgs({ strict: true, args, options, tokens: true }); @@ -601,7 +601,7 @@ test('tokens: strict:true boolean long', () => { file: { short: 'f', type: 'boolean' } }; const expectedTokens = [ - { kind: 'option', name: 'file', optionUsed: '--file', + { kind: 'option', name: 'file', rawName: '--file', index: 0, value: undefined, inlineValue: undefined }, ]; const { tokens } = parseArgs({ strict: true, args, options, tokens: true }); @@ -611,7 +611,7 @@ test('tokens: strict:true boolean long', () => { test('tokens: strict:false boolean short', () => { const args = ['-f']; const expectedTokens = [ - { kind: 'option', name: 'f', optionUsed: '-f', + { kind: 'option', name: 'f', rawName: '-f', index: 0, value: undefined, inlineValue: undefined }, ]; const { tokens } = parseArgs({ strict: false, args, tokens: true }); @@ -621,7 +621,7 @@ test('tokens: strict:false boolean short', () => { test('tokens: strict:false boolean long', () => { const args = ['--file']; const expectedTokens = [ - { kind: 'option', name: 'file', optionUsed: '--file', + { kind: 'option', name: 'file', rawName: '--file', index: 0, value: undefined, inlineValue: undefined }, ]; const { tokens } = parseArgs({ strict: false, args, tokens: true }); @@ -631,9 +631,9 @@ test('tokens: strict:false boolean long', () => { test('tokens: strict:false boolean option group', () => { const args = ['-ab']; const expectedTokens = [ - { kind: 'option', name: 'a', optionUsed: '-a', + { kind: 'option', name: 'a', rawName: '-a', index: 0, value: undefined, inlineValue: undefined }, - { kind: 'option', name: 'b', optionUsed: '-b', + { kind: 'option', name: 'b', rawName: '-b', index: 0, value: undefined, inlineValue: undefined }, ]; const { tokens } = parseArgs({ strict: false, args, tokens: true }); @@ -647,7 +647,7 @@ test('tokens: strict:true string short with value after space', () => { file: { short: 'f', type: 'string' } }; const expectedTokens = [ - { kind: 'option', name: 'file', optionUsed: '-f', + { kind: 'option', name: 'file', rawName: '-f', index: 0, value: 'bar', inlineValue: false }, { kind: 'positional', index: 2, value: 'ppp' }, ]; @@ -661,7 +661,7 @@ test('tokens: strict:true string short with value inline', () => { file: { short: 'f', type: 'string' } }; const expectedTokens = [ - { kind: 'option', name: 'file', optionUsed: '-f', + { kind: 'option', name: 'file', rawName: '-f', index: 0, value: 'BAR', inlineValue: true }, ]; const { tokens } = parseArgs({ strict: true, args, options, tokens: true }); @@ -674,7 +674,7 @@ test('tokens: strict:false string short missing value', () => { file: { short: 'f', type: 'string' } }; const expectedTokens = [ - { kind: 'option', name: 'file', optionUsed: '-f', + { kind: 'option', name: 'file', rawName: '-f', index: 0, value: undefined, inlineValue: undefined }, ]; const { tokens } = parseArgs({ strict: false, args, options, tokens: true }); @@ -688,7 +688,7 @@ test('tokens: strict:true string long with value after space', () => { file: { short: 'f', type: 'string' } }; const expectedTokens = [ - { kind: 'option', name: 'file', optionUsed: '--file', + { kind: 'option', name: 'file', rawName: '--file', index: 0, value: 'bar', inlineValue: false }, { kind: 'positional', index: 2, value: 'ppp' }, ]; @@ -702,7 +702,7 @@ test('tokens: strict:true string long with value inline', () => { file: { short: 'f', type: 'string' } }; const expectedTokens = [ - { kind: 'option', name: 'file', optionUsed: '--file', + { kind: 'option', name: 'file', rawName: '--file', index: 0, value: 'bar', inlineValue: true }, ]; const { tokens } = parseArgs({ strict: true, args, options, tokens: true }); @@ -712,7 +712,7 @@ test('tokens: strict:true string long with value inline', () => { test('tokens: strict:false string long with value inline', () => { const args = ['--file=bar']; const expectedTokens = [ - { kind: 'option', name: 'file', optionUsed: '--file', + { kind: 'option', name: 'file', rawName: '--file', index: 0, value: 'bar', inlineValue: true }, ]; const { tokens } = parseArgs({ strict: false, args, tokens: true }); @@ -725,7 +725,7 @@ test('tokens: strict:false string long missing value', () => { file: { short: 'f', type: 'string' } }; const expectedTokens = [ - { kind: 'option', name: 'file', optionUsed: '--file', + { kind: 'option', name: 'file', rawName: '--file', index: 0, value: undefined, inlineValue: undefined }, ]; const { tokens } = parseArgs({ strict: false, args, options, tokens: true }); @@ -739,9 +739,9 @@ test('tokens: strict:true complex option group with value after space', () => { beta: { short: 'b', type: 'string' }, }; const expectedTokens = [ - { kind: 'option', name: 'alpha', optionUsed: '-a', + { kind: 'option', name: 'alpha', rawName: '-a', index: 0, value: undefined, inlineValue: undefined }, - { kind: 'option', name: 'beta', optionUsed: '-b', + { kind: 'option', name: 'beta', rawName: '-b', index: 0, value: 'c', inlineValue: false }, ]; const { tokens } = parseArgs({ strict: true, args, options, tokens: true }); @@ -755,9 +755,9 @@ test('tokens: strict:true complex option group with inline value', () => { beta: { short: 'b', type: 'string' }, }; const expectedTokens = [ - { kind: 'option', name: 'alpha', optionUsed: '-a', + { kind: 'option', name: 'alpha', rawName: '-a', index: 0, value: undefined, inlineValue: undefined }, - { kind: 'option', name: 'beta', optionUsed: '-b', + { kind: 'option', name: 'beta', rawName: '-b', index: 0, value: 'c', inlineValue: true }, ]; const { tokens } = parseArgs({ strict: true, args, options, tokens: true }); @@ -767,13 +767,13 @@ test('tokens: strict:true complex option group with inline value', () => { test('tokens: strict:false variety', () => { const args = ['-a', '1', '-bc', '2', '--ddd', '--eee=fff', '--', '3']; const expectedTokens = [ - { kind: 'option', name: 'a', optionUsed: '-a', index: 0, value: undefined, inlineValue: undefined }, + { kind: 'option', name: 'a', rawName: '-a', index: 0, value: undefined, inlineValue: undefined }, { kind: 'positional', index: 1, value: '1' }, - { kind: 'option', name: 'b', optionUsed: '-b', index: 2, value: undefined, inlineValue: undefined }, - { kind: 'option', name: 'c', optionUsed: '-c', index: 2, value: undefined, inlineValue: undefined }, + { kind: 'option', name: 'b', rawName: '-b', index: 2, value: undefined, inlineValue: undefined }, + { kind: 'option', name: 'c', rawName: '-c', index: 2, value: undefined, inlineValue: undefined }, { kind: 'positional', index: 3, value: '2' }, - { kind: 'option', name: 'ddd', optionUsed: '--ddd', index: 4, value: undefined, inlineValue: undefined }, - { kind: 'option', name: 'eee', optionUsed: '--eee', index: 5, value: 'fff', inlineValue: true }, + { kind: 'option', name: 'ddd', rawName: '--ddd', index: 4, value: undefined, inlineValue: undefined }, + { kind: 'option', name: 'eee', rawName: '--eee', index: 5, value: 'fff', inlineValue: true }, { kind: 'option-terminator', index: 6 }, { kind: 'positional', index: 7, value: '3' }, ]; @@ -795,16 +795,16 @@ test('tokens: strict:true variety', () => { hhh: { type: 'boolean' }, }; const expectedTokens = [ - { kind: 'option', name: 'alpha', optionUsed: '-a', index: 0, value: undefined, inlineValue: undefined }, + { kind: 'option', name: 'alpha', rawName: '-a', index: 0, value: undefined, inlineValue: undefined }, { kind: 'positional', index: 1, value: '1' }, - { kind: 'option', name: 'beta', optionUsed: '-b', index: 2, value: undefined, inlineValue: undefined }, - { kind: 'option', name: 'cat', optionUsed: '-c', index: 2, value: undefined, inlineValue: undefined }, - { kind: 'option', name: 'delta', optionUsed: '-d', index: 3, value: 'DDD', inlineValue: true }, - { kind: 'option', name: 'epsilon', optionUsed: '-e', index: 4, value: 'EEE', inlineValue: false }, + { kind: 'option', name: 'beta', rawName: '-b', index: 2, value: undefined, inlineValue: undefined }, + { kind: 'option', name: 'cat', rawName: '-c', index: 2, value: undefined, inlineValue: undefined }, + { kind: 'option', name: 'delta', rawName: '-d', index: 3, value: 'DDD', inlineValue: true }, + { kind: 'option', name: 'epsilon', rawName: '-e', index: 4, value: 'EEE', inlineValue: false }, { kind: 'positional', index: 6, value: '2' }, - { kind: 'option', name: 'fff', optionUsed: '--fff', index: 7, value: 'FFF', inlineValue: true }, - { kind: 'option', name: 'ggg', optionUsed: '--ggg', index: 8, value: 'GGG', inlineValue: false }, - { kind: 'option', name: 'hhh', optionUsed: '--hhh', index: 10, value: undefined, inlineValue: undefined }, + { kind: 'option', name: 'fff', rawName: '--fff', index: 7, value: 'FFF', inlineValue: true }, + { kind: 'option', name: 'ggg', rawName: '--ggg', index: 8, value: 'GGG', inlineValue: false }, + { kind: 'option', name: 'hhh', rawName: '--hhh', index: 10, value: undefined, inlineValue: undefined }, { kind: 'option-terminator', index: 11 }, { kind: 'positional', index: 12, value: '3' }, ]; @@ -818,7 +818,7 @@ test('tokens: strict:false with single dashes', () => { file: { short: 'f', type: 'string' }, }; const expectedTokens = [ - { kind: 'option', name: 'file', optionUsed: '--file', + { kind: 'option', name: 'file', rawName: '--file', index: 0, value: '-', inlineValue: false }, { kind: 'positional', index: 2, value: '-' }, ]; From 6f93632abd5249661d141658a7fd44b11dfd4f17 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 4 Jun 2022 12:35:30 +1200 Subject: [PATCH 33/64] Use modern syntax Co-authored-by: Kevin Gibbons --- examples/negate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/negate.js b/examples/negate.js index effe476..f9a2b23 100644 --- a/examples/negate.js +++ b/examples/negate.js @@ -20,7 +20,7 @@ tokens delete values[token.name]; } else { // Resave value so last one wins if both --foo and --no-foo. - values[token.name] = (token.value != null) ? token.value : true; + values[token.name] = token.value ?? true; } }); From 642d8c02e21f776bbbcdf002a26603f8bb46d938 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 4 Jun 2022 12:37:54 +1200 Subject: [PATCH 34/64] Validate new input property --- index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/index.js b/index.js index 4982c1c..43185b6 100644 --- a/index.js +++ b/index.js @@ -278,6 +278,7 @@ const parseArgs = (config = { __proto__: null }) => { validateArray(args, 'args'); validateBoolean(strict, 'strict'); validateBoolean(allowPositionals, 'allowPositionals'); + validateBoolean(returnTokens, 'tokens'); validateObject(options, 'options'); ArrayPrototypeForEach( ObjectEntries(options), From e70609a2d2a1a49e18861d0055684f1bb961b1a0 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 4 Jun 2022 12:49:17 +1200 Subject: [PATCH 35/64] Move strict check outside check routines --- index.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/index.js b/index.js index 43185b6..c3c32fe 100644 --- a/index.js +++ b/index.js @@ -64,12 +64,10 @@ function getMainArgs() { /** * In strict mode, throw for possible usage errors like --foo --bar * - * @param {object} config - from config passed to parseArgs * @param {object} token - from tokens as available from parseArgs */ -function checkOptionLikeValue(config, token) { - if (config.strict && - !token.inlineValue && isOptionLikeValue(token.value)) { +function checkOptionLikeValue(token) { + if (!token.inlineValue && isOptionLikeValue(token.value)) { // Only show short example if user used short option. const example = StringPrototypeStartsWith(token.rawName, '--') ? `'${token.rawName}=-XYZ'` : @@ -88,8 +86,6 @@ To specify an option argument starting with a dash use ${example}.`; * @param {object} token - from tokens as available from parseArgs */ function checkOptionUsage(config, token) { - if (!config.strict) return; - if (!ObjectHasOwn(config.options, token.name)) { throw new ERR_PARSE_ARGS_UNKNOWN_OPTION( token.rawName, config.allowPositionals); @@ -328,8 +324,10 @@ const parseArgs = (config = { __proto__: null }) => { ArrayPrototypePush(result.positionals, token.value); break; case 'option': - checkOptionUsage(parseConfig, token); - checkOptionLikeValue(parseConfig, token); + if (strict) { + checkOptionUsage(parseConfig, token); + checkOptionLikeValue(token); + } storeOption(token.name, token.value, options, result.values); break; } From fdaa55344e66fd1ce22ed0eca6fcfdbd3c92979a Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 4 Jun 2022 12:58:49 +1200 Subject: [PATCH 36/64] Tidy object setup --- examples/ordered-options.mjs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/ordered-options.mjs b/examples/ordered-options.mjs index 1792ea8..093e56c 100644 --- a/examples/ordered-options.mjs +++ b/examples/ordered-options.mjs @@ -11,9 +11,10 @@ function findTokenIndex(tokens, target) { const experimentalName = 'enable-experimental-options'; const unstableName = 'some-unstable-option'; -const options = {}; -options[experimentalName] = { type: 'boolean' }; -options[unstableName] = { type: 'boolean' }; +const options = { + [experimentalName]: { type: 'boolean' }, + [unstableName]: { type: 'boolean' }, +}; const { values, tokens } = parseArgs({ options, tokens: true }); From c29330716edb2259b0c707615169a9274c0637c7 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 4 Jun 2022 13:03:59 +1200 Subject: [PATCH 37/64] Simplify test Co-authored-by: Kevin Gibbons --- examples/limit-long-syntax.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/limit-long-syntax.js b/examples/limit-long-syntax.js index 15be052..d5687fd 100644 --- a/examples/limit-long-syntax.js +++ b/examples/limit-long-syntax.js @@ -15,7 +15,7 @@ const options = { const { values, tokens } = parseArgs({ options, tokens: true }); const badToken = tokens.find((token) => token.kind === 'option' && - options[token.name].type === 'string' && +token.value != null && token.rawName.startsWith('--') && !token.inlineValue); if (badToken) { From 041459dd3ae96877bf3685498d9a6561e2fa5474 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 4 Jun 2022 13:07:00 +1200 Subject: [PATCH 38/64] Indentation and match filename --- examples/limit-long-syntax.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/limit-long-syntax.js b/examples/limit-long-syntax.js index d5687fd..b06f70d 100644 --- a/examples/limit-long-syntax.js +++ b/examples/limit-long-syntax.js @@ -15,9 +15,10 @@ const options = { const { values, tokens } = parseArgs({ options, tokens: true }); const badToken = tokens.find((token) => token.kind === 'option' && -token.value != null && - token.rawName.startsWith('--') && - !token.inlineValue); + token.value != null && + token.rawName.startsWith('--') && + !token.inlineValue +); if (badToken) { throw new Error(`Option value for '${badToken.rawName}' must be inline, like '${badToken.rawName}=VALUE'`); } @@ -25,5 +26,5 @@ if (badToken) { console.log(values); // Try the following: -// node limited-long-syntax.js -f FILE --log=LOG -// node limited-long-syntax.js --file FILE +// node limit-long-syntax.js -f FILE --log=LOG +// node limit-long-syntax.js --file FILE From 7aafaf63df924499df3d82b3fde8c83f4bf85c02 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 4 Jun 2022 20:35:17 +1200 Subject: [PATCH 39/64] Rework with strict:true --- examples/negate.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/examples/negate.js b/examples/negate.js index f9a2b23..4619653 100644 --- a/examples/negate.js +++ b/examples/negate.js @@ -6,10 +6,15 @@ // 2. const { parseArgs } = require('@pkgjs/parseargs'); // from package const { parseArgs } = require('..'); // in repo -const { values, tokens } = parseArgs({ strict: false, tokens: true }); +const options = { + ['color']: { type: 'string' }, + ['no-color']: { type: 'boolean' }, + ['logfile']: { type: 'string' }, + ['no-logfile']: { type: 'boolean' }, +}; +const { values, tokens } = parseArgs({ options, tokens: true }); // Reprocess the option tokens and overwrite the returned values. -// (NB: not supporting `multiples` in this code.) tokens .filter((token) => token.kind === 'option') .forEach((token) => { @@ -24,11 +29,13 @@ tokens } }); -console.log(values); +const color = values.color; +const logfile = values.logfile ?? 'default.log'; + +console.log({ logfile, color }); // Try the following: -// node negate.js --foo -// node negate.js --foo --no-foo -// node negate.js --foo --no-foo --foo -// node negate.js --foo=FOO --no-foo -// node negate.js --no-foo --foo=FOO +// node negate.js +// node negate.js --logfile=test.log --color=red +// node negate.js --no-logfile --no-color +// node negate.js --no-logfile --logfile=test.log --color=red --no-color From 1b6e585621ae475a0adfbcab449fa73f601f37e0 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 4 Jun 2022 21:49:52 +1200 Subject: [PATCH 40/64] Longer but simpler --- examples/no-repeated-options.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/examples/no-repeated-options.js b/examples/no-repeated-options.js index 3113577..3f4295f 100644 --- a/examples/no-repeated-options.js +++ b/examples/no-repeated-options.js @@ -12,19 +12,21 @@ const options = { }; const { values, tokens } = parseArgs({ options, tokens: true }); -// Loop over values and find the options that were repeated. -const repeatedTokens = Object.keys(values) - // Make arrays of tokens for each used option name. - .map((name) => tokens.filter((t) => t.kind === 'option' && t.name === name)) - .filter((used) => used.length > 1); -if (repeatedTokens.length > 0) { - const optionsUsed = repeatedTokens[0].map((token) => token.rawName); - throw new Error(`option used multiple times: ${optionsUsed.join(', ')}`); -} +const seenBefore = new Set(); +const repeatedToken = tokens + .filter((t) => t.kind === 'option') + .find((t) => { + if (seenBefore.has(t.name)) return true; + seenBefore.add(t.name); + return false; + }); +if (repeatedToken) + throw new Error(`option '${repeatedToken.name}' used multiple times`); + console.log(values); // Try the following: // node no-repeated-options --ding --beep -// node no-repeated-options -b --beep +// node no-repeated-options --beep -b // node no-repeated-options -ddd From 20eacb0ebbaccd28f3a29b1ae3c61be93e814ccc Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 4 Jun 2022 23:01:22 +1200 Subject: [PATCH 41/64] Add textual description, and some fixes --- README.md | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7eb8ae8..5be3316 100644 --- a/README.md +++ b/README.md @@ -25,18 +25,41 @@ added: REPLACEME times. If `true`, all values will be collected in an array. If `false`, values for the option are last-wins. **Default:** `false`. * `short` {string} A single character alias for the option. - * `strict`: {boolean} Should an error be thrown when unknown arguments + * `strict` {boolean} Should an error be thrown when unknown arguments are encountered, or when arguments are passed that do not match the `type` configured in `options`. **Default:** `true`. - * `allowPositionals`: {boolean} Whether this command accepts positional + * `allowPositionals` {boolean} Whether this command accepts positional arguments. **Default:** `false` if `strict` is `true`, otherwise `true`. + * `tokens` {boolean} Return an array with the + parsed tokens. This is useful for extending the built-in behaviour, + from adding additional checks through to reprocessing the tokens + in different ways. + **Default:** `false`. * Returns: {Object} The parsed command line arguments: * `values` {Object} A mapping of parsed option names with their {string} or {boolean} values. * `positionals` {string\[]} Positional arguments. + * `tokens` {Object} Parsed tokens. Only present if requested. + +// Maybe leave this out? +Tokens have properties describing the parse results for: + * option: + * `kind`: {'option'} + * `name`: {string} Long name of option. + * `rawName`: {string} How option used in args, like `-f` of `--foo`. + * `index`: { number } Index in `args` of option. + * `value`: { string | undefined } Option value specified in args. Undefined for boolean options. + * `inlineValue`: { boolean | undefined } Whether option value specified inline, like `--foo=bar`. + * positional: + * `kind`: {'positional'} + * `index`: { number } Index in `args` of positional. + * `value`: { string } Positional value (i.e. `args[index]`). + * option-terminator + * `kind`: {'option-terminator'} + * `index`: { number } Index in `args` of `--`. Provides a higher level API for command-line argument parsing than interacting with `process.argv` directly. Takes a specification for the expected arguments @@ -79,7 +102,7 @@ const { positionals } = parseArgs({ args, options }); console.log(values, positionals); -// Prints: [Object: null prototype] { foo: true, bar: 'b' } []ss +// Prints: [Object: null prototype] { foo: true, bar: 'b' } [] ``` `util.parseArgs` is experimental and behavior may change. Join the From 81239d66c2d778f83decc313289a7fbe0d4a0aac Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 4 Jun 2022 23:35:33 +1200 Subject: [PATCH 42/64] Add example output for tokens --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/README.md b/README.md index 5be3316..cafef8a 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,50 @@ console.log(values, positionals); // Prints: [Object: null prototype] { foo: true, bar: 'b' } [] ``` +Detailed parse information is available for adding custom behaviours. +For example, assuming the following script for `inspect.js`, with +automatic detection of options: + +```mjs +import { parseArgs } from 'node:util'; +console.log(parseArgs({ strict: false, tokens: true })); +``` + +```cjs +const { parseArgs } = require('node:util'); +console.log(parseArgs({ strict: false, tokens: true })); +``` + +This call shows the three kinds of token and their properties: + +```console +$ node inspect.js -d --foo=BAR -- file.txt +{ + values: [Object: null prototype] { d: true, foo: 'BAR' }, + positionals: [ 'file.txt' ], + tokens: [ + { + kind: 'option', + name: 'd', + rawName: '-d', + index: 0, + value: undefined, + inlineValue: undefined + }, + { + kind: 'option', + name: 'foo', + rawName: '--foo', + index: 1, + value: 'BAR', + inlineValue: true + }, + { kind: 'option-terminator', index: 2 }, + { kind: 'positional', index: 3, value: 'file.txt' } + ] +} +``` + `util.parseArgs` is experimental and behavior may change. Join the conversation in [pkgjs/parseargs][] to contribute to the design. From 84dde8ec9eb26c7fe967900c5febf3bb4573a591 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 5 Jun 2022 00:09:47 +1200 Subject: [PATCH 43/64] Add example used in new documentation --- README.md | 7 +++++-- examples/tokens.js | 13 +++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 examples/tokens.js diff --git a/README.md b/README.md index cafef8a..32ccca9 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ console.log(values, positionals); ``` Detailed parse information is available for adding custom behaviours. -For example, assuming the following script for `inspect.js`, with +For example, assuming the following script for `tokens.js`, with automatic detection of options: ```mjs @@ -122,7 +122,7 @@ console.log(parseArgs({ strict: false, tokens: true })); This call shows the three kinds of token and their properties: ```console -$ node inspect.js -d --foo=BAR -- file.txt +$ node tokens.js -d --foo=BAR -- file.txt { values: [Object: null prototype] { d: true, foo: 'BAR' }, positionals: [ 'file.txt' ], @@ -149,6 +149,9 @@ $ node inspect.js -d --foo=BAR -- file.txt } ``` +Short option groups like `-abc` expand to a token for each option. The source argument +for a token is `args[token.index]`. + `util.parseArgs` is experimental and behavior may change. Join the conversation in [pkgjs/parseargs][] to contribute to the design. diff --git a/examples/tokens.js b/examples/tokens.js new file mode 100644 index 0000000..b276a9a --- /dev/null +++ b/examples/tokens.js @@ -0,0 +1,13 @@ +'use strict'; + +// This example is used in the documentation. + +// 1. const { parseArgs } = require('node:util'); // from node +// 2. const { parseArgs } = require('@pkgjs/parseargs'); // from package +const { parseArgs } = require('..'); // in repo + +console.log(parseArgs({ strict: false, tokens: true })); + +// Try the following: +// node tokens.js -d --foo=BAR -- file.txt +// node tokens.js one -abc two From 40a62016ff3b9e4310fdb48d077a92a2551fe6f1 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 5 Jun 2022 00:18:14 +1200 Subject: [PATCH 44/64] Refactor documentation --- README.md | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 32ccca9..5b99b3a 100644 --- a/README.md +++ b/README.md @@ -44,23 +44,6 @@ added: REPLACEME * `positionals` {string\[]} Positional arguments. * `tokens` {Object} Parsed tokens. Only present if requested. -// Maybe leave this out? -Tokens have properties describing the parse results for: - * option: - * `kind`: {'option'} - * `name`: {string} Long name of option. - * `rawName`: {string} How option used in args, like `-f` of `--foo`. - * `index`: { number } Index in `args` of option. - * `value`: { string | undefined } Option value specified in args. Undefined for boolean options. - * `inlineValue`: { boolean | undefined } Whether option value specified inline, like `--foo=bar`. - * positional: - * `kind`: {'positional'} - * `index`: { number } Index in `args` of positional. - * `value`: { string } Positional value (i.e. `args[index]`). - * option-terminator - * `kind`: {'option-terminator'} - * `index`: { number } Index in `args` of `--`. - Provides a higher level API for command-line argument parsing than interacting with `process.argv` directly. Takes a specification for the expected arguments and returns a structured object with the parsed options and positionals. @@ -105,9 +88,25 @@ console.log(values, positionals); // Prints: [Object: null prototype] { foo: true, bar: 'b' } [] ``` -Detailed parse information is available for adding custom behaviours. -For example, assuming the following script for `tokens.js`, with -automatic detection of options: +Detailed parse information is available for adding custom behaviours by specifying `tokens: true` in the configuration. The returned tokens have properties describing: + +* option: + * `kind`: {'option'} + * `name`: {string} Long name of option. + * `rawName`: {string} How option used in args, like `-f` of `--foo`. + * `index`: { number } Index in `args` of option. + * `value`: { string | undefined } Option value specified in args. Undefined for boolean options. + * `inlineValue`: { boolean | undefined } Whether option value specified inline, like `--foo=bar`. +* positional: + * `kind`: {'positional'} + * `index`: { number } Index in `args` of positional. + * `value`: { string } Positional value (i.e. `args[index]`). +* option-terminator + * `kind`: {'option-terminator'} + * `index`: { number } Index in `args` of `--`. + +For example, assuming the following script for `tokens.js`, which uses +automatic detection of options and no error checking: ```mjs import { parseArgs } from 'node:util'; @@ -152,7 +151,7 @@ $ node tokens.js -d --foo=BAR -- file.txt Short option groups like `-abc` expand to a token for each option. The source argument for a token is `args[token.index]`. -`util.parseArgs` is experimental and behavior may change. Join the +`util.parseArgs()` is experimental and behavior may change. Join the conversation in [pkgjs/parseargs][] to contribute to the design. ----- From 07fbb91fb08c91b089d8661a1edf47c3651cfb1c Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 5 Jun 2022 11:41:43 +1200 Subject: [PATCH 45/64] Update .editorconfig from Node.js --- .editorconfig | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.editorconfig b/.editorconfig index 8f7d665..b140163 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,10 +3,12 @@ # top-most EditorConfig file root = true +# Copied from Node.js to ease compatibility in PR. [*] +charset = utf-8 end_of_line = lf -insert_final_newline = true -indent_style = space indent_size = 2 -tab_width = 2 -# trim_trailing_whitespace = true +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true +quote_type = single From 35c16f11b842e8d4690c25dbe321961489d11966 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 5 Jun 2022 11:42:40 +1200 Subject: [PATCH 46/64] rework token property descriptions --- README.md | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 5b99b3a..a204986 100644 --- a/README.md +++ b/README.md @@ -90,20 +90,17 @@ console.log(values, positionals); Detailed parse information is available for adding custom behaviours by specifying `tokens: true` in the configuration. The returned tokens have properties describing: -* option: - * `kind`: {'option'} - * `name`: {string} Long name of option. - * `rawName`: {string} How option used in args, like `-f` of `--foo`. - * `index`: { number } Index in `args` of option. +* all tokens + * `kind`: { string } One of 'option', 'positional', or 'option-terminator'. + * `index`: { number } Index of element in `args` containing token. +* option tokens + * `name`: { string } Long name of option. + * `rawName`: { string } How option used in args, like `-f` of `--foo`. * `value`: { string | undefined } Option value specified in args. Undefined for boolean options. * `inlineValue`: { boolean | undefined } Whether option value specified inline, like `--foo=bar`. -* positional: - * `kind`: {'positional'} - * `index`: { number } Index in `args` of positional. +* positional tokens * `value`: { string } Positional value (i.e. `args[index]`). -* option-terminator - * `kind`: {'option-terminator'} - * `index`: { number } Index in `args` of `--`. +* option-terminator token For example, assuming the following script for `tokens.js`, which uses automatic detection of options and no error checking: From 038480af4bd50b0f54451afb16ceaf4aa5fe82b1 Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 7 Jun 2022 17:52:36 +1200 Subject: [PATCH 47/64] Minor refactor of remaining arg processing --- index.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index c3c32fe..35dbf68 100644 --- a/index.js +++ b/index.js @@ -3,7 +3,9 @@ const { ArrayPrototypeForEach, ArrayPrototypeIncludes, + ArrayPrototypeMap, ArrayPrototypePush, + ArrayPrototypePushApply, ArrayPrototypeShift, ArrayPrototypeSlice, ArrayPrototypeUnshiftApply, @@ -164,10 +166,11 @@ function argsToTokens(args, options) { if (arg === '--') { // Everything after a bare '--' is considered a positional argument. ArrayPrototypePush(tokens, { kind: 'option-terminator', index }); - ArrayPrototypeForEach(remainingArgs, (arg) => - ArrayPrototypePush( - tokens, - { kind: 'positional', index: ++index, value: arg })); + ArrayPrototypePushApply( + tokens, ArrayPrototypeMap(remainingArgs, (arg) => { + return { kind: 'positional', index: ++index, value: arg }; + }) + ); break; // Finished processing args, leave while loop. } From d4f96cc9c639de51c04c5a7b190576f07bb56594 Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 14 Jun 2022 19:05:39 +1200 Subject: [PATCH 48/64] Replace switch with if/else --- index.js | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/index.js b/index.js index 35dbf68..eccea86 100644 --- a/index.js +++ b/index.js @@ -317,22 +317,17 @@ const parseArgs = (config = { __proto__: null }) => { result.tokens = tokens; } ArrayPrototypeForEach(tokens, (token) => { - switch (token.kind) { - case 'option-terminator': - break; - case 'positional': - if (!allowPositionals) { - throw new ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL(token.value); - } - ArrayPrototypePush(result.positionals, token.value); - break; - case 'option': - if (strict) { - checkOptionUsage(parseConfig, token); - checkOptionLikeValue(token); - } - storeOption(token.name, token.value, options, result.values); - break; + if (token.kind === 'option') { + if (strict) { + checkOptionUsage(parseConfig, token); + checkOptionLikeValue(token); + } + storeOption(token.name, token.value, options, result.values); + } else if (token.kind === 'positional') { + if (!allowPositionals) { + throw new ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL(token.value); + } + ArrayPrototypePush(result.positionals, token.value); } }); From 122727ebf23d72c2535f710e5fc4a0ae67e429fc Mon Sep 17 00:00:00 2001 From: John Gee Date: Wed, 15 Jun 2022 19:52:55 +1200 Subject: [PATCH 49/64] Remove side-affect, per feedback --- examples/no-repeated-options.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/examples/no-repeated-options.js b/examples/no-repeated-options.js index 3f4295f..51dcdd1 100644 --- a/examples/no-repeated-options.js +++ b/examples/no-repeated-options.js @@ -13,16 +13,13 @@ const options = { const { values, tokens } = parseArgs({ options, tokens: true }); const seenBefore = new Set(); -const repeatedToken = tokens - .filter((t) => t.kind === 'option') - .find((t) => { - if (seenBefore.has(t.name)) return true; - seenBefore.add(t.name); - return false; - }); -if (repeatedToken) - throw new Error(`option '${repeatedToken.name}' used multiple times`); - +tokens.forEach((token) => { + if (token.kind !== 'option') return; + if (seenBefore.has(token.name)) { + throw new Error(`option '${token.name}' used multiple times`); + } + seenBefore.add(token.name); +}); console.log(values); From 5b3518d4c80c2cf1c4adaa8de285859a7c030b60 Mon Sep 17 00:00:00 2001 From: John Gee Date: Wed, 15 Jun 2022 19:56:50 +1200 Subject: [PATCH 50/64] Add tricky case of short option group to token expansion --- README.md | 14 +++++++++++--- examples/tokens.js | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a204986..0c4fce0 100644 --- a/README.md +++ b/README.md @@ -118,15 +118,23 @@ console.log(parseArgs({ strict: false, tokens: true })); This call shows the three kinds of token and their properties: ```console -$ node tokens.js -d --foo=BAR -- file.txt +$ node tokens.js -xy --foo=BAR -- file.txt { values: [Object: null prototype] { d: true, foo: 'BAR' }, positionals: [ 'file.txt' ], tokens: [ { kind: 'option', - name: 'd', - rawName: '-d', + name: 'x', + rawName: '-x', + index: 0, + value: undefined, + inlineValue: undefined + }, + { + kind: 'option', + name: 'y', + rawName: '-y', index: 0, value: undefined, inlineValue: undefined diff --git a/examples/tokens.js b/examples/tokens.js index b276a9a..e6756eb 100644 --- a/examples/tokens.js +++ b/examples/tokens.js @@ -9,5 +9,5 @@ const { parseArgs } = require('..'); // in repo console.log(parseArgs({ strict: false, tokens: true })); // Try the following: -// node tokens.js -d --foo=BAR -- file.txt +// node tokens.js -xy --foo=BAR -- file.txt // node tokens.js one -abc two From c8c2f840827749910800cd4e60c1bc40d32bce1a Mon Sep 17 00:00:00 2001 From: John Gee Date: Wed, 15 Jun 2022 20:10:17 +1200 Subject: [PATCH 51/64] Rework description of token.index after token example. --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0c4fce0..0fe0457 100644 --- a/README.md +++ b/README.md @@ -153,8 +153,10 @@ $ node tokens.js -xy --foo=BAR -- file.txt } ``` -Short option groups like `-abc` expand to a token for each option. The source argument -for a token is `args[token.index]`. +The source argument for a token is `args[token.index]`. +Short option groups like `-xy` expand to a token for each option. +Note that the `x` and `y` tokens above have the same index, since +they come from the same argument. `util.parseArgs()` is experimental and behavior may change. Join the conversation in [pkgjs/parseargs][] to contribute to the design. From 2b119b0dc604abb8d36e8407d9d4d4d29a2f3ad7 Mon Sep 17 00:00:00 2001 From: John Gee Date: Wed, 15 Jun 2022 20:42:16 +1200 Subject: [PATCH 52/64] Be less clever with script naming in example --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0fe0457..ce4768f 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ Detailed parse information is available for adding custom behaviours by specifyi * `value`: { string } Positional value (i.e. `args[index]`). * option-terminator token -For example, assuming the following script for `tokens.js`, which uses +For example, assuming the following script which uses automatic detection of options and no error checking: ```mjs @@ -118,7 +118,7 @@ console.log(parseArgs({ strict: false, tokens: true })); This call shows the three kinds of token and their properties: ```console -$ node tokens.js -xy --foo=BAR -- file.txt +$ node tokens.cjs -xy --foo=BAR -- file.txt { values: [Object: null prototype] { d: true, foo: 'BAR' }, positionals: [ 'file.txt' ], From 1b3c07656bd10cf9cac3f06cca298e6cf3e41f49 Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 17 Jun 2022 20:40:27 +1200 Subject: [PATCH 53/64] Remove superfluous colons in docs --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ce4768f..21a9ee1 100644 --- a/README.md +++ b/README.md @@ -91,15 +91,15 @@ console.log(values, positionals); Detailed parse information is available for adding custom behaviours by specifying `tokens: true` in the configuration. The returned tokens have properties describing: * all tokens - * `kind`: { string } One of 'option', 'positional', or 'option-terminator'. - * `index`: { number } Index of element in `args` containing token. + * `kind` { string } One of 'option', 'positional', or 'option-terminator'. + * `index` { number } Index of element in `args` containing token. * option tokens - * `name`: { string } Long name of option. - * `rawName`: { string } How option used in args, like `-f` of `--foo`. - * `value`: { string | undefined } Option value specified in args. Undefined for boolean options. - * `inlineValue`: { boolean | undefined } Whether option value specified inline, like `--foo=bar`. + * `name` { string } Long name of option. + * `rawName` { string } How option used in args, like `-f` of `--foo`. + * `value` { string | undefined } Option value specified in args. Undefined for boolean options. + * `inlineValue` { boolean | undefined } Whether option value specified inline, like `--foo=bar`. * positional tokens - * `value`: { string } Positional value (i.e. `args[index]`). + * `value` { string } Positional value (i.e. `args[index]`). * option-terminator token For example, assuming the following script which uses From e9e30a7bc6bd4d97719b66b3c5628800e4f58457 Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 17 Jun 2022 21:36:59 +1200 Subject: [PATCH 54/64] Upstream lint --- README.md | 12 ++++++++---- index.js | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 21a9ee1..13778ed 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,9 @@ console.log(values, positionals); // Prints: [Object: null prototype] { foo: true, bar: 'b' } [] ``` -Detailed parse information is available for adding custom behaviours by specifying `tokens: true` in the configuration. The returned tokens have properties describing: +Detailed parse information is available for adding custom behaviours by +specifying `tokens: true` in the configuration. The returned tokens have +properties describing: * all tokens * `kind` { string } One of 'option', 'positional', or 'option-terminator'. @@ -96,8 +98,10 @@ Detailed parse information is available for adding custom behaviours by specifyi * option tokens * `name` { string } Long name of option. * `rawName` { string } How option used in args, like `-f` of `--foo`. - * `value` { string | undefined } Option value specified in args. Undefined for boolean options. - * `inlineValue` { boolean | undefined } Whether option value specified inline, like `--foo=bar`. + * `value` { string | undefined } Option value specified in args. +Undefined for boolean options. + * `inlineValue` { boolean | undefined } Whether option value specified inline, +like `--foo=bar`. * positional tokens * `value` { string } Positional value (i.e. `args[index]`). * option-terminator token @@ -155,7 +159,7 @@ $ node tokens.cjs -xy --foo=BAR -- file.txt The source argument for a token is `args[token.index]`. Short option groups like `-xy` expand to a token for each option. -Note that the `x` and `y` tokens above have the same index, since +The `x` and `y` tokens above have the same index, since they come from the same argument. `util.parseArgs()` is experimental and behavior may change. Join the diff --git a/index.js b/index.js index eccea86..cdc85d0 100644 --- a/index.js +++ b/index.js @@ -144,7 +144,7 @@ function storeOption(longOption, optionValue, options, values) { * - positional * - option-terminator * - * @param {string[]} args, from parseArgs({ args }) or mainArgs + * @param {string[]} args - from parseArgs({ args }) or mainArgs * @param {object} options - option configs, from parseArgs({ options }) */ function argsToTokens(args, options) { From 46903af81566ef0c94472f4211b153b2edb14e5d Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 5 Jul 2022 08:51:58 +1200 Subject: [PATCH 55/64] Improve description --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 13778ed..c68ea70 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ Undefined for boolean options. * `inlineValue` { boolean | undefined } Whether option value specified inline, like `--foo=bar`. * positional tokens - * `value` { string } Positional value (i.e. `args[index]`). + * `value` { string } the value of the positional argument in args (i.e. `args[index]`). * option-terminator token For example, assuming the following script which uses From 89e4532598b5fde4774801f26069b0755b1b60be Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 5 Jul 2022 09:51:23 +1200 Subject: [PATCH 56/64] Use negate as example for tokens. Rework description a little. --- README.md | 122 ++++++++++++++++++++++++++++----------------- examples/negate.js | 6 +-- 2 files changed, 79 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index c68ea70..4f6dbc6 100644 --- a/README.md +++ b/README.md @@ -89,12 +89,13 @@ console.log(values, positionals); ``` Detailed parse information is available for adding custom behaviours by -specifying `tokens: true` in the configuration. The returned tokens have +specifying `tokens: true` in the configuration. +The returned tokens have properties describing: * all tokens * `kind` { string } One of 'option', 'positional', or 'option-terminator'. - * `index` { number } Index of element in `args` containing token. + * `index` { number } Index of element in `args` containing token. So the source argument for a token is `args[token.index]`. * option tokens * `name` { string } Long name of option. * `rawName` { string } How option used in args, like `-f` of `--foo`. @@ -103,65 +104,94 @@ Undefined for boolean options. * `inlineValue` { boolean | undefined } Whether option value specified inline, like `--foo=bar`. * positional tokens - * `value` { string } the value of the positional argument in args (i.e. `args[index]`). + * `value` { string } The value of the positional argument in args (i.e. `args[index]`). * option-terminator token -For example, assuming the following script which uses -automatic detection of options and no error checking: +The returned tokens are in the order encountered in the input args. Options that appear +more than once in args produce a token for each use. +Short option groups like `-xy` expand to a token for each option. So `-xxx` produces +three tokens. + +For example to use the returned tokens to add support for a negated option like `--no-color`, the tokens +can be reprocessed to change the value stored for the negated option. ```mjs import { parseArgs } from 'node:util'; -console.log(parseArgs({ strict: false, tokens: true })); + +const options = { + ['color']: { type: 'boolean' }, + ['no-color']: { type: 'boolean' }, + ['logfile']: { type: 'string' }, + ['no-logfile']: { type: 'boolean' }, +}; +const { values, tokens } = parseArgs({ options, tokens: true }); + +// Reprocess the option tokens and overwrite the returned values. +tokens + .filter((token) => token.kind === 'option') + .forEach((token) => { + if (token.name.startsWith('no-')) { + // Store foo:false for --no-foo + const positiveName = token.name.slice(3); + values[positiveName] = false; + delete values[token.name]; + } else { + // Resave value so last one wins if both --foo and --no-foo. + values[token.name] = token.value ?? true; + } + }); + +const color = values.color; +const logfile = values.logfile ?? 'default.log'; + +console.log({ logfile, color }); ``` ```cjs const { parseArgs } = require('node:util'); -console.log(parseArgs({ strict: false, tokens: true })); + +const options = { + ['color']: { type: 'boolean' }, + ['no-color']: { type: 'boolean' }, + ['logfile']: { type: 'string' }, + ['no-logfile']: { type: 'boolean' }, +}; +const { values, tokens } = parseArgs({ options, tokens: true }); + +// Reprocess the option tokens and overwrite the returned values. +tokens + .filter((token) => token.kind === 'option') + .forEach((token) => { + if (token.name.startsWith('no-')) { + // Store foo:false for --no-foo + const positiveName = token.name.slice(3); + values[positiveName] = false; + delete values[token.name]; + } else { + // Resave value so last one wins if both --foo and --no-foo. + values[token.name] = token.value ?? true; + } + }); + +const color = values.color; +const logfile = values.logfile ?? 'default.log'; + +console.log({ logfile, color }); ``` -This call shows the three kinds of token and their properties: +Example usage showing negated options, and when option use multiple times then last one wins. ```console -$ node tokens.cjs -xy --foo=BAR -- file.txt -{ - values: [Object: null prototype] { d: true, foo: 'BAR' }, - positionals: [ 'file.txt' ], - tokens: [ - { - kind: 'option', - name: 'x', - rawName: '-x', - index: 0, - value: undefined, - inlineValue: undefined - }, - { - kind: 'option', - name: 'y', - rawName: '-y', - index: 0, - value: undefined, - inlineValue: undefined - }, - { - kind: 'option', - name: 'foo', - rawName: '--foo', - index: 1, - value: 'BAR', - inlineValue: true - }, - { kind: 'option-terminator', index: 2 }, - { kind: 'positional', index: 3, value: 'file.txt' } - ] -} +$ node negate.js +{ logfile: 'default.log', color: undefined } +$ node negate.js --no-logfile --no-color +{ logfile: false, color: false } +$ node negate.js --logfile=test.log --color +{ logfile: 'test.log', color: true } +$ node negate.js --no-logfile --logfile=test.log --color --no-color +{ logfile: 'test.log', color: false } ``` -The source argument for a token is `args[token.index]`. -Short option groups like `-xy` expand to a token for each option. -The `x` and `y` tokens above have the same index, since -they come from the same argument. - `util.parseArgs()` is experimental and behavior may change. Join the conversation in [pkgjs/parseargs][] to contribute to the design. diff --git a/examples/negate.js b/examples/negate.js index 4619653..c0556d9 100644 --- a/examples/negate.js +++ b/examples/negate.js @@ -7,7 +7,7 @@ const { parseArgs } = require('..'); // in repo const options = { - ['color']: { type: 'string' }, + ['color']: { type: 'boolean' }, ['no-color']: { type: 'boolean' }, ['logfile']: { type: 'string' }, ['no-logfile']: { type: 'boolean' }, @@ -36,6 +36,6 @@ console.log({ logfile, color }); // Try the following: // node negate.js -// node negate.js --logfile=test.log --color=red +// node negate.js --logfile=test.log --color // node negate.js --no-logfile --no-color -// node negate.js --no-logfile --logfile=test.log --color=red --no-color +// node negate.js --no-logfile --logfile=test.log --color --no-color From 3914949efcc0b04e03243bc059250780e4cce5ca Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 5 Jul 2022 10:08:52 +1200 Subject: [PATCH 57/64] Lint and feedback --- README.md | 33 ++++++++++++++++++--------------- examples/negate.js | 8 ++++---- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 4f6dbc6..76ce4d8 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,8 @@ properties describing: * all tokens * `kind` { string } One of 'option', 'positional', or 'option-terminator'. - * `index` { number } Index of element in `args` containing token. So the source argument for a token is `args[token.index]`. + * `index` { number } Index of element in `args` containing token. So the +source argument for a token is `args[token.index]`. * option tokens * `name` { string } Long name of option. * `rawName` { string } How option used in args, like `-f` of `--foo`. @@ -107,22 +108,23 @@ like `--foo=bar`. * `value` { string } The value of the positional argument in args (i.e. `args[index]`). * option-terminator token -The returned tokens are in the order encountered in the input args. Options that appear -more than once in args produce a token for each use. -Short option groups like `-xy` expand to a token for each option. So `-xxx` produces +The returned tokens are in the order encountered in the input args. Options +that appear more than once in args produce a token for each use. Short option +groups like `-xy` expand to a token for each option. So `-xxx` produces three tokens. -For example to use the returned tokens to add support for a negated option like `--no-color`, the tokens -can be reprocessed to change the value stored for the negated option. +For example to use the returned tokens to add support for a negated option +like `--no-color`, the tokens can be reprocessed to change the value stored +for the negated option. ```mjs import { parseArgs } from 'node:util'; const options = { - ['color']: { type: 'boolean' }, - ['no-color']: { type: 'boolean' }, - ['logfile']: { type: 'string' }, - ['no-logfile']: { type: 'boolean' }, + 'color': { type: 'boolean' }, + 'no-color': { type: 'boolean' }, + 'logfile': { type: 'string' }, + 'no-logfile': { type: 'boolean' }, }; const { values, tokens } = parseArgs({ options, tokens: true }); @@ -151,10 +153,10 @@ console.log({ logfile, color }); const { parseArgs } = require('node:util'); const options = { - ['color']: { type: 'boolean' }, - ['no-color']: { type: 'boolean' }, - ['logfile']: { type: 'string' }, - ['no-logfile']: { type: 'boolean' }, + 'color': { type: 'boolean' }, + 'no-color': { type: 'boolean' }, + 'logfile': { type: 'string' }, + 'no-logfile': { type: 'boolean' }, }; const { values, tokens } = parseArgs({ options, tokens: true }); @@ -179,7 +181,8 @@ const logfile = values.logfile ?? 'default.log'; console.log({ logfile, color }); ``` -Example usage showing negated options, and when option use multiple times then last one wins. +Example usage showing negated options, and when an option is used +multiple ways then last one wins. ```console $ node negate.js diff --git a/examples/negate.js b/examples/negate.js index c0556d9..878b9dc 100644 --- a/examples/negate.js +++ b/examples/negate.js @@ -7,10 +7,10 @@ const { parseArgs } = require('..'); // in repo const options = { - ['color']: { type: 'boolean' }, - ['no-color']: { type: 'boolean' }, - ['logfile']: { type: 'string' }, - ['no-logfile']: { type: 'boolean' }, + 'color': { type: 'boolean' }, + 'no-color': { type: 'boolean' }, + 'logfile': { type: 'string' }, + 'no-logfile': { type: 'boolean' }, }; const { values, tokens } = parseArgs({ options, tokens: true }); From d8126d140c9d7ed3065be1ea230d4462839e559f Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 5 Jul 2022 19:01:05 +1200 Subject: [PATCH 58/64] Expand small token tests and remove uber tests --- test/index.js | 78 +++++++++++++++++---------------------------------- 1 file changed, 25 insertions(+), 53 deletions(-) diff --git a/test/index.js b/test/index.js index 3c3b752..a24bf53 100644 --- a/test/index.js +++ b/test/index.js @@ -648,6 +648,20 @@ test('tokens: strict:false boolean option group', () => { assert.deepStrictEqual(tokens, expectedTokens); }); +test('tokens: strict:false boolean option group with repeated option', () => { + // Also positional to check index correct after grouop + const args = ['-aa', 'pos']; + const expectedTokens = [ + { kind: 'option', name: 'a', rawName: '-a', + index: 0, value: undefined, inlineValue: undefined }, + { kind: 'option', name: 'a', rawName: '-a', + index: 0, value: undefined, inlineValue: undefined }, + { kind: 'positional', index: 1, value: 'pos' }, + ]; + const { tokens } = parseArgs({ strict: false, allowPositionals: true, args, tokens: true }); + assert.deepStrictEqual(tokens, expectedTokens); +}); + test('tokens: strict:true string short with value after space', () => { // Also positional to check index correct after out-of-line. const args = ['-f', 'bar', 'ppp']; @@ -705,15 +719,17 @@ test('tokens: strict:true string long with value after space', () => { }); test('tokens: strict:true string long with value inline', () => { - const args = ['--file=bar']; + // Also positional to check index correct after out-of-line. + const args = ['--file=bar', 'pos']; const options = { file: { short: 'f', type: 'string' } }; const expectedTokens = [ { kind: 'option', name: 'file', rawName: '--file', index: 0, value: 'bar', inlineValue: true }, + { kind: 'positional', index: 1, value: 'pos' }, ]; - const { tokens } = parseArgs({ strict: true, args, options, tokens: true }); + const { tokens } = parseArgs({ strict: true, allowPositionals: true, args, options, tokens: true }); assert.deepStrictEqual(tokens, expectedTokens); }); @@ -741,7 +757,8 @@ test('tokens: strict:false string long missing value', () => { }); test('tokens: strict:true complex option group with value after space', () => { - const args = ['-ab', 'c']; + // Also positional to check index correct afterwards. + const args = ['-ab', 'c', 'pos']; const options = { alpha: { short: 'a', type: 'boolean' }, beta: { short: 'b', type: 'string' }, @@ -751,13 +768,15 @@ test('tokens: strict:true complex option group with value after space', () => { index: 0, value: undefined, inlineValue: undefined }, { kind: 'option', name: 'beta', rawName: '-b', index: 0, value: 'c', inlineValue: false }, + { kind: 'positional', index: 2, value: 'pos' }, ]; - const { tokens } = parseArgs({ strict: true, args, options, tokens: true }); + const { tokens } = parseArgs({ strict: true, allowPositionals: true, args, options, tokens: true }); assert.deepStrictEqual(tokens, expectedTokens); }); test('tokens: strict:true complex option group with inline value', () => { - const args = ['-abc']; + // Also positional to check index correct afterwards. + const args = ['-abc', 'pos']; const options = { alpha: { short: 'a', type: 'boolean' }, beta: { short: 'b', type: 'string' }, @@ -767,54 +786,7 @@ test('tokens: strict:true complex option group with inline value', () => { index: 0, value: undefined, inlineValue: undefined }, { kind: 'option', name: 'beta', rawName: '-b', index: 0, value: 'c', inlineValue: true }, - ]; - const { tokens } = parseArgs({ strict: true, args, options, tokens: true }); - assert.deepStrictEqual(tokens, expectedTokens); -}); - -test('tokens: strict:false variety', () => { - const args = ['-a', '1', '-bc', '2', '--ddd', '--eee=fff', '--', '3']; - const expectedTokens = [ - { kind: 'option', name: 'a', rawName: '-a', index: 0, value: undefined, inlineValue: undefined }, - { kind: 'positional', index: 1, value: '1' }, - { kind: 'option', name: 'b', rawName: '-b', index: 2, value: undefined, inlineValue: undefined }, - { kind: 'option', name: 'c', rawName: '-c', index: 2, value: undefined, inlineValue: undefined }, - { kind: 'positional', index: 3, value: '2' }, - { kind: 'option', name: 'ddd', rawName: '--ddd', index: 4, value: undefined, inlineValue: undefined }, - { kind: 'option', name: 'eee', rawName: '--eee', index: 5, value: 'fff', inlineValue: true }, - { kind: 'option-terminator', index: 6 }, - { kind: 'positional', index: 7, value: '3' }, - ]; - const { tokens } = parseArgs({ strict: false, args, tokens: true }); - assert.deepStrictEqual(tokens, expectedTokens); -}); - -test('tokens: strict:true variety', () => { - const args = ['-a', '1', '-bc', '-dDDD', '-e', 'EEE', '2', - '--fff=FFF', '--ggg', 'GGG', '--hhh', '--', '3']; - const options = { - alpha: { short: 'a', type: 'boolean' }, - beta: { short: 'b', type: 'boolean' }, - cat: { short: 'c', type: 'boolean' }, - delta: { short: 'd', type: 'string' }, - epsilon: { short: 'e', type: 'string' }, - fff: { type: 'string' }, - ggg: { type: 'string' }, - hhh: { type: 'boolean' }, - }; - const expectedTokens = [ - { kind: 'option', name: 'alpha', rawName: '-a', index: 0, value: undefined, inlineValue: undefined }, - { kind: 'positional', index: 1, value: '1' }, - { kind: 'option', name: 'beta', rawName: '-b', index: 2, value: undefined, inlineValue: undefined }, - { kind: 'option', name: 'cat', rawName: '-c', index: 2, value: undefined, inlineValue: undefined }, - { kind: 'option', name: 'delta', rawName: '-d', index: 3, value: 'DDD', inlineValue: true }, - { kind: 'option', name: 'epsilon', rawName: '-e', index: 4, value: 'EEE', inlineValue: false }, - { kind: 'positional', index: 6, value: '2' }, - { kind: 'option', name: 'fff', rawName: '--fff', index: 7, value: 'FFF', inlineValue: true }, - { kind: 'option', name: 'ggg', rawName: '--ggg', index: 8, value: 'GGG', inlineValue: false }, - { kind: 'option', name: 'hhh', rawName: '--hhh', index: 10, value: undefined, inlineValue: undefined }, - { kind: 'option-terminator', index: 11 }, - { kind: 'positional', index: 12, value: '3' }, + { kind: 'positional', index: 1, value: 'pos' }, ]; const { tokens } = parseArgs({ strict: true, allowPositionals: true, args, options, tokens: true }); assert.deepStrictEqual(tokens, expectedTokens); From 87bab7b934f48b63dc5aed4bff9b25435787ed7b Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 5 Jul 2022 21:28:05 +1200 Subject: [PATCH 59/64] Use kEmptyObject per upstream suggestion. Move node lookalikes to internals to match node layout. --- index.js | 12 ++++++++---- errors.js => internal/errors.js | 0 primordials.js => internal/primordials.js | 0 internal/util.js | 14 ++++++++++++++ validators.js => internal/validators.js | 0 utils.js | 4 ++-- 6 files changed, 24 insertions(+), 6 deletions(-) rename errors.js => internal/errors.js (100%) rename primordials.js => internal/primordials.js (100%) create mode 100644 internal/util.js rename validators.js => internal/validators.js (100%) diff --git a/index.js b/index.js index cdc85d0..fb0a10e 100644 --- a/index.js +++ b/index.js @@ -15,7 +15,7 @@ const { StringPrototypeIndexOf, StringPrototypeSlice, StringPrototypeStartsWith, -} = require('./primordials'); +} = require('./internal/primordials'); const { validateArray, @@ -23,7 +23,11 @@ const { validateObject, validateString, validateUnion, -} = require('./validators'); +} = require('./internal/validators'); + +const { + kEmptyObject, +} = require('./internal/util'); const { findLongOptionForShort, @@ -45,7 +49,7 @@ const { ERR_PARSE_ARGS_UNKNOWN_OPTION, ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL, }, -} = require('./errors'); +} = require('./internal/errors'); function getMainArgs() { // Work out where to slice process.argv for user supplied arguments. @@ -264,7 +268,7 @@ function argsToTokens(args, options) { return tokens; } -const parseArgs = (config = { __proto__: null }) => { +const parseArgs = (config = kEmptyObject) => { const args = objectGetOwn(config, 'args') ?? getMainArgs(); const strict = objectGetOwn(config, 'strict') ?? true; const allowPositionals = objectGetOwn(config, 'allowPositionals') ?? !strict; diff --git a/errors.js b/internal/errors.js similarity index 100% rename from errors.js rename to internal/errors.js diff --git a/primordials.js b/internal/primordials.js similarity index 100% rename from primordials.js rename to internal/primordials.js diff --git a/internal/util.js b/internal/util.js new file mode 100644 index 0000000..b9b8fe5 --- /dev/null +++ b/internal/util.js @@ -0,0 +1,14 @@ +'use strict'; + +// This is a placeholder for util.js in node.js land. + +const { + ObjectCreate, + ObjectFreeze, +} = require('./primordials'); + +const kEmptyObject = ObjectFreeze(ObjectCreate(null)); + +module.exports = { + kEmptyObject, +}; diff --git a/validators.js b/internal/validators.js similarity index 100% rename from validators.js rename to internal/validators.js diff --git a/utils.js b/utils.js index b7ab034..a89fb6f 100644 --- a/utils.js +++ b/utils.js @@ -7,11 +7,11 @@ const { StringPrototypeCharAt, StringPrototypeIncludes, StringPrototypeStartsWith, -} = require('./primordials'); +} = require('./internal/primordials'); const { validateObject, -} = require('./validators'); +} = require('./internal/validators'); // These are internal utilities to make the parsing logic easier to read, and // add lots of detail for the curious. They are in a separate file to allow From 02ea5858932ddd058ffb77e303770494fbaa734b Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 5 Jul 2022 21:48:14 +1200 Subject: [PATCH 60/64] Reworks tokens documentation for config --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 76ce4d8..87c4f16 100644 --- a/README.md +++ b/README.md @@ -32,17 +32,17 @@ added: REPLACEME * `allowPositionals` {boolean} Whether this command accepts positional arguments. **Default:** `false` if `strict` is `true`, otherwise `true`. - * `tokens` {boolean} Return an array with the + * `tokens` {boolean} Return the parsed tokens. This is useful for extending the built-in behaviour, from adding additional checks through to reprocessing the tokens in different ways. - **Default:** `false`. * Returns: {Object} The parsed command line arguments: * `values` {Object} A mapping of parsed option names with their {string} or {boolean} values. * `positionals` {string\[]} Positional arguments. - * `tokens` {Object} Parsed tokens. Only present if requested. + * `tokens` {Object} [tokens] Detailed parse information (see below for details). + Only returned if `config` includes `tokens: true`. Provides a higher level API for command-line argument parsing than interacting with `process.argv` directly. Takes a specification for the expected arguments From 56fe4caf24840e3d41199b6704b3a811ea0af79e Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 5 Jul 2022 22:05:42 +1200 Subject: [PATCH 61/64] Add version changes to YAML. --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 87c4f16..b78a24e 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,11 @@ Polyfill of proposal for `util.parseArgs()` > Stability: 1 - Experimental @@ -41,8 +46,8 @@ added: REPLACEME * `values` {Object} A mapping of parsed option names with their {string} or {boolean} values. * `positionals` {string\[]} Positional arguments. - * `tokens` {Object} [tokens] Detailed parse information (see below for details). - Only returned if `config` includes `tokens: true`. + * `tokens` {Object} Array of parsed tokens with detailed information + (see below for more). Only returned if `config` includes `tokens: true`. Provides a higher level API for command-line argument parsing than interacting with `process.argv` directly. Takes a specification for the expected arguments From 722f05e647fae4fbebb53d48162ecc0448248ffe Mon Sep 17 00:00:00 2001 From: John Gee Date: Wed, 6 Jul 2022 09:39:43 +1200 Subject: [PATCH 62/64] Update README with upstream changes --- README.md | 47 ++++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index b78a24e..20ba8c7 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,12 @@ [![Coverage][coverage-image]][coverage-url] -Polyfill of proposal for `util.parseArgs()` +Polyfill of `util.parseArgs()` ## `util.parseArgs([config])` From ae9eca4987d23b475704697700a6e619fa315ebc Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 16 Jul 2022 16:21:24 +1200 Subject: [PATCH 63/64] Update negate example calls to match documentation --- examples/negate.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/negate.js b/examples/negate.js index 878b9dc..2a762c9 100644 --- a/examples/negate.js +++ b/examples/negate.js @@ -35,7 +35,7 @@ const logfile = values.logfile ?? 'default.log'; console.log({ logfile, color }); // Try the following: -// node negate.js -// node negate.js --logfile=test.log --color -// node negate.js --no-logfile --no-color -// node negate.js --no-logfile --logfile=test.log --color --no-color +// node negate.js +// node negate.js --no-logfile --no-color +// negate.js --logfile=test.log --color +// node negate.js --no-logfile --logfile=test.log --color --no-color From f9dcc4fee0e8a4749014ba70c446a877c811a89e Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 16 Jul 2022 16:27:09 +1200 Subject: [PATCH 64/64] Remove extra examples --- examples/limit-long-syntax.js | 30 ---------------------------- examples/negate.js | 2 ++ examples/no-repeated-options.js | 29 --------------------------- examples/ordered-options.mjs | 35 --------------------------------- examples/tokens.js | 13 ------------ 5 files changed, 2 insertions(+), 107 deletions(-) delete mode 100644 examples/limit-long-syntax.js delete mode 100644 examples/no-repeated-options.js delete mode 100644 examples/ordered-options.mjs delete mode 100644 examples/tokens.js diff --git a/examples/limit-long-syntax.js b/examples/limit-long-syntax.js deleted file mode 100644 index b06f70d..0000000 --- a/examples/limit-long-syntax.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - -// How might I require long options with values use '='? -// So allow `--foo=bar`, and not allow `--foo bar`. - -// 1. const { parseArgs } = require('node:util'); // from node -// 2. const { parseArgs } = require('@pkgjs/parseargs'); // from package -const { parseArgs } = require('..'); // in repo - -const options = { - file: { short: 'f', type: 'string' }, - log: { type: 'string' }, -}; - -const { values, tokens } = parseArgs({ options, tokens: true }); - -const badToken = tokens.find((token) => token.kind === 'option' && - token.value != null && - token.rawName.startsWith('--') && - !token.inlineValue -); -if (badToken) { - throw new Error(`Option value for '${badToken.rawName}' must be inline, like '${badToken.rawName}=VALUE'`); -} - -console.log(values); - -// Try the following: -// node limit-long-syntax.js -f FILE --log=LOG -// node limit-long-syntax.js --file FILE diff --git a/examples/negate.js b/examples/negate.js index 2a762c9..b663469 100644 --- a/examples/negate.js +++ b/examples/negate.js @@ -1,5 +1,7 @@ 'use strict'; +// This example is used in the documentation. + // How might I add my own support for --no-foo? // 1. const { parseArgs } = require('node:util'); // from node diff --git a/examples/no-repeated-options.js b/examples/no-repeated-options.js deleted file mode 100644 index 51dcdd1..0000000 --- a/examples/no-repeated-options.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict'; - -// How might I throw if an option is repeated? - -// 1. const { parseArgs } = require('node:util'); // from node -// 2. const { parseArgs } = require('@pkgjs/parseargs'); // from package -const { parseArgs } = require('..'); // in repo - -const options = { - ding: { type: 'boolean', short: 'd' }, - beep: { type: 'boolean', short: 'b' } -}; -const { values, tokens } = parseArgs({ options, tokens: true }); - -const seenBefore = new Set(); -tokens.forEach((token) => { - if (token.kind !== 'option') return; - if (seenBefore.has(token.name)) { - throw new Error(`option '${token.name}' used multiple times`); - } - seenBefore.add(token.name); -}); - -console.log(values); - -// Try the following: -// node no-repeated-options --ding --beep -// node no-repeated-options --beep -b -// node no-repeated-options -ddd diff --git a/examples/ordered-options.mjs b/examples/ordered-options.mjs deleted file mode 100644 index 093e56c..0000000 --- a/examples/ordered-options.mjs +++ /dev/null @@ -1,35 +0,0 @@ -// Now might I enforce that two flags are specified in a specific order? - -import { parseArgs } from '../index.js'; - -function findTokenIndex(tokens, target) { - return tokens.findIndex((token) => token.kind === 'option' && - token.name === target - ); -} - -const experimentalName = 'enable-experimental-options'; -const unstableName = 'some-unstable-option'; - -const options = { - [experimentalName]: { type: 'boolean' }, - [unstableName]: { type: 'boolean' }, -}; - -const { values, tokens } = parseArgs({ options, tokens: true }); - -const experimentalIndex = findTokenIndex(tokens, experimentalName); -const unstableIndex = findTokenIndex(tokens, unstableName); -if (unstableIndex !== -1 && - ((experimentalIndex === -1) || (unstableIndex < experimentalIndex))) { - throw new Error(`'--${experimentalName}' must be specified before '--${unstableName}'`); -} - -console.log(values); - -/* eslint-disable max-len */ -// Try the following: -// node ordered-options.mjs -// node ordered-options.mjs --some-unstable-option -// node ordered-options.mjs --some-unstable-option --enable-experimental-options -// node ordered-options.mjs --enable-experimental-options --some-unstable-option diff --git a/examples/tokens.js b/examples/tokens.js deleted file mode 100644 index e6756eb..0000000 --- a/examples/tokens.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -// This example is used in the documentation. - -// 1. const { parseArgs } = require('node:util'); // from node -// 2. const { parseArgs } = require('@pkgjs/parseargs'); // from package -const { parseArgs } = require('..'); // in repo - -console.log(parseArgs({ strict: false, tokens: true })); - -// Try the following: -// node tokens.js -xy --foo=BAR -- file.txt -// node tokens.js one -abc two