Skip to content

Commit 84c7cb2

Browse files
borkdudemkzampino
authored
Add global cherry evaluator setting + viewer opt (#486)
Add support for cherry as an alternative to sci to evaluate `:render-fn`s. You can change it per form (using form metadata or viewer opts) or doc-wide (using ns metadata) with `{:nextjournal.clerk/render-evaluator :cherry}`. Co-authored-by: Martin Kavalar <[email protected]> Co-authored-by: Andrea Amantini <[email protected]>
1 parent 377dff9 commit 84c7cb2

File tree

9 files changed

+125
-107
lines changed

9 files changed

+125
-107
lines changed

CHANGELOG.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,19 @@ Changes can be:
2222

2323
* 🚨 Change `nextjournal.clerk.render/clerk-eval` to not recompute the currently shown document when using the 1-arity version. Added a second arity that takes an opts map with a `:recompute?` key.
2424

25-
* ⭐️ Adds support for customization of viewer options
25+
* 🍒 Add support for cherry as an alternative to sci to evaluate `:render-fn`s. You can change it per form (using form metadata or viewer opts) or doc-wide (using ns metadata) with `{:nextjournal.clerk/render-evaluator :cherry}`.
26+
27+
* ⭐️ Adds support for customization of viewer options
2628

2729
Support both globally (via ns metadata or a settings marker) or locally (via form metadata or the viewer options map).
28-
30+
2931
Supported options are:
3032
* `:nextjournal.clerk/auto-expand-results?`
3133
* `:nextjournal.clerk/budget`
3234
* `:nextjournal.clerk/css-class`
3335
* `:nextjournal.clerk/visibility`
3436
* `:nextjournal.clerk/width`
37+
* `:nextjournal.clerk/render-evaluator`
3538

3639
* 🔌 Make websocket reconnect automatically on close to avoid having to reload the page
3740

notebooks/cherry.clj

Lines changed: 46 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,53 @@
11
;; # Compile viewer functions using cherry
2-
(ns notebooks.cherry
3-
#_{:nextjournal.clerk/visibility {:code :hide}
4-
:nextjournal.clerk/auto-expand-results? true}
2+
(ns cherry
3+
{:nextjournal.clerk/render-evaluator :cherry}
54
(:require [nextjournal.clerk :as clerk]
65
[nextjournal.clerk.viewer :as viewer]))
76

8-
(comment
9-
(clerk/clear-cache!))
7+
#_(clerk/clear-cache!)
8+
#_(clerk/halt!)
9+
#_(clerk/serve! {:port 7777})
10+
11+
;; Since we set `:nextjournal.clerk/render-evaluator :cherry` on the ns meta, evaluation happens through cherry by default
12+
;; in all codeblocks below. As a test for checking that Cherry is actually being picked up, we drop some `this-as` expressions
13+
;; in the code, since that will throw an exceptions when evaluated by SCI.
1014

1115
(clerk/with-viewer
12-
{:render-fn
13-
'(fn [value]
14-
[:pre (time (do (dotimes [_ 100000]
15-
(js/Math.sin 100))
16-
(pr-str (interleave (cycle [1]) (frequencies [1 2 3 1 2 3])))))])}
17-
(+ 1 2 3 5))
16+
'(fn [value]
17+
[:pre (time (do (dotimes [_ 100000]
18+
(js/Math.sin 100))
19+
(pr-str (interleave (cycle [1]) (frequencies [1 2 3 1 2 3])))))])
20+
{:nextjournal.clerk/render-evaluator :sci} nil)
1821

1922
;; Better performance:
2023

24+
(clerk/with-viewer
25+
'(fn [value]
26+
[:pre
27+
(time (do (dotimes [_ 100000]
28+
(js/Math.sin 100))
29+
(pr-str (interleave (cycle [1]) (frequencies [1 2 3 1 2 3])))))]) nil)
30+
2131
(clerk/with-viewer
2232
{:render-fn
2333
'(fn [value]
24-
[:pre
25-
(time (do (dotimes [_ 100000]
26-
(js/Math.sin 100))
27-
(pr-str (interleave (cycle [1]) (frequencies [1 2 3 1 2 3])))))])
28-
:evaluator :cherry}
34+
[:pre (this-as this value)])}
2935
(+ 1 2 3 5))
3036

3137
;; Let's use a render function in the :render-fn next
3238

33-
(clerk/with-viewer
34-
{:render-fn
35-
'(fn [value]
36-
[nextjournal.clerk.render/render-code "(+ 1 2 3)"])
37-
:evaluator :cherry}
38-
(+ 1 2 3 5))
39+
(clerk/with-viewer '(fn [value]
40+
[nextjournal.clerk.render/render-code "(+ 1 2 3)"]) nil)
3941

4042
;; Recursive ...
4143

42-
(clerk/with-viewer
43-
{:render-fn
44-
'(fn [value]
45-
[nextjournal.clerk.render/inspect {:a (range 30)}])
46-
:evaluator :cherry}
47-
nil)
48-
49-
;; cherry vega viewer!
44+
(clerk/with-viewer '(fn [value]
45+
(this-as this
46+
[nextjournal.clerk.render/inspect {:a (range 30)}])) nil)
5047

51-
(def cherry-vega-viewer (assoc viewer/vega-lite-viewer :evaluator :cherry))
48+
;; vega viewer still works
5249

53-
(clerk/with-viewer
54-
cherry-vega-viewer
50+
(clerk/vl
5551
{:width 700 :height 400 :data {:url "https://vega.github.io/vega-datasets/data/us-10m.json"
5652
:format {:type "topojson" :feature "counties"}}
5753
:transform [{:lookup "id" :from {:data {:url "https://vega.github.io/vega-datasets/data/unemployment.tsv"}
@@ -61,7 +57,7 @@
6157
;; ## Input text and compile on the fly with cherry
6258

6359
(clerk/with-viewer
64-
{:evaluator :cherry
60+
{;; :evaluator :cherry
6561
:render-fn
6662
'(fn [value]
6763
(let [default-value "(defn foo [x] (+ x 10))
@@ -82,17 +78,16 @@
8278
[nextjournal.clerk.render/inspect
8379
(try (js/eval @!compiled)
8480
(catch :default e e))]])))}
81+
{:nextjournal.clerk/render-evaluator :cherry}
8582
nil)
8683

8784
;; ## Functions defined with `defn` are part of the global context
8885

8986
;; (for now) and can be called in successive expressions
9087

91-
(clerk/eval-cljs-str {:evaluator :cherry}
92-
"(defn foo [x] x)")
88+
(clerk/eval-cljs-str "(defn foo [x] (this-as this (inc x)))")
9389

94-
(clerk/eval-cljs-str {:evaluator :cherry}
95-
"(foo 1)")
90+
(clerk/eval-cljs-str "(foo 1)")
9691

9792
;; ## Async/await works cherry
9893

@@ -102,9 +97,7 @@
10297
;; async functions need `^:async`, we use a plain string.
10398

10499

105-
^::clerk/no-cache
106100
(clerk/eval-cljs
107-
{:evaluator :cherry}
108101
'(defn emoji-picker
109102
{:async true}
110103
[]
@@ -115,30 +108,26 @@
115108

116109
;; In the next block we call it:
117110

118-
^::clerk/no-cache
119111
(clerk/with-viewer
120-
{:evaluator :cherry
121-
:render-fn '(fn [_]
122-
[nextjournal.clerk.render/render-promise
123-
(emoji-picker)])}
124-
nil)
112+
'(fn [_]
113+
[nextjournal.clerk.render/render-promise
114+
(emoji-picker)]) nil)
125115

126116
;; ## Macros
127117

128-
^::clerk/no-cache
129118
(clerk/eval-cljs
130-
{:evaluator :cherry}
131119
'(defn clicks []
132120
(reagent.core/with-let [!s (reagent.core/atom 0)]
133-
[:button {:on-click (fn []
134-
(swap! !s inc))}
121+
[:button.bg-teal-500.hover:bg-teal-700.text-white.font-bold.py-2.px-4.rounded.rounded-full.font-sans
122+
{:on-click (fn [] (swap! !s inc))}
135123
"Clicks: " @!s])))
136124

137-
^::clerk/no-cache
138-
(clerk/with-viewer
139-
{:evaluator :cherry
140-
:render-fn '(fn [_]
141-
[clicks])
142-
}
143-
nil)
125+
(clerk/with-viewer '(fn [_] (this-as this [clicks])) nil)
126+
127+
;; ## Evaluator option as form metadata
128+
^{::clerk/visibility {:code :hide :result :hide} ::clerk/no-cache true}
129+
(clerk/add-viewers! [(assoc viewer/code-block-viewer :transform-fn (viewer/update-val :text))])
144130

131+
;; Test reverting the default option via metadata:
132+
^{::clerk/render-evaluator :sci}
133+
(clerk/with-viewer '(fn [_] (load-string "[:h1 \"Ahoi\"]")) nil)

src/nextjournal/clerk/builder.clj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"notebooks/onwards.md"]
2323
(map #(str "notebooks/" % ".clj"))
2424
["cards"
25+
"cherry"
2526
"controlling_width"
2627
"docs"
2728
"document_linking"
@@ -63,8 +64,7 @@
6364
"viewers/plotly"
6465
"viewers/table"
6566
"viewers/tex"
66-
"viewers/vega"
67-
"cherry"]))
67+
"viewers/vega"]))
6868

6969

7070
(defn strip-index [path]

src/nextjournal/clerk/cherry_env.cljs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
[sci.configs.reagent.reagent :as sci.configs.reagent]))
2222

2323
(cherry/preserve-ns 'cljs.core)
24+
(cherry/preserve-ns 'nextjournal.clerk.viewer)
2425
(cherry/preserve-ns 'nextjournal.clerk.render)
2526
(cherry/preserve-ns 'nextjournal.clerk.render.code)
2627
(cherry/preserve-ns 'nextjournal.clerk.render.hooks)

src/nextjournal/clerk/parser.cljc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@
5454
:nextjournal.clerk/css-class
5555
:nextjournal.clerk/visibility
5656
:nextjournal.clerk/opts
57-
:nextjournal.clerk/width})
57+
:nextjournal.clerk/width
58+
:nextjournal.clerk/render-evaluator})
5859

5960
(defn settings-marker? [form]
6061
(boolean (and (map? form)

src/nextjournal/clerk/viewer.cljc

Lines changed: 50 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,13 @@
7676

7777
#?(:clj
7878
(defmethod print-method ViewerFn [v ^java.io.Writer w]
79-
(.write w (str "#viewer-fn" (when (= :cherry (:evaluator v))
79+
(.write w (str "#viewer-fn" (when (= :cherry (:render-evaluator v))
8080
"/cherry")
8181
" " (pr-str (:form v))))))
8282

8383
#?(:clj
8484
(defmethod print-method ViewerEval [v ^java.io.Writer w]
85-
(.write w (str "#viewer-eval" (when (= :cherry (:evaluator v))
85+
(.write w (str "#viewer-eval" (when (= :cherry (:render-evaluator v))
8686
"/cherry")
8787
" " (pr-str (:form v)))))
8888
:cljs
@@ -869,6 +869,7 @@
869869

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

874875
(defn inherit-opts [{:as wrapped-value :nextjournal/keys [viewers]} value path-segment]
@@ -1349,23 +1350,28 @@
13491350

13501351
(declare assign-closing-parens)
13511352

1352-
(defn process-render-fn [{:as viewer :keys [render-fn evaluator]}]
1353+
(defn process-render-fn [{:as viewer :keys [render-fn render-evaluator]}]
13531354
(cond-> viewer
13541355
(and render-fn (not (viewer-fn? render-fn)))
13551356
(update :render-fn (fn [rf]
13561357
(assoc (->viewer-fn rf)
1357-
:evaluator (or evaluator :sci))))))
1358+
:render-evaluator (or render-evaluator :sci))))))
13581359

13591360
(defn hash-sha1 [x]
13601361
#?(:clj (analyzer/valuehash :sha1 x)
13611362
:cljs (let [hasher (goog.crypt.Sha1.)]
13621363
(.update hasher (goog.crypt/stringToUtf8ByteArray (pr-str x)))
13631364
(.digest hasher))))
13641365

1365-
(defn process-viewer [viewer]
1366+
(defn process-viewer [viewer {:nextjournal/keys [render-evaluator]}]
1367+
;; TODO: drop wrapped-value arg here and handle this elsewhere by
1368+
;; passing modified viewer stack
1369+
;; `(clerk/update-viewers viewers {:render-fn #(assoc % :render-evaluator :cherry)})`
13661370
(if-not (map? viewer)
13671371
viewer
13681372
(-> viewer
1373+
(cond-> (and (not (:render-evaluator viewer)) render-evaluator)
1374+
(assoc :render-evaluator render-evaluator))
13691375
(dissoc :pred :transform-fn :update-viewers-fn)
13701376
(as-> viewer (assoc viewer :hash (hash-sha1 viewer)))
13711377
(process-render-fn))))
@@ -1380,7 +1386,7 @@
13801386
(cond-> (-> wrapped-value
13811387
(select-keys processed-keys)
13821388
(dissoc :nextjournal/budget)
1383-
(update :nextjournal/viewer process-viewer))
1389+
(update :nextjournal/viewer process-viewer wrapped-value))
13841390
present-elision-fn (vary-meta assoc :present-elision-fn present-elision-fn)))
13851391

13861392
#_(process-wrapped-value (apply-viewers 42))
@@ -1699,37 +1705,49 @@
16991705
([x] (print-hide-result-deprecation-warning) (with-viewer hide-result-viewer {} x))
17001706
([viewer-opts x] (print-hide-result-deprecation-warning) (with-viewer hide-result-viewer viewer-opts x)))
17011707

1702-
1703-
(defn eval-cljs [opts & forms]
1704-
;; because ViewerEval's are evaluated at read time we can no longer
1705-
;; check after read if there was any in the doc. Thus we set the
1706-
;; `:nextjournal.clerk/remount` attribute to a hash of the code (so
1707-
;; it changes when the code changes and shows up in the doc patch.
1708-
;; TODO: simplify, maybe by applying Clerk's analysis to the cljs
1709-
;; part as well
1710-
(let [[opts forms] (if (map? opts)
1711-
[opts forms]
1712-
[nil (cons opts forms)])]
1713-
(with-viewer (assoc viewer-eval-viewer :nextjournal.clerk/remount (hash-sha1 forms))
1714-
(if (= :cherry (:evaluator opts))
1715-
(assoc (->viewer-eval
1716-
`(do ~@forms))
1717-
:evaluator (:evaluator opts))
1718-
(->viewer-eval
1719-
`(binding [*ns* *ns*]
1720-
~@forms))))))
1708+
(defn ^:private rewrite-for-cherry
1709+
"Takes a form as generated by `eval-cljs` or `eval-cljs-str` and rewrites it for cherry compatibility meaning:
1710+
* dropping the `(binding [*ns* *ns*] ,,,)`
1711+
* rewriting `load-string`"
1712+
[form]
1713+
(let [form-without-binding (last form)]
1714+
(if (and (list? form-without-binding) (= 'load-string (first form-without-binding)))
1715+
(list 'js/global_eval (list 'nextjournal.clerk.cherry-env/cherry-compile-string (second form-without-binding)))
1716+
form-without-binding)))
1717+
1718+
#_(rewrite-for-cherry '(binding [*ns* *ns*] (prn :foo)))
1719+
#_(rewrite-for-cherry '(binding [*ns* *ns*] (load-string "(prn :foo)")))
1720+
1721+
(defn ^:private maybe-rewrite-cljs-form-for-cherry [{:as wrapped-value :nextjournal/keys [render-evaluator]}]
1722+
(cond-> wrapped-value
1723+
(= :cherry render-evaluator)
1724+
(update :nextjournal/value
1725+
(fn [{:as viewer-eval :keys [form]}]
1726+
(-> viewer-eval
1727+
(assoc :render-evaluator render-evaluator)
1728+
(update :form rewrite-for-cherry))))))
1729+
1730+
(defn eval-cljs
1731+
([form] (eval-cljs {} form))
1732+
([viewer-opts form]
1733+
;; because ViewerEval's are evaluated at read time we can no longer
1734+
;; check after read if there was any in the doc. Thus we set the
1735+
;; `:nextjournal.clerk/remount` attribute to a hash of the code (so
1736+
;; it changes when the code changes and shows up in the doc patch.
1737+
;; TODO: simplify, maybe by applying Clerk's analysis to the cljs
1738+
;; part as well
1739+
(with-viewer (-> viewer-eval-viewer
1740+
(update :transform-fn comp maybe-rewrite-cljs-form-for-cherry)
1741+
(assoc :nextjournal.clerk/remount (hash-sha1 form)))
1742+
viewer-opts
1743+
(->viewer-eval (list 'binding '[*ns* *ns*] form)))))
17211744

17221745
(defn eval-cljs-str
1723-
([code-string] (eval-cljs-str nil code-string))
1746+
([code-string] (eval-cljs-str {} code-string))
17241747
([opts code-string]
17251748
;; NOTE: this relies on implementation details on how SCI code is evaluated
17261749
;; and will change in a future version of Clerk
1727-
(if (= :cherry (:evaluator opts))
1728-
(assoc (->viewer-eval
1729-
`(let [prog# (nextjournal.clerk.cherry-env/cherry-compile-string ~code-string)]
1730-
(js/global_eval prog#)))
1731-
:evaluator :cherry)
1732-
(eval-cljs (list 'load-string code-string)))))
1750+
(eval-cljs opts (list 'load-string code-string))))
17331751

17341752
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
17351753
;; examples

ui_tests/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"dependencies": {
33
"nbb": "^1.1.147",
4-
"playwright": "^1.19.1"
4+
"playwright": "^1.33.0"
55
}
66
}

0 commit comments

Comments
 (0)