taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit f350c5022477ece9c7257cf5a85ce028d6e5e099
parent 0f232a22481e1b6f65218a1fe40b8f70db78b79c
Author: Florian Dold <dold@taler.net>
Date:   Tue, 21 Jul 2026 02:51:20 +0200

util: convert positionals, honor optional defaults, and guard clk key reuse

Diffstat:
Mpackages/taler-util/src/clk.ts | 102+++++++++++++++++++++++++++++++++++++++++++------------------------------------
1 file changed, 56 insertions(+), 46 deletions(-)

diff --git a/packages/taler-util/src/clk.ts b/packages/taler-util/src/clk.ts @@ -107,6 +107,24 @@ export namespace clk { } } + function convertArg( + name: string, + conv: Converter<any> | undefined, + value: string, + ): any { + if (conv === INT) { + const n = Number.parseInt(value, 10); + if (Number.isNaN(n)) { + throw Error(`invalid integer value for '${name}'`); + } + return n; + } + if (conv == null || conv === STRING || conv === AMOUNT) { + return value; + } + throw Error("unknown converter"); + } + export class CommandGroup<GN extends keyof any, TG> { private shortOptions: { [name: string]: OptionDef } = {}; private longOptions: { [name: string]: OptionDef } = {}; @@ -284,13 +302,13 @@ export namespace clk { } } usageSpec += (this.name ?? progName) + " "; - if (this.subcommands.length != 0) { - usageSpec += "COMMAND "; - } for (const a of this.arguments) { const argName = a.args.metavar ?? a.name; usageSpec += `<${argName}> `; } + if (this.subcommands.length != 0) { + usageSpec += "COMMAND "; + } usageSpec = usageSpec.trimRight(); console.log(`Usage: ${usageSpec}`); if (this.scArgs.help) { @@ -331,24 +349,17 @@ export namespace clk { let argsTerminated = false; let i; let foundSubcommand: CommandGroup<any, any> | undefined = undefined; + if (Object.prototype.hasOwnProperty.call(parsedArgs, this.argKey)) { + throw Error( + `command argument key '${this.argKey}' collides with a parent command`, + ); + } const myArgs: any = (parsedArgs[this.argKey] = {}); const foundOptions: { [name: string]: boolean } = {}; const currentName = this.name ?? progname; const storeOption = (def: OptionDef, value: string) => { foundOptions[def.name] = true; - if (def.conv === INT) { - const n = Number.parseInt(value, 10); - if (Number.isNaN(n)) { - throw Error(`invalid integer value for option '${def.name}'`); - } - myArgs[def.name] = n; - } else if (def.conv == null || def.conv === STRING) { - myArgs[def.name] = value; - } else if (def.conv === AMOUNT) { - myArgs[def.name] = value; - } else { - throw Error("unknown converter"); - } + myArgs[def.name] = convertArg(def.name, def.conv, value); }; const storeFlag = (def: OptionDef, value: boolean) => { foundOptions[def.name] = true; @@ -424,7 +435,11 @@ export namespace clk { continue; } } - if (this.subcommands.length != 0) { + if (posArgIndex < this.arguments.length) { + const d = this.arguments[posArgIndex]; + myArgs[d.name] = convertArg(d.name, d.conv, argVal); + posArgIndex++; + } else if (this.subcommands.length != 0) { const subcmd = this.subcommandMap[argVal]; if (!subcmd) { console.error(`error: unknown command '${argVal}'`); @@ -434,14 +449,9 @@ export namespace clk { foundSubcommand = subcmd.commandGroup; break; } else { - const d = this.arguments[posArgIndex]; - if (!d) { - console.error(`error: too many arguments for ${currentName}`); - processExit(-1); - throw Error("not reached"); - } - myArgs[d.name] = unparsedArgs[i]; - posArgIndex++; + console.error(`error: too many arguments for ${currentName}`); + processExit(-1); + throw Error("not reached"); } } @@ -453,31 +463,31 @@ export namespace clk { for (let i = posArgIndex; i < this.arguments.length; i++) { const d = this.arguments[i]; - if (d.required) { - if (d.args.default !== undefined) { - myArgs[d.name] = d.args.default; - } else { - console.error( - `error: missing positional argument '${d.name}' for ${currentName}`, - ); - processExit(-1); - throw Error("not reached"); - } + if (d.args.default !== undefined) { + myArgs[d.name] = d.args.default; + } else if (d.required) { + console.error( + `error: missing positional argument '${d.name}' for ${currentName}`, + ); + processExit(-1); + throw Error("not reached"); } } for (const option of this.options) { - if (option.isFlag == false && option.required == true) { - if (!foundOptions[option.name]) { - if (option.args.default !== undefined) { - myArgs[option.name] = option.args.default; - } else { - const name = option.flagspec.join(","); - console.error(`error: missing option '${name}'`); - processExit(-1); - throw Error("not reached"); - } - } + if (foundOptions[option.name]) { + continue; + } + if (option.isFlag) { + myArgs[option.name] = + option.args.default !== undefined ? option.args.default : false; + } else if (option.args.default !== undefined) { + myArgs[option.name] = option.args.default; + } else if (option.required) { + const name = option.flagspec.join(","); + console.error(`error: missing option '${name}'`); + processExit(-1); + throw Error("not reached"); } }