Skip to content

Commit dd6a429

Browse files
authored
Merge pull request #141 from clj-commons/hls/20250630-vars
Add `clj-commons.pretty.nrepl/wrap-pretty` nREPL middleware
2 parents 26676f0 + 4e36d19 commit dd6a429

File tree

10 files changed

+140
-50
lines changed

10 files changed

+140
-50
lines changed

CHANGES.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 3.5.0 -- UNRELEASED
2+
3+
- Default style for `print-table` can be overridden via a dynamic var
4+
- Use a vertical bar (│) not a pipe character (|) as column separator in binary output
5+
- New `clj-commons.pretty.nrepl` namespace to set up pretty inside nREPL
6+
17
## 3.4.1 -- 23 Jun 2025
28

39
- Removed some reflection warnings

README.md

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,19 @@ Or, same thing, but with Pretty enabled:
2828

2929
The point is, you can scan down to see things in chronological order; the important parts are highlighted, the names are the same (or closer) to your source code, unnecessary details are omitted, and it's much easier to pick out the most important parts, such as file names and line numbers.
3030

31+
## Pretty and nREPL
32+
33+
[nREPL](https://nrepl.org) is the framework that allows an IDE such as [Emacs](https://cider.mx/)
34+
or [Cursive](https://cursive-ide.com/), or even a CLI such as
35+
[Leiningen](https://leiningen.org/), to interoperate with a running REPL in a subprocess.
36+
37+
Pretty includes an nREPL middleware function, `clj-commons.pretty.nrepl/wrap-pretty`, that will install pretty exception reporting into the REPL.
38+
39+
The nREPL documentation describes how to enable such middleware
40+
inside [project.clj or deps.edn](https://nrepl.org/nrepl/usage/server.html#starting-a-server) or
41+
in [.nrepl/nrepl.edn](https://nrepl.org/nrepl/usage/server.html#server-configuration) (for instance, when developing
42+
with [Cursive](https://cursive-ide.com/userguide/repl.html#configuring-middleware-for-nrepl-repls))
43+
3144
## Beyond Exceptions
3245

3346
Pretty can print out a sequence of bytes; it includes color-coding inspired by
@@ -43,19 +56,22 @@ to indicate where the two sequences differ.
4356
Pretty can output pretty tabular data:
4457

4558
```
59+
(def routes
60+
[{:method :get
61+
:path "/"
62+
:route-name :root-page}
63+
{:method :post
64+
:path "/reset"
65+
:route-name :reset}
66+
{:method :get
67+
:path "/status"
68+
:route-name :status}])
69+
=> #'table-demo/routes
4670
(print-table
47-
[:method
48-
:path
49-
{:key :route-name :title "Name"}]
50-
[{:method :get
51-
:path "/"
52-
:route-name :root-page}
53-
{:method :post
54-
:path "/reset"
55-
:route-name :reset}
56-
{:method :get
57-
:path "/status"
58-
:route-name :status}])
71+
[:method
72+
:path
73+
{:key :route-name :title "Name" :title-align :left}]
74+
routes)
5975
┌────────┬─────────┬────────────┐
6076
│ Method │ Path │ Name │
6177
├────────┼─────────┼────────────┤
@@ -66,7 +82,22 @@ Pretty can output pretty tabular data:
6682
=> nil
6783
```
6884

69-
The `print-table` function has many options to easily adjust the output to your needs, including fonts, text alignment, and the table border.
85+
The `print-table` function has many options to easily adjust the output to your needs, including fonts, text alignment, and line annotations. It also supplies several different table styles:
86+
87+
```
88+
(print-table
89+
{:columns [:method
90+
:path
91+
{:key :route-name :title "Name" :title-align :left}]
92+
:style table/skinny-style}
93+
routes)
94+
Method | Path | Name
95+
-------+---------+-----------
96+
:get | / | :root-page
97+
:post | /reset | :reset
98+
:get | /status | :status
99+
=> nil
100+
```
70101

71102

72103
## Compatibility

build.clj

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
;; clj -T:build <var>
22

33
(ns build
4-
(:require [clojure.tools.build.api :as build]
4+
(:require [clojure.set :as set]
5+
[clojure.tools.build.api :as build]
56
[net.lewisship.build :as b]
7+
[clojure.set :as set]
68
[clojure.string :as str]))
79

810
(def lib 'org.clj-commons/pretty)
@@ -19,6 +21,12 @@
1921
[_params]
2022
(b/create-jar jar-params))
2123

24+
(defn install
25+
[_params]
26+
(build/install (-> (jar nil)
27+
(assoc :lib lib)
28+
(set/rename-keys {:jar-path :jar-file}))))
29+
2230
(defn deploy
2331
[_params]
2432
(clean nil)

deps.edn

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,20 @@
55
{:test
66
;; clj -X:test
77
{:extra-paths ["test"]
8-
:extra-deps {criterium/criterium {:mvn/version "0.4.6"}
9-
org.clojure/core.async {:mvn/version "1.8.741"}
10-
nubank/matcher-combinators {:mvn/version "3.9.1"}
11-
io.github.tonsky/clj-reload {:mvn/version "0.9.7"}
8+
:extra-deps {criterium/criterium {:mvn/version "0.4.6"}
9+
nrepl/nrepl {:mvn/version "1.3.1"}
10+
org.clojure/core.async {:mvn/version "1.8.741"}
11+
nubank/matcher-combinators {:mvn/version "3.9.1"}
12+
io.github.tonsky/clj-reload {:mvn/version "0.9.7"}
1213
io.github.cognitect-labs/test-runner {:git/tag "v0.5.1"
1314
:git/sha "dfb30dd"}}
14-
:jvm-opts ["-Dclj-commons.ansi.enabled=true"]
15-
:exec-fn cognitect.test-runner.api/test}
15+
:jvm-opts ["-Dclj-commons.ansi.enabled=true"]
16+
:exec-fn cognitect.test-runner.api/test}
1617

1718
;; clj -T:build <command>
1819
:build
19-
{:deps {io.github.hlship/build-tools
20-
{:git/tag "0.11.0" :git/sha "8c67d11"}}
20+
{:deps {io.github.hlship/build-tools
21+
{:git/tag "0.11.0" :git/sha "8c67d11"}}
2122
:ns-default build}
2223

2324
:1.11
@@ -36,20 +37,20 @@
3637
;; clj -M:lint
3738

3839
:lint
39-
{:deps {clj-kondo/clj-kondo {:mvn/version "2025.06.05"}}
40+
{:deps {clj-kondo/clj-kondo {:mvn/version "2025.06.05"}}
4041
:main-opts ["-m" "clj-kondo.main" "--lint" "src"]}
4142

4243
:nrepl
4344
{:extra-deps {nrepl/nrepl {:mvn/version "1.3.1"}}
44-
:main-opts ["-m" "nrepl.cmdline"]}
45+
:main-opts ["-m" "nrepl.cmdline"]}
4546

4647
:repl
4748
{:main-opts ["-m" "clj-commons.pretty.repl"]}}
4849

4950
:net.lewisship.build/scm
50-
{:url "https://github.com/clj-commons/pretty"
51+
{:url "https://github.com/clj-commons/pretty"
5152
:license :asl}
5253

5354
:codox/config
5455
{:description "Clojure library to help print things, prettily"
55-
:source-uri "https://github.com/clj-commons/pretty/blob/master/{filepath}#L{line}"}}
56+
:source-uri "https://github.com/clj-commons/pretty/blob/master/{filepath}#L{line}"}}

src/clj_commons/format/binary.clj

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,10 @@
124124
(when write-ascii?
125125
(list
126126
(padding (* 3 (- per-line line-count)))
127-
" |"
127+
" "
128128
(map to-ascii line-bytes)
129129
(padding (- per-line line-count))
130-
"|")))))
130+
"")))))
131131

132132
(defn print-binary
133133
"Formats a BinaryData into a hex-dump string, consisting of multiple lines; each line formatted as:
@@ -147,10 +147,10 @@
147147
148148
In ASCII mode, the output is 16 bytes per line, but each line includes the ASCII printable characters:
149149
150-
0000: 43 68 6F 6F 73 65 20 69 6D 6D 75 74 61 62 69 6C |Choose immutabil|
151-
0010: 69 74 79 2C 20 61 6E 64 20 73 65 65 20 77 68 65 |ity, and see whe|
152-
0020: 72 65 20 74 68 61 74 20 74 61 6B 65 73 20 79 6F |re that takes yo|
153-
0030: 75 2E |u. |
150+
0000: 43 68 6F 6F 73 65 20 69 6D 6D 75 74 61 62 69 6C Choose immutabil
151+
0010: 69 74 79 2C 20 61 6E 64 20 73 65 65 20 77 68 65 ity, and see whe
152+
0020: 72 65 20 74 68 61 74 20 74 61 6B 65 73 20 79 6F re that takes yo
153+
0030: 75 2E u.
154154
155155
When ANSI is enabled, the individual bytes and characters are color-coded as per the [[*fonts*]]."
156156
([data]
@@ -210,7 +210,7 @@
210210
[{:align :left
211211
:width (* 3 bytes-per-diff-line)}
212212
(compose-deltas :bright-green-bg offset expected-length expected actual-length actual)]
213-
" |"
213+
" "
214214
(compose-deltas :bright-red-bg offset actual-length actual expected-length expected)))
215215

216216
(defn print-binary-delta

src/clj_commons/format/table.clj

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,12 @@
107107
:row-right nil
108108
:footer? false})
109109

110+
(def ^{:added "3.5.0"
111+
:dynamic true}
112+
*default-style*
113+
"Default style used for table output. Defaults to [[default-style]]."
114+
default-style)
115+
110116
(defn print-table
111117
"Similar to clojure.pprint/print-table, but with fancier graphics and more control
112118
over column titles.
@@ -172,7 +178,7 @@
172178
{:columns opts}
173179
opts)
174180
{:keys [columns style default-decorator row-annotator]
175-
:or {style default-style}} opts'
181+
:or {style *default-style*}} opts'
176182
{:keys [header?
177183
footer?
178184
header-left

src/clj_commons/pretty/nrepl.clj

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
(ns clj-commons.pretty.nrepl
2+
"Middleware to setup pretty exception reporting in nREPL."
3+
{:added "3.5.0"}
4+
(:require [clj-commons.pretty.repl :as repl]
5+
[nrepl.middleware :as middleware]
6+
[nrepl.middleware.caught :as caught]))
7+
8+
(defn wrap-pretty
9+
"Ensures that exceptions are printed using pretty, including uncaught REPL exceptions.
10+
11+
This sets the message key :nrepl.middleware.caught/caught, if not previously set."
12+
[handler]
13+
(repl/install-pretty-exceptions)
14+
(fn with-pretty
15+
[msg]
16+
(let [msg' (if (contains? msg ::caught/caught)
17+
msg
18+
(assoc msg ::caught/caught `repl/pretty-repl-caught))]
19+
(handler msg'))))
20+
21+
(middleware/set-descriptor! #'wrap-pretty
22+
{:doc (-> #'wrap-pretty meta :doc)
23+
:handles {}
24+
:requires #{}
25+
:expects #{#'caught/wrap-caught}})

src/clj_commons/pretty/repl.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070

7171
(defmethod print-method Throwable
7272
[t ^Writer writer]
73-
(.write writer ^String (System/lineSeparator))
73+
(.write writer ^String System/lineSeparator)
7474
(.write writer (e/format-exception t)))
7575

7676
;; This is necessary due to direct linking (from clojure.test to clojure.stacktrace).

test/clj_commons/binary_test.clj

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545

4646
(deftest binary-fonts
4747
(let [byte-data (byte-array [0x59 0x65 073 0x20 0x4e 0x00 0x00 0x09 0x80 0xff])]
48-
(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}××{} |"]
48+
(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}××{} "]
4949
(-> (b/format-binary byte-data {:ascii true})
5050
fixup-sgr
5151
string/split-lines)))))
@@ -87,14 +87,14 @@
8787
(format-binary-delta-plain expected actual))
8888

8989
"123" "123"
90-
["0000: 31 32 33 | 31 32 33"]
90+
["0000: 31 32 33 31 32 33"]
9191

9292
"abcdefghijklmnopqrstuvwyz" "abCdefghijklmnopqrs"
93-
["0000: 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 | 61 62 43 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70"
94-
"0010: 71 72 73 74 75 76 77 79 7A | 71 72 73 -- -- -- -- -- --"]
93+
["0000: 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 61 62 43 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70"
94+
"0010: 71 72 73 74 75 76 77 79 7A 71 72 73 -- -- -- -- -- --"]
9595

9696
"abc" "abcdef"
97-
["0000: 61 62 63 -- -- -- | 61 62 63 64 65 66"]))
97+
["0000: 61 62 63 -- -- -- 61 62 63 64 65 66"]))
9898

9999

100100
(deftest deltas-with-fonts
@@ -110,14 +110,14 @@
110110
;; 32 is green for whitespace
111111
;; 102 is bright green backround,
112112
;; 101 is bright red background
113-
["{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{}"]
113+
["{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{}"]
114114

115115
"1234" "12"
116-
["{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}--{}"]
116+
["{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}--{}"]
117117

118118
;; 2 is faint for non-printable
119119
"\u001B" "\u001Cxyz"
120-
["{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{}"]
120+
["{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{}"]
121121
))
122122

123123

test/table_demo.clj

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
(ns table-demo
2-
(:require [clj-commons.format.table :refer [print-table] :as t]))
2+
(:require [clj-commons.format.table :refer [print-table] :as table]))
33

44
(def row [{:first "Arthur" :middle "C" :last "Clark"}
55
{:first "Alan" :last "Turing"}
@@ -16,8 +16,8 @@
1616
:bold))}])
1717
(comment
1818
(print-table columns row)
19-
(print-table {:style t/minimal-style :columns columns} row)
20-
(print-table {:style t/skinny-style
19+
(print-table {:style table/minimal-style :columns columns} row)
20+
(print-table {:style table/skinny-style
2121
:row-annotator (fn [i row]
2222
(when (= i 2)
2323
[:italic " (prescient)"]))
@@ -27,10 +27,7 @@
2727
:blue))
2828
:columns columns} row)
2929

30-
(print-table
31-
[:method
32-
:path
33-
{:key :route-name :title "Name" :title-align :left}]
30+
(def routes
3431
[{:method :get
3532
:path "/"
3633
:route-name :root-page}
@@ -41,4 +38,20 @@
4138
:path "/status"
4239
:route-name :status}])
4340

41+
(print-table
42+
[:method
43+
:path
44+
{:key :route-name :title "Name" :title-align :left}]
45+
routes)
46+
47+
(print-table
48+
{:columns [:method
49+
:path
50+
{:key :route-name :title "Name" :title-align :left}]
51+
:style table/skinny-style}
52+
routes)
53+
54+
55+
56+
;
4457
)

0 commit comments

Comments
 (0)