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:
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");
}
}