Babashka CLI is a new library for command line argument parsing. The main ideas:
-X
style invocations. For lazy people like me! If you are not familiar with clj -X
, read the docs here.:dir foo
instead of :dir '"foo"'
or who knows how to write the latter in cmd.exe
or Powershell.Given the function:
(defn foo [{:keys [force dir] :as m}]
(prn m))
and with a little bit of config in your deps.edn
, you can call the function from the command line using:
clj -M:foo --force --dir=src
or:
clj -M:foo --force --dir src
which will then print:
{:force true, :dir "src"}
We did not have to teach babashka CLI anything about the expected arguments.
Another accepted syntax is:
clj -M:foo :force false :dir src
and this is parsed as:
{:force false, :dir "src"}
Booleans, numbers and keywords are auto-coerced, but if you want to make things strict, you can use metadata. E.g. if we want to accept a keyword for the option mode
:
clj -M:foo :force false :dir src :mode overwrite
and parse it as:
{:force false, :dir "src" :mode :overwrite}
you can teach babashka CLI using metadata:
(defn foo
{:org.babashka/cli {:coerce {:mode :keyword}}}
[{:keys [force dir mode] :as m}]
(prn m))
A leading colon is also accepted (and auto-coerced as keyword):
clj -M:foo :force false :dir src :mode :overwrite
The metadata format is set up in such a way that libraries need not have a dependency on babashka CLI itself.
Did you notice that the -M
invocation now becomes almost identical to -X
, but without quotes?
clj -M:foo :force true :dir src :mode :overwrite
clj -X:foo :force true :dir '"src"' :mode :overwrite
Let's look at a recent project, http-server, where I used babashka CLI to serve both -X
, and -M
needs.
The only argument hints defined there right now are:
(def ^:private cli-opts {:coerce {:port :long}})
although that could have been left out since numbers are auto-coerced.
The -main
function simply defers to the clojure exec
API function (intended for -X
usage) with the parsed arguments:
(defn ^:no-doc -main [& args]
(exec (cli/parse-opts args cli-opts)))
In turn, the exec
function adds some light logic making it suitable for command line usage. It prints help when :help
is true. Because I'm lazy, I just print the docstring of serve
, the function that's going to be called:
(defn exec
"Exec function, intended for command line usage. Same API as serve but
blocks until process receives SIGINT."
{:org.babashka/cli cli-opts}
[opts]
(if (:help opts)
(println (:doc (meta #'serve)))
(do (serve opts)
@(promise))))
Also the exec
function blocks, preventing the process from immediately exiting.
Now when I add this function to deps.edn
using:
:serve {:deps {org.babashka/http-server {:mvn/version "0.1.3"}}
:main-opts ["-m" "babashka.http-server"]
:exec-fn babashka.http-server/exec}
it can be called both with -M
and -X
:
$ clj -M:serve --port 1339
or:
$ clj -M:serve :port 1339
or:
$ clj -X:serve :port 1339
And help printing is supported in both styles:
$ clj -M:serve --help
Serves static assets using web server.
Options:
* `:dir` - directory from which to serve assets
* `:port` - port
or:
$ clj -X:serve :help true
The -main
function can also be used in babashka scripts:
#!/usr/bin/env bb
(require '[babashka.deps :as deps])
(deps/add-deps
'{:deps {org.babashka/http-server {:mvn/version "0.1.3"}}})
(require '[babashka.http-server :as http-server])
(apply http-server/-main *command-line-args*)
$ http-server --help
$ http-server --port 1339
I hope you're convinced that with very little code, babashka CLI can let you support both -M
, -X
style invocations and babashka scripts, while improving command line UX!
Published: 2022-06-24
Tagged: clojure