diff --git a/CHANGELOG.md b/CHANGELOG.md index d2bf2e6..9dcf9ab 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 + +- [#233](https://github.com/babashka/cli/issues/233): Generated help no longer prints extra spaces when `:prog` is not specified + ## 0.12.91 - zsh completion: disable sorting for registered program names to preserve candidate order diff --git a/src/babashka/cli.cljc b/src/babashka/cli.cljc index 0808394..d3f6d4f 100644 --- a/src/babashka/cli.cljc +++ b/src/babashka/cli.cljc @@ -1248,7 +1248,8 @@ (remove (comp :no-doc second) pairs))) (defn- help-usage-line [prog node any-options?] - (str "Usage: " prog + (str "Usage:" + (when prog (str " " prog)) (when any-options? " [options]") ;; `` reflects the runtime contract (the node accepts/requires ;; a command), so it keys on the dispatchable children, not the @@ -1318,12 +1319,12 @@ (conj (str "Inherited options:\n" (format-opts {:spec inherited}))) (seq cmds) - (conj (str "Run \"" prog " --help\" for more information on a command.")) + (conj (str "Run \"" (when prog (str prog " ")) " --help\" for more information on a command.")) (seq parents) (conj (str/join "\n" (for [{p :prog n :name} parents] - (str "Run \"" p " --help\" for " n " options.")))) + (str "Run \"" (when p (str p " ")) "--help\" for " n " options.")))) ;; free-text the entry supplies (examples, notes), rendered verbatim ;; last, as a closing footer (argparse epilog / picocli footer convention) @@ -1944,6 +1945,10 @@ $env.config.completions.external.completer = {|spans| (or (str/starts-with? s "-") (str/starts-with? s ":")))) +(defn- prog-cmd [prog path] + (let [cmd-path (if (seq prog) (cons prog path) path)] + (when (seq cmd-path) + (str/join " " cmd-path)))) (defn- command-help-context "Given a dispatch `tree`, command path `cmds`, `prog` name and dispatch-level @@ -1960,7 +1965,7 @@ $env.config.completions.external.completer = {|spans| spec may be a vec-of-pairs); display order is handled by `render-help`." [tree cmds prog inherit global-spec] (let [node-at (fn [path] (get-in tree (interleave (repeat :cmd) path))) - prog-at (fn [path] (str/join " " (cons prog path))) + prog-at (fn [path] (prog-cmd prog path)) ;; options available at the target level itself (e.g. an injected --help, ;; or a redefined option) - those never need a "must precede" pointer here (set (keys (->spec-map (:spec (node-at cmds))))) @@ -2011,7 +2016,7 @@ $env.config.completions.external.completer = {|spans| * `:table` - a `dispatch` table, or a tree (hand-written or from [[table->tree]]) - see [[dispatch]] (required) * `:cmds` - the command path, e.g. `[\"deps\" \"outdated\"]` (default `[]`) - * `:prog` - program name shown in the usage line (required) + * `:prog` - program name shown in the usage line * `:inherit` - only needed when you pass a dispatch-level `:inherit` to `dispatch`; pass the same value so `Inherited options:` matches. Per-option `:inherit true` is detected automatically. @@ -2101,8 +2106,9 @@ $env.config.completions.external.completer = {|spans| (let [tree (table->tree tree) path (or dispatch []) ctx-at (fn [p] (command-help-context tree (vec p) prog inherit (::global-spec data))) - hint (str "Run \"" (str/join " " (cons prog path)) - " --help\" for more information.") + prog-cmd-path (prog-cmd prog path) + hint (str "Run \"" (when prog-cmd-path (str prog-cmd-path " ")) + "--help\" for more information.") usage (fn [p] (let [{:keys [node prog inherited]} (ctx-at p)] (help-usage-line prog node (or (seq (:spec node)) diff --git a/test/babashka/cli_test.cljc b/test/babashka/cli_test.cljc index 07255e6..9d559f2 100644 --- a/test/babashka/cli_test.cljc +++ b/test/babashka/cli_test.cljc @@ -1231,6 +1231,12 @@ "Options:\n -v, --verbose Verbose output\n\n" "Run \"example --help\" for more information on a command.") (cli/format-command-help {:table table :prog "example"})))) + (testing "no :prog" + (is (= (str "Usage: [options] \n\n" + "Commands:\n copy Copy a file\n delete Delete a file\n\n" + "Options:\n -v, --verbose Verbose output\n\n" + "Run \" --help\" for more information on a command.") + (cli/format-command-help {:table table})))) (testing "leaf: own options + option inherited from an ancestor" (is (= (str "Usage: example copy [options]\n\n" "Copy a file\n\n" @@ -1312,7 +1318,30 @@ (println (cli/format-command-help {:table tree :cmds dispatch :prog prog :inherit inherit})))})] (is (str/includes? out "BANNER")) - (is (str/includes? out "Usage: p")))))) + (is (str/includes? out "Usage: p")))) + (testing "multi-word commands with inheritance" + (is (= (str "Usage: myprog foo bar [options]\n\n" + "Options:\n" + " --some-foo-bar-opt\n\n" + "Run \"myprog --help\" for global options.\n" + "Run \"myprog foo --help\" for foo options.") + (cli/format-command-help + {:table [{:cmds [] :spec {:some-default-opt {}}} + {:cmds ["foo"] :spec {:some-foo-opt {}}} + {:cmds ["foo" "bar"] :spec {:some-foo-bar-opt {}}}] + :cmds ["foo" "bar"] + :prog "myprog"}))) + (testing "no :prog" + (is (= (str "Usage: foo bar [options]\n\n" + "Options:\n" + " --some-foo-bar-opt\n\n" + "Run \"--help\" for global options.\n" + "Run \"foo --help\" for foo options.") + (cli/format-command-help + {:table [{:cmds [] :spec {:some-default-opt {}}} + {:cmds ["foo"] :spec {:some-foo-opt {}}} + {:cmds ["foo" "bar"] :spec {:some-foo-bar-opt {}}}] + :cmds ["foo" "bar"]}))))))) (deftest format-command-error-test (let [table [{:cmds [] :doc "tool"} @@ -1328,6 +1357,14 @@ "Commands:\n dev Start dev.\n deps Dep tools\n\n" "Run \"tool --help\" for more information.") s)))) + (testing "no :prog" + (let [s (cli/format-command-error {:cause :no-match :wrong-input "nope" + :dispatch [] + :tree (cli/table->tree table)})] + (is (= (str "Unknown command: nope\n\n" + "Commands:\n dev Start dev.\n deps Dep tools\n\n" + "Run \"--help\" for more information.") + s)))) (testing ":input-exhausted (incomplete multi-word command) renders the group's commands" (let [s (cli/format-command-error {:cause :input-exhausted :dispatch ["deps"] :prog "tool" @@ -1396,6 +1433,7 @@ (testing ":help true works (no :prog; usage line omits it)" (let [{:keys [out exit]} (run-dispatch [{:cmds [] :doc "t"} {:cmds ["go"] :fn identity :doc "Go"}] ["--help"] {:help true})] + (is (str/includes? out "Usage: [options] ")) (is (str/includes? out "Commands:")) (is (nil? exit)))) (testing "*exit-fn* codes can be remapped by :cause (e.g. group -> 0)"