From 8ec9b9d9badb5e32c2a763340c463c5049c1b491 Mon Sep 17 00:00:00 2001 From: Michiel Borkent Date: Thu, 23 Jul 2026 18:15:29 +0200 Subject: [PATCH 1/2] Support :doc and :epilog as a vector of lines --- CHANGELOG.md | 4 ++++ src/babashka/cli.cljc | 15 +++++++++++---- test/babashka/cli_test.cljc | 14 ++++++++++++++ 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 411ea45..6cc069b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ For breaking changes, check [here](#breaking-changes). [Babashka CLI](https://github.com/babashka/cli): turn Clojure functions into CLIs! +## Unreleased + +- Support `:doc` and `:epilog` as a vector of lines, joined with newlines + ## v0.12.79 - Verify that `:cmd` is a string, keyword or symbol diff --git a/src/babashka/cli.cljc b/src/babashka/cli.cljc index 05451ff..0b03d80 100644 --- a/src/babashka/cli.cljc +++ b/src/babashka/cli.cljc @@ -1077,13 +1077,19 @@ :wrap wrap :max-width-fn max-width-fn})) +(defn- doc-str + "A `:doc`/`:epilog` value as a string: a vector of lines joins with newlines." + [s] + (if (vector? s) (str/join "\n" s) s)) + (defn- help-first-line [s] - (when s (first (str/split-lines s)))) + (when-let [s (doc-str s)] + (first (str/split-lines s)))) (defn- help-description "Full doc string, each line left-trimmed, leading/trailing blank lines dropped." [s] - (when s + (when-let [s (doc-str s)] (let [ls (->> (str/split-lines s) (map str/triml) (drop-while str/blank?) @@ -1212,7 +1218,7 @@ ;; free-text the entry supplies (examples, notes), rendered verbatim ;; last, as a closing footer (argparse epilog / picocli footer convention) (:epilog node) - (conj (str/trim (:epilog node))))] + (conj (str/trim (doc-str (:epilog node)))))] (str/join "\n\n" sections))) (defn- split [a b] @@ -1845,7 +1851,8 @@ $env.config.completions.external.completer = {|spans| An entry's `:epilog` (a string) is rendered verbatim after the options - use it for examples, notes or links. Put it on the root entry (`:cmds []`) for the - top-level help. + top-level help. `:doc` and `:epilog` also accept a vector of strings, joined + with newlines - convenient in edn, which has no `str/join`. Takes a single map: * `:table` - a `dispatch` table, or a tree (hand-written or from diff --git a/test/babashka/cli_test.cljc b/test/babashka/cli_test.cljc index 81569ec..9a55cda 100644 --- a/test/babashka/cli_test.cljc +++ b/test/babashka/cli_test.cljc @@ -1114,6 +1114,20 @@ "Options:\n --limit Max\n\n" "Examples:\n\n p search foo") (cli/format-command-help {:table t :cmds ["search"] :prog "p"}))))) + (testing ":doc and :epilog as vectors of lines join with newlines" + (let [t [{:cmds [] :doc ["Tool." "" "Second paragraph."]} + {:cmds ["go"] :fn identity + :doc ["Runs it." "" "Details."] + :epilog ["Examples:" " p go"]}]] + (is (= (str "Usage: p \n\n" + "Tool.\n\nSecond paragraph.\n\n" + "Commands:\n go Runs it.\n\n" + "Run \"p --help\" for more information on a command.") + (cli/format-command-help {:table t :prog "p"}))) + (is (= (str "Usage: p go\n\n" + "Runs it.\n\nDetails.\n\n" + "Examples:\n p go") + (cli/format-command-help {:table t :cmds ["go"] :prog "p"}))))) (testing "an entry :order sets the Options order; a vec-of-pairs spec keeps its order" (let [t [{:cmds [] :spec {:a {:desc "A"} :b {:desc "B"} :c {:desc "C"}} :order [:c :a :b]}]] (is (= (str "Usage: p [options]\n\n" From 5f7f69da01f57d37f2dc4e2f265ef13c5be7be79 Mon Sep 17 00:00:00 2001 From: Michiel Borkent Date: Thu, 23 Jul 2026 18:24:56 +0200 Subject: [PATCH 2/2] Document vector :doc/:epilog in README --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0a693fd..1cfd0b9 100644 --- a/README.md +++ b/README.md @@ -738,6 +738,16 @@ Options: -h, --help Show this help ``` +`:doc` (and [`:epilog`](#help)) also accept a vector of strings, joined with +newlines. Longer texts read better that way, especially in edn files, where +`str/join` is not available: + +``` clojure +:doc ["Copy a file" + "" + "More details here"] +``` + Running `bb -m try-cmds copy the-file --dry-run` calls `copy`, which prints: ``` clojure @@ -1041,7 +1051,7 @@ This also works for multi-word commands, e.g., `some-prog deps outdated --help` `--help` is appended. - `--help`/`-h` are reserved when `:help true` is specified (a command may still define its own `:help`). -- A command entry's `:epilog` (a string) is rendered verbatim after that command's +- A command entry's `:epilog` (a string, or a vector of lines) is rendered verbatim after that command's options, for examples, notes or links. Specify it at the root of the commands tree format (or `:cmds []` entry for commands table format) for the top-level help. The `:help true` option works for a command-less CLI too.