diff --git a/API.md b/API.md index 0020679..a2e1fd6 100644 --- a/API.md +++ b/API.md @@ -61,7 +61,7 @@ Terminates the process after `dispatch`'s `:help` option prints an *error* Must exit or throw. Default: `System/exit` (JVM), `js/process.exit` (Node), `throw` (browser). -

Source

+

Source

## `apply-defaults` ``` clojure @@ -76,7 +76,7 @@ Fills missing keys in `m` from defaults. Existing keys in `m` win. Supported options: * `:exec-args` - map of defaults. Not subject to `:restrict`. * `:spec` - spec; `:default` entries become defaults via `spec->opts`. -

Source

+

Source

## `auto-coerce` ``` clojure @@ -89,7 +89,7 @@ Auto-coerces `s` to data. Does not coerce when `s` is not a string. * is `true` or `false`, it is coerced as boolean * starts with number, it is coerced as a number (through Clojure's `edn/read-string`) * starts with `:`, it is coerced as a keyword (through [`parse-keyword`](#babashka.cli/parse-keyword)) -

Source

+

Source

## `coerce` ``` clojure @@ -101,7 +101,7 @@ Coerce string `s` using `f`. Does not coerce when `s` is not a string. `f` may be a keyword (`:boolean`, `:int`, `:double`, `:symbol`, `:keyword`) or a function. When `f` return `nil`, this is interpreted as a parse failure and throws. -

Source

+

Source

## `coerce-opts` ``` clojure @@ -123,7 +123,7 @@ Coerces values in the map `m` using the provided configuration. `"--foo"`, `"-f"`, or `":foo"`), as opposed to `:option`, the normalized keyword (`:foo`). It lets a handler echo what the user actually typed rather than reconstruct it. It is omitted when no originating token is known. -

Source

+

Source

## `default-width-fn` ``` clojure @@ -136,7 +136,7 @@ The default `:max-width-fn` for [`format-table`](#babashka.cli/format-table)/[`f width or nil: node `process.stdout.columns`, else `$COLUMNS`, else a JLine provider probe (clj, when JLine is on the classpath, e.g. babashka), else nil (the caller then falls back to 80). -

Source

+

Source

## `dispatch` ``` clojure @@ -209,7 +209,7 @@ Command dispatcher. Each entry in the table may have additional [`parse-args`](#babashka.cli/parse-args) options. For more information and examples, see [README.md](README.md#commands). -

Source

+

Source

## `format-command-error` ``` clojure @@ -233,7 +233,7 @@ Render a terse, helpful message (a string) for a dispatch error. this, then calls [`*exit-fn*`](#babashka.cli/*exit-fn*)). Call it from a custom `:error-fn` to keep the standard message and add your own output. `--help`/`-h` is not an error - it goes to the `:help-fn`, rendered by [`format-command-help`](#babashka.cli/format-command-help). -

Source

+

Source

## `format-command-help` ``` clojure @@ -278,7 +278,7 @@ Render conventional `--help` text (a string) for the command at path `cmds` This is the renderer the `:help` option uses; call it from a custom `:help-fn` to render the standard help and then add your own output. An entry may carry `:no-doc true` to be omitted from `Commands:`. -

Source

+

Source

## `format-opts` ``` clojure @@ -289,7 +289,7 @@ Function. Formats options into an options usage help string. See [Printing options](/README.md#printing-options). -

Source

+

Source

## `format-table` ``` clojure @@ -302,7 +302,7 @@ Function. Formats `rows` into a table (string). See [Printing options](/README.md#printing-options). -

Source

+

Source

## `merge-opts` ``` clojure @@ -311,7 +311,7 @@ Formats `rows` into a table (string). Function. Merges babashka CLI options. -

Source

+

Source

## `opts->table` ``` clojure @@ -321,7 +321,7 @@ Function. Converts options to a table of rows. See [Printing options](/README.md#printing-options). -

Source

+

Source

## `parse-args` ``` clojure @@ -335,7 +335,7 @@ Same as [`parse-opts`](#babashka.cli/parse-opts) with return data reshaped. Returns a map with: * `:opts` parsed opts * `:args` remaining unparsed `args` -

Source

+

Source

## `parse-cmds` ``` clojure @@ -347,7 +347,7 @@ Function. Parses sub-commands (arguments not starting with an option prefix). Returns a map with: * `:cmds` - The parsed commands * `:args` - The remaining (unparsed) arguments -

Source

+

Source

## `parse-keyword` ``` clojure @@ -356,7 +356,7 @@ Parses sub-commands (arguments not starting with an option prefix). Returns a ma Function. Parse keyword from `s`. Ignores leading `:`. -

Source

+

Source

## `parse-opts` ``` clojure @@ -395,7 +395,7 @@ Returns a map of options parsed from command line arguments `args`, a seq of str ;; => throws 'Unknown option --qux' exception b/c there is no :qux key in the spec ``` See also: [`parse-args`](#babashka.cli/parse-args) -

Source

+

Source

## `parse-opts*` ``` clojure @@ -406,7 +406,7 @@ Function. Parses CLI `args` into a raw opts map. Returns string values unchanged (no coercion), does not apply `:exec-args` defaults, does not run `:restrict`/`:require`/`:validate`. Result map includes - `:org.babashka/cli` metadata and internal `::implicit-true-keys` / + `:org.babashka/cli` metadata and internal `::implicit-values` / `::keys-order` metadata used by `coerce-opts`. Use this when you want to merge other sources (e.g. config files) @@ -415,7 +415,7 @@ Parses CLI `args` into a raw opts map. Returns string values unchanged Supported options (subset of `parse-opts`): `:alias`/`:aliases`, `:coerce`, `:collect`, `:no-keyword-opts`, `:repeated-opts`, `:args->opts`, `:spec`. -

Source

+

Source

## `spec->opts` ``` clojure @@ -425,7 +425,7 @@ Parses CLI `args` into a raw opts map. Returns string values unchanged Function. Converts spec into opts format. Pass existing opts as optional second argument. -

Source

+

Source

## `table->tree` ``` clojure @@ -445,7 +445,7 @@ Converts a `dispatch` table into a tree. Each `:cmds` becomes a path of ``` A tree passed in is normalized and returned, so the function is idempotent. -

Source

+

Source

## `validate-opts` ``` clojure @@ -469,7 +469,7 @@ Validates the map `m` using the provided configuration. Returns `m`. keyword (`:foo`). It lets a handler echo what the user actually typed rather than reconstruct it. It is present for `:restrict` and `:validate`, and absent for `:require` (a missing required option was never typed, so it has no token). -

Source

+

Source

----- # babashka.cli.exec diff --git a/CHANGELOG.md b/CHANGELOG.md index a98d972..72bcc1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ For breaking changes, check [here](#breaking-changes). [Babashka CLI](https://github.com/babashka/cli): turn Clojure functions into CLIs! +## Unreleased + +- [#174](https://github.com/babashka/cli/issues/174): `:edn` `:coerce` option must provide an explicit value +([@lread](https://github.com/lread)) + ## v0.12.75 (2026-06-25) - Squint support (v0.14.196+) diff --git a/README.md b/README.md index 3f1551e..c4f5f39 100644 --- a/README.md +++ b/README.md @@ -454,6 +454,19 @@ It automatically tries to convert booleans, numbers, and keywords. ;; :str ["bar" java.lang.String]} ``` +### Coercion to EDN + +`:edn` coercion behaves like Clojure CLI `-X` coercion: + +```clojure +(cli/parse-opts ["--foo" ":some-keyword"] {:spec {:foo {:coerce :edn}}}) +;; => {:foo :some-keyword} +(cli/parse-opts ["--foo" "some-symbol"] {:spec {:foo {:coerce :edn}}}) +;; => {:foo some-symbol} +(cli/parse-opts ["--foo" "\"some-string\""] {:spec {:foo {:coerce :edn}}}) +;; => {:foo "some-string"} +``` + ### Aliases An `:alias` specifies a synonym short option name for the option name. diff --git a/src/babashka/cli.cljc b/src/babashka/cli.cljc index 0c98ccd..ae0743b 100644 --- a/src/babashka/cli.cljc +++ b/src/babashka/cli.cljc @@ -155,7 +155,10 @@ :symbol symbol :keyword parse-keyword :string identity - :edn edn/read-string + :edn (fn [s] + (when (some? implicit-value) + (throw-coerce s implicit-value f nil)) + (edn/read-string s)) :auto auto-coerce ;; default f) diff --git a/test/babashka/cli_test.cljc b/test/babashka/cli_test.cljc index 265bd11..4f2d656 100644 --- a/test/babashka/cli_test.cljc +++ b/test/babashka/cli_test.cljc @@ -98,7 +98,13 @@ (cli/parse-opts [":foo"] {:coerce {:foo :string}}))) (is (thrown-with-msg? #?(:cljd Object :default Exception) #"Missing value for option :foo" - (cli/parse-opts [":foo"] {:coerce {:foo [:string]}})))) + (cli/parse-opts [":foo"] {:coerce {:foo [:string]}}))) + (is (thrown-with-msg? + #?(:cljd Object :default Exception) #"Missing value for option :foo" + (cli/parse-opts [":foo"] {:coerce {:foo :edn}}))) + (is (thrown-with-msg? + #?(:cljd Object :default Exception) #"Missing value for option :foo" + (cli/parse-opts [":foo"] {:coerce {:foo [:edn]}})))) (testing "composite opts" (is (= {:a true, :b true, :c true, :foo true} (cli/parse-opts ["--foo" "-abc"])))) @@ -112,7 +118,7 @@ (is (= {:opts {:option false}} (cli/parse-args [":no-option"])))) (testing "--no-foo throws on non-boolean" - (doseq [coerce-fn [:long :number :symbol :keyword :string]] + (doseq [coerce-fn [:long :number :symbol :keyword :string :edn]] (is (thrown-with-msg? #?(:cljd Object :default Exception) #"Cannot negate option --foo" (cli/parse-opts ["--no-foo"] {:coerce {:foo coerce-fn}})) @@ -130,6 +136,15 @@ (cli/parse-opts [":no-foo"] {:coerce {:foo [coerce-fn]}})) (str "for coerce to: [" coerce-fn "]"))))) +(deftest coerce-to-edn-test + (testing "explicits aren't confused with implicits" + (is (= {:foo nil} (cli/parse-opts ["--foo" "nil"] {:coerce {:foo :edn}}))) + (is (= {:foo true} (cli/parse-opts ["--foo" "true"] {:coerce {:foo :edn}}))) + (is (= {:foo false} (cli/parse-opts ["--foo" "false"] {:coerce {:foo :edn}})))) + (testing "show behaviour is just like clojure CLI -X" + (is (= {:foo "string"} (cli/parse-opts ["-foo" "\"string\""] {:coerce {:foo :edn}}))) + (is (= {:foo 'some-symbol} (cli/parse-opts ["-foo" "some-symbol"] {:coerce {:foo :edn}}))))) + (deftest equals-value-test (testing "--opt=val splits on the first = only" (is (= {:header "k=v"} (cli/parse-opts ["--header=k=v"])))