Skip to content
Open
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
47 changes: 33 additions & 14 deletions src/nextjournal/clerk/viewer.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -429,27 +429,46 @@
(defn var-from-def? [x]
(var? (get-safe x :nextjournal.clerk/var-from-def)))

(def unwrap-var-value (some-fn :nextjournal.clerk/var-snapshot
(comp deref :nextjournal.clerk/var-from-def)))

(def var-from-def-viewer
{:name `var-from-def-viewer
:pred var-from-def?
:transform-fn (update-val (some-fn :nextjournal.clerk/var-snapshot
(comp deref :nextjournal.clerk/var-from-def)))})
:transform-fn (update-val unwrap-var-value)})

(defn apply-viewer-unwrapping-var-from-def
"Applies the `viewer` (if set) to the given result `result`. In case
the `value` is a `var-from-def?` it will be unwrapped unless the
viewer opts out with a truthy `:nextjournal.clerk/var-from-def`."
"Applies the `viewer` (if set) to the given result `result`. By default
the `value` of a `var-from-def?` is unwrapped so that the viewer
operates on the def's value. A viewer that wants the raw wrapper
sets `:var-from-def? true` — on the viewer map directly, or on the
viewer map that a fn viewer returns (the fn will be called a second
time with the raw `var-from-def?` value in that case)."
[{:as result :nextjournal/keys [value viewer]}]
(if viewer
(let [value+viewer (if (or (var? viewer) (fn? viewer))
(viewer value)
{:nextjournal/value value
:nextjournal/viewer (normalize-viewer viewer)})
{unwrap-var :transform-fn var-from-def? :pred} var-from-def-viewer]
(assoc result :nextjournal/value (cond-> value+viewer
(and (var-from-def? value)
(-> value+viewer ->viewer :var-from-def? not))
unwrap-var)))
(let [fn-viewer? (or (var? viewer) (fn? viewer))
is-var? (var-from-def? value)
keep-wrapped? (and (map? viewer) (:var-from-def? viewer))
apply-vw (fn [v]
(if fn-viewer?
(viewer v)
{:nextjournal/value v
:nextjournal/viewer (normalize-viewer viewer)}))
first-try (apply-vw (cond-> value
(and is-var? fn-viewer? (not keep-wrapped?))
unwrap-var-value))
return-keep-wrapped? (boolean (-> first-try ->viewer :var-from-def?))
value+viewer (cond
;; map viewer that doesn't want the wrapper -> unwrap
(and is-var? (not fn-viewer?) (not keep-wrapped?))
(update first-try :nextjournal/value unwrap-var-value)

;; fn viewer returned a viewer that wants the wrapper -> re-run with it
(and is-var? fn-viewer? return-keep-wrapped?)
(apply-vw value)

:else first-try)]
(assoc result :nextjournal/value value+viewer))
result))

#?(:clj
Expand Down
44 changes: 33 additions & 11 deletions test/nextjournal/clerk/viewer_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -212,19 +212,41 @@
(is (= [{:render-fn 'foo}] (v/get-viewers 'nextjournal.clerk.viewer-test.random-ns-name)))))

(def my-test-var [:h1 "hi"])
(def my-test-var2 1)

(def apply+get-value #(-> % v/apply-viewer-unwrapping-var-from-def :nextjournal/value :nextjournal/value))

(deftest apply-viewer-unwrapping-var-from-def
(let [apply+get-value #(-> % v/apply-viewer-unwrapping-var-from-def :nextjournal/value :nextjournal/value)]
(testing "unwraps var when viewer doens't opt out"
(is (= my-test-var
(apply+get-value {:nextjournal/value [:h1 "hi"] :nextjournal/viewer v/html})
(apply+get-value {:nextjournal/value {:nextjournal.clerk/var-from-def #'my-test-var} :nextjournal/viewer v/html})
(apply+get-value {:nextjournal/value {:nextjournal.clerk/var-from-def #'my-test-var} :nextjournal/viewer v/html-viewer}))))

(testing "leaves var wrapped when viewer opts out"
(is (= {:nextjournal.clerk/var-from-def #'my-test-var}
(apply+get-value {:nextjournal/value {:nextjournal.clerk/var-from-def #'my-test-var}
:nextjournal/viewer (assoc v/html-viewer :var-from-def? true)}))))))
(testing "unwraps var when viewer doens't opt out"
(is (= my-test-var
(apply+get-value {:nextjournal/value [:h1 "hi"] :nextjournal/viewer v/html})
(apply+get-value {:nextjournal/value {:nextjournal.clerk/var-from-def #'my-test-var} :nextjournal/viewer v/html})
(apply+get-value {:nextjournal/value {:nextjournal.clerk/var-from-def #'my-test-var} :nextjournal/viewer v/html-viewer}))))

(testing "leaves var wrapped when viewer opts out"
(is (= {:nextjournal.clerk/var-from-def #'my-test-var}
(apply+get-value {:nextjournal/value {:nextjournal.clerk/var-from-def #'my-test-var}
:nextjournal/viewer (assoc v/html-viewer :var-from-def? true)}))))

(testing "function viewer receives the deref'd value, not the var-from-def wrapper (fixes #770)"
(is (= [my-test-var2]
(apply+get-value {:nextjournal/value {:nextjournal.clerk/var-from-def #'my-test-var2
:nextjournal.clerk/var-snapshot my-test-var2}
:nextjournal/viewer v/row}))))

(testing "function viewer on a seq-valued def spreads across row cells"
(is (= [1 2 3]
(apply+get-value {:nextjournal/value {:nextjournal.clerk/var-from-def #'my-test-var
:nextjournal.clerk/var-snapshot [1 2 3]}
:nextjournal/viewer v/row}))))

(testing "function viewer whose returned viewer opts out keeps the raw var-from-def (e.g. cx/slider via render-eval-viewer)"
(let [viewer-fn (fn [v] (v/with-viewer (assoc v/html-viewer :var-from-def? true) v))]
(is (= {:nextjournal.clerk/var-from-def #'my-test-var
:nextjournal.clerk/var-snapshot [:h1 "hi"]}
(apply+get-value {:nextjournal/value {:nextjournal.clerk/var-from-def #'my-test-var
:nextjournal.clerk/var-snapshot [:h1 "hi"]}
:nextjournal/viewer viewer-fn}))))))


(deftest resolve-aliases
Expand Down
Loading