babashka / cli

Turn Clojure functions into CLIs!
MIT License
232 stars 18 forks source link

cli/format-table: handling ANSI escape codes #102

Closed eval closed 3 months ago

eval commented 3 months ago

problem

Currently cli/format-table can't handle ANSI escape codes.

Given for example:

(cli/format-table {:rows [["foo" "<- something plain"]
                          ["\033[31mbar\033[0m" "<- something red"]]})

...the foo- and bar-cell have the same width when printed (ie 3). But as the bar-cell is considered to have a width of 12, the foo-cell gets padded to this length as well, making it look off when printed.

Less common, but same idea when using emoji's: (count "πŸš€ bar") ;;=> 6 (but 5 when printed).

possible approaches

provide widths

Providing the width of every cell, e.g. via metadata on a row:

user=> rows
[^{:column-widths [3 18]}
 ["foo" "<- something plain"]
 ^{:column-widths [3 16]}
 ["\033[31mbar\033[0m" "<- something red"]]

This metadata would be used instead of (map count row) in #cli/pad-cells:

https://github.com/babashka/cli/blob/836b15f8784c7a575462f15135fd943dc64dede1/src/babashka/cli.cljc#L526-L532

This solution would also double as a solution to the problem 'how to customise spacing between columns'.

Happy to provide a PR.

borkdude commented 3 months ago

Perhaps there's a better solution to decide the actual printed width for a string? I wouldn't be surprised if more people have had this problem in the past.

borkdude commented 3 months ago

I found this: https://stackoverflow.com/a/64677848/6264

borkdude commented 3 months ago

I also found this as a solution for emojis:

(let [s  "πŸš€ bar"] (.codePointCount s 0 (count s)) )
borkdude commented 3 months ago

but perhaps in the emoji example, 6 is the right answer since the rocket takes up more space than a single digit? I'm not sure :)

borkdude commented 3 months ago

This one is weird though:

user=> (let [s "πŸ€¦β€β™‚οΈ"] (.codePointCount s 0 (count s)) )
4

Let's just adjust the ansi escape codes then with the regex maybe as a first attempt?

eval commented 3 months ago

Getting that logic on board is an option as well. I looked at https://github.com/lambdaisland/ansi/blob/main/src/lambdaisland/ansi.cljc for inspiration.

but perhaps in the emoji example, 6 is the right answer since the rocket takes up more space than a single digit? I'm not sure :)

Seems to work indeed for some emoji's

podman run -it babashka/babashka bash -c 'bb -e '"'"'(require (quote [babashka.cli :as cli]))(println (cli/format-table {:rows [["πŸ†˜ foo" "description of foo"] ["bar" "Description of bar"]] :indent 2}))'"'"''

But facepalm is off indeed.

Let's just adjust the ansi escape codes then with the regex maybe as a first attempt?

Will take a stab at that!