From b4d0e1caf13b107a29961c556028157f0764b56c Mon Sep 17 00:00:00 2001 From: bcoe Date: Sat, 5 Feb 2022 18:44:03 -0500 Subject: [PATCH 1/4] feat(parser): support short-option groups --- README.md | 3 ++- errors.js | 8 -------- index.js | 16 ++++++++-------- test/index.js | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index f050690..b037b15 100644 --- a/README.md +++ b/README.md @@ -195,7 +195,8 @@ const { flags, values, positionals } = parseArgs(argv, options); - Does the API specify whether a `--` was present/relevant? - no - Is `-foo` the same as `--foo`? - - no, `-foo` is a short option or options (WIP: https://github.com/pkgjs/parseargs/issues/2) + - no, `-foo` is a short option or options, with expansion logic that follows the + [Utility Syntax Guidelines in POSIX.1-2017](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html). `--foo bar`, `withValue: ['foo']`, expands to `-f`, `-o`, `-o bar`. - Is `---foo` the same as `--foo`? - no - the first flag would be parsed as `'-foo'` diff --git a/errors.js b/errors.js index e16a094..2220b66 100644 --- a/errors.js +++ b/errors.js @@ -7,16 +7,8 @@ class ERR_INVALID_ARG_TYPE extends TypeError { } } -class ERR_NOT_IMPLEMENTED extends Error { - constructor(feature) { - super(`${feature} not implemented`); - this.code = 'ERR_NOT_IMPLEMENTED'; - } -} - module.exports = { codes: { ERR_INVALID_ARG_TYPE, - ERR_NOT_IMPLEMENTED } }; diff --git a/index.js b/index.js index 84882e7..6948332 100644 --- a/index.js +++ b/index.js @@ -4,6 +4,7 @@ const { ArrayPrototypeConcat, ArrayPrototypeIncludes, ArrayPrototypeSlice, + ArrayPrototypeSplice, ArrayPrototypePush, ObjectHasOwn, StringPrototypeCharAt, @@ -13,12 +14,6 @@ const { StringPrototypeStartsWith, } = require('./primordials'); -const { - codes: { - ERR_NOT_IMPLEMENTED - } -} = require('./errors'); - const { validateArray, validateObject @@ -119,9 +114,14 @@ const parseArgs = ( ); return result; } else if (StringPrototypeCharAt(arg, 1) !== '-') { - // Look for shortcodes: -fXzy + // Look for shortcodes: -fXzy and expand them to -f -X -z -y: if (arg.length > 2) { - throw new ERR_NOT_IMPLEMENTED('short option groups'); + for (let i = 2; i < arg.length; i++) { + const short = StringPrototypeCharAt(arg, i); + // Add 'i' to 'pos' such that short options are parsed in order + // of definition: + ArrayPrototypeSplice(argv, pos + (i - 1), 0, `-${short}`); + } } arg = StringPrototypeCharAt(arg, 1); // short diff --git a/test/index.js b/test/index.js index 50957f2..9ce91c7 100644 --- a/test/index.js +++ b/test/index.js @@ -70,6 +70,58 @@ test('when short option withValue used without value then stored as flag', funct t.end(); }); +test('short option group behaves like multiple short options', function(t) { + const passedArgs = ['-rf']; + const passedOptions = { }; + const expected = { flags: { r: true, f: true }, values: { r: undefined, f: undefined }, positionals: [] }; + const args = parseArgs(passedArgs, passedOptions); + + t.deepEqual(args, expected); + + t.end(); +}); + +test('short option group does not consume subsequent positional', function(t) { + const passedArgs = ['-rf', 'foo']; + const passedOptions = { }; + const expected = { flags: { r: true, f: true }, values: { r: undefined, f: undefined }, positionals: ['foo'] }; + const args = parseArgs(passedArgs, passedOptions); + t.deepEqual(args, expected); + + t.end(); +}); + +// See: Guideline 5 https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html +test('if terminal of short-option group configured withValue, subsequent positional is stored', function(t) { + const passedArgs = ['-rvf', 'foo']; + const passedOptions = { withValue: ['f'] }; + const expected = { flags: { r: true, f: true, v: true }, values: { r: undefined, v: undefined, f: 'foo' }, positionals: [] }; + const args = parseArgs(passedArgs, passedOptions); + t.deepEqual(args, expected); + + t.end(); +}); + +test('handles short-option groups in conjunction with long-options', function(t) { + const passedArgs = ['-rf', '--foo', 'foo']; + const passedOptions = { withValue: ['foo'] }; + const expected = { flags: { r: true, f: true, foo: true }, values: { r: undefined, f: undefined, foo: 'foo' }, positionals: [] }; + const args = parseArgs(passedArgs, passedOptions); + t.deepEqual(args, expected); + + t.end(); +}); + +test('handles short-option groups with "short" alias configured', function(t) { + const passedArgs = ['-rf']; + const passedOptions = { short: { r: 'remove' } }; + const expected = { flags: { remove: true, f: true }, values: { remove: undefined, f: undefined }, positionals: [] }; + const args = parseArgs(passedArgs, passedOptions); + t.deepEqual(args, expected); + + t.end(); +}); + test('Everything after a bare `--` is considered a positional argument', function(t) { const passedArgs = ['--', 'barepositionals', 'mopositionals']; const expected = { flags: {}, values: {}, positionals: ['barepositionals', 'mopositionals'] }; From 1ded7c394e966cb448cb9e876b4a64f993d2f3ba Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Sat, 5 Feb 2022 18:58:16 -0500 Subject: [PATCH 2/4] Update README.md Co-authored-by: John Gee --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b037b15..f3138b8 100644 --- a/README.md +++ b/README.md @@ -196,7 +196,7 @@ const { flags, values, positionals } = parseArgs(argv, options); - no - Is `-foo` the same as `--foo`? - no, `-foo` is a short option or options, with expansion logic that follows the - [Utility Syntax Guidelines in POSIX.1-2017](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html). `--foo bar`, `withValue: ['foo']`, expands to `-f`, `-o`, `-o bar`. + [Utility Syntax Guidelines in POSIX.1-2017](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html). `-bar` expands to `-b`, `-a`, `-r`. - Is `---foo` the same as `--foo`? - no - the first flag would be parsed as `'-foo'` From 6606ef3b55b956b9b14ce79f710f218d87c52f20 Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Sat, 5 Feb 2022 18:58:21 -0500 Subject: [PATCH 3/4] Update README.md Co-authored-by: John Gee --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f3138b8..093b2a9 100644 --- a/README.md +++ b/README.md @@ -194,7 +194,7 @@ const { flags, values, positionals } = parseArgs(argv, options); - If `--` signals the end, is `--` included as a positional? is `program -- foo` the same as `program foo`? Are both `{positionals:['foo']}`, or is the first one `{positionals:['--', 'foo']}`? - Does the API specify whether a `--` was present/relevant? - no -- Is `-foo` the same as `--foo`? +- Is `-bar` the same as `--bar`? - no, `-foo` is a short option or options, with expansion logic that follows the [Utility Syntax Guidelines in POSIX.1-2017](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html). `-bar` expands to `-b`, `-a`, `-r`. - Is `---foo` the same as `--foo`? From 833201bbcc54c288e6c7133bdda157f6ebd055a8 Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Sat, 5 Feb 2022 18:58:34 -0500 Subject: [PATCH 4/4] Update README.md Co-authored-by: John Gee --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 093b2a9..d62a01a 100644 --- a/README.md +++ b/README.md @@ -195,7 +195,7 @@ const { flags, values, positionals } = parseArgs(argv, options); - Does the API specify whether a `--` was present/relevant? - no - Is `-bar` the same as `--bar`? - - no, `-foo` is a short option or options, with expansion logic that follows the + - no, `-bar` is a short option or options, with expansion logic that follows the [Utility Syntax Guidelines in POSIX.1-2017](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html). `-bar` expands to `-b`, `-a`, `-r`. - Is `---foo` the same as `--foo`? - no