Skip to content

Commit

Permalink
Ensure ex-data is always presentable (#617)
Browse files Browse the repository at this point in the history
Prevent browser from crashing when the file watcher is started with `:show-filter-fn` and a namespace throws exceptions
  • Loading branch information
zampino authored Jan 25, 2024
1 parent 31326c1 commit 8d2929b
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 40 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ Changes can be:

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

* 🐜 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).

* 🐜 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`.

* 🐞 Fix `:nextjournal.clerk/page-size` option throwing when set on string values, fixes [#584][https://github.com/nextjournal/clerk/issues/584]
Expand Down
7 changes: 7 additions & 0 deletions notebooks/pagination.clj
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,10 @@

;; Images are displayed correctly when expanding elided data:
(concat (range 20) (list (clerk/image "trees.png")))

;; Elisions in exception data are fetched correctly:
(ex-info "Boink 💥"
{:boom (fn boom [x] x)
:range (range 30)
:image (clerk/image "trees.png")}
(RuntimeException. "no way"))
2 changes: 1 addition & 1 deletion src/nextjournal/clerk.clj
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
(println (str "Clerk evaluated '" file "' in " time-ms "ms."))
(webserver/update-doc! result))
(catch Exception e
(webserver/update-doc! (assoc (-> e ex-data ::doc) :error e))
(webserver/update-doc! (-> e ex-data ::doc (assoc :error e) (update :ns #(or % (find-ns 'user)))))
(throw e))))))

#_(show! "notebooks/exec_status.clj")
Expand Down
16 changes: 1 addition & 15 deletions src/nextjournal/clerk/paths.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
"Clerk's paths expansion and paths-fn handling."
(:require [babashka.fs :as fs]
[clojure.edn :as edn]
[clojure.string :as str]
[nextjournal.clerk.git :as git])
[clojure.string :as str])
(:import [java.net URL]))

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

(defn process-paths [{:as opts :keys [paths paths-fn index]}]
(merge (if (or paths paths-fn index)
(expand-paths opts)
opts)
(git/read-git-attrs)))

#_(process-paths {:paths ["notebooks/rule_30.clj"]})
#_(process-paths {:paths ["notebooks/rule_30.clj"] :index "notebooks/links.md"})
#_(process-paths {:paths ["notebooks/no_rule_30.clj"]})
#_(v/route-index? (process-paths @!server))
#_(route-index (process-paths @!server) "")


(defn path-in-cwd
"Turns `file` into a unixified (forward slashed) path if the is in the cwd,
returns `nil` otherwise."
Expand Down
28 changes: 21 additions & 7 deletions src/nextjournal/clerk/render.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@
[:div.overflow-x-auto.overflow-y-hidden.w-full.shadow.sticky-table-header
[:table.text-xs.sans-serif.text-gray-900.dark:text-white.not-prose {:ref !table-clone-ref :style {:margin 0}}]]]))

(defn throwable-view [{:keys [via trace]}]
(defn throwable-view [{:keys [via trace]} opts]
[:div.bg-white.max-w-6xl.mx-auto.text-xs.monospace.not-prose
(into
[:div]
Expand All @@ -491,7 +491,7 @@
[:div.font-bold "Unhandled " type])
[:div.font-bold.mt-1 message]
(when data
[:div.mt-1 [inspect (viewer/inspect-wrapped-values data)]])])
[:div.mt-1 [inspect-presented opts data]])])
via))
[:div.py-6.overflow-x-auto
[:table.w-full
Expand All @@ -503,10 +503,10 @@
[:td.py-1.pr-6 call]]))
trace)]]])

(defn render-throwable [ex]
(defn render-throwable [ex opts]
(if (or (:stack ex) (instance? js/Error ex))
[error-view ex]
[throwable-view ex]))
[throwable-view ex opts]))

(defn render-tagged-value
([tag value] (render-tagged-value {:space? true} tag value))
Expand Down Expand Up @@ -560,16 +560,30 @@

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

(defn with-fetch-fn [{:nextjournal/keys [presented blob-id]} body-fn]
;; TODO: unify with result-viewer
(let [!presented-value (hooks/use-state presented)
body-fn* (hooks/use-callback body-fn)]
[view-context/provide
{:fetch-fn (fn [elision]
(.then (fetch! {:blob-id blob-id} elision)
(fn [more] (swap! !presented-value viewer/merge-presentations more elision))))}
[body-fn* @!presented-value]]))

(defn root []
[:<>
[:div.fixed.w-full.z-20.top-0.left-0.w-full
(when-let [status (:nextjournal.clerk.sci-env/connection-status @!doc)]
[connection-status status])
(when-let [status (:status @!doc)]
[exec-status status])]
(when-let [error (get-in @!doc [:nextjournal/value :error])]
[:div.fixed.top-0.left-0.w-full.h-full
[inspect-presented error]])
(when-let [{:as wrapped-value :nextjournal/keys [blob-id]} (get-in @!doc [:nextjournal/value :error])]
(let [!expanded-at (r/atom {})]
^{:key blob-id}
[with-fetch-fn wrapped-value
(fn [presented-value]
[:div.fixed.top-0.left-0.w-full.h-full
[inspect-presented {:!expanded-at !expanded-at} presented-value]])]))
(when (:nextjournal/value @!doc)
[inspect-presented @!doc])
(into [:<>]
Expand Down
44 changes: 30 additions & 14 deletions src/nextjournal/clerk/viewer.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -901,12 +901,35 @@
:transform-fn (comp #?(:cljs var->symbol :clj symbol) ->value)
:render-fn '(fn [x] [:span.inspected-value [:span.cmt-meta "#'" (str x)]])})

(defn ->opts [wrapped-value]
(select-keys wrapped-value [:nextjournal/budget :nextjournal/css-class :nextjournal/width :nextjournal/render-opts
:nextjournal/render-evaluator
:!budget :store!-wrapped-value :present-elision-fn :path :offset]))

(defn inherit-opts [{:as wrapped-value :nextjournal/keys [viewers]} value path-segment]
(-> (ensure-wrapped-with-viewers viewers value)
(merge (select-keys (->opts wrapped-value) [:!budget :store!-wrapped-value :present-elision-fn :nextjournal/budget :path]))
(update :path (fnil conj []) path-segment)))

(defn present-ex-data [parent throwable-map]
(let [present-child (fn [idx data] (present (inherit-opts parent data idx)))]
(-> throwable-map
(update-if :data (partial present-child 0))
(update-if :via (fn [exs]
(mapv (fn [i ex] (update-if ex :data (partial present-child (inc i))))
(range (count exs))
exs))))))

(def throwable-viewer
{:name `throwable-viewer
:render-fn 'nextjournal.clerk.render/render-throwable
:pred (fn [e] (instance? #?(:clj Throwable :cljs js/Error) e))
:transform-fn (comp mark-presented (update-val (comp demunge-ex-data
datafy/datafy)))})
:transform-fn (fn [wrapped-value]
(-> wrapped-value
mark-presented
(update :nextjournal/value (comp demunge-ex-data
(partial present-ex-data wrapped-value)
datafy/datafy))))})

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

(defn ->opts [wrapped-value]
(select-keys wrapped-value [:nextjournal/budget :nextjournal/css-class :nextjournal/width :nextjournal/render-opts
:nextjournal/render-evaluator
:!budget :store!-wrapped-value :present-elision-fn :path :offset]))

(defn inherit-opts [{:as wrapped-value :nextjournal/keys [viewers]} value path-segment]
(-> (ensure-wrapped-with-viewers viewers value)
(merge (select-keys (->opts wrapped-value) [:!budget :store!-wrapped-value :present-elision-fn :nextjournal/budget :path]))
(update :path (fnil conj []) path-segment)))


(defn transform-html [{:as wrapped-value :keys [path]}]
(let [!path-idx (atom -1)]
(update wrapped-value
Expand Down Expand Up @@ -1254,6 +1266,10 @@
:transform-fn transform-toc
:render-fn 'nextjournal.clerk.render.navbar/render-items})

(defn present-error [error]
{:nextjournal/presented (present error)
:nextjournal/blob-id (str (gensym "error"))})

(defn process-blocks [viewers {:as doc :keys [ns]}]
(-> doc
(assoc :atom-var-name->state (atom-var-name->state doc))
Expand All @@ -1278,7 +1294,7 @@
:toc-visibility
:header
:footer])
(update-if :error present)
(update-if :error present-error)
(assoc :sidenotes? (boolean (seq (:footnotes doc))))
#?(:clj (cond-> ns (assoc :scope (datafy-scope ns))))))

Expand Down
18 changes: 15 additions & 3 deletions src/nextjournal/clerk/webserver.clj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
[clojure.string :as str]
[editscript.core :as editscript]
[nextjournal.clerk.config :as config]
[nextjournal.clerk.git :as git]
[nextjournal.clerk.paths :as paths]
[nextjournal.clerk.view :as view]
[nextjournal.clerk.viewer :as v]
Expand Down Expand Up @@ -173,10 +174,21 @@

(declare present+reset!)

(defn get-build-opts []
(paths/process-paths @!server))
(defn get-build-opts
([] (get-build-opts @!server))
([{:as opts :keys [paths paths-fn index]}]
(merge (git/read-git-attrs)
(if (or paths paths-fn index)
(paths/expand-paths opts)
opts))))

#_(get-build-opts)
#_(get-build-opts {:paths ["notebooks/rule_30.clj"]})
#_(get-build-opts {:paths ["notebooks/rule_30.clj"] :index "notebooks/links.md"})
#_(get-build-opts {:paths ["notebooks/no_rule_30.clj"]})
#_(v/route-index? (get-build-opts @!server))
#_(route-index (get-build-opts @!server) "")
#_(route-index (get-build-opts {:index "notebooks/rule_30.clj"}) "")

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

(defn serve-notebook [{:as req :keys [uri]}]
(let [opts (paths/process-paths @!server)
(let [opts (get-build-opts)
nav-path (maybe-route-index opts (subs uri 1))]
(cond
(prefetch-request? req)
Expand Down
22 changes: 22 additions & 0 deletions test/nextjournal/clerk/viewer_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,28 @@
(is (= :full
(:nextjournal/width (v/apply-viewers (v/table {:nextjournal.clerk/width :full} {:a [1] :b [2] :c [3]})))))))

(deftest present-exceptions
(testing "can represent ex-data in a readable way"
(binding [*data-readers* v/data-readers]

(is (-> (eval-test/eval+extract-doc-blocks "(ex-info \"💥\" {:foo 123 :boom (fn boom [x] x)})")
second :nextjournal/value :nextjournal/presented
v/->edn
read-string))

(is (-> (eval-test/eval+extract-doc-blocks "(ex-info \"💥\" {:foo 123 :boom (fn boom [x] x)} (RuntimeException. \"no way\"))")
second :nextjournal/value :nextjournal/presented
v/->edn
read-string)))))

(deftest present-functions
(testing "can represent functions in a readable way"
(binding [*data-readers* v/data-readers]
(is (-> (eval-test/eval+extract-doc-blocks "(fn boom [x] x)")
second :nextjournal/value :nextjournal/presented
v/->edn
read-string)))))

(deftest datafy-scope
(is (= (ns-name *ns*)
(v/datafy-scope *ns*)
Expand Down

0 comments on commit 8d2929b

Please sign in to comment.