Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ For breaking changes, check [here](#breaking-changes).

[Babashka CLI](https://github.com/babashka/cli): turn Clojure functions into CLIs!

## Unreleased

- A required `:inherit` option may be supplied after its command, not only before it

## 0.12.80

- Add ordered `:enum` values for validation, help and completion
Expand Down
33 changes: 20 additions & 13 deletions src/babashka/cli.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -532,19 +532,26 @@
:opts m}
flag (assoc :flag flag)))))))
(when require
(doseq [k require]
(when-not (find m k)
(let [flag (get opt->flag k)
arg (when (contains? positional k) (arg-label spec-map k))]
(error-fn (cond-> {:cause :require
:msg (if arg
(str "Required argument: " arg)
(str "Required option: " (flag-for k)))
:require require
:option k
:opts m}
arg (assoc :arg arg)
flag (assoc :flag flag)))))))
;; in a dispatch tree, a leading command word after this level's options
;; routes to a child, where an `:inherit` option can still be supplied
;; (`group sub --opt v`). Defer its requirement to that child; the terminal
;; level, which does not descend, enforces it.
(let [descending? (contains? (::dispatch-tree-ignored-args opts)
(first (-> (meta m) :org.babashka/cli :args)))]
(doseq [k require]
(when (and (not (find m k))
(not (and descending? (:inherit (get spec-map k)))))
(let [flag (get opt->flag k)
arg (when (contains? positional k) (arg-label spec-map k))]
(error-fn (cond-> {:cause :require
:msg (if arg
(str "Required argument: " arg)
(str "Required option: " (flag-for k)))
:require require
:option k
:opts m}
arg (assoc :arg arg)
flag (assoc :flag flag))))))))
(when validate
(doseq [[k vf] validate]
(let [f (or (and
Expand Down
37 changes: 37 additions & 0 deletions test/babashka/cli_test.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -1638,6 +1638,43 @@
(is (thrown-with-msg?
#?(:cljd Object :default Exception) #"Unknown option: --registry"
(cli/dispatch table ["deps" "outdated" "--registry" "X"] {:restrict true})))))
(testing "a non-:inherit parent option's spec does not reach the child after the command"
(let [table [{:cmds ["deps"] :spec {:registry {:coerce :keyword
:validate #{:a :b}}}}
{:cmds ["deps" "outdated"] :fn identity}]]
(testing "parent :coerce is not applied (value stays a raw string)"
(is (= {:registry "X"}
(:opts (cli/dispatch table ["deps" "outdated" "--registry" "X"])))))
(testing "parent :validate is not applied (an invalid value is not rejected)"
(is (= {:registry "zzz"}
(:opts (cli/dispatch table ["deps" "outdated" "--registry" "zzz"])))))
(testing "the same option before the command IS parsed by the parent spec"
(is (= {:registry :a}
(:opts (cli/dispatch table ["deps" "--registry" "a" "outdated"]))))
(is (thrown-with-msg?
#?(:cljd Object :default Exception) #"Invalid value for option --registry"
(cli/dispatch table ["deps" "--registry" "zzz" "outdated"]))))))
(testing "a required :inherit option may be supplied on either side of the command"
(let [table [{:cmds ["deps"] :spec {:registry {:require true :inherit true}}}
{:cmds ["deps" "outdated"] :fn identity}]]
(testing "before the command"
(is (= {:registry "X"} (:opts (cli/dispatch table ["deps" "--registry" "X" "outdated"])))))
(testing "after the command (require deferred to where it is supplied)"
(is (= {:registry "X"} (:opts (cli/dispatch table ["deps" "outdated" "--registry" "X"])))))
(testing "still required when supplied nowhere"
(is (thrown-with-msg?
#?(:cljd Object :default Exception) #"Required option: --registry"
(cli/dispatch table ["deps" "outdated"]))))
(testing "required at the group itself (no command)"
(is (thrown-with-msg?
#?(:cljd Object :default Exception) #"Required option: --registry"
(cli/dispatch table ["deps"])))))
(testing "a required option WITHOUT :inherit is still parent-only (errors after the command)"
(let [table [{:cmds ["deps"] :spec {:registry {:require true}}}
{:cmds ["deps" "outdated"] :fn identity}]]
(is (thrown-with-msg?
#?(:cljd Object :default Exception) #"Required option: --registry"
(cli/dispatch table ["deps" "outdated" "--registry" "X"]))))))
(testing "dispatch-level :inherit makes options inherit without per-option marking"
(let [table [{:cmds ["deps"] :spec {:registry {} :token {}}}
{:cmds ["deps" "outdated"] :fn identity :spec {:format {}}}]]
Expand Down