Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ANSI escape codes don't count towards a cell's width #103

Merged
merged 1 commit into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ For breaking changes, check [here](#breaking-changes).

[Babashka CLI](https://github.com/babashka/cli): turn Clojure functions into CLIs!

## Unreleased

- Fix [#102](https://github.com/babashka/cli/issues/102): `format-table` correctly pads cells containing ANSI escape codes

## v0.8.60 (2024-07-23)

- Fix [#98](https://github.com/babashka/cli/issues/98): internal options should not interfere with :restrict
Expand Down
19 changes: 13 additions & 6 deletions src/babashka/cli.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -521,17 +521,24 @@
(defn- kw->str [kw]
(subs (str kw) 1))

(defn pad [len s] (str s (apply str (repeat (- len (count s)) " "))))
(defn- str-width
"Width of `s` when printed, i.e. without ANSI escape codes."
[s]
(let [strip-escape-codes #(str/replace %
(re-pattern "(\\x9B|\\x1B\\[)[0-?]*[ -\\/]*[@-~]") "")]
(count (strip-escape-codes s))))

(defn pad [len s] (str s (apply str (repeat (- len (str-width s)) " "))))

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

(defn format-table [{:keys [rows indent]}]
(defn format-table [{:keys [rows indent] :or {indent 2}}]
(let [rows (pad-cells rows)
fmt-row (fn [leader divider trailer row]
(str leader
Expand Down
15 changes: 15 additions & 0 deletions test/babashka/cli_test.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,21 @@
:desc "Barbarbar" :default-desc "Mos def"}}}))
:indent 2})))))

(deftest format-table-test
(let [contains-row-matching (fn [re table]
(let [rows (str/split-lines table)]
(is (some #(re-find re %) rows)
(str "expected " (pr-str rows)
" to contain a row matching " (pr-str re)))))]
(testing "ANSI escape codes don't count towards a cell's width"
(let [table (cli/format-table {:rows [["widest" "<- sets column width to 6"]
["\033[31mfoo\033[0m" "<- needs 3+1 padding"]
["bar" "<- needs 3+1 padding"]]})]
(contains-row-matching #"\033\[31mfoo\033\[0m <-"
table)
(contains-row-matching #"bar <-"
table)))))

(deftest require-test
(is (thrown-with-msg?
Exception #"Required option: :bar"
Expand Down
Loading