{"id":13760334,"url":"https://github.com/babashka/cli","last_synced_at":"2025-06-18T17:07:59.312Z","repository":{"id":37033295,"uuid":"499106419","full_name":"babashka/cli","owner":"babashka","description":"Turn Clojure functions into CLIs!","archived":false,"fork":false,"pushed_at":"2025-05-02T10:11:01.000Z","size":353,"stargazers_count":243,"open_issues_count":3,"forks_count":19,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-06-05T06:24:41.543Z","etag":null,"topics":["babashka","clojure","command-line"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/babashka.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-06-02T11:24:04.000Z","updated_at":"2025-05-26T08:45:38.000Z","dependencies_parsed_at":"2023-02-15T08:31:47.744Z","dependency_job_id":"461c0100-b06e-4699-9e41-844838a3b203","html_url":"https://github.com/babashka/cli","commit_stats":{"total_commits":333,"total_committers":12,"mean_commits":27.75,"dds":0.03903903903903905,"last_synced_commit":"d011ad3516be08c829a98013b3839d10b5e4fef2"},"previous_names":[],"tags_count":62,"template":false,"template_full_name":null,"purl":"pkg:github/babashka/cli","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babashka%2Fcli","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babashka%2Fcli/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babashka%2Fcli/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babashka%2Fcli/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/babashka","download_url":"https://codeload.github.com/babashka/cli/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babashka%2Fcli/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260595744,"owners_count":23033790,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["babashka","clojure","command-line"],"created_at":"2024-08-03T13:01:08.053Z","updated_at":"2025-06-18T17:07:54.297Z","avatar_url":"https://github.com/babashka.png","language":"Clojure","funding_links":[],"categories":["Clojure"],"sub_categories":[],"readme":"# babashka.cli\n\n[![Clojars Project](https://img.shields.io/clojars/v/org.babashka/cli.svg)](https://clojars.org/org.babashka/cli)\n[![bb built-in](https://raw.githubusercontent.com/babashka/babashka/master/logo/built-in-badge.svg)](https://book.babashka.org#badges)\n\nTurn Clojure functions into CLIs!\n\n## [API](API.md)\n\n## Status\n\nThis library is still in design phase and may still undergo breaking changes.\nCheck [breaking changes](CHANGELOG.md#breaking-changes) before upgrading!\n\n## Installation\n\nAdd to your `deps.edn` or `bb.edn` `:deps` entry:\n\n``` clojure\norg.babashka/cli {:mvn/version \"\u003clatest-version\u003e\"}\n```\n\n## Intro\n\nCommand line arguments in clojure and babashka CLIs are often in the form:\n\n``` clojure\n$ cli command :opt1 v1 :opt2 v2\n```\n\nor the more Unixy:\n\n``` clojure\n$ cli command --long-opt1 v1 -o v2\n```\n\nThe main ideas:\n\n- Put as little effort as possible into turning a clojure function into a CLI,\n  similar to `-X` style invocations. For lazy people like me! If you are not\n  familiar with `clj -X`, read the docs\n  [here](https://clojure.org/reference/clojure_cli#use_fn).\n- But with a better UX by not having to use quotes on the command line as a\n  result of having to pass EDN directly: `:dir foo` instead of `:dir '\"foo\"'` or\n  who knows how to write the latter in `cmd.exe` or Powershell.\n- Open world assumption: passing extra arguments does not break and arguments\n  can be re-used in multiple contexts.\n\nBoth `:` and `--` are supported as the initial characters of a named option, but\ncannot be mixed. See [options](https://github.com/babashka/cli#options) for more\ndetails.\n\nSee [clojure CLI](https://github.com/babashka/cli#clojure-cli) for how to turn\nyour exec functions into CLIs.\n\n## Projects using babashka CLI\n\n- [jet](https://github.com/borkdude/jet)\n- [http-server](https://github.com/babashka/http-server)\n- [neil](https://github.com/babashka/neil)\n- [quickdoc](https://github.com/borkdude/quickdoc#clojure-cli)\n- [clj-new](https://github.com/seancorfield/clj-new#babashka-cli)\n- [deps-new](https://github.com/seancorfield/deps-new#babashka-cli)\n\n## TOC\n\n- [Simple example](#simple-example)\n- [Options](#options)\n- [Arguments](#arguments)\n- [Subcommands](#subcommands)\n- [Babashka tasks](#babashka-tasks)\n- [Clojure CLI](#clojure-cli)\n- [Leiningen](#leiningen)\n\n## Simple example\nHere is an example script to get you started!\n\n```clojure\n#!/usr/bin/env bb\n(require '[babashka.cli :as cli]\n         '[babashka.fs :as fs])\n\n(defn dir-exists?\n  [path]\n  (fs/directory? path))\n\n(defn show-help\n  [spec]\n  (cli/format-opts (merge spec {:order (vec (keys (:spec spec)))})))\n\n(def cli-spec\n  {:spec\n   {:num {:coerce :long\n          :desc \"Number of some items\"\n          :alias :n                     ; adds -n alias for --num\n          :validate pos?                ; tests if supplied --num \u003e0\n          :require true}                ; --num,-n is required\n    :dir {:desc \"Directory name to do stuff\"\n          :alias :d\n          :validate dir-exists?}        ; tests if --dir exists\n    :flag {:coerce :boolean             ; defines a boolean flag\n           :desc \"I am just a flag\"}}\n   :error-fn                           ; a function to handle errors\n   (fn [{:keys [spec type cause msg option] :as data}]\n     (when (= :org.babashka/cli type)\n       (case cause\n         :require\n         (println\n           (format \"Missing required argument: %s\\n\" option))\n         :validate\n         (println\n           (format \"%s does not exist!\\n\" msg)))))})\n\n(defn -main\n  [args]\n  (let [opts (cli/parse-opts args cli-spec)]\n    (if (or (:help opts) (:h opts))\n      (println (show-help cli-spec))\n      (println \"Here are your cli args!:\" opts))))\n\n(-main *command-line-args*)\n```\n\nAnd this is how you run it:\n```\n$ bb try-me.clj --num 1 --dir my_dir --flag\nHere are your cli args!: {:num 1, :dir my_dir, :flag true}\n\n$ bb try-me.clj --help\nMissing required argument: :num\n\n  -n, --num  Number of some items\n  -d, --dir  Directory name to do stuff\n      --flag I am just a flag\n```\n\nUsing the [`spec`](https://github.com/babashka/cli#spec) format is optional and you can implement you own parsing logic just with [`parse-opts`/`parse-args`](https://github.com/babashka/cli#options).\nHowever, many would find the above example familiar.\n\n## Options\n\nFor parsing options, use either [`parse-opts`](https://github.com/babashka/cli/blob/main/API.md#parse-opts) or [`parse-args`](https://github.com/babashka/cli/blob/main/API.md#parse-args).\n\nExamples:\n\nParse `{:port 1339}` from command line arguments:\n\n``` clojure\n(require '[babashka.cli :as cli])\n\n(cli/parse-opts [\"--port\" \"1339\"] {:coerce {:port :long}})\n;;=\u003e {:port 1339}\n```\n\nUse an alias (short option):\n\n``` clojure\n(cli/parse-opts [\"-p\" \"1339\"] {:alias {:p :port} :coerce {:port :long}})\n;; {:port 1339}\n```\n\nCoerce values into a collection:\n\n``` clojure\n(cli/parse-opts [\"--paths\" \"src\" \"--paths\" \"test\"] {:coerce {:paths []}})\n;;=\u003e {:paths [\"src\" \"test\"]}\n\n(cli/parse-opts [\"--paths\" \"src\" \"test\"] {:coerce {:paths []}})\n;;=\u003e {:paths [\"src\" \"test\"]}\n```\n\nTransforming to a collection of a certain type:\n\n``` clojure\n(cli/parse-opts [\"--foo\" \"bar\" \"--foo\" \"baz\"] {:coerce {:foo [:keyword]}})\n;; =\u003e {:foo [:bar :baz]}\n```\n\nBooleans need no explicit `true` value and `:coerce` option:\n\n``` clojure\n(cli/parse-opts [\"--verbose\"])\n;;=\u003e {:verbose true}\n\n(cli/parse-opts [\"-v\" \"-v\" \"-v\"] {:alias {:v :verbose}\n                                  :coerce {:verbose []}})\n;;=\u003e {:verbose [true true true]}\n```\n\nLong options also support the syntax `--foo=bar`:\n\n``` clojure\n(cli/parse-opts [\"--foo=bar\"])\n;;=\u003e {:foo \"bar\"}\n```\n\nFlags may be combined into a single short option (since 0.7.51):\n\n``` clojure\n(cli/parse-opts [\"-abc\"])\n;;=\u003e {:a true :b true :c true}\n```\n\nArguments that start with `--no-` arg parsed as negative flags (since 0.7.51):\n\n``` clojure\n(cli/parse-opts [\"--no-colors\"])\n;;=\u003e {:colors false}\n```\n\n### Custom collection handling\n\nUsually the above will suffice, but for custom transformation to a collection, you can use `:collect`.\nHere's an example of parsing out `,` separated multi-arg-values:\n\n``` clojure\n(cli/parse-opts [\"--foo\" \"a,b\" \"--foo=c,d,e\" \"--foo\" \"f\"]\n                {:collect {:foo (fn [coll arg-value]\n                                  (into (or coll [])\n                                        (str/split arg-value #\",\")))}})\n;; =\u003e {:foo [\"a\" \"b\" \"c\" \"d\" \"e\" \"f\"]}\n```\n\n### Auto-coercion\n\nSince `v0.3.35` babashka CLI auto-coerces values that have no explicit coercion\nwith\n[`auto-coerce`](https://github.com/babashka/cli/blob/main/API.md#auto-coerce):\nit automatically tries to convert booleans, numbers and keywords.\n\n## Arguments\n\nTo parse positional arguments, you can use `parse-args` and/or the `:args-\u003eopts`\noption. E.g. to parse arguments for the `git push` command:\n\n``` clojure\n(cli/parse-args [\"--force\" \"ssh://foo\"] {:coerce {:force :boolean}})\n;;=\u003e {:args [\"ssh://foo\"], :opts {:force true}}\n\n(cli/parse-args [\"ssh://foo\" \"--force\"] {:coerce {:force :boolean}})\n;;=\u003e {:args [\"ssh://foo\"], :opts {:force true}}\n```\n\nNote that this library can only disambiguate correctly between values for\noptions and trailing arguments with enough `:coerce` information\navailable. Without the `:force :boolean` info, we get:\n\n``` clojure\n(cli/parse-args [\"--force\" \"ssh://foo\"])\n{:opts {:force \"ssh://foo\"}}\n```\n\nIn case of ambiguity `--` may also be used to communicate the boundary between\noptions and arguments:\n\n``` clojure\n(cli/parse-args [\"--paths\" \"src\" \"test\" \"--\" \"ssh://foo\"] {:coerce {:paths []}})\n{:args [\"ssh://foo\"], :opts {:paths [\"src\" \"test\"]}}\n```\n\n### :args-\u003eopts\n\nTo fold positional arguments into the parsed options, you can use `:args-\u003eopts`:\n\n``` clojure\n(def cli-opts {:coerce {:force :boolean} :args-\u003eopts [:url]})\n\n(cli/parse-opts [\"--force\" \"ssh://foo\"] cli-opts)\n;;=\u003e {:force true, :url \"ssh://foo\"}\n```\n\n``` clojure\n(cli/parse-opts [\"ssh://foo\" \"--force\"] cli-opts)\n;;=\u003e {:url \"ssh://foo\", :force true}\n```\n\nIf you want to fold a variable amount of arguments, you can coerce into a vector\nand specify the variable number of arguments with `repeat`:\n\n``` clojure\n(def cli-opts {:coerce {:bar []} :args-\u003eopts (cons :foo (repeat :bar))})\n(cli/parse-opts [\"arg1\" \"arg2\" \"arg3\" \"arg4\"] cli-opts)\n;;=\u003e {:foo \"arg1\", :bar [\"arg2\" \"arg3\" \"arg4\"]}\n```\n\n## Restrict\n\nUse the `:restrict` option to restrict options to only those explicitly mentioned in configuration:\n\n``` clojure\n(cli/parse-args [\"--foo\"] {:restrict [:bar]})\n;;=\u003e\nExecution error (ExceptionInfo) at babashka.cli/parse-opts (cli.cljc:357).\nUnknown option: :foo\n```\n\n## Require\n\nUse the `:require` option to throw an error when an option is not present:\n\n``` clojure\n(cli/parse-args [\"--foo\"] {:require [:bar]})\n;;=\u003e\nExecution error (ExceptionInfo) at babashka.cli/parse-opts (cli.cljc:363).\nRequired option: :bar\n```\n\n## Validate\n\n``` clojure\n(cli/parse-args [\"--foo\" \"0\"] {:validate {:foo pos?}})\nExecution error (ExceptionInfo) at babashka.cli/parse-opts (cli.cljc:378).\nInvalid value for option :foo: 0\n```\n\nTo gain more control over the error message, use `:pred` and `:ex-msg`:\n\n``` clojure\n(cli/parse-args [\"--foo\" \"0\"] {:validate {:foo {:pred pos? :ex-msg (fn [m] (str \"Not a positive number: \" (:value m)))}}})\n;;=\u003e\nExecution error (ExceptionInfo) at babashka.cli/parse-opts (cli.cljc:378).\nNot a positive number: 0\n```\n\n## Adding default args\n\nYou can supply default args with `:exec-args`:\n\n``` clojure\n(cli/parse-args [\"--foo\" \"0\"] {:exec-args {:bar 1}})\n;;=\u003e {:foo 0, :bar 1}\n```\n\nNote that args specified in `args` will override defaults in `:exec-args`:\n\n``` clojure\n(cli/parse-args [\"--foo\" \"0\" \"--bar\" \"42\"] {:exec-args {:bar 1}})\n;;=\u003e {:foo 0, :bar 42}\n```\n\n## Error handling\n\nBy default, an exception will be thrown in the following situations:\n- A restricted option is encountered\n- A required option is missing\n- Validation fails for an option\n- Coercion fails for an option\n\nYou may supply a custom error handler function with `:error-fn`. The function\nwill be called with a map containing the following keys:\n- `:type` - `:org.babashka/cli` (for filtering out other types of errors).\n- `:cause` - one of:\n  - `:restrict` - a restricted option was encountered.\n  - `:require` - a required option was missing.\n  - `:validate` - validation failed for an option.\n  - `:coerce` - coercion failed for an option.\n- `:msg` - default error message.\n- `:option` - the option being parsed when the error occurred.\n- `:spec` - the spec passed into `parse-opts` (see the [Spec](#spec) section).\n\nThe following keys are present depending on `:cause`:\n- `:cause :restrict`\n  - `:restrict` - the value of the `:restrict` opt to `parse-args` (see the\n    [Restrict](#restrict) section).\n- `:cause :require`\n  - `:require` - the value of the `:require` opt to `parse-args` (see the\n    [Require](#require) section).\n- `:cause :validate`\n  - `:value` - the value of the option that failed validation.\n  - `:validate` - the value of the `:validate` opt to `parse-args` (see the\n    [Validate](#validate) section).\n- `:cause :coerce`\n  - `:value` - the value of the option that failed coercion.\n\nIt is recommended to either throw an exception or otherwise exit in the error\nhandler function, unless you want to collect all of the errors and act on them\nin the end (see `babashka.cli-test/error-fn-test` for an example of this).\n\nFor example:\n\n``` clojure\n(cli/parse-opts\n []\n {:spec {:foo {:desc \"You know what this is.\"\n         :ref \"\u003cval\u003e\"\n         :require true}}\n  :error-fn\n  (fn [{:keys [spec type cause msg option] :as data}]\n    (if (= :org.babashka/cli type)\n      (case cause\n        :require\n        (println\n         (format \"Missing required argument:\\n%s\"\n                 (cli/format-opts {:spec (select-keys spec [option])})))\n        (println msg))\n      (throw (ex-info msg data)))\n    (System/exit 1))})\n```\n\nwould print:\n\n```\nMissing required argument:\n  --foo \u003cval\u003e You know what this is.\n```\n\n## Spec\n\nThis library can work with partial information to parse options. As such, the\noptions to `parse-opts` and `parse-args` are optimized for terseness. However,\nwhen writing a CLI that supports automated printing of options, it is recommended to use the spec format:\n\n``` clojure\n(def spec {:from   {:ref          \"\u003cformat\u003e\"\n                    :desc         \"The input format. \u003cformat\u003e can be edn, json or transit.\"\n                    :coerce       :keyword\n                    :alias        :i\n                    :default-desc \"edn\"\n                    :default      :edn}\n           :to     {:ref          \"\u003cformat\u003e\"\n                    :desc         \"The output format. \u003cformat\u003e can be edn, json or transit.\"\n                    :coerce       :keyword\n                    :alias        :o\n                    :default-desc \"json\"\n                    :default      :json}\n           :pretty {:desc         \"Pretty-print output.\"\n                    :alias        :p}\n           :paths  {:desc         \"Paths of files to transform.\"\n                    :coerce       []\n                    :default      [\"src\" \"test\"]\n                    :default-desc \"src test\"}})\n```\n\nYou can pass the spec to `parse-opts` under the `:spec` key: `(parse-opts args {:spec spec})`.\nAn explanation of each key:\n\n- `:ref`: a name which can be used as a reference in the description (`:desc`)\n- `:desc`: a description of the option.\n- `:coerce`: coerce string to given type.\n- `:alias`: mapping of short name to long name.\n- `:default`: default value.\n- `:default-desc`: a string representation of the default value.\n- `:require`: `true` make this opt required.\n- `:validate`: a function used to validate the value of this opt (as described\n  in the [Validate](#validate) section).\n- `:collect`: for custom collection/transformation of argument values\n\n## Help\n\nGiven the above `spec` you can print options as follows:\n\n``` clojure\n(println (cli/format-opts {:spec spec :order [:from :to :paths :pretty]}))\n```\n\nThis will print:\n\n```\n  -i, --from   \u003cformat\u003e edn      The input format. \u003cformat\u003e can be edn, json or transit.\n  -o, --to     \u003cformat\u003e json     The output format. \u003cformat\u003e can be edn, json or transit.\n      --paths           src test Paths of files to transform.\n  -p, --pretty                   Pretty-print output.\n```\n\nAs options can often be re-used in multiple subcommands, you can determine the\norder _and_ selection of printed options with `:order`. If you don't want to use\n`:order` and simply want to present the options as written, you can also use a\nvector of vectors for the spec:\n\n``` clojure\n[[:pretty {:desc \"Pretty-print output.\"\n           :alias :p}]\n [:paths {:desc \"Paths of files to transform.\"\n          :coerce []\n          :default [\"src\" \"test\"]\n          :default-desc \"src test\"}]]\n```\n\nIf you need more flexibility, you can also use `opts-\u003etable`, which turns a spec into a vector of vectors, representing rows of a table.\nYou can then use`format-table` to produce a table as returned by `format-opts`.\nFor example to add a header row with labels for each column, you could do something like:\n\n``` clojure\n(cli/format-table\n {:rows (concat [[\"alias\" \"option\" \"ref\" \"default\" \"description\"]]\n                (cli/opts-\u003etable\n                 {:spec {:foo {:alias :f, :default \"yupyupyupyup\", :ref \"\u003cfoo\u003e\"\n                               :desc \"Thingy\"}\n                         :bar {:alias :b, :default \"sure\", :ref \"\u003cbar\u003e\"\n                               :desc \"Barbarbar\" :default-desc \"Mos def\"}}}))\n  :indent 2})\n```\n\n### Aliases\n\nAn `:alias` specifies a mapping from short to long name.\n\nThe library can distinguish aliases with characters in common, so a way to implement the common `-v`/`-vv` unix pattern is:\n``` clojure\n(def spec {:verbose      {:alias :v\n                          :desc  \"Enable verbose output.\"}\n           :very-verbose {:alias :vv\n                          :desc  \"Enable very verbose output.\"}})\n```\n\nYou get:\n\n```clojure\n(cli/parse-opts [\"-v\"] {:spec spec})\n;;=\u003e {:verbose true}\n\n(cli/parse-opts [\"-vv\"] {:spec spec})\n;;=\u003e {:very-verbose true}\n```\n\nAnother way would be to collect the flags in a vector with `:coerce` (and base verbosity on the size of that vector):\n\n``` clojure\n(def spec {:verbose {:alias :v\n                     :desc  \"Enable verbose output.\"\n                     :coerce []}})\n\nuser=\u003e (cli/parse-opts [\"-vvv\"] {:spec spec})\n{:verbose [true true true]}\n```\n\n## Subcommands\n\nTo handle subcommands, use\n[dispatch](https://github.com/babashka/cli/blob/main/API.md#dispatch).\n\nAn example. Say we want to create a CLI that can be called as:\n\n``` clojure\n$ example copy \u003cfile\u003e --dry-run\n$ example delete \u003cfile\u003e --recursive --depth 3\n```\n\nThis can be accomplished by doing the following:\n\n``` clojure\n(ns example\n  (:require [babashka.cli :as cli]))\n\n(defn copy [m]\n  (assoc m :fn :copy))\n\n(defn delete [m]\n  (assoc m :fn :delete))\n\n(defn help [m]\n  (assoc m :fn :help))\n\n(def table\n  [{:cmds [\"copy\"]   :fn copy   :args-\u003eopts [:file]}\n   {:cmds [\"delete\"] :fn delete :args-\u003eopts [:file]}\n   {:cmds []         :fn help}])\n\n(defn -main [\u0026 args]\n  (cli/dispatch table args {:coerce {:depth :long}}))\n```\n\nCalling the `example` namespace's `-main` function can be done using `clojure -M -m example` or `bb -m example`.\nThe last entry in the `dispatch-table` always matches and calls the help function.\n\nWhen running `clj -M -m example --help`, `dispatch` calls `help` which returns:\n\n``` clojure\n{:opts {:help true}, :dispatch [], :fn :help}\n```\n\nWhen running `clj -M -m example copy the-file --dry-run`, `dispatch` calls `copy`,\nwhich returns:\n\n``` clojure\n{:cmds [\"copy\" \"the-file\"], :opts {:file \"the-file\" :dry-run true},\n :dispatch [\"copy\"], :fn :copy}\n```\n\nWhen running `clj -M -m example delete the-file --depth 3`, `dispatch` calls `delete` which returns:\n\n``` clojure\n{:cmds [\"delete\" \"the-file\"], :opts {:depth 3, :file \"the-file\"},\n :dispatch [\"delete\"], :fn :delete}\n```\n\nSee [neil](https://github.com/babashka/neil) for a real world example of a CLI\nthat uses subcommands.\n\nAdditional `parse-arg` options may be passed in each table entry:\n\n``` clojure\n(def table\n  [{:cmds [\"copy\"]   :fn copy   :args-\u003eopts [:file] :alias {:f :file :restrict true}}\n   {:cmds [\"delete\"] :fn delete :args-\u003eopts [:file]}\n   {:cmds []         :fn help}])\n```\n\nSince cli 0.8.54 the order of `:cmds` in the table doesn't matter.\n\n### Shared options\n\nSince cli 0.8.54, babashka.cli supports parsing shared options in between and before the subcommands.\n\nE.g.:\n\n``` clojure\n(def global-spec {:foo {:coerce :keyword}})\n(def sub1-spec {:bar {:coerce :keyword}})\n(def sub2-spec {:baz {:coerce :keyword}})\n\n(def table\n  [{:cmds [] :spec global-spec}\n   {:cmds [\"sub1\"] :fn identity :spec sub1-spec}\n   {:cmds [\"sub1\" \"sub2\"] :fn identity :spec sub2-spec}])\n\n(cli/dispatch table [\"--foo\" \"a\" \"sub1\" \"--bar\" \"b\" \"sub2\" \"--baz\" \"c\" \"arg\"])\n\n;;=\u003e\n\n{:dispatch [\"sub1\" \"sub2\"],\n :opts {:foo :a, :bar :b, :baz :c},\n :args [\"arg\"]}\n```\n\nNote that specs are not merged, such that:\n\n``` clojure\n(cli/dispatch table [\"sub1\" \"--foo\" \"bar\"])\n```\n\nreturns `{:dispatch [\"sub1\"], :opts {:foo \"bar\"}}` (`\"bar\"` is not coerced as a keyword).\n\nNote that it is possible to use `:args-\u003eopts` but subcommands are always prioritized over arguments:\n\n``` clojure\n(def table\n  [{:cmds [\"sub1\"] :fn identity :spec sub1-spec :args-\u003eopts [:some-opt]}\n   {:cmds [\"sub1\" \"sub2\"] :fn identity :spec sub2-spec}])\n\n(cli/dispatch table [\"sub1\" \"dude\"]) ;;=\u003e {:dispatch [\"sub1\"], :opts {:some-opt \"dude\"}}\n(cli/dispatch table [\"sub1\" \"sub2\"]) ;;=\u003e {:dispatch [\"sub1\" \"sub2\"], :opts {}}\n```\n\n## Babashka tasks\n\nFor documentation on babashka tasks, go\n[here](https://book.babashka.org/#tasks).\n\nSince babashka `0.9.160`, `babashka.cli` has become a built-in and has better\nintegration through `-x` and `exec`.  Read about that in the [babashka\nbook](https://book.babashka.org/#cli).\n\n## Clojure CLI\n\nYou can control parsing behavior by adding `:org.babashka/cli` metadata to\nClojure functions. It does not introduce a dependency on `babashka.cli`\nitself. Not adding any metadata will result in string values, which in many\ncases may already be a reasonable default.\n\nAdding support for this library will cause less friction with shell usage,\nespecially on Windows since you need less quoting. You can support the same\nfunction for both `clojure -X` and `clojure -M` style invocations without\nwriting extra boilerplate.\n\nIn your `deps.edn` `:aliases` entry, add:\n\n``` clojure\n:exec {:extra-deps {org.babashka/cli {:mvn/version \"\u003clatest-version\u003e\"}}\n       :main-opts [\"-m\" \"babashka.cli.exec\"]}\n```\n\nNow you can call any function that accepts a map argument. E.g.:\n\n``` clojure\n$ clojure -M:exec clojure.core prn :a 1 :b 2\n{:a \"1\", :b \"2\"}\n```\n\nUse `:org.babashka/cli` metadata for coercions:\n\n``` clojure\n(ns my-ns)\n\n(defn foo\n  {:org.babashka/cli {:coerce {:a :symbol\n                               :b :long}}}\n  ;; map argument:\n  [m]\n  ;; print map argument:\n  (prn m))\n```\n\n``` clojure\n$ clojure -M:exec my-ns foo :a foo/bar :b 2 :c vanilla\n{:a foo/bar, :b 2, :c \"vanilla\"}\n```\n\nNote that any library can add support for babashka CLI without depending on\nbabashka CLI.\n\nAn example that specializes `babashka.cli` usage to a function:\n\n``` clojure\n:prn {:extra-deps {org.babashka/cli {:mvn/version \"\u003clatest-version\u003e\"}}\n      :main-opts [\"-m\" \"babashka.cli.exec\" \"clojure.core\" \"prn\"]}\n```\n\n``` clojure\n$ clojure -M:prn --foo=bar --baz\n{:foo \"bar\" :baz true}\n```\n\nYou can also pre-define the exec function in `:exec-fn`:\n\n``` clojure\n:prn {:extra-deps {org.babashka/cli {:mvn/version \"\u003clatest-version\u003e\"}}\n      :exec-fn clojure.core/prn\n      :main-opts [\"-m\" \"babashka.cli.exec\"]}\n```\n\nTo alter the parsing behavior of functions you don't control, you can add\n`:org.babashka/cli` data in the `deps.edn` alias:\n\n``` clojure\n:prn {:deps {org.babashka/cli {:mvn/version \"\u003clatest-version\u003e\"}}\n      :exec-fn clojure.core/prn\n      :main-opts [\"-m\" \"babashka.cli.exec\"]\n      :org.babashka/cli {:coerce {:foo :long}}}\n```\n\n``` clojure\n$ clojure -M:prn --foo=1\n{:foo 1}\n```\n\n### [antq](https://github.com/liquidz/antq)\n\n`.clojure/deps.edn` alias:\n\n``` clojure\n:antq {:deps {org.babashka/cli {:mvn/version \"\u003clatest-version\u003e\"}\n              com.github.liquidz/antq {:mvn/version \"1.7.798\"}}\n       :paths []\n       :main-opts [\"-m\" \"babashka.cli.exec\" \"antq.tool\" \"outdated\"]\n       :org.babashka/cli {:coerce {:skip []}}}\n```\n\nOn the command line you can now run it with:\n\n``` clojure\n$ clj -M:antq --upgrade\n```\n\nNote that we are calling the same `outdated` function that you normally call\nwith `-T`:\n\n``` clojure\n$ clj -Tantq outdated :upgrade true\n```\neven though antq has its own `-main` function.\n\nNote that we added the `:org.babashka/cli {:coerce {:skip []}}` data in the\nalias to make sure that `--skip` options get collected into a vector:\n\n``` clojure\nclj -M:antq --upgrade --skip github-action\n```\n\nvs.\n\n``` clojure\nclj -Tantq outdated :upgrade true :skip '[\"github-action\"]'\n```\n\nThe following projects have added support for babashka CLI. Feel free to add a PR to\nlist your project as well!\n\n### [clj-new](https://github.com/seancorfield/clj-new#babashka-cli)\n\n### [codox](https://github.com/weavejester/codox)\n\nIn `deps.edn` create an alias:\n\n``` clojure\n:codox {:extra-deps {org.babashka/cli {:mvn/version \"\u003clatest-version\u003e\"}\n                     codox/codox {:mvn/version \"0.10.8\"}}\n        :exec-fn codox.main/generate-docs\n        ;; default arguments:\n        :exec-args {:source-paths [\"src\"]}\n        :org.babashka/cli {:coerce {:source-paths []\n                                    :doc-paths []\n                                    :themes [:keyword]}}\n        :main-opts [\"-m\" \"babashka.cli.exec\"]}\n```\n\nCLI invocation:\n\n``` clojure\n$ clojure -M:codox --output-path /tmp/out\n```\n\n### [deps-new](https://github.com/seancorfield/deps-new#babashka-cli)\n\n### [kaocha](https://github.com/lambdaisland/kaocha)\n\nIn `deps.edn` create an alias:\n\n``` clojure\n:kaocha {:extra-deps {org.babashka/cli {:mvn/version \"\u003clatest-version\u003e\"}\n                      lambdaisland/kaocha {:mvn/version \"1.66.1034\"}}\n         :exec-fn kaocha.runner/exec-fn\n         :exec-args {} ;; insert default arguments here\n         :org.babashka/cli {:alias {:watch :watch?\n                                      :fail-fast :fail-fast?}\n                            :coerce {:skip-meta :keyword\n                                     :kaocha/reporter [:symbol]}}\n         :main-opts [\"-m\" \"babashka.cli.exec\"]}\n```\n\nNow you are able to use kaocha's exec-fn to be used as a CLI:\n\n``` clojure\n$ clj -M:kaocha --watch --fail-fast --kaocha/reporter kaocha.report/documentation\n```\n\n### [quickdoc](https://github.com/borkdude/quickdoc#clojure-cli)\n\n### [tools.build](https://github.com/clojure/tools.build)\n\nIn `deps.edn` create an alias:\n\n``` clojure\n:build {:deps {org.babashka/cli {:mvn/version \"\u003clatest-version\u003e\"}\n               io.github.clojure/tools.build {:git/tag \"v0.8.2\" :git/sha \"ba1a2bf\"}}\n        :paths [\".\"]\n        :ns-default build\n        :main-opts [\"-m\" \"babashka.cli.exec\"]}\n```\n\nNow you can call your build functions as CLIs:\n\n``` clojure\nclj -M:build jar --verbose\n```\n\n### [tools.deps.graph](https://github.com/clojure/tools.deps.graph)\n\nIn `deps.edn` create an alias:\n\n``` clojure\n:graph {:deps {org.babashka/cli {:mvn/version \"\u003clatest-version\u003e\"}\n               org.clojure/tools.deps.graph {:mvn/version \"1.1.68\"}}\n        :exec-fn clojure.tools.deps.graph/graph\n        :exec-args {} ;; insert default arguments here\n        :org.babashka/cli {:coerce {:trace-omit [:symbol]}}\n        :main-opts [\"-m\" \"babashka.cli.exec\"]}\n```\n\nThen invoke on the command line:\n\n``` clojure\nclj -M:graph --size --output graph.png\n```\n\n## Leiningen\n\nThis tool can be used to run clojure exec functions with [lein](https://leiningen.org/).\n\nAn example with `clj-new`:\n\nIn `~/.lein/profiles.clj` put:\n\n``` clojure\n{:clj-1.11 {:dependencies [[org.clojure/clojure \"1.11.1\"]]}\n :clj-new {:dependencies [[org.babashka/cli \"\u003clatest-version\u003e\"]\n                          [com.github.seancorfield/clj-new \"1.2.381\"]]}\n :user {:aliases {\"clj-new\" [\"with-profiles\" \"+clj-1.11,+clj-new\"\n                             \"run\" \"-m\" \"babashka.cli.exec\"\n                             {:exec-args {:env {:description \"My project\"}}\n                              :coerce {:verbose :long\n                                       :args []}\n                              :alias {:f :force}}\n                             \"clj-new\"]}}}\n```\n\nAfter that you can use `lein clj-new app` to create a new app:\n\n``` clojure\n$ lein clj-new app --name foobar/baz --verbose 3 -f\n```\n\n\u003c!-- ## Future ideas --\u003e\n\n\u003c!-- ### Command line syntax for `:coerce` and `:collect` --\u003e\n\n\u003c!-- Perhaps this library can consider a command line syntax for `:coerce` and --\u003e\n\u003c!-- `:collect`, e.g.: --\u003e\n\n\u003c!-- ``` clojure --\u003e\n\u003c!-- $ clj -M:example --skip.0=github-actions --skip.1=clojure-cli --\u003e\n\u003c!-- ``` --\u003e\n\n\u003c!-- ``` clojure --\u003e\n\u003c!-- $ clj -M:example --lib%sym=org.babashka/cli --\u003e\n\u003c!-- ``` --\u003e\n\n\u003c!-- Things to look out for here is if the delimiter works well with bash / zsh / --\u003e\n\u003c!-- cmd.exe and Powershell. --\u003e\n\n\u003c!-- ### Merge args from a file --\u003e\n\n\u003c!-- Merge default arguments from a file so you don't have to write them on the command line: --\u003e\n\n\u003c!-- ``` clojure --\u003e\n\u003c!-- --org.babashka/cli-defaults=foo.edn --\u003e\n\u003c!-- ``` --\u003e\n\n## License\n\nCopyright © 2022 Michiel Borkent\n\nDistributed under the MIT License. See LICENSE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbabashka%2Fcli","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbabashka%2Fcli","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbabashka%2Fcli/lists"}