diff --git a/example/negative-numbers.ts b/example/negative-numbers.ts new file mode 100644 index 0000000..4514fbb --- /dev/null +++ b/example/negative-numbers.ts @@ -0,0 +1,23 @@ +import { binary, command, run, number, option } from '../src'; + +export function createCmd() { + const cmd = command({ + name: 'test', + args: { + number: option({ + long: 'number', + type: number, + }), + }, + async handler(args) { + console.log({ args }); + }, + }); + + return cmd; +} + +if (require.main === module) { + const cmd = createCmd(); + run(binary(cmd), process.argv); +} diff --git a/src/argparser.ts b/src/argparser.ts index ed44ba7..fdfd0a5 100644 --- a/src/argparser.ts +++ b/src/argparser.ts @@ -30,9 +30,24 @@ export type ParseContext = { export type ParsingResult = Result; +/** + * A weird thing about command line interfaces is that they are not consistent without some context. + * Consider the following argument list: `my-app --server start` + * + * Should we parse it as `[positional my-app] [option --server start]` + * or should we parse it as `[positional my-app] [flag --server] [positional start]`? + * + * The answer is — it depends. A good command line utility has the context to know which key is a flag + * and which is an option that can take a value. We aim to be a good command line utility library, so + * we need to have the ability to provide this context. + * + * This is the small object that has this context. + */ export type RegisterOptions = { forceFlagLongNames: Set; forceFlagShortNames: Set; + forceOptionLongNames: Set; + forceOptionShortNames: Set; }; export type Register = { diff --git a/src/multioption.ts b/src/multioption.ts index c951cc8..bb25ad5 100644 --- a/src/multioption.ts +++ b/src/multioption.ts @@ -38,7 +38,12 @@ export function multioption>( }, ]; }, - register(_opts) {}, + register(opts) { + opts.forceOptionLongNames.add(config.long); + if (config.short) { + opts.forceOptionShortNames.add(config.short); + } + }, async parse({ nodes, visitedNodes, @@ -46,7 +51,7 @@ export function multioption>( const options = findOption(nodes, { longNames: [config.long], shortNames: config.short ? [config.short] : [], - }).filter(x => !visitedNodes.has(x)); + }).filter((x) => !visitedNodes.has(x)); for (const option of options) { visitedNodes.add(option); diff --git a/src/newparser/parser.ts b/src/newparser/parser.ts index 84e78a6..df75baa 100644 --- a/src/newparser/parser.ts +++ b/src/newparser/parser.ts @@ -1,5 +1,6 @@ import { Token } from './tokenizer'; import createDebugger from 'debug'; +import type { RegisterOptions } from '../argparser'; const debug = createDebugger('cmd-ts:parser'); @@ -46,44 +47,21 @@ interface ForcePositional extends BaseAstNode<'forcePositional'> { type: 'forcePositional'; } -/** - * A weird thing about command line interfaces is that they are not consistent without some context. - * Consider the following argument list: `my-app --server start` - * - * Should we parse it as `[positional my-app] [option --server start]` - * or should we parse it as `[positional my-app] [flag --server] [positional start]`? - * - * The answer is — it depends. A good command line utility has the context to know which key is a flag - * and which is an option that can take a value. We aim to be a good command line utility library, so - * we need to have the ability to provide this context. - * - * This is the small object that has this context. - */ -type ForceFlag = { - /** - * Short keys that we will force to read as flags - */ - shortFlagKeys: Set; - /** - * Long keys that we will force to read as flags - */ - longFlagKeys: Set; -}; - /** * Create an AST from a token list * * @param tokens A token list, coming from `tokenizer.ts` * @param forceFlag Keys to force as flag. {@see ForceFlag} to read more about it. */ -export function parse(tokens: Token[], forceFlag: ForceFlag): AstNode[] { +export function parse(tokens: Token[], forceFlag: RegisterOptions): AstNode[] { if (debug.enabled) { - debug( - `Registered short flags: ${JSON.stringify([...forceFlag.shortFlagKeys])}` - ); - debug( - `Registered long flags: ${JSON.stringify([...forceFlag.longFlagKeys])}` - ); + const registered = { + shortFlags: [...forceFlag.forceFlagShortNames], + longFlags: [...forceFlag.forceFlagLongNames], + shortOptions: [...forceFlag.forceOptionShortNames], + longOptions: [...forceFlag.forceOptionLongNames], + }; + debug(`Registered:`, JSON.stringify(registered)); } const nodes: AstNode[] = []; @@ -162,9 +140,10 @@ export function parse(tokens: Token[], forceFlag: ForceFlag): AstNode[] { const parsedValue = parseOptionValue({ key, delimiterToken: nextToken, - forceFlag: forceFlag.longFlagKeys, + forceFlag: forceFlag.forceFlagLongNames, getToken, peekToken, + forceOption: forceFlag.forceOptionLongNames, }); let raw = `--${key}`; @@ -208,7 +187,8 @@ export function parse(tokens: Token[], forceFlag: ForceFlag): AstNode[] { const parsedValue = parseOptionValue({ key: lastKey.raw, delimiterToken: nextToken, - forceFlag: forceFlag.shortFlagKeys, + forceFlag: forceFlag.forceFlagShortNames, + forceOption: forceFlag.forceOptionShortNames, getToken, peekToken, }); @@ -272,10 +252,13 @@ function parseOptionValue(opts: { peekToken(): Token | undefined; key: string; forceFlag: Set; + forceOption: Set; }): OptionValue | undefined { - let { getToken, delimiterToken, forceFlag, key } = opts; + let { getToken, delimiterToken, forceFlag, key, forceOption } = opts; + const shouldReadKeyAsOption = forceOption.has(key); const shouldReadKeyAsFlag = - forceFlag.has(key) || opts.peekToken()?.type !== 'char'; + !shouldReadKeyAsOption && + (forceFlag.has(key) || opts.peekToken()?.type !== 'char'); if (!delimiterToken || (delimiterToken.raw !== '=' && shouldReadKeyAsFlag)) { return; diff --git a/src/option.ts b/src/option.ts index c7dfe3c..da2e938 100644 --- a/src/option.ts +++ b/src/option.ts @@ -74,7 +74,12 @@ function fullOption>( }, ]; }, - register(_opts) {}, + register(opts) { + opts.forceOptionLongNames.add(config.long); + if (config.short) { + opts.forceOptionShortNames.add(config.short); + } + }, async parse({ nodes, visitedNodes, diff --git a/src/runner.ts b/src/runner.ts index afb5e38..a9dad5c 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -38,19 +38,22 @@ export async function runSafely>( ap: R, strings: string[] ): Promise>> { + const longFlagKeys = new Set(); + const shortFlagKeys = new Set(); const longOptionKeys = new Set(); const shortOptionKeys = new Set(); const hotPath: string[] = []; - ap.register({ - forceFlagShortNames: shortOptionKeys, - forceFlagLongNames: longOptionKeys, - }); + const registerContext = { + forceFlagShortNames: shortFlagKeys, + forceFlagLongNames: longFlagKeys, + forceOptionShortNames: shortOptionKeys, + forceOptionLongNames: longOptionKeys, + }; + + ap.register(registerContext); const tokens = tokenize(strings); - const nodes = parse(tokens, { - longFlagKeys: longOptionKeys, - shortFlagKeys: shortOptionKeys, - }); + const nodes = parse(tokens, registerContext); try { const result = await ap.run({ nodes, visitedNodes: new Set(), hotPath }); diff --git a/test/command.test.ts b/test/command.test.ts index f589bbc..5c13c7b 100644 --- a/test/command.test.ts +++ b/test/command.test.ts @@ -6,6 +6,7 @@ import { parse } from '../src/newparser/parser'; import { command } from '../src/command'; import { number, string, boolean } from './test-types'; import * as Result from '../src/Result'; +import { createRegisterOptions } from './createRegisterOptions'; const cmd = command({ name: 'My command', @@ -18,26 +19,20 @@ const cmd = command({ }), flag: flag({ type: boolean, long: 'flag' }), }, - handler: _ => {}, + handler: (_) => {}, }); test('merges options, positionals and flags', async () => { - const argv = `first --option=666 second --second-option works-too --flag third`.split( - ' ' - ); + const argv = + `first --option=666 second --second-option works-too --flag third`.split( + ' ' + ); const tokens = tokenize(argv); - const longOptionKeys = new Set(); - const shortOptionKeys = new Set(); - cmd.register({ - forceFlagLongNames: longOptionKeys, - forceFlagShortNames: shortOptionKeys, - }); + const registerOptions = createRegisterOptions(); + cmd.register(registerOptions); - const nodes = parse(tokens, { - longFlagKeys: longOptionKeys, - shortFlagKeys: shortOptionKeys, - }); + const nodes = parse(tokens, registerOptions); const result = await cmd.parse({ nodes, visitedNodes: new Set() }); const expected: typeof result = Result.ok({ positionals: ['first', 'second', 'third'], @@ -50,22 +45,17 @@ test('merges options, positionals and flags', async () => { }); test('fails if an argument fail to parse', async () => { - const argv = `first --option=hello second --second-option works-too --flag=fails-too third`.split( - ' ' - ); + const argv = + `first --option=hello second --second-option works-too --flag=fails-too third`.split( + ' ' + ); const tokens = tokenize(argv); - const longOptionKeys = new Set(); - const shortOptionKeys = new Set(); - cmd.register({ - forceFlagLongNames: longOptionKeys, - forceFlagShortNames: shortOptionKeys, - }); + const registerOptions = createRegisterOptions(); - const nodes = parse(tokens, { - longFlagKeys: longOptionKeys, - shortFlagKeys: shortOptionKeys, - }); + cmd.register(registerOptions); + + const nodes = parse(tokens, registerOptions); const result = cmd.parse({ nodes, visitedNodes: new Set(), @@ -75,11 +65,11 @@ test('fails if an argument fail to parse', async () => { Result.err({ errors: [ { - nodes: nodes.filter(x => x.raw.startsWith('--option')), + nodes: nodes.filter((x) => x.raw.startsWith('--option')), message: 'Not a number', }, { - nodes: nodes.filter(x => x.raw.startsWith('--flag')), + nodes: nodes.filter((x) => x.raw.startsWith('--flag')), message: `expected value to be either "true" or "false". got: "fails-too"`, }, ], @@ -97,22 +87,15 @@ test('fails if providing unknown arguments', async () => { args: { positionals: restPositionals({ type: string }), }, - handler: _ => {}, + handler: (_) => {}, }); const argv = `okay --option=failing alright --another=fail`.split(' '); const tokens = tokenize(argv); - const longOptionKeys = new Set(); - const shortOptionKeys = new Set(); - cmd.register({ - forceFlagLongNames: longOptionKeys, - forceFlagShortNames: shortOptionKeys, - }); + const registerOptions = createRegisterOptions(); + cmd.register(registerOptions); - const nodes = parse(tokens, { - longFlagKeys: longOptionKeys, - shortFlagKeys: shortOptionKeys, - }); + const nodes = parse(tokens, registerOptions); const result = await cmd.parse({ nodes, visitedNodes: new Set(), @@ -124,7 +107,7 @@ test('fails if providing unknown arguments', async () => { { message: 'Unknown arguments', nodes: nodes.filter( - node => + (node) => node.raw.startsWith('--option') || node.raw.startsWith('--another') ), @@ -142,7 +125,7 @@ test('should fail run when an async handler fails', async () => { const cmd = command({ name: 'my command', args: {}, - handler: async _ => { + handler: async (_) => { throw error; }, }); @@ -166,16 +149,9 @@ test('succeeds when rest is quoted', async () => { '--', '--restPositionals --trailing-comma all {{scripts,src}/**/*.{js,ts},{scripts,src}/*.{js,ts},*.{js,ts}}', ]); - const longOptionKeys = new Set(); - const shortOptionKeys = new Set(); - cmd.register({ - forceFlagLongNames: longOptionKeys, - forceFlagShortNames: shortOptionKeys, - }); - const nodes = parse(tokens, { - longFlagKeys: longOptionKeys, - shortFlagKeys: shortOptionKeys, - }); + const registerOptions = createRegisterOptions(); + cmd.register(registerOptions); + const nodes = parse(tokens, registerOptions); const result = cmd.parse({ nodes, visitedNodes: new Set(), diff --git a/test/createRegisterOptions.ts b/test/createRegisterOptions.ts new file mode 100644 index 0000000..4ec0e70 --- /dev/null +++ b/test/createRegisterOptions.ts @@ -0,0 +1,10 @@ +import type { RegisterOptions } from '../src/argparser'; + +export function createRegisterOptions(): RegisterOptions { + return { + forceFlagLongNames: new Set(), + forceFlagShortNames: new Set(), + forceOptionLongNames: new Set(), + forceOptionShortNames: new Set(), + }; +} diff --git a/test/errorBox.test.ts b/test/errorBox.test.ts index d84e3fa..e967d53 100644 --- a/test/errorBox.test.ts +++ b/test/errorBox.test.ts @@ -5,15 +5,13 @@ import { errorBox } from '../src/errorBox'; import { option } from '../src/option'; import { number } from './test-types'; import * as Result from '../src/Result'; +import { createRegisterOptions } from './createRegisterOptions'; test('works for multiple nodes', async () => { const argv = `hello world --some arg --flag --some another --flag --this-is=option -abcde=f -abcde`; const tokens = tokenize(argv.split(' ')); - const tree = parse(tokens, { - longFlagKeys: new Set(), - shortFlagKeys: new Set(), - }); + const tree = parse(tokens, createRegisterOptions()); const opt = option({ type: number, @@ -37,10 +35,7 @@ test('works for a short flag', async () => { const argv = `hello world -fn not_a_number hey`; const tokens = tokenize(argv.split(' ')); - const tree = parse(tokens, { - longFlagKeys: new Set(), - shortFlagKeys: new Set(), - }); + const tree = parse(tokens, createRegisterOptions()); const opt = option({ type: number, @@ -65,10 +60,7 @@ test('works for a single node', async () => { const argv = `hello world --flag --some not_a_number --flag --this-is=option -abcde=f -abcde`; const tokens = tokenize(argv.split(' ')); - const tree = parse(tokens, { - longFlagKeys: new Set(), - shortFlagKeys: new Set(), - }); + const tree = parse(tokens, createRegisterOptions()); const opt = option({ type: number, @@ -92,10 +84,7 @@ test('works when no nodes', async () => { const argv = `hello world --flag --flag --this-is=option -abcde=f -abcde`; const tokens = tokenize(argv.split(' ')); - const tree = parse(tokens, { - longFlagKeys: new Set(), - shortFlagKeys: new Set(), - }); + const tree = parse(tokens, createRegisterOptions()); const opt = option({ type: number, diff --git a/test/flag.test.ts b/test/flag.test.ts index bfb78c1..f324c8a 100644 --- a/test/flag.test.ts +++ b/test/flag.test.ts @@ -3,25 +3,19 @@ import { tokenize } from '../src/newparser/tokenizer'; import { parse } from '../src/newparser/parser'; import { boolean } from '../src/types'; import * as Result from '../src/Result'; +import { createRegisterOptions } from './createRegisterOptions'; test('fails on incompatible value', async () => { const argv = `--hello=world`; const tokens = tokenize(argv.split(' ')); - const shortOptionKeys = new Set(); - const longOptionKeys = new Set(); const argparser = flag({ type: boolean, long: 'hello', description: 'description', }); - argparser.register({ - forceFlagShortNames: shortOptionKeys, - forceFlagLongNames: longOptionKeys, - }); - const nodes = parse(tokens, { - shortFlagKeys: shortOptionKeys, - longFlagKeys: longOptionKeys, - }); + const registerOptions = createRegisterOptions(); + argparser.register(registerOptions); + const nodes = parse(tokens, registerOptions); const result = argparser.parse({ nodes, @@ -44,21 +38,14 @@ test('fails on incompatible value', async () => { test('defaults to false', async () => { const argv = ``; const tokens = tokenize(argv.split(' ')); - const shortOptionKeys = new Set(); - const longOptionKeys = new Set(); const argparser = flag({ type: boolean, long: 'hello', description: 'description', }); - argparser.register({ - forceFlagShortNames: shortOptionKeys, - forceFlagLongNames: longOptionKeys, - }); - const nodes = parse(tokens, { - shortFlagKeys: shortOptionKeys, - longFlagKeys: longOptionKeys, - }); + const registerOptions = createRegisterOptions(); + argparser.register(registerOptions); + const nodes = parse(tokens, registerOptions); const result = argparser.parse({ nodes, @@ -71,22 +58,15 @@ test('defaults to false', async () => { test('allows short arguments', async () => { const argv = `-abc`; const tokens = tokenize(argv.split(' ')); - const shortOptionKeys = new Set(); - const longOptionKeys = new Set(); const argparser = flag({ type: boolean, long: 'hello', short: 'b', description: 'description', }); - argparser.register({ - forceFlagShortNames: shortOptionKeys, - forceFlagLongNames: longOptionKeys, - }); - const nodes = parse(tokens, { - shortFlagKeys: shortOptionKeys, - longFlagKeys: longOptionKeys, - }); + const registerOptions = createRegisterOptions(); + argparser.register(registerOptions); + const nodes = parse(tokens, registerOptions); const result = argparser.parse({ nodes, diff --git a/test/negative-numbers.test.ts b/test/negative-numbers.test.ts new file mode 100644 index 0000000..f7c8de0 --- /dev/null +++ b/test/negative-numbers.test.ts @@ -0,0 +1,18 @@ +import { createCmd } from '../example/negative-numbers'; +import { runSafely } from '../src'; + +test('negative numbers', async () => { + const result = await new Promise(async (resolve, reject) => { + const cmd = createCmd(); + cmd.handler = async ({ number }) => { + resolve(number); + }; + + const runResult = await runSafely(cmd, ['--number', '-10']); + if (runResult._tag === 'error') { + reject(runResult.error); + } + }); + + expect(result).toEqual(-10); +}); diff --git a/test/newparser/findOption.test.ts b/test/newparser/findOption.test.ts index 6f3a09f..bb95670 100644 --- a/test/newparser/findOption.test.ts +++ b/test/newparser/findOption.test.ts @@ -1,14 +1,12 @@ import { tokenize } from '../../src/newparser/tokenizer'; import { parse } from '../../src/newparser/parser'; import { findOption } from '../../src/newparser/findOption'; +import { createRegisterOptions } from '../createRegisterOptions'; test('finds options', () => { const argv = `hello world --some arg --flag --this-is=option -abcde=f -abcde`; const tokens = tokenize(argv.split(' ')); - const nodes = parse(tokens, { - longFlagKeys: new Set(), - shortFlagKeys: new Set(), - }); + const nodes = parse(tokens, createRegisterOptions()); const options = findOption(nodes, { longNames: ['some'], shortNames: ['c'] }); diff --git a/test/newparser/parser.test.ts b/test/newparser/parser.test.ts index 3cb2d6c..fb0b061 100644 --- a/test/newparser/parser.test.ts +++ b/test/newparser/parser.test.ts @@ -1,12 +1,10 @@ import { tokenize } from '../../src/newparser/tokenizer'; import { parse } from '../../src/newparser/parser'; +import { createRegisterOptions } from '../createRegisterOptions'; test('dash in the middle of a word', () => { const tokens = tokenize(['hello', 'world', 'you-know', 'my', 'friend']); - const tree = parse(tokens, { - longFlagKeys: new Set(), - shortFlagKeys: new Set(), - }); + const tree = parse(tokens, createRegisterOptions()); expect(tree).toMatchInlineSnapshot(` Array [ Object { @@ -43,10 +41,7 @@ test('welp', () => { ' ' ); const tokens = tokenize(argv); - const tree = parse(tokens, { - longFlagKeys: new Set(), - shortFlagKeys: new Set(), - }); + const tree = parse(tokens, createRegisterOptions()); expect(tree).toMatchInlineSnapshot(` Array [ Object { diff --git a/test/restPositionals.test.ts b/test/restPositionals.test.ts index 028fd52..66459e5 100644 --- a/test/restPositionals.test.ts +++ b/test/restPositionals.test.ts @@ -3,14 +3,12 @@ import { tokenize } from '../src/newparser/tokenizer'; import { parse, AstNode } from '../src/newparser/parser'; import { number } from './test-types'; import * as Result from '../src/Result'; +import { createRegisterOptions } from './createRegisterOptions'; test('fails on specific positional', async () => { const argv = `10 20 --mamma mia hello 40`; const tokens = tokenize(argv.split(' ')); - const nodes = parse(tokens, { - shortFlagKeys: new Set(), - longFlagKeys: new Set(), - }); + const nodes = parse(tokens, createRegisterOptions()); const argparser = restPositionals({ type: number, }); @@ -21,7 +19,7 @@ test('fails on specific positional', async () => { Result.err({ errors: [ { - nodes: nodes.filter(x => x.raw === 'hello'), + nodes: nodes.filter((x) => x.raw === 'hello'), message: 'Not a number', }, ], @@ -32,16 +30,13 @@ test('fails on specific positional', async () => { test('succeeds when all unused positional decode successfuly', async () => { const argv = `10 20 --mamma mia hello 40`; const tokens = tokenize(argv.split(' ')); - const nodes = parse(tokens, { - shortFlagKeys: new Set(), - longFlagKeys: new Set(), - }); + const nodes = parse(tokens, createRegisterOptions()); const argparser = restPositionals({ type: number, }); const visitedNodes = new Set(); - const alreadyUsedNode = nodes.find(x => x.raw === 'hello'); + const alreadyUsedNode = nodes.find((x) => x.raw === 'hello'); if (!alreadyUsedNode) { throw new Error('Node `hello` not found. please rewrite the find function'); } diff --git a/test/subcommands.test.ts b/test/subcommands.test.ts index d084de0..6f3a219 100644 --- a/test/subcommands.test.ts +++ b/test/subcommands.test.ts @@ -7,6 +7,7 @@ import { command } from '../src/command'; import { subcommands } from '../src/subcommands'; import { string, boolean } from './test-types'; import * as Result from '../src/Result'; +import { createRegisterOptions } from './createRegisterOptions'; const logMock = jest.fn(); @@ -17,7 +18,7 @@ const greeter = command({ exclaim: flag({ type: boolean, long: 'exclaim', short: 'e' }), greeting: option({ type: string, long: 'greeting', short: 'g' }), }, - handler: x => { + handler: (x) => { logMock('greeter', x); }, }); @@ -27,7 +28,7 @@ const howdyPrinter = command({ args: { name: positional({ type: string, displayName: 'name' }), }, - handler: x => { + handler: (x) => { logMock('howdy', x); }, }); @@ -43,16 +44,9 @@ const subcmds = subcommands({ test('chooses one subcommand', async () => { const argv = `greeter Gal -eg Hello`.split(' '); const tokens = tokenize(argv); - const longOptionKeys = new Set(); - const shortOptionKeys = new Set(); - subcmds.register({ - forceFlagLongNames: longOptionKeys, - forceFlagShortNames: shortOptionKeys, - }); - const nodes = parse(tokens, { - longFlagKeys: longOptionKeys, - shortFlagKeys: shortOptionKeys, - }); + const registerOptions = createRegisterOptions(); + subcmds.register(registerOptions); + const nodes = parse(tokens, registerOptions); const result = await subcmds.parse({ nodes, visitedNodes: new Set() }); const expected: typeof result = Result.ok({ args: { @@ -69,16 +63,9 @@ test('chooses one subcommand', async () => { test('chooses the other subcommand', async () => { const argv = `howdy joe`.split(' '); const tokens = tokenize(argv); - const longOptionKeys = new Set(); - const shortOptionKeys = new Set(); - subcmds.register({ - forceFlagLongNames: longOptionKeys, - forceFlagShortNames: shortOptionKeys, - }); - const nodes = parse(tokens, { - longFlagKeys: longOptionKeys, - shortFlagKeys: shortOptionKeys, - }); + const registerOptions = createRegisterOptions(); + subcmds.register(registerOptions); + const nodes = parse(tokens, registerOptions); const result = await subcmds.parse({ nodes, visitedNodes: new Set() }); const expected: typeof result = Result.ok({ command: 'howdy', @@ -93,21 +80,14 @@ test('chooses the other subcommand', async () => { test('fails when using unknown subcommand', async () => { const argv = `--hello yes how are you joe`.split(' '); const tokens = tokenize(argv); - const longOptionKeys = new Set(); - const shortOptionKeys = new Set(); - subcmds.register({ - forceFlagLongNames: longOptionKeys, - forceFlagShortNames: shortOptionKeys, - }); - const nodes = parse(tokens, { - longFlagKeys: longOptionKeys, - shortFlagKeys: shortOptionKeys, - }); + const registerOptions = createRegisterOptions(); + subcmds.register(registerOptions); + const nodes = parse(tokens, registerOptions); const result = await subcmds.parse({ nodes, visitedNodes: new Set() }); const expected: typeof result = Result.err({ errors: [ { - nodes: nodes.filter(x => x.raw === 'how'), + nodes: nodes.filter((x) => x.raw === 'how'), message: `Not a valid subcommand name`, }, ], @@ -120,21 +100,14 @@ test('fails when using unknown subcommand', async () => { test('fails for a subcommand argument parsing issue', async () => { const argv = `greeter Gal -g Hello --exclaim=hell-no`.split(' '); const tokens = tokenize(argv); - const longOptionKeys = new Set(); - const shortOptionKeys = new Set(); - subcmds.register({ - forceFlagLongNames: longOptionKeys, - forceFlagShortNames: shortOptionKeys, - }); - const nodes = parse(tokens, { - longFlagKeys: longOptionKeys, - shortFlagKeys: shortOptionKeys, - }); + const registerOptions = createRegisterOptions(); + subcmds.register(registerOptions); + const nodes = parse(tokens, registerOptions); const result = await subcmds.parse({ nodes, visitedNodes: new Set() }); const expected = Result.err({ errors: [ { - nodes: nodes.filter(x => x.raw.includes('hell-no')), + nodes: nodes.filter((x) => x.raw.includes('hell-no')), message: `expected value to be either "true" or "false". got: "hell-no"`, }, ],