Skip to content

Commit 8d2929b

Browse files
authored
Ensure ex-data is always presentable (#617)
Prevent browser from crashing when the file watcher is started with `:show-filter-fn` and a namespace throws exceptions
1 parent 31326c1 commit 8d2929b

File tree

8 files changed

+99
-40
lines changed

8 files changed

+99
-40
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ Changes can be:
3131

3232
* 🐜 Fix blank screen caused by react unmounting when an exception occurs during `clerk/show!`, fixes [#586](https://github.com/nextjournal/clerk/issues/586) @elken
3333

34+
* 🐜 Fix browser crashing when file watcher is used with `:show-filter-fn` option in notebooks with exceptions, fixes [#616](https://github.com/nextjournal/clerk/issues/616).
35+
3436
* 🐜 Make edn transmission resilient to symbols and keywords containing multiple slashes like `foo/bar/baz`. Those can be read by `read-string` but not in ClojureScript which is based on `tools.reader`.
3537

3638
* 🐞 Fix `:nextjournal.clerk/page-size` option throwing when set on string values, fixes [#584][https://github.com/nextjournal/clerk/issues/584]

notebooks/pagination.clj

+7
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,10 @@
9393

9494
;; Images are displayed correctly when expanding elided data:
9595
(concat (range 20) (list (clerk/image "trees.png")))
96+
97+
;; Elisions in exception data are fetched correctly:
98+
(ex-info "Boink 💥"
99+
{:boom (fn boom [x] x)
100+
:range (range 30)
101+
:image (clerk/image "trees.png")}
102+
(RuntimeException. "no way"))

src/nextjournal/clerk.clj

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
(println (str "Clerk evaluated '" file "' in " time-ms "ms."))
7676
(webserver/update-doc! result))
7777
(catch Exception e
78-
(webserver/update-doc! (assoc (-> e ex-data ::doc) :error e))
78+
(webserver/update-doc! (-> e ex-data ::doc (assoc :error e) (update :ns #(or % (find-ns 'user)))))
7979
(throw e))))))
8080

8181
#_(show! "notebooks/exec_status.clj")

src/nextjournal/clerk/paths.clj

+1-15
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
"Clerk's paths expansion and paths-fn handling."
33
(:require [babashka.fs :as fs]
44
[clojure.edn :as edn]
5-
[clojure.string :as str]
6-
[nextjournal.clerk.git :as git])
5+
[clojure.string :as str])
76
(:import [java.net URL]))
87

98
(defn ^:private ensure-not-empty [build-opts {:as opts :keys [error expanded-paths]}]
@@ -123,19 +122,6 @@
123122
#_(index-paths {:paths ["CHANGELOG.md"]})
124123
#_(index-paths {:paths-fn "boom"})
125124

126-
(defn process-paths [{:as opts :keys [paths paths-fn index]}]
127-
(merge (if (or paths paths-fn index)
128-
(expand-paths opts)
129-
opts)
130-
(git/read-git-attrs)))
131-
132-
#_(process-paths {:paths ["notebooks/rule_30.clj"]})
133-
#_(process-paths {:paths ["notebooks/rule_30.clj"] :index "notebooks/links.md"})
134-
#_(process-paths {:paths ["notebooks/no_rule_30.clj"]})
135-
#_(v/route-index? (process-paths @!server))
136-
#_(route-index (process-paths @!server) "")
137-
138-
139125
(defn path-in-cwd
140126
"Turns `file` into a unixified (forward slashed) path if the is in the cwd,
141127
returns `nil` otherwise."

src/nextjournal/clerk/render.cljs

+21-7
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@
480480
[:div.overflow-x-auto.overflow-y-hidden.w-full.shadow.sticky-table-header
481481
[:table.text-xs.sans-serif.text-gray-900.dark:text-white.not-prose {:ref !table-clone-ref :style {:margin 0}}]]]))
482482

483-
(defn throwable-view [{:keys [via trace]}]
483+
(defn throwable-view [{:keys [via trace]} opts]
484484
[:div.bg-white.max-w-6xl.mx-auto.text-xs.monospace.not-prose
485485
(into
486486
[:div]
@@ -491,7 +491,7 @@
491491
[:div.font-bold "Unhandled " type])
492492
[:div.font-bold.mt-1 message]
493493
(when data
494-
[:div.mt-1 [inspect (viewer/inspect-wrapped-values data)]])])
494+
[:div.mt-1 [inspect-presented opts data]])])
495495
via))
496496
[:div.py-6.overflow-x-auto
497497
[:table.w-full
@@ -503,10 +503,10 @@
503503
[:td.py-1.pr-6 call]]))
504504
trace)]]])
505505

506-
(defn render-throwable [ex]
506+
(defn render-throwable [ex opts]
507507
(if (or (:stack ex) (instance? js/Error ex))
508508
[error-view ex]
509-
[throwable-view ex]))
509+
[throwable-view ex opts]))
510510

511511
(defn render-tagged-value
512512
([tag value] (render-tagged-value {:space? true} tag value))
@@ -560,16 +560,30 @@
560560

561561
#_(show-panel :test {:content [:div "Test"] :width 600 :height 600})
562562

563+
(defn with-fetch-fn [{:nextjournal/keys [presented blob-id]} body-fn]
564+
;; TODO: unify with result-viewer
565+
(let [!presented-value (hooks/use-state presented)
566+
body-fn* (hooks/use-callback body-fn)]
567+
[view-context/provide
568+
{:fetch-fn (fn [elision]
569+
(.then (fetch! {:blob-id blob-id} elision)
570+
(fn [more] (swap! !presented-value viewer/merge-presentations more elision))))}
571+
[body-fn* @!presented-value]]))
572+
563573
(defn root []
564574
[:<>
565575
[:div.fixed.w-full.z-20.top-0.left-0.w-full
566576
(when-let [status (:nextjournal.clerk.sci-env/connection-status @!doc)]
567577
[connection-status status])
568578
(when-let [status (:status @!doc)]
569579
[exec-status status])]
570-
(when-let [error (get-in @!doc [:nextjournal/value :error])]
571-
[:div.fixed.top-0.left-0.w-full.h-full
572-
[inspect-presented error]])
580+
(when-let [{:as wrapped-value :nextjournal/keys [blob-id]} (get-in @!doc [:nextjournal/value :error])]
581+
(let [!expanded-at (r/atom {})]
582+
^{:key blob-id}
583+
[with-fetch-fn wrapped-value
584+
(fn [presented-value]
585+
[:div.fixed.top-0.left-0.w-full.h-full
586+
[inspect-presented {:!expanded-at !expanded-at} presented-value]])]))
573587
(when (:nextjournal/value @!doc)
574588
[inspect-presented @!doc])
575589
(into [:<>]

src/nextjournal/clerk/viewer.cljc

+30-14
Original file line numberDiff line numberDiff line change
@@ -901,12 +901,35 @@
901901
:transform-fn (comp #?(:cljs var->symbol :clj symbol) ->value)
902902
:render-fn '(fn [x] [:span.inspected-value [:span.cmt-meta "#'" (str x)]])})
903903

904+
(defn ->opts [wrapped-value]
905+
(select-keys wrapped-value [:nextjournal/budget :nextjournal/css-class :nextjournal/width :nextjournal/render-opts
906+
:nextjournal/render-evaluator
907+
:!budget :store!-wrapped-value :present-elision-fn :path :offset]))
908+
909+
(defn inherit-opts [{:as wrapped-value :nextjournal/keys [viewers]} value path-segment]
910+
(-> (ensure-wrapped-with-viewers viewers value)
911+
(merge (select-keys (->opts wrapped-value) [:!budget :store!-wrapped-value :present-elision-fn :nextjournal/budget :path]))
912+
(update :path (fnil conj []) path-segment)))
913+
914+
(defn present-ex-data [parent throwable-map]
915+
(let [present-child (fn [idx data] (present (inherit-opts parent data idx)))]
916+
(-> throwable-map
917+
(update-if :data (partial present-child 0))
918+
(update-if :via (fn [exs]
919+
(mapv (fn [i ex] (update-if ex :data (partial present-child (inc i))))
920+
(range (count exs))
921+
exs))))))
922+
904923
(def throwable-viewer
905924
{:name `throwable-viewer
906925
:render-fn 'nextjournal.clerk.render/render-throwable
907926
:pred (fn [e] (instance? #?(:clj Throwable :cljs js/Error) e))
908-
:transform-fn (comp mark-presented (update-val (comp demunge-ex-data
909-
datafy/datafy)))})
927+
:transform-fn (fn [wrapped-value]
928+
(-> wrapped-value
929+
mark-presented
930+
(update :nextjournal/value (comp demunge-ex-data
931+
(partial present-ex-data wrapped-value)
932+
datafy/datafy))))})
910933

911934
#?(:clj
912935
(defn buffered-image->bytes [^BufferedImage image]
@@ -957,17 +980,6 @@
957980
(def mathjax-viewer
958981
{:name `mathjax-viewer :render-fn 'nextjournal.clerk.render/render-mathjax :transform-fn mark-presented})
959982

960-
(defn ->opts [wrapped-value]
961-
(select-keys wrapped-value [:nextjournal/budget :nextjournal/css-class :nextjournal/width :nextjournal/render-opts
962-
:nextjournal/render-evaluator
963-
:!budget :store!-wrapped-value :present-elision-fn :path :offset]))
964-
965-
(defn inherit-opts [{:as wrapped-value :nextjournal/keys [viewers]} value path-segment]
966-
(-> (ensure-wrapped-with-viewers viewers value)
967-
(merge (select-keys (->opts wrapped-value) [:!budget :store!-wrapped-value :present-elision-fn :nextjournal/budget :path]))
968-
(update :path (fnil conj []) path-segment)))
969-
970-
971983
(defn transform-html [{:as wrapped-value :keys [path]}]
972984
(let [!path-idx (atom -1)]
973985
(update wrapped-value
@@ -1254,6 +1266,10 @@
12541266
:transform-fn transform-toc
12551267
:render-fn 'nextjournal.clerk.render.navbar/render-items})
12561268

1269+
(defn present-error [error]
1270+
{:nextjournal/presented (present error)
1271+
:nextjournal/blob-id (str (gensym "error"))})
1272+
12571273
(defn process-blocks [viewers {:as doc :keys [ns]}]
12581274
(-> doc
12591275
(assoc :atom-var-name->state (atom-var-name->state doc))
@@ -1278,7 +1294,7 @@
12781294
:toc-visibility
12791295
:header
12801296
:footer])
1281-
(update-if :error present)
1297+
(update-if :error present-error)
12821298
(assoc :sidenotes? (boolean (seq (:footnotes doc))))
12831299
#?(:clj (cond-> ns (assoc :scope (datafy-scope ns))))))
12841300

src/nextjournal/clerk/webserver.clj

+15-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
[clojure.string :as str]
88
[editscript.core :as editscript]
99
[nextjournal.clerk.config :as config]
10+
[nextjournal.clerk.git :as git]
1011
[nextjournal.clerk.paths :as paths]
1112
[nextjournal.clerk.view :as view]
1213
[nextjournal.clerk.viewer :as v]
@@ -173,10 +174,21 @@
173174

174175
(declare present+reset!)
175176

176-
(defn get-build-opts []
177-
(paths/process-paths @!server))
177+
(defn get-build-opts
178+
([] (get-build-opts @!server))
179+
([{:as opts :keys [paths paths-fn index]}]
180+
(merge (git/read-git-attrs)
181+
(if (or paths paths-fn index)
182+
(paths/expand-paths opts)
183+
opts))))
178184

179185
#_(get-build-opts)
186+
#_(get-build-opts {:paths ["notebooks/rule_30.clj"]})
187+
#_(get-build-opts {:paths ["notebooks/rule_30.clj"] :index "notebooks/links.md"})
188+
#_(get-build-opts {:paths ["notebooks/no_rule_30.clj"]})
189+
#_(v/route-index? (get-build-opts @!server))
190+
#_(route-index (get-build-opts @!server) "")
191+
#_(route-index (get-build-opts {:index "notebooks/rule_30.clj"}) "")
180192

181193
(defn ->nav-path [file-or-ns]
182194
(cond (or (= 'nextjournal.clerk.index file-or-ns)
@@ -247,7 +259,7 @@
247259
(defn prefetch-request? [req] (= "prefetch" (-> req :headers (get "purpose"))))
248260

249261
(defn serve-notebook [{:as req :keys [uri]}]
250-
(let [opts (paths/process-paths @!server)
262+
(let [opts (get-build-opts)
251263
nav-path (maybe-route-index opts (subs uri 1))]
252264
(cond
253265
(prefetch-request? req)

test/nextjournal/clerk/viewer_test.clj

+22
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,28 @@
132132
(is (= :full
133133
(:nextjournal/width (v/apply-viewers (v/table {:nextjournal.clerk/width :full} {:a [1] :b [2] :c [3]})))))))
134134

135+
(deftest present-exceptions
136+
(testing "can represent ex-data in a readable way"
137+
(binding [*data-readers* v/data-readers]
138+
139+
(is (-> (eval-test/eval+extract-doc-blocks "(ex-info \"💥\" {:foo 123 :boom (fn boom [x] x)})")
140+
second :nextjournal/value :nextjournal/presented
141+
v/->edn
142+
read-string))
143+
144+
(is (-> (eval-test/eval+extract-doc-blocks "(ex-info \"💥\" {:foo 123 :boom (fn boom [x] x)} (RuntimeException. \"no way\"))")
145+
second :nextjournal/value :nextjournal/presented
146+
v/->edn
147+
read-string)))))
148+
149+
(deftest present-functions
150+
(testing "can represent functions in a readable way"
151+
(binding [*data-readers* v/data-readers]
152+
(is (-> (eval-test/eval+extract-doc-blocks "(fn boom [x] x)")
153+
second :nextjournal/value :nextjournal/presented
154+
v/->edn
155+
read-string)))))
156+
135157
(deftest datafy-scope
136158
(is (= (ns-name *ns*)
137159
(v/datafy-scope *ns*)

0 commit comments

Comments
 (0)