taler-typescript-core

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

commit 4280aa2434c66d5f658b6f5e0d166b3dd7bb030b
parent 060c357ce2205596e7d40774fa751465712f00f6
Author: Florian Dold <dold@taler.net>
Date:   Thu,  6 Aug 2026 17:07:31 +0200

util: show only the first sentence of a command's help in the listing

A command listing is a table of contents, so a multi-sentence description
pushed the other entries apart; the full text is still on the command's own
--help.

Diffstat:
Mpackages/taler-util/src/clk.test.ts | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-util/src/clk.ts | 33++++++++++++++++++++++++++++++++-
2 files changed, 81 insertions(+), 1 deletion(-)

diff --git a/packages/taler-util/src/clk.test.ts b/packages/taler-util/src/clk.test.ts @@ -246,3 +246,52 @@ test("CLK-12: without a policy, marks change nothing", (t) => { captureHelp(() => prog.run(["prog", "old"])); assert.ok(ran); }); + +test("CLK-13: the command listing is cut down to the first sentence", (t) => { + const prog = clk.program("s1"); + prog + .subcommand("sub", "sub", { + help: "Do the thing. Also does other things that only matter once you run it.", + }) + .action(() => {}); + const out = captureHelp(() => prog.run(["prog", "--help"])); + assert.ok(out.includes("Do the thing.")); + assert.ok(!out.includes("other things")); +}); + +test("CLK-14: the command's own help shows the whole text", (t) => { + const prog = clk.program("s2"); + prog + .subcommand("sub", "sub", { + help: "Do the thing. Also does other things that only matter once you run it.", + }) + .action(() => {}); + const out = captureHelp(() => prog.run(["prog", "sub", "--help"])); + assert.ok(out.includes("other things that only matter once you run it.")); +}); + +test("CLK-15: an abbreviation does not end the first sentence", (t) => { + const prog = clk.program("s3"); + prog + .subcommand("sub", "sub", { + help: "Wait for a duration (e.g. '30s'). Blocks forever without one.", + }) + .action(() => {}); + const out = captureHelp(() => prog.run(["prog", "--help"])); + assert.ok(out.includes("Wait for a duration (e.g. '30s').")); + assert.ok(!out.includes("Blocks forever")); +}); + +test("CLK-16: a mark is still shown in front of the shortened help", (t) => { + const policy = testPolicy(["legacy"]); + const prog = clk.program("s4", { markPolicy: policy }); + prog + .subcommand("old", "old", { + help: "An old one. Kept around for scripts.", + mark: "legacy", + }) + .action(() => {}); + const out = captureHelp(() => prog.run(["prog", "--help"])); + assert.ok(out.includes("[legacy] An old one.")); + assert.ok(!out.includes("Kept around")); +}); diff --git a/packages/taler-util/src/clk.ts b/packages/taler-util/src/clk.ts @@ -141,6 +141,37 @@ export namespace clk { } } + /** + * Abbreviations that end in a period without ending a sentence. + */ + const ABBREVIATIONS = ["e.g", "i.e", "cf", "etc", "resp", "vs", "approx"]; + + /** + * The first sentence of a help text. + * + * A command's one-line entry in its parent's help only gets this much; + * the rest is what the command's own '--help' is for. + */ + function firstSentence(text: string): string { + for (let i = 0; i < text.length; i++) { + const c = text[i]; + if (c !== "." && c !== "!" && c !== "?") { + continue; + } + // A sentence ends where whitespace or the end of the text follows, + // so that "3.5" or "taler://" don't count as one. + if (i + 1 < text.length && !/\s/.test(text[i + 1])) { + continue; + } + const before = text.substring(0, i).toLowerCase(); + if (ABBREVIATIONS.some((a) => before.endsWith(a))) { + continue; + } + return text.substring(0, i + 1); + } + return text; + } + function convertArg( name: string, conv: Converter<any> | undefined, @@ -447,7 +478,7 @@ export namespace clk { for (const subcmd of listed) { const help = [ subcmd.args.mark ? `[${subcmd.args.mark}]` : undefined, - subcmd.args.help, + subcmd.args.help ? firstSentence(subcmd.args.help) : undefined, ] .filter((x) => x) .join(" ");