@babashka/cli 0.12.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,1837 @@
1
+ # babashka.cli
2
+
3
+ [![Clojars Project](https://img.shields.io/clojars/v/org.babashka/cli.svg)](https://clojars.org/org.babashka/cli)
4
+ [![bb built-in](https://raw.githubusercontent.com/babashka/babashka/master/logo/built-in-badge.svg)](https://book.babashka.org#badges)
5
+
6
+ Turn Clojure functions into Command Line Interfaces! This library can be used from:
7
+ - [Babashka](https://book.babashka.org/) - included as a built-in library
8
+ - [Clojure on the JVM](https://www.clojure.org/guides/install_clojure) - we support Clojure 1.10.3 and above on Java 11 and above
9
+ - [ClojureScript](https://clojurescript.org) - we test against the current release
10
+ - [Squint](https://github.com/squint-cljs/squint) - we test againt the current release
11
+ - [ClojureDart](https://github.com/Tensegritics/ClojureDart) - we test against the current release
12
+
13
+ ## [API](API.md)
14
+
15
+ ## Installation
16
+
17
+ For Clojure, ClojureScript, and ClojureDart include a `:deps` entry in your `deps.edn` file:
18
+
19
+ ``` clojure
20
+ org.babashka/cli {:mvn/version "<latest-version>"}
21
+ ```
22
+
23
+ For babashka, no changes are needed; `org.babashka/cli` is a babashka built-in library.
24
+
25
+ For JavaScript, install from NPM:
26
+
27
+ ```
28
+ npm install @babashka/cli
29
+ ```
30
+
31
+ See [JavaScript](#javascript) for how to use this library in JS.
32
+
33
+ ## Intro
34
+
35
+ Turn a Clojure function into a CLI that takes Unix-style command line arguments. E.g.:
36
+
37
+ ```shell
38
+ $ program command --verbose --long-opt1 v1 -o v2 arg
39
+ ```
40
+
41
+ Where:
42
+ - `program` is your executable program or script (other libraries might call this "command")
43
+ - `command` is a single or multi-word command for your program (other libraries might call this "subcommand")
44
+ - `--verbose` is a boolean option
45
+ - `--long-opt1 v1` is an option
46
+ - `-o v2` is a short option
47
+ - `arg` is a positional argument
48
+
49
+ See [Terminology](#terminology) for precise definitions.
50
+
51
+ The main ideas:
52
+
53
+ - Put as little effort as possible into turning a Clojure function into a CLI,
54
+ similar to `-X` exec style invocations. For lazy people like me! If you are not
55
+ familiar with `clj -X`, read the docs
56
+ [here](https://clojure.org/reference/clojure_cli#use_fn).
57
+ - But with a better user experience by not having to use quotes on the command line as a
58
+ result of having to pass EDN directly: `--dir foo` instead of `:dir '"foo"'` (or
59
+ who knows how to write the latter in Windows' `cmd.exe` or Powershell?).
60
+ - By default, employ an open world assumption: passing extra arguments does not break, and arguments
61
+ can be reused in multiple contexts.
62
+ - But also support incremental restrictions and validations as a way to polish a CLI for production use.
63
+
64
+ See [clojure CLI](#clojure-cli) for how to turn your `-X` exec functions into CLIs.
65
+
66
+ ## Terminology
67
+
68
+ ```shell
69
+ $ program command --verbose --long-opt1 v1 -o v2 arg
70
+ ```
71
+
72
+ | Term | Meaning |
73
+ | --- | --- |
74
+ | `program` | The executable program, typically launched in a terminal |
75
+ | `command` | A single or multi-word command for the program. Other libraries often call this a subcommand |
76
+ | `option` | A named option, written `--opt val` or short alias `-o val` |
77
+ | `flag` | The literal `--opt` part of `--opt val`, `-o` in `-o v2`, or `:opt` _as the user passed it_. We use the word flag both for boolean and non-boolean options. |
78
+ | `alias` | A single character alias for the option name |
79
+ | `argument` | A positional argument |
80
+
81
+ > [!NOTE]
82
+ > To explain terminology choices, a concrete example:
83
+ > ```shell
84
+ > git remote show
85
+ > ```
86
+ > Some CLI libraries call `git` the `command`, and `remote show` the `subcommand`.
87
+ > These same libraries typically use the term `command` when describing `remote show` in usage help.
88
+ > To keep terminology consistent for CLI users and CLI developers, we avoid `subcommand` entirely.
89
+ > We use `program` for `git` and `command` for `remote show`.
90
+
91
+ ## Projects using babashka CLI
92
+
93
+ - [jet](https://github.com/borkdude/jet)
94
+ - [http-server](https://github.com/babashka/http-server)
95
+ - [neil](https://github.com/babashka/neil)
96
+ - [quickdoc](https://github.com/borkdude/quickdoc#clojure-cli)
97
+ - [clj-new](https://github.com/seancorfield/clj-new#babashka-cli)
98
+ - [deps-new](https://github.com/seancorfield/deps-new#babashka-cli)
99
+
100
+ ## TOC
101
+
102
+ - [Simple example](#simple-example)
103
+ - [Options](#options)
104
+ - [Arguments](#arguments)
105
+ - [Commands](#commands)
106
+ - [Completions](#completions)
107
+ - [Adding Production Polish](#adding-production-polish)
108
+ - [Babashka tasks](#babashka-tasks)
109
+ - [Clojure CLI](#clojure-cli)
110
+ - [Leiningen](#leiningen)
111
+
112
+ ## Simple example
113
+
114
+ Here is an example babashka script to get you started. We'll write a small
115
+ stand-in for `git`. Save it to `mygit.clj`.
116
+
117
+ ```clojure
118
+ #!/usr/bin/env bb
119
+ (require '[babashka.cli :as cli]
120
+ '[babashka.fs :as fs])
121
+
122
+ (defn dir-exists? [path]
123
+ (fs/directory? path))
124
+
125
+ (def spec
126
+ {:depth {:coerce :long
127
+ :alias :d ; adds -d alias for --depth
128
+ :desc "Number of commits to fetch"
129
+ :validate pos? ; tests if supplied --depth > 0
130
+ :require true} ; --depth,-d is required
131
+ :dir {:alias :C ; like git's own -C
132
+ :desc "Run as if git was started in <dir>"
133
+ :validate ; tests if --dir exists,
134
+ {:pred dir-exists? ; with a custom error message
135
+ :ex-msg (fn [{:keys [value]}]
136
+ (str "Directory does not exist: " value))}}
137
+ :bare {:coerce :boolean ; defines a boolean flag
138
+ :desc "Create a bare repository"}})
139
+
140
+ (defn run [{:keys [opts]}]
141
+ (println "Here are your cli args!:" opts))
142
+
143
+ (defn -main [& args]
144
+ (cli/dispatch {:fn run :spec spec} args {:prog "mygit" :help true}))
145
+
146
+ (apply -main *command-line-args*)
147
+ ```
148
+
149
+ The `:help true` option supplied to `dispatch` wires up automatic `--help`/`-h` support and terse error messages (as opposed to thrown exceptions) for you.
150
+
151
+ Let's request usage help:
152
+ ```
153
+ $ bb mygit.clj --help
154
+ Usage: mygit [options]
155
+
156
+ Options:
157
+ -d, --depth Number of commits to fetch (required)
158
+ -C, --dir Run as if git was started in <dir>
159
+ --bare Create a bare repository
160
+ -h, --help Show this help
161
+ ```
162
+ See [Commands > Help](#help) to customize help.
163
+
164
+ Let's try running with some options:
165
+ ```
166
+ $ bb mygit.clj --depth 1 --dir my_dir --bare
167
+ Error: Directory does not exist: my_dir
168
+
169
+ Usage: mygit [options]
170
+
171
+ Run "mygit --help" for more information.
172
+ ```
173
+ The directory was validated with `dir-exists?`. Because the directory does not exist, you see the custom error message produced by the `:ex-msg` function.
174
+
175
+ Let's create `my_dir`, then try again:
176
+ ```
177
+ $ mkdir my_dir
178
+ $ bb mygit.clj --depth 1 --dir my_dir --bare
179
+ Here are your cli args!: {:depth 1, :dir my_dir, :bare true}
180
+ ```
181
+ All validations passed, and the `run` function was invoked.
182
+
183
+ The `:depth` option includes `:require true`, let's see what happens when we don't include it on the command line:
184
+ ```
185
+ $ bb mygit.clj
186
+ Error: Required option: --depth
187
+
188
+ Usage: mygit [options]
189
+
190
+ Run "mygit --help" for more information.
191
+ ```
192
+ We, appropriately, get a terse error message and an exit status of 1.
193
+
194
+ ### Adding commands
195
+
196
+ To add commands to this CLI, we need to specify a command structure. We'll just give an example here. See [Commands](#commands) for more info.
197
+
198
+ Alter `mygit.clj`:
199
+ ``` clojure
200
+ ;; renamed from `run`
201
+ (defn clone [{:keys [opts]}]
202
+ (println "Here are your cli args!:" opts))
203
+
204
+ ;; new
205
+ (defn version [_]
206
+ (println "mygit 1.0"))
207
+
208
+ ;; new
209
+ (def tree
210
+ {:cmd {"clone" {:fn clone :doc "Clone a repository" :spec spec}
211
+ "version" {:fn version :doc "Print version"}}})
212
+
213
+ ;; updated to use `tree` command structure
214
+ (defn -main [& args]
215
+ (cli/dispatch tree args {:prog "mygit" :help true}))
216
+ ```
217
+
218
+ `--help` now lists the available commands:
219
+ ```
220
+ $ bb mygit.clj --help
221
+ Usage: mygit [options] <command>
222
+
223
+ Commands:
224
+ clone Clone a repository
225
+ version Print version
226
+
227
+ Options:
228
+ -h, --help Show this help
229
+
230
+ Run "mygit <command> --help" for more information on a command.
231
+ ```
232
+
233
+ The `clone` command calls the `clone` function:
234
+ ```
235
+ $ bb mygit.clj clone --depth 1 --bare
236
+ Here are your cli args!: {:depth 1, :bare true}
237
+ ```
238
+
239
+ The `version` command calls the `version` function:
240
+ ```
241
+ $ bb mygit.clj version
242
+ mygit 1.0
243
+ ```
244
+
245
+ See [Commands](#commands) for shared options, inheritance, and help customization.
246
+
247
+ ## Options
248
+
249
+ If you'd like to parse options yourself (instead of using [`dispatch`](/API.md#dispatch)),
250
+ use either lower-level [`parse-opts`](/API.md#parse-opts) or [`parse-args`](/API.md#parse-args). We will
251
+ use these parse functions in this section to demonstrate how options parsing works.
252
+
253
+ On the command line, a named option is written as `--opt val` or short-form [alias](#aliases) `-o val`.
254
+
255
+ Options are configured with a [spec](#spec) (short for "options specification", not
256
+ `clojure.spec`): a map keyed by option name, each value a map of `:coerce`,
257
+ `:alias`, `:validate`, `:require`, `:desc`, etc., passed under `:spec`:
258
+
259
+ ``` clojure
260
+ {:spec {:port {:coerce :long :alias :p}}}
261
+ ```
262
+
263
+ A terser shape is also supported, where each key is lifted to the top level and
264
+ keyed by option name: `{:coerce {:port :long} :alias {:p :port}}`. It is handy
265
+ for quick scripts and partial parsing, but only a spec can carry `:desc`/`:ref`,
266
+ so generated help and option printing need a spec. The two are otherwise
267
+ equivalent. The examples below use the spec shape.
268
+
269
+ Examples:
270
+
271
+ Parse `{:port 1339}` from command line arguments:
272
+
273
+ ``` clojure
274
+ (require '[babashka.cli :as cli])
275
+
276
+ (cli/parse-opts ["--port" "1339"] {:spec {:port {:coerce :long}}})
277
+ ;;=> {:port 1339}
278
+ ```
279
+
280
+ Use an alias (short option):
281
+
282
+ ``` clojure
283
+ (cli/parse-opts ["-p" "1339"] {:spec {:port {:coerce :long :alias :p}}})
284
+ ;; {:port 1339}
285
+ ```
286
+
287
+ Coerce values into a collection:
288
+
289
+ ``` clojure
290
+ (cli/parse-opts ["--paths" "src" "--paths" "test"] {:spec {:paths {:coerce []}}})
291
+ ;;=> {:paths ["src" "test"]}
292
+
293
+ (cli/parse-opts ["--paths" "src" "test"] {:spec {:paths {:coerce []}}})
294
+ ;;=> {:paths ["src" "test"]}
295
+ ```
296
+
297
+ Transforming into a collection of a certain type:
298
+
299
+ ``` clojure
300
+ (cli/parse-opts ["--foo" "bar" "--foo" "baz"] {:spec {:foo {:coerce [:keyword]}}})
301
+ ;; => {:foo [:bar :baz]}
302
+ ```
303
+
304
+ In addition to the built-in coercion keywords, `:coerce` accepts any function (called
305
+ with the option's value as a string):
306
+
307
+ ``` clojure
308
+ (cli/parse-opts ["--letter" "alpha"] {:spec {:letter {:coerce (fn [s] (subs s 0 1))}}})
309
+ ;;=> {:letter "a"}
310
+ ```
311
+
312
+ Boolean flags are assumed by default, like so:
313
+
314
+ ``` clojure
315
+ (cli/parse-opts ["--verbose"])
316
+ ;;=> {:verbose true}
317
+
318
+ (cli/parse-opts ["-v" "-v" "-v"] {:spec {:verbose {:alias :v :coerce []}}})
319
+ ;;=> {:verbose [true true true]}
320
+ ```
321
+
322
+ But you can explicitly specify `:boolean` coercion (and will sometimes need to, see [Arguments](#arguments)):
323
+
324
+ ``` clojure
325
+ (cli/parse-opts ["--verbose"] {:spec {:verbose {:coerce :boolean}}})
326
+ ;;=> {:verbose true}
327
+
328
+ (cli/parse-opts ["-v" "-v" "-v"] {:spec {:verbose {:alias :v :coerce [:boolean]}}})
329
+ ;;=> {:verbose [true true true]}
330
+ ```
331
+
332
+ Long options also support the syntax `--foo=bar`:
333
+
334
+ ``` clojure
335
+ (cli/parse-opts ["--foo=bar"])
336
+ ;;=> {:foo "bar"}
337
+ ```
338
+
339
+ Flags may be combined into a single short option:
340
+
341
+ ``` clojure
342
+ (cli/parse-opts ["-abc"])
343
+ ;;=> {:a true :b true :c true}
344
+ ```
345
+
346
+ Long options that start with `--no-` are parsed as negative flags:
347
+
348
+ ``` clojure
349
+ (cli/parse-opts ["--no-colors"])
350
+ ;;=> {:colors false}
351
+ ```
352
+ This works for any option. For a boolean option where the negation is meaningful,
353
+ set `:negatable true` in its spec to advertise it in help as `--[no-]colors`.
354
+
355
+
356
+ Babashka CLI also accepts a `:`-prefixed form, `:opt val`, to
357
+ match the Clojure CLI `-X` invocation style. The two forms cannot be mixed in a single
358
+ invocation. Use `--`/`-` or `:`, not both. If you prefer to only allow only `--`/`-` style options, specify `:no-keyword-opts true`:
359
+
360
+ ```clojure
361
+ (cli/parse-args [":foo" "bar"])
362
+ ;; => {:opts {:foo "bar"}}
363
+
364
+ (cli/parse-args [":foo" "bar"] {:no-keyword-opts true})
365
+ ;; => {:args [":foo" "bar"], :opts {}}
366
+
367
+ (cli/parse-args ["--foo" "bar" ":no" "mixing"])
368
+ ;; => {:args [":no" "mixing"], :opts {:foo "bar"}}
369
+ ```
370
+ Notice how unrecognized options are considered [Arguments](#arguments).
371
+
372
+ ### Spec
373
+
374
+ A spec (short for options specification, not `clojure.spec`) is a map keyed by option
375
+ name; each value configures one option.
376
+ Alongside the parsing keys (`:coerce`, `:alias`, `:validate`, ...), it carries
377
+ `:desc`, `:ref`, and `:default-desc` used when printing options (see [Printing options](#printing-options)). For
378
+ example:
379
+
380
+ ``` clojure
381
+ (def spec {:from {:ref "<format>"
382
+ :desc "The input format. <format> can be edn, json or transit."
383
+ :coerce :keyword
384
+ :alias :i
385
+ :default-desc "edn"
386
+ :default :edn}
387
+ :to {:ref "<format>"
388
+ :desc "The output format. <format> can be edn, json or transit."
389
+ :coerce :keyword
390
+ :alias :o
391
+ :default-desc "json"
392
+ :default :json}
393
+ :pretty {:desc "Pretty-print output."
394
+ :alias :p}
395
+ :paths {:desc "Paths of files to transform."
396
+ :coerce []
397
+ :default ["src" "test"]
398
+ :default-desc "src test"}})
399
+ ```
400
+
401
+ You can pass the spec to `parse-opts` under the `:spec` key: `(parse-opts args {:spec spec})`, or when using
402
+ `dispatch`, in the entry for each command.
403
+ An explanation of each key:
404
+
405
+ - `:ref`: a name that describes the option value, which is typically used as a reference in the description (`:desc`)
406
+ - `:desc`: a description of the option.
407
+ - `:coerce`: coerce a string value to a type. Built-in keywords: `:boolean`
408
+ (`:bool`), `:int` (`:long`), `:double`, `:number`, `:symbol`, `:keyword`,
409
+ `:string`, `:edn`, `:auto`. A collection collects repeated values: `[]`
410
+ (vector), `#{}` (set) or `()` (list); put a coercion keyword inside the collection to
411
+ coerce each element (e.g., `[:keyword]`, `#{:int}`). A function is also
412
+ accepted: it is called with the option value as a string and returns the coerced value.
413
+ - `:alias`: an alternative short name; a synonym for the option name.
414
+ - `:default`: default value.
415
+ - `:default-desc`: a string representation of the default value.
416
+ - `:require`: `true` make this opt required.
417
+ - `:validate`: a function used to validate the value of this option (as described
418
+ in the [Validate](#validate) section).
419
+ - `:collect`: collect repeated values into a collection (`[]` vector, `#{}` set
420
+ or `()` list), or a function `(fn [coll arg-value] ...)` for custom collection
421
+ - `:negatable`: `true` shows a boolean option as `--[no-]name` in help (the `--no-name` form parses regardless)
422
+
423
+ ### Custom collection handling
424
+
425
+ For those rare cases when you need it, you can use a `:collect` function for custom collection.
426
+ Here's an example of parsing out `,` separated multi-arg-values:
427
+
428
+ ``` clojure
429
+ (cli/parse-opts ["--foo" "a,b" "--foo=c,d,e" "--foo" "f"]
430
+ {:spec {:foo {:collect (fn [coll arg-value]
431
+ (into (or coll [])
432
+ (str/split arg-value #",")))}}})
433
+ ;; => {:foo ["a" "b" "c" "d" "e" "f"]}
434
+ ```
435
+
436
+ ### Auto-coercion
437
+
438
+ Babashka CLI auto-coerces values that have no explicit coercion
439
+ with [`auto-coerce`](/API.md#auto-coerce):
440
+ It automatically tries to convert booleans, numbers, and keywords.
441
+
442
+ ``` clojure
443
+ (cli/parse-opts ["--num" "1339" "--kw" ":foo" "--bool" "false" "--str" "bar"])
444
+ ;; => {:num 1339, :kw :foo, :bool false, :str "bar"}
445
+
446
+ ;; the actual types...:
447
+ (->> (cli/parse-opts ["--num" "1339" "--kw" ":foo" "--bool" "false" "--str" "bar"])
448
+ (reduce-kv (fn [m k v]
449
+ (assoc m k [v (type v)]))
450
+ {}))
451
+ ;; => {:num [1339 java.lang.Long],
452
+ ;; :kw [:foo clojure.lang.Keyword],
453
+ ;; :bool [false java.lang.Boolean],
454
+ ;; :str ["bar" java.lang.String]}
455
+ ```
456
+
457
+ ### Aliases
458
+
459
+ An `:alias` specifies a synonym short option name for the option name.
460
+
461
+ Babashka CLI distinguishes aliases with characters in common, so a way to implement the common `-v`/`-vv` Unix pattern is:
462
+ ``` clojure
463
+ (def spec {:verbose {:alias :v
464
+ :desc "Enable verbose output."}
465
+ :very-verbose {:alias :vv
466
+ :desc "Enable very verbose output."}})
467
+ ```
468
+
469
+ You get:
470
+
471
+ ```clojure
472
+ (cli/parse-opts ["-v"] {:spec spec})
473
+ ;;=> {:verbose true}
474
+
475
+ (cli/parse-opts ["-vv"] {:spec spec})
476
+ ;;=> {:very-verbose true}
477
+ ```
478
+
479
+ Another way would be to collect the flags in a vector with `:coerce` (and base verbosity on the size of that vector):
480
+
481
+ ``` clojure
482
+ (def spec {:verbose {:alias :v
483
+ :desc "Enable verbose output."
484
+ :coerce []}})
485
+
486
+ user=> (cli/parse-opts ["-vvv"] {:spec spec})
487
+ {:verbose [true true true]}
488
+ ```
489
+
490
+ ## Arguments
491
+
492
+ To parse positional arguments, you can use `parse-args` and/or the `:args->opts`
493
+ option. E.g., to parse arguments for the `git push` command:
494
+
495
+ ``` clojure
496
+ (cli/parse-args ["--force" "ssh://foo"] {:spec {:force {:coerce :boolean}}})
497
+ ;;=> {:args ["ssh://foo"], :opts {:force true}}
498
+
499
+ (cli/parse-args ["ssh://foo" "--force"] {:spec {:force {:coerce :boolean}}})
500
+ ;;=> {:args ["ssh://foo"], :opts {:force true}}
501
+ ```
502
+
503
+ Note that babashka CLI can only disambiguate correctly between values for
504
+ options and trailing arguments with enough `:coerce` information
505
+ available. Without the `:coerce :boolean` info, we get:
506
+
507
+ ``` clojure
508
+ (cli/parse-args ["--force" "ssh://foo"])
509
+ {:opts {:force "ssh://foo"}}
510
+ ```
511
+
512
+ In case of ambiguity `--` may also be used to communicate the boundary between
513
+ options and arguments:
514
+
515
+ ``` clojure
516
+ (cli/parse-args ["--paths" "src" "test" "--" "ssh://foo"] {:spec {:paths {:coerce []}}})
517
+ {:args ["ssh://foo"], :opts {:paths ["src" "test"]}}
518
+ ```
519
+
520
+ ### :args->opts
521
+
522
+ To fold positional arguments into the parsed options, you can use `:args->opts`:
523
+
524
+ ``` clojure
525
+ (def cli-opts {:spec {:force {:coerce :boolean}} :args->opts [:url]})
526
+
527
+ (cli/parse-opts ["--force" "ssh://foo"] cli-opts)
528
+ ;;=> {:force true, :url "ssh://foo"}
529
+ ```
530
+
531
+ ``` clojure
532
+ (cli/parse-opts ["ssh://foo" "--force"] cli-opts)
533
+ ;;=> {:url "ssh://foo", :force true}
534
+ ```
535
+
536
+ If you want to fold a variable number of arguments, you can coerce them into a vector
537
+ and specify the variable number of arguments with `repeat`:
538
+
539
+ ``` clojure
540
+ (def cli-opts {:spec {:bar {:coerce []}} :args->opts (cons :foo (repeat :bar))})
541
+ (cli/parse-opts ["arg1" "arg2" "arg3" "arg4"] cli-opts)
542
+ ;;=> {:foo "arg1", :bar ["arg2" "arg3" "arg4"]}
543
+ ```
544
+
545
+ Options may be interspersed with the positional arguments:
546
+
547
+ ``` clojure
548
+ (def cli-opts {:spec {:foo {:coerce :keyword}
549
+ :bar {:coerce []}
550
+ :force {:coerce :boolean}}
551
+ :args->opts (cons :foo (repeat :bar))})
552
+
553
+ (cli/parse-opts ["arg1" "arg2" "--force" "arg3"] cli-opts)
554
+ ;; => {:foo :arg1, :bar ["arg2" "arg3"], :force true}
555
+ ```
556
+
557
+ This also holds for a command leaf in [dispatch](#commands): a command with
558
+ variadic `:args->opts` parses options before, among, or after its positional
559
+ arguments. Without `:args->opts`, `dispatch` stops at the first positional argument
560
+ (to route commands), so trailing options would not be parsed.
561
+
562
+ ## Commands
563
+
564
+ Babashka CLI handles commands with [dispatch](/API.md#dispatch).
565
+
566
+ ### Single-word Commands
567
+
568
+ Say we want a CLI with a `copy` command, a `delete` command, and an undocumented `debug` command:
569
+
570
+ ```
571
+ $ example copy <file> --dry-run
572
+ $ example delete <file> --recursive --depth 3
573
+ $ example debug
574
+ ```
575
+
576
+ Commands can be specified in two ways: as a tree or a table.
577
+ The difference is more apparent for [Multi-word Commands](#multi-word-commands).
578
+ We'll use the tree structure for this example, save it to `try_cmds.clj`.
579
+
580
+ ```clojure
581
+ (ns try-cmds
582
+ (:require [babashka.cli :as cli]))
583
+
584
+ (defn copy [{:keys [opts]}]
585
+ (prn :copy opts))
586
+
587
+ (defn delete [{:keys [opts]}]
588
+ (prn :delete opts))
589
+
590
+ (def tree
591
+ {:cmd {"copy" {:fn copy :doc "Copy a file\nMore details here" :args->opts [:file]
592
+ :spec {:dry-run {:coerce :boolean :desc "Do a dry run"}}}
593
+ "delete" {:fn delete :doc "Delete a file" :args->opts [:file]
594
+ :spec {:recursive {:coerce :boolean :desc "Recurse"}
595
+ :depth {:coerce :long :desc "Max depth"}}}
596
+ "debug" {:fn prn :doc "Dump internal state"}}
597
+ ;; specify which commands to show and in what order (we exclude hidden debug command)
598
+ :cmd-order ["copy" "delete"]})
599
+
600
+ (defn -main [& args]
601
+ (cli/dispatch tree args {:prog "try-cmds" :help true}))
602
+ ```
603
+
604
+ The same command structure expressed as a table is:
605
+
606
+ ```clojure
607
+ (def table
608
+ [{:cmds ["copy"] :fn copy :doc "Copy a file\nMore details here" :args->opts [:file]
609
+ :spec {:dry-run {:coerce :boolean :desc "Do a dry run"}}}
610
+ {:cmds ["delete"] :fn delete :doc "Delete a file" :args->opts [:file]
611
+ :spec {:recursive {:coerce :boolean :desc "Recurse"}
612
+ :depth {:coerce :long :desc "Max depth"}}}
613
+ ;; hide debug command from usage help and completions with :no-doc
614
+ {:cmds ["debug"] :fn prn :doc "Dump internal state" :no-doc true}])
615
+
616
+ (defn -main [& args]
617
+ (cli/dispatch table args {:prog "try-cmds" :help true}))
618
+ ```
619
+ The order of the entries in the table does not matter when matching commands, but it is used for `--help`.
620
+
621
+ Regardless of tree or table format, each command entry accepts any [parse-args](#options) option (`:spec`,
622
+ `:args->opts`, `:alias`, `:restrict`, ...).
623
+
624
+ > [!NOTE]
625
+ > If you want to try `try_cmds.clj` from your terminal:
626
+ >
627
+ > - For babashka, create `bb.edn` in the same dir:
628
+ > ```clojure
629
+ > {:paths ["."]}
630
+ > ```
631
+ > Then run with `bb -m try-cmds ...` (Our examples below use babashka).
632
+ >
633
+ > - For Clojure, create a `deps.edn` in the same dir:
634
+ > ```clojure
635
+ > {:paths ["."] :deps {org.babashka/cli {:mvn/version "<latest-version>"}}}
636
+ > ```
637
+ > Then run with `clojure -M -m try-cmds ...`
638
+
639
+ `dispatch` matches the given command line args against specified commands and calls the matching entry's `:fn` with the parsed result.
640
+ `:help true` wires up `--help`/`-h` and prints terse errors instead of throwing exceptions (see [Help](#help)):
641
+
642
+ ```
643
+ $ bb -m try-cmds --help
644
+ Usage: try-cmds [options] <command>
645
+
646
+ Commands:
647
+ copy Copy a file
648
+ delete Delete a file
649
+
650
+ Options:
651
+ -h, --help Show this help
652
+
653
+ Run "try-cmds <command> --help" for more information on a command.
654
+ ```
655
+
656
+ The `Commands:` descriptions come from each command entry's `:doc` key.
657
+ The first line of `:doc` is used as a summary for the command.
658
+
659
+ The `debug` command is absent in the help because it is absent from `:cmd-order` (it was suppressed in table format via `:no-doc true`). This also hides `debug` from [completions](#completions).
660
+
661
+ > [!TIP]
662
+ > Like `:cmd-order`, options can be hidden with `:order`.
663
+ > And `:no-doc true` can also be used on an option to hide it.
664
+ > Hiding works well for deprecated or internal commands and options.
665
+
666
+ The full text of `:doc` is shown as the description on the command's `--help` output, between the usage line and `Options:`:
667
+
668
+ ```
669
+ $ bb -m try-cmds copy --help
670
+ Usage: try-cmds copy [options] <file>
671
+
672
+ Copy a file
673
+ More details here
674
+
675
+ Options:
676
+ --dry-run Do a dry run
677
+ -h, --help Show this help
678
+ ```
679
+
680
+ Running `bb -m try-cmds copy the-file --dry-run` calls `copy`, which prints:
681
+
682
+ ``` clojure
683
+ :copy {:file "the-file", :dry-run true}
684
+ ```
685
+
686
+ The `copy` command entry `:fn` is called with a map of the parsed result:
687
+
688
+ - `:opts`: the parsed options (`{:file "the-file" :dry-run true}`; `:file` comes
689
+ from `:args->opts`)
690
+ - `:dispatch`: the matched command path `["copy"]` from the given `dispatch` command structure.
691
+ - `:args`: any leftover positional args (`nil` here)
692
+
693
+ An unknown or missing command prints a terse message and exits the process with a status of 1:
694
+
695
+ ```
696
+ $ bb -m try-cmds bogus
697
+ Unknown command: bogus
698
+
699
+ Commands:
700
+ copy Copy a file
701
+ delete Delete a file
702
+
703
+ Run "try-cmds --help" for more information.
704
+ ```
705
+
706
+ ### Multi-word Commands
707
+
708
+ Sometimes a command can be made up of multiple words.
709
+ Think of `git`, for example. We have `git remote`, `git remote add ...`, `git remote delete ...`, etc.
710
+
711
+ The command hierarchy is:
712
+ - `remote` (level 1) is a parent of both:
713
+ - `remote add` (level 2)
714
+ - `remote delete` (level 2)
715
+
716
+ A `dispatch` tree might be expressed as:
717
+ ```clojure
718
+ {:cmd {"remote"
719
+ {:fn remotes-list :doc "show list of remotes"
720
+ :cmd {"add" {:fn remote-add :doc "add a new remote"}
721
+ "delete" {:fn remote-delete :doc "delete a remote"}}
722
+ :cmd-order ["add" "delete"]}}}
723
+ ```
724
+
725
+ The same commands, expressed as a `dispatch` table:
726
+ ```clojure
727
+ [{:cmds ["remote"] :fn remotes-list :doc "show list of remotes"}
728
+ {:cmds ["remote" "add"] :fn remote-add :doc "add a new remote"}
729
+ {:cmds ["remote" "delete"] :fn remote-delete :doc "delete a remote"}]
730
+ ```
731
+
732
+ Multi-word commands are matched from the command line in the specified order, and the longest matching entry's `:fn` is called.
733
+ So `git remote add` would result in a call to the `remote-add` `:fn` and not the `remotes-list` `:fn`.
734
+
735
+ A command line can have options before and between commands and command hierarchy levels.
736
+
737
+ Root-level options are specified in the root spec.
738
+ A contrived example to illustrate:
739
+
740
+ ``` clojure
741
+ (def root-spec {:foo {:coerce #(str "global-" %)}})
742
+ (def sub1-spec {:bar {:coerce #(str "sub1-" %)}})
743
+ (def sub2-spec {:baz {:coerce #(str "sub2-" %)}})
744
+
745
+ (def tree
746
+ {:spec root-spec
747
+ :cmd {"sub1" {:fn identity :spec sub1-spec
748
+ :cmd {"sub2" {:fn identity :spec sub2-spec}}}}})
749
+
750
+ (cli/dispatch tree ["--foo" "a" "sub1" "--bar" "b" "sub2" "--baz" "c" "arg"])
751
+ ;; => {:dispatch ["sub1" "sub2"],
752
+ ;; :opts {:foo "global-a", :bar "sub1-b", :baz "sub2-c"},
753
+ ;; :args ["arg"]}
754
+ ```
755
+
756
+ <details>
757
+ <summary>For reference, the equivalent command table structure:</summary>
758
+
759
+ ```clojure
760
+ (def table
761
+ [{:cmds [] :spec root-spec} ;; root spec specified with `:cmds []`
762
+ {:cmds ["sub1"] :fn identity :spec sub1-spec}
763
+ {:cmds ["sub1" "sub2"] :fn identity :spec sub2-spec}])
764
+ ```
765
+ </details>
766
+
767
+ Specs are not merged across command hierarchy levels.
768
+ An option is parsed with the spec of the current matching command (while parsing the command line from left to right):
769
+ - `--foo a` appears before any matching command, so is coerced with `root-spec`
770
+ - `--bar b` appears after a matching `sub1` but before `sub2`, so is coerced with `sub1`'s `sub1-spec`
771
+ - `--baz c` appears after matching `sub1` and `sub2`, so is coerced with `sub2`s `sub2-spec`
772
+
773
+ Let's compare with a different ordering on the command line:
774
+ ```clojure
775
+ (cli/dispatch tree ["--foo" "a" "sub1" "sub2" "--bar" "b" "--baz" "c" "arg"])
776
+ ;; => {:dispatch ["sub1" "sub2"],
777
+ ;; :opts {:foo "global-a", :bar "b", :baz "sub2-c"},
778
+ ;; :args ["arg"]}
779
+ ```
780
+ Notice that `--bar` is now after `sub1` and `sub2` and gets default string coercion treatment.
781
+ This is because it was processed with `sub2-spec`, which has no specific coercion for `:bar`.
782
+
783
+ Let's explore how `:restrict` works with command hierarchies.
784
+ ``` clojure
785
+ (def tree
786
+ {:cmd {"group"
787
+ {:spec {:registry {}}
788
+ :cmd {"sub"
789
+ {:fn identity :spec {:format {}}}}}}})
790
+ ```
791
+ <details>
792
+ <summary>Equivalent table syntax</summary>
793
+
794
+ ```clojure
795
+ (def table
796
+ [{:cmds ["group"] :spec {:registry {}}}
797
+ {:cmds ["group" "sub"] :fn identity :spec {:format {}}}])
798
+ ```
799
+ </details>
800
+
801
+ Because `:registry` belongs to the `group` command, it is expected only to be used with the `group` command:
802
+ ```clojure
803
+ (cli/dispatch tree ["group" "--registry" "X" "sub"] {:restrict true})
804
+ ;; => {:dispatch ["group" "sub"], :opts {:registry "X"}, :args nil}
805
+ ```
806
+ and not the `group sub` command:
807
+ ```clojure
808
+ (cli/dispatch tree ["group" "sub" "--registry" "X"] {:restrict true})
809
+ ;; throws: Unknown option: --registry
810
+ ```
811
+
812
+ Mark an option with `:inherit true` to also accept it at any command hierarchy descendant level.
813
+ The option is coerced and restrict-checked wherever it appears:
814
+ ``` clojure
815
+ (def tree
816
+ {:cmd {"group"
817
+ {:spec {:registry {:inherit true}} ;; <--
818
+ :cmd {"sub"
819
+ {:fn identity :spec {:format {}}}}}}})
820
+
821
+ (cli/dispatch tree ["group" "sub" "--registry" "X"] {:restrict true})
822
+ ;; => {:dispatch ["group" "sub"], :opts {:registry "X"}, :args nil}
823
+ ```
824
+
825
+ <details>
826
+ <summary>Equivalent table syntax</summary>
827
+
828
+ ```clojure
829
+ (def table
830
+ [{:cmds ["group"] :spec {:registry {:inherit true}}} ;; <--
831
+ {:cmds ["group" "sub"] :fn identity :spec {:format {}}}])
832
+ ```
833
+ </details>
834
+
835
+ A descendant command may redefine an option in its own spec, in which case the descendant's spec wins.
836
+
837
+ Instead of marking individual options, you can pass an `:inherit` option to `dispatch`.
838
+ Specify `true` to inherit all options, or a set of keys to inherit only those options:
839
+
840
+ ``` clojure
841
+ (def tree
842
+ {:cmd {"group"
843
+ {:spec {:registry {}}
844
+ :cmd {"sub"
845
+ {:fn identity :spec {:format {}}}}}}})
846
+
847
+ (cli/dispatch tree ["group" "sub" "--registry" "X"] {:inherit true})
848
+ ;; => {:dispatch ["group" "sub"], :opts {:registry "X"}, :args nil}
849
+ (cli/dispatch tree ["group" "sub" "--registry" "X"] {:inherit #{:registry}})
850
+ ;; => {:dispatch ["group" "sub"], :opts {:registry "X"}, :args nil}
851
+ ```
852
+
853
+ You can use `:args->opts`, but command matching is always prioritized first:
854
+ ``` clojure
855
+ (def tree
856
+ {:cmd {"sub1"
857
+ {:fn identity :spec sub1-spec :args->opts [:some-opt]
858
+ :cmd {"sub2"
859
+ {:fn identity :spec sub2-spec}}}}})
860
+
861
+ (cli/dispatch tree ["sub1" "dude"])
862
+ ;; => {:dispatch ["sub1"], :opts {:some-opt "dude"}, :args nil}
863
+ (cli/dispatch tree ["sub1" "sub2"])
864
+ ;; => {:dispatch ["sub1" "sub2"], :opts {}, :args nil}
865
+ ```
866
+
867
+ <details>
868
+ <summary>Equivalent table syntax</summary>
869
+
870
+ ```clojure
871
+ (def table
872
+ [{:cmds ["sub1"] :fn identity :spec sub1-spec :args->opts [:some-opt]}
873
+ {:cmds ["sub1" "sub2"] :fn identity :spec sub2-spec}])
874
+ ```
875
+ </details>
876
+
877
+ See [neil](https://github.com/babashka/neil) for a real-world CLI using multi-word commands.
878
+
879
+ ### Command formats
880
+ Commands can be specified in a tree or table format.
881
+ Both formats are supported; use the format that best suits you.
882
+
883
+ The difference between formats becomes apparent when multi-word commands are used.
884
+ For example, let's say we have a CLI with commands:
885
+ - `copy` (hierarchy level 1)
886
+ - `cache` (hierarchy level 1)
887
+ - `cache clean` (hierarchy level 2)
888
+
889
+ The table format represents this structure flatly:
890
+
891
+ ```clojure
892
+ (def table
893
+ [{:cmds [] :spec {:verbose {:coerce :boolean :inherit true :desc "Verbose output"}}} ;; top-level options
894
+ {:cmds ["copy"] :fn copy :doc "Copy a file" :args->opts [:file]
895
+ :spec {:dry-run {:coerce :boolean :desc "Do a dry run"}}}
896
+ {:cmds ["cache"] :doc "Manage the cache"}
897
+ {:cmds ["cache" "clean"] :fn clean :doc "Clean the cache"}])
898
+
899
+ (cli/dispatch table args {:prog "example" :help true})
900
+ ```
901
+
902
+ The tree format, as you would guess, uses nesting.
903
+ The root accepts a `:spec` for top-level options.
904
+ The first level of commands is specified under `:cmd`
905
+ in a map of strings to command options, which are the same as in the table
906
+ above, minus the `:cmds` entry. You can nest arbitrarily deep.
907
+
908
+ ``` clojure
909
+ (def tree
910
+ {:spec {:verbose {:coerce :boolean :inherit true :desc "Verbose output"}}
911
+ :cmd {"copy" {:fn copy :doc "Copy a file" :args->opts [:file]
912
+ :spec {:dry-run {:coerce :boolean :desc "Do a dry run"}}}
913
+ "cache" {:doc "Manage the cache"
914
+ ;; clean is nested under cache
915
+ :cmd {"clean" {:fn clean :doc "Clean the cache"}}}}})
916
+
917
+ (cli/dispatch tree args {:prog "example" :help true})
918
+ ```
919
+
920
+ The table or tree format can be used interchangeably in `dispatch`,
921
+ `format-command-help` and the like.
922
+
923
+ You'll want consistent ordering for help output.
924
+ The tree format uses a map; the nature of Clojure maps is that they become unordered hash-maps after 8 entries.
925
+ You probably don't want to rely on this implementation detail and can explicitly control order with `:cmd-order`.
926
+ Commands not mentioned in `:cmd-order` are left out of printed output, but are still callable on the
927
+ command line.
928
+
929
+ ``` clojure
930
+ {:cmd-order ["copy" "cache"]
931
+ :cmd {"copy" {...}
932
+ "cache" {...}}}
933
+ ```
934
+
935
+ ### Help
936
+
937
+ > For a guided walkthrough of automatic help and shell completions, see this
938
+ > [blog post](https://blog.michielborkent.nl/babashka-cli-help-and-completions.html).
939
+
940
+ Pass `:help true` to `dispatch` (and `:prog`, the program name) to add help to a
941
+ CLI:
942
+
943
+ ``` clojure
944
+ (cli/dispatch tree args {:prog "some-prog" :help true})
945
+ ```
946
+
947
+ - `--help`/`-h` alone prints help for all commands (or usage help for a command-less CLI) and exits with status 0.
948
+ - `some-command --help`/`some-command -h` prints help for `some-command` and exits with status 0.
949
+ This also works for multi-word commands, e.g., `some-prog deps outdated --help` would show help for the `deps outdated` command.
950
+ - A mistyped or missing command prints a terse message and exits with a status of 1.
951
+ - `-h, --help` is listed in each command's available options, appended last. To control
952
+ the order, give the command entry an `:order` (a vector of option keys); it
953
+ is used verbatim, so you decide the order, which options to list, and whether
954
+ to list `--help` at all (omit `:help` from `:order` to hide it; but note, it will still work).
955
+ An example `dispatch` command entry that lists `--help` first:
956
+
957
+ - tree format
958
+ ```clojure
959
+ {:cmd {"foo" {:spec {...} :order [:help :port :verbose]}}}
960
+ ```
961
+ - table format
962
+ ``` clojure
963
+ {:cmds ["foo"] :spec {...} :order [:help :port :verbose]}
964
+ ```
965
+
966
+ Without `:order`, the order is taken from the spec (a vec-of-pairs spec keeps
967
+ its order; a map follows its key order, which Clojure does not guarantee), and
968
+ `--help` is appended.
969
+ - `--help`/`-h` are reserved when `:help true` is specified (a command may still define its
970
+ own `:help`).
971
+ - A command entry's `:epilog` (a string) is rendered verbatim after that command's
972
+ 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.
973
+
974
+ The `:help true` option works for a command-less CLI too.
975
+ `some-prog --help` then shows Usage + Options:
976
+
977
+ - tree format
978
+ ```clojure
979
+ (cli/dispatch {:fn run :spec {:port {:coerce :long :desc "Port"}}}
980
+ args
981
+ {:prog "some-prog" :help true})
982
+ ```
983
+ - table format
984
+ ``` clojure
985
+ (cli/dispatch [{:cmds [] :fn run :spec {:port {:coerce :long :desc "Port"}}}]
986
+ args
987
+ {:prog "some-prog" :help true})
988
+ ```
989
+
990
+ `--help`/`-h` are success paths: they print help and return naturally (no exit call), so
991
+ your `-main` ends and the process exits with a status of 0, like a normal command. Errors go
992
+ through the dynamic `*exit-fn*`, which exits non-zero:
993
+
994
+ | invocation | outcome |
995
+ |---|---|
996
+ | `--help` / `-h` | print help, return (status 0), no `*exit-fn*` |
997
+ | no command or incomplete multi-word command | terse message, `*exit-fn*` exit 1, `:cause :input-exhausted` |
998
+ | unknown command | terse message, `*exit-fn*` exit 1, `:cause :no-match` |
999
+ | option error | terse message, `*exit-fn*` exit 1, `:cause` = the babashka.cli cause |
1000
+
1001
+ You can include a `:doc` without a `:fn` to describe a grouping of multi-word commands.
1002
+ For example, `git bisect` is not something we can invoke, but we can get `--help` for it:
1003
+ ```clojure
1004
+ (cli/dispatch
1005
+ {:cmd {"bisect"
1006
+ {:doc "general bisect help"
1007
+ :cmd {"start" {:fn identity :doc "start the bisect"}
1008
+ "good" {:fn identity :doc "commit is good"}
1009
+ "bad" {:fn identity :doc "commit is bad"}}}}}
1010
+ ["bisect" "--help"]
1011
+ {:prog "git" :help true})
1012
+ ```
1013
+ Outputs:
1014
+ ```
1015
+ Usage: git bisect [options] <command>
1016
+
1017
+ general bisect help
1018
+
1019
+ Commands:
1020
+ start start the bisect
1021
+ good commit is good
1022
+ bad commit is bad
1023
+
1024
+ Options:
1025
+ -h, --help Show this help
1026
+
1027
+ Run "git bisect <command> --help" for more information on a command.
1028
+ ```
1029
+
1030
+ `*exit-fn*` is called on errors, with a map with keys:
1031
+ - `:exit` exit code
1032
+ - `:cause` can be `:no-match`, `:input-exhausted`, or an option cause.
1033
+ - `:dispatch` the matched command
1034
+ - `:data` raw dispatch error data
1035
+
1036
+ The default `*exit-fn*` implementation exits the process (`System/exit` on JVM, `js/process.exit` on Node).
1037
+ Rebind it to not exit (for tests, REPL use) or to remap codes by `:cause`, for example:
1038
+
1039
+ ``` clojure
1040
+ ;; treat a missing command or incomplete multi-word command as success (exit 0) instead of a usage error
1041
+ (binding [cli/*exit-fn* (fn [{:keys [exit cause]}]
1042
+ (System/exit (if (= :input-exhausted cause) 0 exit)))]
1043
+ (cli/dispatch table args {:prog "example" :help true}))
1044
+ ```
1045
+
1046
+ You can optionally override help and error handlers via `dispatch` `:help-fn` and `:error-fn` options.
1047
+
1048
+ To render the standard help and add to it, call `format-command-help`, the same renderer
1049
+ the default uses:
1050
+
1051
+ ``` clojure
1052
+ (cli/dispatch table args
1053
+ {:prog "example" :help true
1054
+ :help-fn (fn [{:keys [tree dispatch prog inherit]}]
1055
+ (println "my-tool v1.2.3")
1056
+ (println (cli/format-command-help
1057
+ {:table tree :cmds dispatch :prog prog :inherit inherit})))})
1058
+ ```
1059
+
1060
+ The function `format-command-help` is also usable on its own (without `dispatch`): pass
1061
+ `:table` (a `dispatch` [table or tree](#tree-format)), `:cmds` (the command
1062
+ path, default `[]`), `:prog`, and optional `:inherit`. It returns the help
1063
+ string.
1064
+
1065
+ A custom `:error-fn` receives the dispatch error data
1066
+ (`{:cause :dispatch :prog :inherit :tree :msg ...}`) and is responsible for
1067
+ exiting (call `*exit-fn*` or exit yourself). To keep the standard terse message
1068
+ and add to it, call `format-command-error` (the same renderer the default uses)
1069
+ and exit afterwards:
1070
+
1071
+ ``` clojure
1072
+ (cli/dispatch table args
1073
+ {:prog "example" :help true
1074
+ :error-fn (fn [data]
1075
+ (println (cli/format-command-error data))
1076
+ (println "See https://example.com/docs")
1077
+ (cli/*exit-fn* {:exit 1 :cause (:cause data)}))})
1078
+ ```
1079
+
1080
+ ## Completions
1081
+
1082
+ The `dispatch` function can generate dynamic shell completions for `bash`,
1083
+ `zsh`, `fish`, `powershell` and `nushell`. Shells call back into your program on
1084
+ each TAB to generate completions. The `:prog` (program name) value is essential
1085
+ in the `dispatch` call. The generated snippet registers completion for that
1086
+ name, so it must match the command you type.
1087
+
1088
+ ``` clojure
1089
+ (cli/dispatch table args {:prog "mycli" :help true})
1090
+ ```
1091
+
1092
+ Under babashka the snippet also registers the running script's own file name (from
1093
+ the `babashka.file` system property), so a script invoked directly by path
1094
+ (`./mycli.clj`, `/abs/mycli.clj`) completes without a `:prog`-named symlink. See
1095
+ [Developing completions](#developing-completions).
1096
+
1097
+ If the installed command has a different name, e.g., when a distro renames it, pass
1098
+ `--prog <name>` when generating the snippet to register that name instead. `--prog`
1099
+ may be repeated to register several names (aliases):
1100
+
1101
+ ``` bash
1102
+ mycli org.babashka.cli/completions snippet --shell zsh --prog sq
1103
+ mycli org.babashka.cli/completions snippet --shell zsh --prog squint --prog sq
1104
+ ```
1105
+
1106
+ The completions call goes through a hidden `org.babashka.cli/completions`
1107
+ command group that `dispatch` adds for you. Running `mycli
1108
+ org.babashka.cli/completions snippet --shell <shell>` prints the install snippet
1109
+ for that specific shell to stdout. It does not write files or edit your shell
1110
+ config for you.
1111
+
1112
+ Commands and options come with completion support out of the box. Descriptions come from the
1113
+ same `:desc` (options) and `:doc` (commands) you already write for `--help`. A
1114
+ `:no-doc` command or option is hidden. Options that already appeared are filtered
1115
+ out of later suggestions, except repeatable options (e.g., `:coerce [:string]`).
1116
+
1117
+ Instructions follow to enable auto-completions in your shell.
1118
+
1119
+ ### Bash
1120
+
1121
+ Add this code to your bash init file:
1122
+
1123
+ ``` bash
1124
+ source <(mycli org.babashka.cli/completions snippet --shell bash)
1125
+ ```
1126
+
1127
+ Bash completes values only and does not show descriptions. For correct handling of
1128
+ `=` and `:` inside values, install the bash-completion package, which needs bash 4.1
1129
+ or newer. The macOS system bash 3.2 still works for the common cases.
1130
+
1131
+ ### Zsh
1132
+
1133
+ Add this to your zsh init file, after `compinit`:
1134
+
1135
+ ``` bash
1136
+ source <(mycli org.babashka.cli/completions snippet --shell zsh)
1137
+ ```
1138
+
1139
+ or save the output as `_mycli` on your `$fpath`. Option and command
1140
+ descriptions show inline. Completions also fire when the program is invoked by
1141
+ path, such as `./mycli`.
1142
+
1143
+ ### Fish
1144
+
1145
+ ``` fish
1146
+ mycli org.babashka.cli/completions snippet --shell fish | source
1147
+ ```
1148
+
1149
+ Option and command descriptions show inline. Completion also fires on a path
1150
+ invocation.
1151
+
1152
+ ### Powershell
1153
+
1154
+ Add this to your `$PROFILE`:
1155
+
1156
+ ``` powershell
1157
+ mycli org.babashka.cli/completions snippet --shell powershell | Out-String | Invoke-Expression
1158
+ ```
1159
+
1160
+ Descriptions show in menu-completion mode, which you can enable with
1161
+ `Set-PSReadLineKeyHandler -Key Tab -Function MenuComplete`.
1162
+
1163
+ ### Nushell
1164
+
1165
+ Nushell cannot `source` from a pipe, so save the snippet to a file in the
1166
+ autoload directory and restart `nu`:
1167
+
1168
+ ``` nu
1169
+ mkdir ($nu.user-autoload-dirs | first)
1170
+ mycli org.babashka.cli/completions snippet --shell nushell | save -f (($nu.user-autoload-dirs | first) | path join "mycli.nu")
1171
+ ```
1172
+
1173
+ On nushell versions without autoload dirs, save it anywhere and add
1174
+ `source <literal path>` to your `config.nu`. Descriptions show in the completion
1175
+ menu.
1176
+
1177
+ Unlike the other shells, nushell has no per-command completion registration. Instead, one
1178
+ global hook (`$env.config.completions.external.completer`) handles TAB for all
1179
+ external commands. The snippet does not overwrite a completer you already have
1180
+ there: it saves the previous one and falls back to it for every command other
1181
+ than `mycli`, so several tools can install side by side.
1182
+
1183
+ ### Developing completions
1184
+
1185
+ Under babashka, the snippet registers completion for the running script's file
1186
+ name (read from the `babashka.file` system property) in addition to `:prog`. A
1187
+ dev build invoked directly completes as-is, by bare name or by path, with no
1188
+ symlink to the `:prog` name required:
1189
+
1190
+ ``` bash
1191
+ ./run.clj <TAB>
1192
+ /abs/path/to/run.clj <TAB>
1193
+ run.clj <TAB> # when its directory is on PATH
1194
+ ```
1195
+
1196
+ Source the snippet for the shell you are testing, using the install command from
1197
+ its section above, and re-source it after each change to your CLI so new commands
1198
+ and options show up.
1199
+
1200
+ When `babashka.file` is not set (e.g. running via `clojure` or an uberjar) the
1201
+ script file name is unknown, so completion is registered for `:prog` only. Make
1202
+ the build callable under that name on `PATH`, for example with a symlink:
1203
+
1204
+ ``` bash
1205
+ ln -sf "$PWD/run.clj" /tmp/mycli # name the link :prog
1206
+ export PATH="/tmp:$PATH" # bash and zsh
1207
+ ```
1208
+
1209
+ In fish use `set -gx PATH /tmp $PATH`, in nushell
1210
+ `$env.PATH = ($env.PATH | prepend /tmp)`. On Windows, put a `mycli` wrapper
1211
+ script on your `PATH` instead of a symlink.
1212
+
1213
+ You can also register one or more explicit names by passing `--prog` (repeatable)
1214
+ when generating the snippet, e.g. `--prog mycli --prog mc`.
1215
+
1216
+ Now `mycli <TAB>` completes commands and `mycli sub --<TAB>` its options. To see the
1217
+ completer's raw output directly, without a shell, call the hidden command
1218
+ yourself. The tokens after `--` are what the shell would pass on TAB, here the
1219
+ command `sub` and a `--` to complete its options. It prints one candidate per
1220
+ line, as the value, a tab, then the description:
1221
+
1222
+ ``` bash
1223
+ mycli org.babashka.cli/completions complete --shell zsh -- sub --
1224
+ ```
1225
+
1226
+ ### Completing option values
1227
+
1228
+ To complete an option's value, give it one of:
1229
+
1230
+ - `:complete` - a static collection of values (or `{:value .. :description ..}`
1231
+ maps)
1232
+ - A set-valued `:validate`, whose members double as completions
1233
+ - `:complete-fn` - a function for dynamic completion
1234
+
1235
+ ``` clojure
1236
+ {:env {:coerce :string
1237
+ :complete ["dev" "staging" "prod"]} ; static list
1238
+ :level {:coerce :keyword
1239
+ :validate #{:local :global :system}} ; reused as completions
1240
+ :branch {:coerce :string
1241
+ :complete-fn (fn [{:keys [to-complete opts]}] ; dynamic
1242
+ (git-branches to-complete))}}
1243
+ ```
1244
+
1245
+ The `:complete-fn` is called with `{:to-complete <partial> :opts <opts parsed so
1246
+ far> :option <key>}` and returns values (strings) (or `{:value .. :description
1247
+ ..}` maps). All three sources are prefix-filtered against the partial value for
1248
+ you.
1249
+
1250
+ An option value with none of these defaults to the shell's own file completion.
1251
+ For a value where file suggestions are not appropriate, you can opt out with
1252
+ `:complete false`.
1253
+
1254
+ Positional arguments mapped with [`:args->opts`](#args-opts) complete in the same
1255
+ way. A positional resolves to its spec key by position, so the same `:complete`,
1256
+ `:complete-fn` or set `:validate` on that key completes the positional too. With
1257
+ `:args->opts [:env]` and `:env {:complete ["dev" "prod"]}`, `mycli deploy <TAB>`
1258
+ completes `dev`/`prod`.
1259
+
1260
+ A positional declared in `:args->opts` with no value completion defaults to the
1261
+ shell's own file completion the same way. So `:args->opts [:file]` with a bare
1262
+ `:file` makes `mycli cat <TAB>` complete filenames and `:complete false` opts
1263
+ out here too.
1264
+
1265
+ ## Creating a Standalone CLI
1266
+ We've covered how to run your CLI via a supported Clojure dialect, e.g.:
1267
+ - `clojure -M -m my-cli --foo bar`
1268
+ - `bb -m my-cli --foo bar`
1269
+ - `bb my_clj.clj --foo bar`
1270
+
1271
+ Sometimes you'll want to run your CLI as a standalone program, e.g. `my-cli --foo bar`.
1272
+ This hides the launching dialect as an implementation detail and supports [shell completions](#completions).
1273
+
1274
+ Some techniques to achieve this are:
1275
+
1276
+ | Technique | macOS & Linux | Windows | Babashka | Clojure | ClojureScript | ClojureDart | Description |
1277
+ |----------------------------------|--------------------|--------------------|--------------------|--------------------|--------------------|--------------------|--------------------------------------------------------------------------------------------------------------------------|
1278
+ | shebang on script | :heavy_check_mark: | :x: | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | For self-contained scripts, a `#!/usr/bin/env bb` at the top of your script |
1279
+ | wrapper shell script | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | An appropriate shell-specific (e.g., bash, CMD, Powershell) wrapper script that launches your CLI. |
1280
+ | bbin | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | :x: | An elegant way to distribute CLIs created with babashka. |
1281
+ | GraalVM native image | :heavy_check_mark: | :heavy_check_mark: | :x: | :heavy_check_mark: | :x: | :x: | Compile your Clojure CLI to an OS-specific executable |
1282
+ | ClojureDart native image | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | :x: | :heavy_check_mark: | Compile your ClojureDart CLI to an OS-specific executable |
1283
+ | use JavaScript native image tool | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | :x: | [Bun](https://bun.com/docs/bundler/executables), for example, supports compiling JavaScript to an os-specific executable |
1284
+ | shebang on JavaScript | :heavy_check_mark: | :x: | :x: | :x: | :heavy_check_mark: | :x: | Prepend compiled ClojureScript with `#!/usr/bin/env node` |
1285
+ | use npm | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | :x: | Distribute your CLI through npm. For a stand-alone experience users would install globally via `npm install -g` |
1286
+
1287
+ ## Adding Production Polish
1288
+ Babashka CLI lets you get up and running quickly.
1289
+ As you move toward production quality, it's helpful to let users know when their inputs are invalid.
1290
+ Strict validation can be introduced with [:restrict](#restrict), [:require](#require), and [:validate](#validate).
1291
+
1292
+ As you add polish, you'll likely make use of a [:spec](#spec) and maybe a custom [:error_fn](#error-handling). Even if your program does not use [commands](#commands), consider using `dispatch` with the `:help true` option (as shown in [Simple Example](#simple-example)) for printed terse error messages (instead of exceptions), and automatic `--help` generation.
1293
+
1294
+ ## Restrict
1295
+
1296
+ Use the `:restrict` option to restrict options to only those explicitly mentioned in configuration:
1297
+
1298
+ ``` clojure
1299
+ (cli/parse-args ["--foo"] {:spec {:bar {}} :restrict true})
1300
+ ;;=>
1301
+ Execution error (ExceptionInfo) at babashka.cli/parse-opts (cli.cljc:357).
1302
+ Unknown option: --foo
1303
+ ```
1304
+
1305
+ ## Require
1306
+
1307
+ Mark an option required in its [spec](#spec) with `:require true`; parsing throws
1308
+ when it is not present:
1309
+
1310
+ ``` clojure
1311
+ (cli/parse-args ["--foo"] {:spec {:bar {:require true}}})
1312
+ ;;=>
1313
+ Execution error (ExceptionInfo) at babashka.cli/parse-opts (cli.cljc:363).
1314
+ Required option: --bar
1315
+ ```
1316
+
1317
+ Required options are shown as `(required)` in `--help`, in the slot a default
1318
+ would otherwise occupy.
1319
+
1320
+ ## Validate
1321
+
1322
+ ``` clojure
1323
+ (cli/parse-args ["--foo" "0"] {:spec {:foo {:validate pos?}}})
1324
+ Execution error (ExceptionInfo) at babashka.cli/parse-opts (cli.cljc:378).
1325
+ Invalid value for option --foo: 0
1326
+ ```
1327
+
1328
+ To gain more control over the error message, use `:pred` and `:ex-msg`:
1329
+
1330
+ ``` clojure
1331
+ (cli/parse-args ["--foo" "0"] {:spec {:foo {:validate {:pred pos? :ex-msg (fn [m] (str "Not a positive number: " (:value m)))}}}})
1332
+ ;;=>
1333
+ Execution error (ExceptionInfo) at babashka.cli/parse-opts (cli.cljc:378).
1334
+ Not a positive number: 0
1335
+ ```
1336
+
1337
+ ## Error handling
1338
+
1339
+ By default, an exception will be thrown in the following situations:
1340
+ - A restricted option is encountered
1341
+ - A required option is missing
1342
+ - Validation fails for an option
1343
+ - Coercion fails for an option
1344
+
1345
+ You may supply a custom error handler function with `:error-fn`. The function
1346
+ will be called with a map containing the following keys:
1347
+ - `:type` - `:org.babashka/cli` (for filtering out other types of errors).
1348
+ - `:cause` - one of:
1349
+ - `:restrict` - a restricted option was encountered.
1350
+ - `:require` - a required option was missing.
1351
+ - `:validate` - validation failed for an option.
1352
+ - `:coerce` - coercion failed for an option.
1353
+ - `:msg` - default error message.
1354
+ - `:option` - the option being parsed when the error occurred.
1355
+ - `:spec` - the spec passed into `parse-opts` (see the [Spec](#spec) section).
1356
+
1357
+ The following keys are present depending on `:cause`:
1358
+ - `:cause :restrict`
1359
+ - `:restrict` - the value of the `:restrict` opt to `parse-args` (see the
1360
+ [Restrict](#restrict) section).
1361
+ - `:cause :require`
1362
+ - `:require` - the value of the `:require` opt to `parse-args` (see the
1363
+ [Require](#require) section).
1364
+ - `:cause :validate`
1365
+ - `:value` - the value of the option that failed validation.
1366
+ - `:validate` - the value of the `:validate` opt to `parse-args` (see the
1367
+ [Validate](#validate) section).
1368
+ - `:cause :coerce`
1369
+ - `:value` - the value of the option that failed coercion.
1370
+
1371
+ By default, babashka CLI will throw exceptions on errors it detects.
1372
+ You can do the same from your custom error handler.
1373
+
1374
+ For a more polished user experience, you might choose to have your custom error handler print the error and exit. For example:
1375
+ ``` clojure
1376
+ (cli/parse-opts
1377
+ []
1378
+ {:spec {:foo {:desc "You know what this is."
1379
+ :ref "<val>"
1380
+ :require true}}
1381
+ :error-fn
1382
+ (fn [{:keys [spec type cause msg option] :as data}]
1383
+ (if (= :org.babashka/cli type)
1384
+ (case cause
1385
+ :require
1386
+ (println
1387
+ (format "Missing required argument:\n%s"
1388
+ (cli/format-opts {:spec (select-keys spec [option])})))
1389
+ (println msg))
1390
+ (throw (ex-info msg data)))
1391
+ (System/exit 1))})
1392
+ ```
1393
+ would print:
1394
+
1395
+ ```
1396
+ Missing required argument:
1397
+ --foo <val> You know what this is.
1398
+ ```
1399
+
1400
+ You can also choose to collect and then report all detected errors (see `babashka.cli-test/error-fn-test` for an example of this).
1401
+
1402
+ ## Adding default args
1403
+
1404
+ You can supply default args with `:exec-args`:
1405
+
1406
+ ``` clojure
1407
+ (cli/parse-args ["--foo" "0"] {:exec-args {:bar 1}})
1408
+ ;;=> {:foo 0, :bar 1}
1409
+ ```
1410
+
1411
+ Note that args specified in `args` will override defaults in `:exec-args`:
1412
+
1413
+ ``` clojure
1414
+ (cli/parse-args ["--foo" "0" "--bar" "42"] {:exec-args {:bar 1}})
1415
+ ;;=> {:foo 0, :bar 42}
1416
+ ```
1417
+
1418
+ ## Printing options
1419
+
1420
+ Given a [spec](#spec) (like the `from`/`to`/`paths`/`pretty` one above), print
1421
+ its options with `format-opts`:
1422
+
1423
+ ``` clojure
1424
+ (println (cli/format-opts {:spec spec :order [:from :to :paths :pretty]}))
1425
+ ```
1426
+
1427
+ This will print:
1428
+
1429
+ ```
1430
+ -i, --from <format> The input format. <format> can be edn, json or transit. (default: edn)
1431
+ -o, --to <format> The output format. <format> can be edn, json or transit. (default: json)
1432
+ --paths Paths of files to transform. (default: src test)
1433
+ -p, --pretty Pretty-print output.
1434
+ ```
1435
+
1436
+ As options can often be reused in multiple commands, you can determine the
1437
+ order _and_ selection of printed options with `:order`. If you don't want to use
1438
+ `:order` and simply want to present the options as written, you can also use a
1439
+ vector of vectors for the spec:
1440
+
1441
+ ``` clojure
1442
+ [[:pretty {:desc "Pretty-print output."
1443
+ :alias :p}]
1444
+ [:paths {:desc "Paths of files to transform."
1445
+ :coerce []
1446
+ :default ["src" "test"]
1447
+ :default-desc "src test"}]]
1448
+ ```
1449
+
1450
+ If you need more flexibility, you can also use `opts->table`, which turns a spec into a vector of vectors, representing rows of a table.
1451
+ You can then use `format-table` to produce a table as returned by `format-opts`.
1452
+ For example, to add a header row with labels for each column, you could do something like:
1453
+
1454
+ ``` clojure
1455
+ (cli/format-table
1456
+ {:rows (concat [["alias" "option" "ref" "default" "description"]]
1457
+ (cli/opts->table
1458
+ {:spec {:foo {:alias :f, :default "yupyupyupyup", :ref "<foo>"
1459
+ :desc "Thingy"}
1460
+ :bar {:alias :b, :default "sure", :ref "<bar>"
1461
+ :desc "Barbarbar" :default-desc "Mos def"}}}))
1462
+ :indent 2})
1463
+ ```
1464
+
1465
+ ### Terminal width
1466
+
1467
+ `format-opts` and `format-table` wrap long descriptions to the terminal width,
1468
+ aligning continuation lines under the description column:
1469
+
1470
+ ```
1471
+ --copy-resources <resource> Copy non cljs/cljc files from --paths as
1472
+ resources; a keyword matches by extension,
1473
+ otherwise by regex
1474
+ ```
1475
+
1476
+ On by default; `:wrap false` disables it.
1477
+
1478
+ The width comes from `:max-width-fn`, a `(fn [cfg] -> width)` defaulting to
1479
+ `cli/default-width-fn`: on node it reads `process.stdout.columns`; on the JVM it
1480
+ reads `$COLUMNS` then probes JLine (when on the classpath). Falls back to 80.
1481
+ Override per call:
1482
+
1483
+ ``` clojure
1484
+ (cli/format-opts {:spec spec :max-width-fn (constantly 80)})
1485
+ ```
1486
+
1487
+ On the JVM, `default-width-fn` reads the real width via JLine when it is on the
1488
+ classpath. babashka bundles it, so bb scripts get it for free; without JLine the
1489
+ width falls back to `$COLUMNS`/80. If you want real-width detection on another
1490
+ JVM, you can add a JLine provider (FFM is the lightest):
1491
+
1492
+ ``` clojure
1493
+ ;; deps.edn
1494
+ org.jline/jline-terminal {:mvn/version "3.30.4"}
1495
+ org.jline/jline-terminal-ffm {:mvn/version "3.30.4"}
1496
+ ```
1497
+
1498
+ ## Babashka tasks
1499
+
1500
+ For documentation on babashka tasks, go
1501
+ [here](https://book.babashka.org/#tasks).
1502
+
1503
+ Since babashka `0.9.160`, `babashka.cli` has become a built-in and has better
1504
+ integration through `-x` and `exec`. Read about that in the [babashka
1505
+ book](https://book.babashka.org/#cli).
1506
+
1507
+ ## Clojure CLI
1508
+
1509
+ The Clojure CLI supports [invoking a function with a single map arg](https://clojure.org/reference/clojure_cli#use_fn) via the `-X` command line option.
1510
+ Because `-X` does no automatic coercion of values, getting the command-line correct can be, at best, awkward on macOS and Linux, and often an exercise of real frustration on Windows.
1511
+
1512
+ You can control parsing behavior by adding `:org.babashka/cli` metadata to
1513
+ Clojure functions. It does not introduce a dependency on `babashka.cli`
1514
+ itself. Not adding any metadata will result in string values, which in many
1515
+ cases may already be a reasonable default.
1516
+
1517
+ Adding support for babashka CLI will cause less friction with shell usage.
1518
+ You can support the same function for both `clojure -X` and `clojure -M` style invocations without
1519
+ writing extra boilerplate.
1520
+
1521
+ In your `deps.edn` `:aliases` entry, add:
1522
+
1523
+ ``` clojure
1524
+ :exec {:extra-deps {org.babashka/cli {:mvn/version "<latest-version>"}}
1525
+ :main-opts ["-m" "babashka.cli.exec"]}
1526
+ ```
1527
+
1528
+ Now you can call any function that accepts a map argument. E.g.:
1529
+
1530
+ ``` clojure
1531
+ $ clojure -M:exec clojure.core prn :a 1 :b 2
1532
+ {:a "1", :b "2"}
1533
+ ```
1534
+
1535
+ Use `:org.babashka/cli` metadata for coercions:
1536
+
1537
+ ``` clojure
1538
+ (ns my-ns)
1539
+
1540
+ (defn foo
1541
+ {:org.babashka/cli {:coerce {:a :symbol
1542
+ :b :long}}}
1543
+ ;; map argument:
1544
+ [m]
1545
+ ;; print map argument:
1546
+ (prn m))
1547
+ ```
1548
+
1549
+ ``` clojure
1550
+ $ clojure -M:exec my-ns foo :a foo/bar :b 2 :c vanilla
1551
+ {:a foo/bar, :b 2, :c "vanilla"}
1552
+ ```
1553
+
1554
+ Note that any library can add support for babashka CLI without depending on
1555
+ babashka CLI.
1556
+
1557
+ An example that specializes `babashka.cli` usage to a function:
1558
+
1559
+ ``` clojure
1560
+ :prn {:extra-deps {org.babashka/cli {:mvn/version "<latest-version>"}}
1561
+ :main-opts ["-m" "babashka.cli.exec" "clojure.core" "prn"]}
1562
+ ```
1563
+
1564
+ ``` clojure
1565
+ $ clojure -M:prn --foo=bar --baz
1566
+ {:foo "bar" :baz true}
1567
+ ```
1568
+
1569
+ You can also pre-define the exec function in `:exec-fn`:
1570
+
1571
+ ``` clojure
1572
+ :prn {:extra-deps {org.babashka/cli {:mvn/version "<latest-version>"}}
1573
+ :exec-fn clojure.core/prn
1574
+ :main-opts ["-m" "babashka.cli.exec"]}
1575
+ ```
1576
+
1577
+ To alter the parsing behavior of functions you don't control, you can add
1578
+ `:org.babashka/cli` data in the `deps.edn` alias:
1579
+
1580
+ ``` clojure
1581
+ :prn {:deps {org.babashka/cli {:mvn/version "<latest-version>"}}
1582
+ :exec-fn clojure.core/prn
1583
+ :main-opts ["-m" "babashka.cli.exec"]
1584
+ :org.babashka/cli {:coerce {:foo :long}}}
1585
+ ```
1586
+
1587
+ ``` clojure
1588
+ $ clojure -M:prn --foo=1
1589
+ {:foo 1}
1590
+ ```
1591
+
1592
+ ### [antq](https://github.com/liquidz/antq)
1593
+
1594
+ `.clojure/deps.edn` alias:
1595
+
1596
+ ``` clojure
1597
+ :antq {:deps {org.babashka/cli {:mvn/version "<latest-version>"}
1598
+ com.github.liquidz/antq {:mvn/version "1.7.798"}}
1599
+ :paths []
1600
+ :main-opts ["-m" "babashka.cli.exec" "antq.tool" "outdated"]
1601
+ :org.babashka/cli {:coerce {:skip []}}}
1602
+ ```
1603
+
1604
+ On the command line you can now run it with:
1605
+
1606
+ ``` clojure
1607
+ $ clj -M:antq --upgrade
1608
+ ```
1609
+
1610
+ Note that we are calling the same `outdated` function that you normally call
1611
+ with `-T`:
1612
+
1613
+ ``` clojure
1614
+ $ clj -Tantq outdated :upgrade true
1615
+ ```
1616
+ even though antq has its own `-main` function.
1617
+
1618
+ Note that we added the `:org.babashka/cli {:coerce {:skip []}}` data in the
1619
+ alias to make sure that `--skip` options get collected into a vector:
1620
+
1621
+ ``` clojure
1622
+ clj -M:antq --upgrade --skip github-action
1623
+ ```
1624
+
1625
+ vs.
1626
+
1627
+ ``` clojure
1628
+ clj -Tantq outdated :upgrade true :skip '["github-action"]'
1629
+ ```
1630
+
1631
+ The following projects have added support for babashka CLI. Feel free to add a PR to
1632
+ list your project as well!
1633
+
1634
+ ### [clj-new](https://github.com/seancorfield/clj-new#babashka-cli)
1635
+
1636
+ ### [codox](https://github.com/weavejester/codox)
1637
+
1638
+ In `deps.edn` create an alias:
1639
+
1640
+ ``` clojure
1641
+ :codox {:extra-deps {org.babashka/cli {:mvn/version "<latest-version>"}
1642
+ codox/codox {:mvn/version "0.10.8"}}
1643
+ :exec-fn codox.main/generate-docs
1644
+ ;; default arguments:
1645
+ :exec-args {:source-paths ["src"]}
1646
+ :org.babashka/cli {:coerce {:source-paths []
1647
+ :doc-paths []
1648
+ :themes [:keyword]}}
1649
+ :main-opts ["-m" "babashka.cli.exec"]}
1650
+ ```
1651
+
1652
+ CLI invocation:
1653
+
1654
+ ``` clojure
1655
+ $ clojure -M:codox --output-path /tmp/out
1656
+ ```
1657
+
1658
+ ### [deps-new](https://github.com/seancorfield/deps-new#babashka-cli)
1659
+
1660
+ ### [kaocha](https://github.com/lambdaisland/kaocha)
1661
+
1662
+ In `deps.edn` create an alias:
1663
+
1664
+ ``` clojure
1665
+ :kaocha {:extra-deps {org.babashka/cli {:mvn/version "<latest-version>"}
1666
+ lambdaisland/kaocha {:mvn/version "1.66.1034"}}
1667
+ :exec-fn kaocha.runner/exec-fn
1668
+ :exec-args {} ;; insert default arguments here
1669
+ :org.babashka/cli {:alias {:watch :watch?
1670
+ :fail-fast :fail-fast?}
1671
+ :coerce {:skip-meta :keyword
1672
+ :kaocha/reporter [:symbol]}}
1673
+ :main-opts ["-m" "babashka.cli.exec"]}
1674
+ ```
1675
+
1676
+ Now you are able to use kaocha's exec-fn to be used as a CLI:
1677
+
1678
+ ``` clojure
1679
+ $ clj -M:kaocha --watch --fail-fast --kaocha/reporter kaocha.report/documentation
1680
+ ```
1681
+
1682
+ ### [quickdoc](https://github.com/borkdude/quickdoc#clojure-cli)
1683
+
1684
+ ### [tools.build](https://github.com/clojure/tools.build)
1685
+
1686
+ In `deps.edn` create an alias:
1687
+
1688
+ ``` clojure
1689
+ :build {:deps {org.babashka/cli {:mvn/version "<latest-version>"}
1690
+ io.github.clojure/tools.build {:git/tag "v0.8.2" :git/sha "ba1a2bf"}}
1691
+ :paths ["."]
1692
+ :ns-default build
1693
+ :main-opts ["-m" "babashka.cli.exec"]}
1694
+ ```
1695
+
1696
+ Now you can call your build functions as CLIs:
1697
+
1698
+ ``` clojure
1699
+ clj -M:build jar --verbose
1700
+ ```
1701
+
1702
+ ### [tools.deps.graph](https://github.com/clojure/tools.deps.graph)
1703
+
1704
+ In `deps.edn` create an alias:
1705
+
1706
+ ``` clojure
1707
+ :graph {:deps {org.babashka/cli {:mvn/version "<latest-version>"}
1708
+ org.clojure/tools.deps.graph {:mvn/version "1.1.68"}}
1709
+ :exec-fn clojure.tools.deps.graph/graph
1710
+ :exec-args {} ;; insert default arguments here
1711
+ :org.babashka/cli {:coerce {:trace-omit [:symbol]}}
1712
+ :main-opts ["-m" "babashka.cli.exec"]}
1713
+ ```
1714
+
1715
+ Then invoke on the command line:
1716
+
1717
+ ``` clojure
1718
+ clj -M:graph --size --output graph.png
1719
+ ```
1720
+
1721
+ ## Leiningen
1722
+
1723
+ This tool can be used to run clojure exec functions with [lein](https://leiningen.org/).
1724
+
1725
+ An example with `clj-new`:
1726
+
1727
+ In `~/.lein/profiles.clj` put:
1728
+
1729
+ ``` clojure
1730
+ {:clj-1.11 {:dependencies [[org.clojure/clojure "1.11.1"]]}
1731
+ :clj-new {:dependencies [[org.babashka/cli "<latest-version>"]
1732
+ [com.github.seancorfield/clj-new "1.2.381"]]}
1733
+ :user {:aliases {"clj-new" ["with-profiles" "+clj-1.11,+clj-new"
1734
+ "run" "-m" "babashka.cli.exec"
1735
+ {:exec-args {:env {:description "My project"}}
1736
+ :coerce {:verbose :long
1737
+ :args []}
1738
+ :alias {:f :force}}
1739
+ "clj-new"]}}}
1740
+ ```
1741
+
1742
+ After that you can use `lein clj-new app` to create a new app:
1743
+
1744
+ ``` clojure
1745
+ $ lein clj-new app --name foobar/baz --verbose 3 -f
1746
+ ```
1747
+
1748
+ <!-- ## Future ideas -->
1749
+
1750
+ <!-- ### Command line syntax for `:coerce` and `:collect` -->
1751
+
1752
+ <!-- Perhaps this library can consider a command line syntax for `:coerce` and -->
1753
+ <!-- `:collect`, e.g.: -->
1754
+
1755
+ <!-- ``` clojure -->
1756
+ <!-- $ clj -M:example --skip.0=github-actions --skip.1=clojure-cli -->
1757
+ <!-- ``` -->
1758
+
1759
+ <!-- ``` clojure -->
1760
+ <!-- $ clj -M:example --lib%sym=org.babashka/cli -->
1761
+ <!-- ``` -->
1762
+
1763
+ <!-- Things to look out for here is if the delimiter works well with bash / zsh / -->
1764
+ <!-- cmd.exe and Powershell. -->
1765
+
1766
+ <!-- ### Merge args from a file -->
1767
+
1768
+ <!-- Merge default arguments from a file so you don't have to write them on the command line: -->
1769
+
1770
+ <!-- ``` clojure -->
1771
+ <!-- --org.babashka/cli-defaults=foo.edn -->
1772
+ <!-- ``` -->
1773
+
1774
+ ## JavaScript
1775
+
1776
+ Babashka CLI is published to NPM as [`@babashka/cli`](https://www.npmjs.com/package/@babashka/cli),
1777
+ compiled from the same source with [Squint](https://github.com/squint-cljs/squint).
1778
+
1779
+ Install it with:
1780
+
1781
+ ```
1782
+ npm install @babashka/cli
1783
+ ```
1784
+
1785
+ and use it:
1786
+
1787
+ ```js
1788
+ import { parseOpts, parseArgs, dispatch, coerce, formatOpts } from '@babashka/cli';
1789
+
1790
+ parseOpts(['--foo', '1', '--bar']);
1791
+ // => { foo: 1, bar: true }
1792
+
1793
+ parseOpts(['--port', '8080'], { coerce: { port: 'int' } });
1794
+ // => { port: 8080 }
1795
+
1796
+ parseArgs(['--foo', '1', 'x', 'y']);
1797
+ // => { opts: { foo: 1 }, args: ['x', 'y'] }
1798
+
1799
+ coerce('42', 'int');
1800
+ // => 42
1801
+
1802
+ const tree = {
1803
+ fn: () => console.log('usage: add'),
1804
+ cmd: {
1805
+ add: {
1806
+ fn: (m) => console.log('add', m.opts),
1807
+ spec: {
1808
+ file: { desc: 'File to add', alias: 'f' },
1809
+ verbose: { coerce: 'boolean', desc: 'Verbose output' },
1810
+ },
1811
+ },
1812
+ },
1813
+ };
1814
+
1815
+ // `help: true` gives every (sub)command an auto-generated --help:
1816
+ dispatch(tree, process.argv.slice(2), { help: true, prog: 'myapp' });
1817
+ ```
1818
+
1819
+ ```
1820
+ $ myapp add --help
1821
+ Usage: myapp add [options]
1822
+
1823
+ Options:
1824
+ -f, --file File to add
1825
+ --verbose Verbose output
1826
+ -h, --help Show this help
1827
+ ```
1828
+
1829
+ Every function is exported under a friendly camelCase name (`parseOpts`,
1830
+ `specToOpts`, `tableToTree`, ...) and under its squint-compiled name
1831
+ (`parse_opts`, `spec__GT_opts`, ...). The `parse-opts*` function is exposed also as `parseOptsRaw`.
1832
+
1833
+ ## License
1834
+
1835
+ Copyright © 2022-2026 Michiel Borkent
1836
+
1837
+ Distributed under the MIT License. See LICENSE.