Skip to content

Commit

Permalink
feat(parser): support short-option groups (#59)
Browse files Browse the repository at this point in the history
  • Loading branch information
bcoe committed Feb 6, 2022
1 parent 5cdf9e8 commit 882067b
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 18 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,9 @@ 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`?
- no, `-foo` is a short option or options (WIP: https://github.com/pkgjs/parseargs/issues/2)
- Is `-bar` the same as `--bar`?
- 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
- the first flag would be parsed as `'-foo'`
Expand Down
8 changes: 0 additions & 8 deletions errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
};
16 changes: 8 additions & 8 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const {
ArrayPrototypeConcat,
ArrayPrototypeIncludes,
ArrayPrototypeSlice,
ArrayPrototypeSplice,
ArrayPrototypePush,
ObjectHasOwn,
StringPrototypeCharAt,
Expand All @@ -13,12 +14,6 @@ const {
StringPrototypeStartsWith,
} = require('./primordials');

const {
codes: {
ERR_NOT_IMPLEMENTED
}
} = require('./errors');

const {
validateArray,
validateObject
Expand Down Expand Up @@ -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
Expand Down
52 changes: 52 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'] };
Expand Down

0 comments on commit 882067b

Please sign in to comment.