Skip to content

Commit

Permalink
Fix #72: add possibility to add a header to format-opts (#73)
Browse files Browse the repository at this point in the history
  • Loading branch information
Sohalt committed Sep 28, 2023
1 parent d47ca14 commit bf26389
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 58 deletions.
4 changes: 2 additions & 2 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,10 @@ Subcommand dispatcher.
## `format-opts`
``` clojure

(format-opts {:keys [spec indent order], :or {indent 2}})
(format-opts {:keys [spec indent order header], :or {indent 2}})
```

<sub>[source](https://github.com/babashka/cli/blob/main/src/babashka/cli.cljc#L481-L537)</sub>
<sub>[source](https://github.com/babashka/cli/blob/main/src/babashka/cli.cljc#L504-L569)</sub>
## `merge-opts`
``` clojure

Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,20 @@ vector of vectors for the spec:
:default-desc "src test"}]]
```

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.
You can then use`format-table` to produce a table as returned by `format-opts`.
For example to add a header row with labels for each column, you could do something like:

``` clojure
(cli/format-table
{:rows (concat [["alias" "option" "ref" "default" "description"]]
(cli/opts->table
{:spec {:foo {:alias :f, :default "yupyupyupyup", :ref "<foo>"
:desc "Thingy"}
:bar {:alias :b, :default "sure", :ref "<bar>"
:desc "Barbarbar" :default-desc "Mos def"}}}))
:indent 2})```

## Subcommands

To handle subcommands, use
Expand Down
119 changes: 65 additions & 54 deletions src/babashka/cli.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -501,63 +501,74 @@
(defn- kw->str [kw]
(subs (str kw) 1))

(defn format-opts [{:keys [spec
(defn pad [len s] (str s (apply str (repeat (- len (count s)) " "))))

(defn pad-cells [rows]
(let [widths (reduce
(fn [widths row]
(map max (map count row) widths)) (repeat 0) rows)
pad-row (fn [row]
(map (fn [width col] (pad width col)) widths row))]
(map pad-row rows)))

(defn format-table [{:keys [rows indent]}]
(let [rows (pad-cells rows)
fmt-row (fn [leader divider trailer row]
(str leader
(apply str (interpose divider row))
trailer))]
(->> rows
(map (fn [row]
#_(fmt-row "| " " | " " |" row)
(fmt-row (apply str (repeat indent " ")) " " "" row)))
(map str/trimr)
(str/join "\n"))))

(comment
(def rows [["a" "fooo" "bara" "bazzz" "aa"]
["foo" "bar" "bazzz"]
["fooo" "bara" "bazzz"]])
(pad-cells rows)
(format-table {:rows rows
:indent 2}))

(defn opts->table [{:keys [spec order]}]
(let [columns (set (mapcat (fn [[_ s]] (keys s)) spec))]
(mapv (fn [[long-opt {:keys [alias default default-desc ref desc]}]]
(keep identity
[(when (:alias columns)
(if alias (str "-" (kw->str alias) ",") ""))
(str "--" (kw->str long-opt))
(when (:ref columns)
(if ref ref ""))
(when (or (:default-desc columns)
(:default columns))
(str (or default-desc default "")))
(when (:desc columns)
(if desc desc ""))]))
(if (map? spec)
(let [order (or order (keys spec))]
(map (fn [k] [k (spec k)]) order))
spec))))

(comment
(opts->table {:spec (def spec [[:pretty {:desc "Pretty-print output."
:alias :p}]
[:paths {:desc "Paths of files to transform."
:coerce []
:default ["src" "test"]
:default-desc "src test"}]])})
(opts->table {:spec {:foo {:alias :f, :default "yupyupyupyup", :ref "<foo>"
:desc "Thingy"}
:bar {:alias :b, :default "sure", :ref "<bar>"
:desc "Barbarbar" :default-desc "Mos def"}}}))
(defn format-opts [{:as cfg
:keys [spec
indent
order]
:or {indent 2}}]
(let [{:keys [alias-width long-opt-width default-width
ref-width]}
(reduce (fn [{:keys [alias-width long-opt-width default-width description-width
ref-width]}
[option {:keys [ref default default-desc desc alias]}]]
(let [alias-width (max alias-width (if alias (count (kw->str alias)) 0))
long-opt-width (max long-opt-width (count (kw->str option)))
ref-width (max ref-width (if ref (count (str ref)) 0))
default? (or default-desc default)
default-width (max default-width (if default? (count (or default-desc
(-> default str not-empty))) 0))
description-width (max description-width (if desc (count (str desc)) 0))]
{:alias-width alias-width
:long-opt-width long-opt-width
:default-width default-width
:description-width description-width
:ref-width ref-width}))
{:alias-width 0
:long-opt-width 0
:default-width 0
:description-width 0
:ref-width 0}
spec)]
(str/join "\n"
(map (fn [[option {:keys [ref default default-desc desc alias]}]]
(with-out-str
(run! print (repeat indent " "))
(when alias (print (str "-" (kw->str alias) ", ")))
(run! print (repeat (- (+ (if (pos? alias-width)
3 0)
alias-width) (if alias
(+ 3 (count (kw->str alias)))
0)) " "))
(print (str "--" (kw->str option)))
(run! print (repeat (inc (- long-opt-width (count (kw->str option)))) " "))
(when ref (print ref))
(let [spaces (+ (if (pos? ref-width)
1
0) (- ref-width (count (str ref))))]
(run! print (repeat spaces " ")))
(print (or default-desc (str default)))
(let [spaces (+ (if (pos? default-width)
1
0) (- default-width (count (or default-desc
(str default)))))]
;; (prn :spaces spaces)
(run! print (repeat spaces " ")))
(print (str desc))))
(if order
(map (fn [k]
[k (get spec k)])
order)
spec)))))
(format-table {:rows (opts->table cfg)
:indent indent}))

(defn- split [a b]
(let [[prefix suffix] (split-at (count a) b)]
Expand Down
15 changes: 13 additions & 2 deletions test/babashka/cli_test.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@
:coerce []
:default ["src" "test"]
:default-desc "src test"}}]
(is (= (str/trim " -i, --from <format> edn The input format. <format> can be edn, json or transit.
(is (= (str/trim "
-i, --from <format> edn The input format. <format> can be edn, json or transit.
-o, --to <format> json The output format. <format> can be edn, json or transit.
--paths src test Paths of files to transform.
-p, --pretty Pretty-print output.")
Expand Down Expand Up @@ -317,7 +318,17 @@
{:spec {:foo {:alias :f, :default "yupyupyupyup", :ref "<foo>"
:desc "Thingy"}
:bar {:alias :b, :default "sure", :ref "<bar>"
:desc "Barbarbar" :default-desc "Mos def"}}})))))
:desc "Barbarbar" :default-desc "Mos def"}}}))))
(testing "header"
(is (= " alias option ref default description\n -f, --foo <foo> yupyupyupyup Thingy\n -b, --bar <bar> Mos def Barbarbar"
(cli/format-table
{:rows (concat [["alias" "option" "ref" "default" "description"]]
(cli/opts->table
{:spec {:foo {:alias :f, :default "yupyupyupyup", :ref "<foo>"
:desc "Thingy"}
:bar {:alias :b, :default "sure", :ref "<bar>"
:desc "Barbarbar" :default-desc "Mos def"}}}))
:indent 2})))))

(deftest require-test
(is (thrown-with-msg?
Expand Down

0 comments on commit bf26389

Please sign in to comment.