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

Fix faint, add pout #131

Merged
merged 1 commit into from
Sep 20, 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
15 changes: 12 additions & 3 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
## 3.2.0 - UNRELEASED

Added `clj-commons.format.exceptions/default-frame-rules` with the defaults for `*default-frame-rules*`
which makes it much easier to override the rules.
Added `clj-commons.ansi/pout` to replace the `pcompose` function; they are identical, but the `pout` name makes more
sense, given that `perr` exists.

Changed how `clj-commons.ansi/compose` creates ANSI SGR strings; this works around an issue in many terminal emulators
where changing boldness from faint to normal, or faint to bold, is not implemented correctly. `compose` now resets fonts
before each font change, which allows such transitions to render correctly.

Added `clj-commons.format.exceptions/default-frame-rules` to supply defaults for `*default-frame-rules*`
which makes it much easier to override the default rules.

Added function `clj-commons.format.exceptions/format-stack-trace-element` which can be used to convert a Java
StackTraceElement into demangled, readable string, using the same logic used by `format-exception.`
StackTraceElement into demangled, readable string, using the same logic as `format-exception.`

[Closed Issues](https://github.com/clj-commons/pretty/milestone/52?closed=1)

## 3.1.1 - 22 Aug 2024

Expand Down
1 change: 1 addition & 0 deletions deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
:extra-deps {criterium/criterium {:mvn/version "0.4.6"}
org.clojure/core.async {:mvn/version "1.6.681"}
nubank/matcher-combinators {:mvn/version "3.9.1"}
io.github.tonsky/clj-reload {:mvn/version "0.7.1"}
io.github.cognitect-labs/test-runner {:git/tag "v0.5.1"
:git/sha "dfb30dd"}}
:jvm-opts ["-Dclj-commons.ansi.enabled=true"]
Expand Down
74 changes: 44 additions & 30 deletions src/clj_commons/ansi.clj
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@
(str csi sgr))

(def ^:private font-terms
;; Map a keyword to a tuple of characteristic and SGR parameter value.
;; We track the current value for each characteristic.
(reduce merge
{:bold [:bold "1"]
:plain [:bold "22"]
Expand Down Expand Up @@ -83,11 +85,15 @@
current-value)))

(defn- compose-font
^String [active current]
"Uses values in current to build a font string that will reset all fonts characteristics then,
as necessary, add back needed font characteristics."
^String [current]
(when-color-enabled
(let [codes (keep #(delta active current %) [:foreground :background :bold :italic :inverse :underlined])]
(when (seq codes)
(str csi (str/join ";" codes) sgr)))))
(let [codes (keep #(get current %) [:foreground :background :bold :italic :inverse :underlined])]
(if (seq codes)
(str csi "0;" (str/join ";" codes) sgr)
;; there were active characteristics, but current has none, so just reset font characteristics
reset-font))))

(defn- split-font-def*
[font-def]
Expand Down Expand Up @@ -138,7 +144,9 @@
(throw (ex-info "invalid span declaration"
{:font-decl value}))))

(defn- blank? [value]
(defn- nil-or-empty-string?
"True if an empty string, or nil; false otherwise, such as for numbers, etc."
[value]
(or (nil? value)
(= "" value)))

Expand All @@ -152,7 +160,8 @@
(mod x 2)
0))))

(defn- apply-padding [terms pad width actual-width]
(defn- apply-padding
[terms pad width actual-width]
(let [padding-needed (- width actual-width)
left-padding (case pad
(:left nil) padding-needed
Expand Down Expand Up @@ -197,7 +206,7 @@
[coll *width]
(let [f (fn reducer [result input]
(cond
(blank? input)
(nil-or-empty-string? input)
result

(vector? input)
Expand Down Expand Up @@ -225,7 +234,7 @@
(defn- collect-markup
[state input]
(cond
(blank? input)
(nil-or-empty-string? input)
state

(vector? input)
Expand All @@ -236,12 +245,12 @@
;; Normal (no width tracking)
(let [{:keys [current]} state]
(-> (reduce collect-markup
(-> state
(update :current update-font-data-from-font-def font)
(update :stack conj current))
(update state :current update-font-data-from-font-def font)
inputs)
(assoc :current current)
(update :stack pop)))))
;; At the end of the vector, return current (but not the active)
;; to what it was previously. We leave active alone until we're about
;; to output.
(assoc :current current)))))

;; Lists, lazy-lists, etc: processed recursively
(sequential? input)
Expand All @@ -251,27 +260,21 @@
(let [{:keys [active current ^StringBuilder buffer]} state
state' (if (= active current)
state
(let [font-str (compose-font active current)]
(let [font-str (compose-font current)]
(when font-str
(.append buffer font-str))
(cond-> (assoc state :active current)
;; Signal that a reset is needed at the very end
font-str (assoc :dirty? true))))]
font-str
(assoc :dirty? (not= font-str reset-font)))))]
(.append buffer (str input))
state')))

(defn- compose*
[inputs]
(let [initial-font {:foreground "39"
:background "49"
:bold "22"
:italic "23"
:inverse "27"
:underlined "24"}
buffer (StringBuilder. 100)
{:keys [dirty?]} (collect-markup {:stack []
:active initial-font
:current initial-font
(let [buffer (StringBuilder. 100)
{:keys [dirty?]} (collect-markup {:active {}
:current {}
:buffer buffer}
inputs)]
(when dirty?
Expand All @@ -289,8 +292,8 @@
`map` or `for` to be mixed into the composed string seamlessly.

Nested vectors represent _spans_, a sequence of values with a specific visual representation.
The first element in a span vector declares the visual properties of the span: the color (including
other characteristics such as bold or underline), and the width and padding (described later).
The first element in a span vector declares the visual properties of the span: the font color
and other font characteristics, and the width and padding (described later).
Spans may be nested.

The declaration is usually a keyword, to define just the font.
Expand Down Expand Up @@ -322,7 +325,7 @@
Font defs apply on top of the font def of the enclosing span, and the outer span's font def
is restored at the end of the inner span, e.g. `[:red \" RED \" [:bold \"RED/BOLD\"] \" RED \"]`.

Alternately, a font def may be a vector of individual keyword, e.g., `[[:bold :red] ...]` rather than
Alternately, a font def may be a vector of individual keywords, e.g., `[[:bold :red] ...]` rather than
`[:bold.red ...]`. This works better when the exact font characteristics are determined
dynamically.

Expand Down Expand Up @@ -370,9 +373,18 @@
[& inputs]
(compose* inputs))

(defn pcompose
(defn pout
"Composes its inputs as with [[compose]] and then prints the results, with a newline."
{:added "2.2"}
{:added "3.2"}
[& inputs]
(println (compose* inputs)))

(defn pcompose
"Composes its inputs as with [[compose]] and then prints the results, with a newline.

Deprecated: use [[pout]] instead."
{:added "2.2"
:deprecated "3.2.0"}
[& inputs]
(println (compose* inputs)))

Expand All @@ -382,3 +394,5 @@
[& inputs]
(binding [*out* *err*]
(println (compose* inputs))))


15 changes: 8 additions & 7 deletions test/clj_commons/ansi_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
["Simple"]
"Simple"


["String" \space :keyword \space 'symbol \space 123 \space 44.5]
"String :keyword symbol 123 44.5"

Expand Down Expand Up @@ -75,7 +76,7 @@
" are operating at "
[:green "98.7%"]
"."]
"Notice: the [CSI]33mshields[CSI]39m are operating at [CSI]32m98.7%[CSI]39m.[CSI]m"
"Notice: the [CSI]0;33mshields[CSI]m are operating at [CSI]0;32m98.7%[CSI]m."

;; nil is allowed (this is used when formatting is optional, such as the fonts in exceptions).

Expand All @@ -89,13 +90,13 @@
["NORMAL"
[:red "-RED"]
[:bright-red "-BR/RED"]]
"NORMAL[CSI]31m-RED[CSI]91m-BR/RED[CSI]m"
"NORMAL[CSI]0;31m-RED[CSI]0;91m-BR/RED[CSI]m"

["NORMAL-"
[:inverse "-INVERSE" [:bold "-INV/BOLD"]]
[:inverse.bold "-INV/BOLD"]
"-NORMAL"]
"NORMAL-[CSI]7m-INVERSE[CSI]1m-INV/BOLD-INV/BOLD[CSI]22;27m-NORMAL[CSI]m"
"NORMAL-[CSI]0;7m-INVERSE[CSI]0;1;7m-INV/BOLD-INV/BOLD[CSI]m-NORMAL"


;; Basic tests for width:
Expand All @@ -117,8 +118,8 @@
[{:width 10
:font :red} "BBB"]
"|")
"START |[CSI]32mAAA [CSI]39m|[CSI]31m BBB[CSI]39m|[CSI]m"
; 0123456789 0123456789
"START |[CSI]0;32mAAA [CSI]m|[CSI]0;31m BBB[CSI]m|"
; 0123456789 0123456789

'("START |"
[{:width 10
Expand All @@ -128,8 +129,8 @@
[{:width 10
:font :red} "XYZ"]
"|")
"START |[CSI]32mAB[CSI]34mC[CSI]32m [CSI]39m|[CSI]31m XYZ[CSI]39m|[CSI]m"
; 01 2 3456789 0123456789
"START |[CSI]0;32mAB[CSI]0;34mC[CSI]0;32m [CSI]m|[CSI]0;31m XYZ[CSI]m|"
; 01 2 3456789 0123456789

;; Only pads, never truncates

Expand Down
14 changes: 8 additions & 6 deletions test/clj_commons/binary_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

(deftest binary-fonts
(let [byte-data (byte-array [0x59 0x65 073 0x20 0x4e 0x00 0x00 0x09 0x80 0xff])]
(is (= ["{90}0000:{39} {36}59{39} {36}65{39} {36}3B{39} {32}20{39} {36}4E{39} {90}00{39} {90}00{39} {32}09{39} {33}80{39} {33}FF{39} |{36}Ye;{32} {36}N{90}••{32}_{33}××{39} |{}"]
(is (= ["{0;90}0000:{} {0;36}59{} {0;36}65{} {0;36}3B{} {0;32}20{} {0;36}4E{} {0;90}00{} {0;90}00{} {0;32}09{} {0;33}80{} {0;33}FF{} |{0;36}Ye;{0;32} {0;36}N{0;90}••{0;32}_{0;33}××{} |"]
(-> (b/format-binary byte-data {:ascii true})
fixup-sgr
string/split-lines)))))
Expand Down Expand Up @@ -96,23 +96,25 @@

(deftest deltas-with-fonts
(are [expected actual expected-output]
(= expected-output
(match? expected-output
(format-binary-delta expected actual))

"123\t" "123\n"
;; {} is reset font
;; 0 is reset font (as a prefix)
;; 90 is bright black for offset
;; 36 is cyan for printable ASCII
;; 32 is green for whitespace
;; 102 is bright green backround,
;; 101 is bright red background
["{90}0000:{39} {36}31{39} {36}32{39} {36}33{39} {32;102}09{39;49} | {36}31{39} {36}32{39} {36}33{39} {32;101}0A{}"]
["{0;90}0000:{} {0;36}31{} {0;36}32{} {0;36}33{} {0;32;102}09{} | {0;36}31{} {0;36}32{} {0;36}33{} {0;32;101}0A{}"]

"1234" "12"
["{90}0000:{39} {36}31{39} {36}32{39} {36;102}33{39;49} {36;102}34{39;49} | {36}31{39} {36}32{39} {101}--{49} {101}--{}"]
["{0;90}0000:{} {0;36}31{} {0;36}32{} {0;36;102}33{} {0;36;102}34{} | {0;36}31{} {0;36}32{} {0;101}--{} {0;101}--{}"]

;; 2 is faint for non-printable
"\u001B" "\u001Cxyz"
["{90}0000:{39} {32;102;2}1B{39;49;22} {102}--{49} {102}--{49} {102}--{49} | {32;101;2}1C{39;49;22} {36;101}78{39;49} {36;101}79{39;49} {36;101}7A{}"]))

["{0;90}0000:{} {0;32;102;2}1B{} {0;102}--{} {0;102}--{} {0;102}--{} | {0;32;101;2}1C{} {0;36;101}78{} {0;36;101}79{} {0;36;101}7A{}"]
))