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