diff --git a/index.test.ts b/index.test.ts index 244085f..52c2f7c 100644 --- a/index.test.ts +++ b/index.test.ts @@ -6,11 +6,18 @@ describe('parseArgs', () => { }); test('parses an arg', () => { - expect(parseArgs(['--foo'])).toEqual({ '': [], foo: [] }); + expect(parseArgs(['--foo'])).toEqual({ '': [], foo: null }); + expect(parseArgs(['--foo', '--foo'])).toEqual({ '': [], foo: [null, null] }); + expect(parseArgs(['--foo', '--bar'])).toEqual({ '': [], foo: null, bar: null }); + }); + + test('parses invalid arg as a value', () => { + expect(parseArgs(['---foo'])).toEqual({ '': ['---foo'] }); }); test('parses a flag', () => { expect(parseArgs(['--foo'], { flags: ['foo'] })).toEqual({ '': [], foo: true }); + expect(parseArgs(['--foo', '--foo'], { flags: ['foo'] })).toEqual({ '': [], foo: true }); }); test('parses a flag shorthand', () => { @@ -20,16 +27,20 @@ describe('parseArgs', () => { }); test('parses a mixed shorthands', () => { - expect(parseArgs(['-ab', 'aaa'], { flags: ['a'], keepShorthands: true })).toEqual({ '': [], a: true, b: ['aaa'] }); - expect(parseArgs(['-ba', 'aaa'], { flags: ['a'], keepShorthands: true })).toEqual({ '': ['aaa'], a: true, b: [] }); + expect(parseArgs(['-ab', 'aaa'], { flags: ['a'], keepShorthands: true })).toEqual({ '': [], a: true, b: 'aaa' }); + expect(parseArgs(['-ba', 'aaa'], { flags: ['a'], keepShorthands: true })).toEqual({ + '': ['aaa'], + a: true, + b: null, + }); }); test('parses minus as a value', () => { - expect(parseArgs(['--foo', '-'])).toEqual({ '': [], foo: ['-'] }); + expect(parseArgs(['--foo', '-'])).toEqual({ '': [], foo: '-' }); }); test('parses a shorthand', () => { - expect(parseArgs(['-f'], { shorthands: { f: 'foo' } })).toEqual({ '': [], foo: [] }); + expect(parseArgs(['-f'], { shorthands: { f: 'foo' } })).toEqual({ '': [], foo: null }); }); test('does not parse a shorthand', () => { @@ -37,31 +48,42 @@ describe('parseArgs', () => { }); test('keeps a shorthand', () => { - expect(parseArgs(['-f'], { keepShorthands: true })).toEqual({ '': [], f: [] }); + expect(parseArgs(['-f'], { keepShorthands: true })).toEqual({ '': [], f: null }); }); test('parses a multiple merged shorthands', () => { - expect(parseArgs(['-abc'], { shorthands: { a: 'aaa', b: 'bbb' } })).toEqual({ '': [], aaa: [], bbb: [] }); + expect(parseArgs(['-abc'], { shorthands: { a: 'aaa', b: 'bbb' } })).toEqual({ '': [], aaa: null, bbb: null }); }); test('parses a multiple separate shorthands', () => { - expect(parseArgs(['-a -b'], { shorthands: { a: 'aaa', b: 'bbb' } })).toEqual({ '': [], aaa: [], bbb: [] }); + expect(parseArgs(['-a -b'], { shorthands: { a: 'aaa', b: 'bbb' } })).toEqual({ '': [], aaa: null, bbb: null }); }); test('parses an arg value', () => { - expect(parseArgs(['--foo', 'bar'])).toEqual({ '': [], foo: ['bar'] }); + expect(parseArgs(['--foo', ''])).toEqual({ '': [], foo: '' }); + expect(parseArgs(['--foo', 'bar'])).toEqual({ '': [], foo: 'bar' }); }); test('parses a repeated args', () => { expect(parseArgs(['--foo', 'bar', '--foo', 'baz'])).toEqual({ '': [], foo: ['bar', 'baz'] }); }); + test('does not parse an unknown shorthand with a value', () => { + expect(parseArgs(['-f', 'bar'])).toEqual({ '': [] }); + expect(parseArgs(['-f', 'bar', 'qux'])).toEqual({ '': ['qux'] }); + expect(parseArgs(['-f', 'bar', '-b'], { shorthands: { b: 'bar' } })).toEqual({ '': [], bar: null }); + expect(parseArgs(['-f', 'bar', 'qux', '--bar'])).toEqual({ '': ['qux'], bar: null }); + expect(parseArgs(['-f', 'bar', '--bar'])).toEqual({ '': [], bar: null }); + expect(parseArgs(['-b', '-f', 'bar'], { shorthands: { b: 'bar' } })).toEqual({ '': [], bar: null }); + expect(parseArgs(['--bar', '-f', 'bar'])).toEqual({ '': [], bar: null }); + }); + test('parses a shorthand with a value', () => { - expect(parseArgs(['-f', 'bar'], { shorthands: { f: 'foo' } })).toEqual({ '': [], foo: ['bar'] }); + expect(parseArgs(['-f', 'bar'], { shorthands: { f: 'foo' } })).toEqual({ '': [], foo: 'bar' }); }); test('keeps a shorthand with a value', () => { - expect(parseArgs(['-f', 'bar'], { keepShorthands: true })).toEqual({ '': [], f: ['bar'] }); + expect(parseArgs(['-f', 'bar'], { keepShorthands: true })).toEqual({ '': [], f: 'bar' }); }); test('parses a shorthand with multiple values', () => { @@ -74,13 +96,13 @@ describe('parseArgs', () => { test('parses multiple shorthands with a value', () => { expect(parseArgs(['-ab', 'bar'], { shorthands: { a: 'aaa', b: 'bbb' } })).toEqual({ '': [], - aaa: [], - bbb: ['bar'], + aaa: null, + bbb: 'bar', }); }); test('unknown shorthands do not receive a value', () => { - expect(parseArgs(['-ab', 'bar'], { shorthands: { a: 'aaa' } })).toEqual({ '': [], aaa: [] }); + expect(parseArgs(['-ab', 'bar'], { shorthands: { a: 'aaa' } })).toEqual({ '': [], aaa: null }); }); test('puts value without an option under ""', () => { diff --git a/index.ts b/index.ts index b73a8c9..2d0a7e3 100644 --- a/index.ts +++ b/index.ts @@ -22,9 +22,15 @@ export interface ParseArgsOptions { export interface ParsedArgs { /** - * Map from an option key to the list of associated values. + * Map from an option key (non-blank string) to the associated value. + * + * - `undefined` if a key wasn't provided; + * - `null` if a key was provided once without a value; + * - A string if the key was provided once with a value; + * - An array of `null`s and strings, if a key was provided multiple times; + * - `true` if the key {@link ParseArgsOptions.flags is a flag} and was provided at least once. */ - [key: string]: string[] | true | undefined; + [key: string]: string | null | Array | true | undefined; /** * The list of args that weren't associated with any option. @@ -46,7 +52,9 @@ export interface ParsedArgs { export function parseArgs(args: string[], options: ParseArgsOptions = {}): ParsedArgs { const { flags, shorthands, keepShorthands } = options; - const result: ParsedArgs = { '': [] }; + const result: ParsedArgs = Object.create(null); + + result[''] = []; let key: string | null = ''; @@ -60,26 +68,36 @@ export function parseArgs(args: string[], options: ParseArgsOptions = {}): Parse if (arg.charCodeAt(1) === CHAR_CODE_MINUS) { // '--' if (argLength === 2) { + if (key) { + putValue(result, key, null); + } + result['--'] = args.slice(i + 1); + key = null; break; } - // '--foo' + // '--foo', but not '---*' if (arg.charCodeAt(2) !== CHAR_CODE_MINUS) { + if (key) { + putValue(result, key, null); + } + key = arg.substring(2); if (flags && flags.includes(key)) { result[key] = true; key = ''; - } else { - result[key] ||= []; } continue; } } else { // -abc is the same as -a -b -c - for (let j = 1; j < argLength; j++) { + if (key) { + putValue(result, key, null); + } + const shorthand = arg.charAt(j); const longhand = shorthands ? shorthands[shorthand] : undefined; @@ -95,22 +113,31 @@ export function parseArgs(args: string[], options: ParseArgsOptions = {}): Parse if (flags && (flags.includes(shorthand) || (longhand && flags.includes(longhand)))) { result[key] = true; key = ''; - } else { - result[key] ||= []; } } continue; } } - if (key === null) { - continue; + if (key !== null) { + // Only ignore value of an unknown shorthand + putValue(result, key, arg); } - - ((result[key] ||= []) as string[]).push(arg); - key = ''; } + if (key) { + putValue(result, key, null); + } return result; } + +function putValue(result: ParsedArgs, key: string, arg: string | null): void { + const value = result[key]; + + if (Array.isArray(value)) { + value.push(arg); + } else { + result[key] = value === undefined || value === true ? arg : [value, arg]; + } +}