Claude
Skills
Sign in
Back

babashka.cli

Included with Lifetime
$97 forever

Command-line argument parsing for turning Clojure functions into CLIs

General

What this skill does


# babashka.cli

Command-line argument parsing library for transforming Clojure functions into CLIs with minimal effort.

## Overview

babashka.cli converts command-line arguments into Clojure data structures, supporting both keyword-style (`:opt value`) and Unix-style (`--opt value`) arguments. Designed to minimize friction when creating CLIs from existing Clojure functions.

**Key Features:**
- Automatic type coercion
- Flexible argument syntax (`:foo` or `--foo`)
- Subcommand dispatch
- Validation and error handling
- Boolean flags and negative flags
- Collection handling for repeated options
- Default values

**Artifact:** `org.babashka/cli`
**Latest Version:** 0.8.60
**License:** MIT
**Repository:** https://github.com/babashka/cli

## Installation

Add to `deps.edn`:
```clojure
{:deps {org.babashka/cli {:mvn/version "0.8.60"}}}
```

Or `bb.edn` for Babashka:
```clojure
{:deps {org.babashka/cli {:mvn/version "0.8.60"}}}
```

Since babashka 0.9.160, babashka.cli is built-in.

## Core Concepts

### Parsing vs. Args Separation

- `parse-opts` - Returns flat map of parsed options
- `parse-args` - Separates into `:opts`, `:cmds`, `:rest-args`

### Open World Assumption

Extra arguments don't cause errors by default. Use `:restrict` for strict validation.

### Coercion Strategy

Values are coerced based on specifications, not inferred from values alone. This ensures predictable type handling.

## API Reference

### Parsing Functions

#### parse-opts

Parse command-line arguments into options map.

```clojure
(require '[babashka.cli :as cli])

;; Basic parsing
(cli/parse-opts ["--port" "8080"])
;;=> {:port "8080"}

;; With coercion
(cli/parse-opts ["--port" "8080"] {:coerce {:port :long}})
;;=> {:port 8080}

;; With aliases
(cli/parse-opts ["-p" "8080"]
  {:alias {:p :port}
   :coerce {:port :long}})
;;=> {:port 8080}
```

**Options:**
- `:coerce` - Type coercion map (`:boolean`, `:int`, `:long`, `:double`, `:symbol`, `:keyword`)
- `:alias` - Short name to long name mappings
- `:spec` - Structured option specifications
- `:restrict` - Restrict to specified options only
- `:require` - Required option keys
- `:validate` - Validation predicates
- `:exec-args` - Default values
- `:args->opts` - Map positional args to option keys
- `:no-keyword-opts` - Only accept `--foo` style (not `:foo`)
- `:error-fn` - Custom error handler

#### parse-args

Parse arguments with separation of options, commands, and rest args.

```clojure
(cli/parse-args ["--verbose" "deploy" "prod" "--force"]
  {:coerce {:verbose :boolean :force :boolean}})
;;=> {:cmds ["deploy" "prod"]
;;    :opts {:verbose true :force true}
;;    :rest-args []}
```

Returns map with:
- `:opts` - Parsed options
- `:cmds` - Subcommands (non-option arguments)
- `:rest-args` - Arguments after `--`

#### parse-cmds

Extract subcommands from arguments.

```clojure
(cli/parse-cmds ["deploy" "prod" "--force"])
;;=> {:cmds ["deploy" "prod"]
;;    :args ["--force"]}

;; Without keyword opts
(cli/parse-cmds ["deploy" ":env" "prod"]
  {:no-keyword-opts true})
;;=> {:cmds ["deploy" ":env" "prod"]
;;    :args []}
```

### Coercion

#### Type Keywords

- `:boolean` - True/false values
- `:int` - Integer
- `:long` - Long integer
- `:double` - Floating point
- `:symbol` - Clojure symbol
- `:keyword` - Clojure keyword

#### Collection Coercion

Use empty vector to collect multiple values:

```clojure
(cli/parse-opts ["--path" "src" "--path" "test"]
  {:coerce {:path []}})
;;=> {:path ["src" "test"]}
```

Typed collections:

```clojure
(cli/parse-opts ["--port" "8080" "--port" "8081"]
  {:coerce {:port [:long]}})
;;=> {:port [8080 8081]}
```

#### auto-coerce

Automatic coercion for unspecified options (enabled by default):

```clojure
(cli/parse-opts ["--enabled" "true" "--count" "42" "--mode" ":prod"])
;;=> {:enabled true :count 42 :mode :prod}
```

Converts:
- `"true"`/`"false"` → boolean
- Numeric strings → numbers via `edn/read-string`
- Strings starting with `:` → keywords

### Boolean Flags

```clojure
;; Flag present = true
(cli/parse-opts ["--verbose"])
;;=> {:verbose true}

;; Combined short flags
(cli/parse-opts ["-vvv"])
;;=> {:v true}

;; Negative flags
(cli/parse-opts ["--no-colors"])
;;=> {:colors false}

;; Explicit values
(cli/parse-opts ["--force" "false"]
  {:coerce {:force :boolean}})
;;=> {:force false}
```

### Positional Arguments

#### Basic args->opts

Map positional arguments to named options:

```clojure
(cli/parse-opts ["deploy" "production"]
  {:args->opts [:action :env]})
;;=> {:action "deploy" :env "production"}
```

#### Variable Length Collections

Use `repeat` for collecting remaining args:

```clojure
(cli/parse-opts ["build" "foo.clj" "bar.clj" "baz.clj"]
  {:args->opts (cons :cmd (repeat :files))
   :coerce {:files []}})
;;=> {:cmd "build" :files ["foo.clj" "bar.clj" "baz.clj"]}
```

#### Mixed Options and Arguments

```clojure
(cli/parse-opts ["--verbose" "deploy" "prod" "--force"]
  {:coerce {:verbose :boolean :force :boolean}
   :args->opts [:action :env]})
;;=> {:verbose true :action "deploy" :env "prod" :force true}
```

### Validation

#### Required Options

```clojure
(cli/parse-args ["--name" "app"]
  {:require [:name :version]})
;; Throws: Required option: :version
```

#### Restricted Options

```clojure
(cli/parse-args ["--verbose" "--debug"]
  {:restrict [:verbose]})
;; Throws: Unknown option: :debug
```

#### Custom Validators

```clojure
(cli/parse-args ["--port" "0"]
  {:coerce {:port :long}
   :validate {:port pos?}})
;; Throws: Invalid value for option :port: 0

;; With custom message
(cli/parse-args ["--port" "-1"]
  {:coerce {:port :long}
   :validate {:port {:pred pos?
                      :ex-msg (fn [{:keys [option value]}]
                                (str option " must be positive, got: " value))}}})
;; Throws: :port must be positive, got: -1
```

### Default Values

Provide defaults via `:exec-args`:

```clojure
(cli/parse-args ["--port" "9000"]
  {:coerce {:port :long}
   :exec-args {:port 8080 :host "localhost"}})
;;=> {:opts {:port 9000 :host "localhost"}}
```

### Error Handling

Custom error handler:

```clojure
(defn error-handler [{:keys [type cause msg option]}]
  (when (= type :org.babashka/cli)
    (println "Error:" msg)
    (when option
      (println "Option:" option))
    (System/exit 1)))

(cli/parse-args ["--invalid"]
  {:restrict [:valid]
   :error-fn error-handler})
```

Error causes:
- `:restrict` - Unknown option
- `:require` - Missing required option
- `:validate` - Validation failed
- `:coerce` - Type coercion failed

### Subcommand Dispatch

#### dispatch

Route execution based on subcommands:

```clojure
(defn deploy [opts]
  (println "Deploying to" (:env opts)))

(defn rollback [opts]
  (println "Rolling back" (:version opts)))

(def table
  [{:cmds ["deploy"] :fn deploy :args->opts [:env]}
   {:cmds ["rollback"] :fn rollback :args->opts [:version]}
   {:cmds [] :fn (fn [_] (println "No command specified"))}])

(cli/dispatch table ["deploy" "production"])
;; Prints: Deploying to production

(cli/dispatch table ["rollback" "v1.2.3"])
;; Prints: Rolling back v1.2.3
```

#### Nested Subcommands

```clojure
(def table
  [{:cmds ["db" "migrate"] :fn db-migrate}
   {:cmds ["db" "rollback"] :fn db-rollback}
   {:cmds ["db"] :fn (fn [_] (println "db requires subcommand"))}])

(cli/dispatch table ["db" "migrate" "--env" "prod"])
```

#### Dispatch Options

Pass options to parse-args:

```clojure
(cli/dispatch table args
  {:coerce {:port :long}
   :exec-args {:host "localhost"}})
```

The `:fn` receives enhanced parse-args result:
- `:dispatch` - Matched command path
- `:args` - Remaining unparsed arguments
- `:opts` - Parsed options
- `:cmds` - Subcommands

### Formatting & Help

#### format-opts

Generate help text from spec:

```clojure
(def spec
  {:port {:desc "Port to listen on"
          :default 8080
          :coerce :long}
   :host {:desc "Host address"
          :default "localhost"
          :alias :h}
   :verbose {:desc "Enable

Related in General