From 1f419f9cab207dff52902428eee77c1777662ba1 Mon Sep 17 00:00:00 2001 From: Martin Kavalar Date: Tue, 18 Jan 2022 16:51:14 +0100 Subject: [PATCH 1/5] Make simple meta viewers work --- notebooks/viewers_meta.clj | 31 +++++++++++++++++++++++++++++++ src/nextjournal/clerk/viewer.cljc | 6 ++++-- 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 notebooks/viewers_meta.clj diff --git a/notebooks/viewers_meta.clj b/notebooks/viewers_meta.clj new file mode 100644 index 000000000..9ecfa92a7 --- /dev/null +++ b/notebooks/viewers_meta.clj @@ -0,0 +1,31 @@ +;; # Viewers Meta +^{:nextjournal.clerk/visibility :hide-ns} +(ns viewers-meta + (:require [nextjournal.clerk :as clerk])) + +;; Clerk's viewer api has been based on functions like in the following example. +(def tabular-data + (clerk/table {:col-1 ["a" "b" "c"] :col-2 ["a" "b" "c"]})) + +;; This isn't always what you want as it performs a tranformation of your data. +(keys tabular-data) + +;; If this isn't what you want because you depend on it downstream, you can alternatively use metadata to convey the viewer. +^{::clerk/viewer :html} +[:h1 "hi"] + +^{::clerk/viewer clerk/html} +[:h1 "hi"] + + +^{::clerk/viewer clerk/table} +{:col-1 ["a" "b" "c"] :col-2 ["a" "b" "c"]} + +^{::clerk/viewer :table} +{:col-1 ["a" "b" "c"] :col-2 ["a" "b" "c"]} + +^{::clerk/viewer clerk/table} +(def tabular-data-untouched + {:col-1 ["a" "b" "c"] :col-2 ["a" "b" "c"]}) + +(keys tabular-data-untouched) diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index c8ee21085..4198db201 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -71,12 +71,14 @@ (defn viewer "Returns the `:nextjournal/viewer` for a given wrapped value `x`, `nil` otherwise." [x] - (when (map? x) - (:nextjournal/viewer x))) + (or (and (map? x) (:nextjournal/viewer x)) + (when-let [m (meta x)] + (:nextjournal.clerk/viewer m)))) #_(viewer (with-viewer :code '(+ 1 2 3))) #_(viewer "123") +#_(viewer ^{:nextjournal.clerk/viewer :table} {:col-1 [1 2 3] :col-2 [1 2 3]}) (defn viewers "Returns the `:nextjournal/viewers` for a given wrapped value `x`, `nil` otherwise." From 37b1e9329d56dac6437ea7f2af474326469814b8 Mon Sep 17 00:00:00 2001 From: Martin Kavalar Date: Wed, 19 Jan 2022 20:49:34 +0100 Subject: [PATCH 2/5] Allow vars to select viewer via meta on form --- notebooks/viewers_meta.clj | 21 +++++++++++++++------ src/nextjournal/clerk.clj | 22 ++++++++++++++++------ 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/notebooks/viewers_meta.clj b/notebooks/viewers_meta.clj index 9ecfa92a7..5b202ae5f 100644 --- a/notebooks/viewers_meta.clj +++ b/notebooks/viewers_meta.clj @@ -1,8 +1,21 @@ ;; # Viewers Meta ^{:nextjournal.clerk/visibility :hide-ns} -(ns viewers-meta +(ns ^:nextjournal.clerk/no-cache viewers-meta (:require [nextjournal.clerk :as clerk])) +;; Simple examples + +^{::clerk/viewer :html} +[:h1 "hi"] + +^{::clerk/viewer :html} +(def markup + [:h1 "hi"]) + +^{::clerk/viewer clerk/html} +(def markup-viewer-fn + [:h1 "hio"]) + ;; Clerk's viewer api has been based on functions like in the following example. (def tabular-data (clerk/table {:col-1 ["a" "b" "c"] :col-2 ["a" "b" "c"]})) @@ -11,12 +24,8 @@ (keys tabular-data) ;; If this isn't what you want because you depend on it downstream, you can alternatively use metadata to convey the viewer. -^{::clerk/viewer :html} -[:h1 "hi"] - -^{::clerk/viewer clerk/html} -[:h1 "hi"] +markup ^{::clerk/viewer clerk/table} {:col-1 ["a" "b" "c"] :col-2 ["a" "b" "c"]} diff --git a/src/nextjournal/clerk.clj b/src/nextjournal/clerk.clj index 02131e770..96a66cb96 100644 --- a/src/nextjournal/clerk.clj +++ b/src/nextjournal/clerk.clj @@ -108,6 +108,17 @@ (wrapped-with-metadata (cond-> result introduced-var var-from-def) visibility blob-id)))) +(defn maybe-assign-viewer [result {::keys [viewer]}] + (if-let [var (and viewer (get-in result [:nextjournal/value ::var-from-def]))] + (assoc result :nextjournal/value (if-let [viewer-fn (and (symbol? viewer) (resolve viewer))] + (viewer-fn @var) + {:nextjournal/value @var + :nextjournal/viewer viewer})) + result)) + +#_(eval-string "^{:nextjournal.clerk/viewer :html} (def markup [:h1 \"hi\"])") +#_(eval-string "^{:nextjournal.clerk/viewer nextjournal.clerk/html} (def markup [:h1 \"hi\"])") + (defn read+eval-cached [results-last-run ->hash doc-visibility codeblock] (let [{:keys [ns-effect? form var]} codeblock no-cache? (or ns-effect? @@ -126,10 +137,10 @@ :else :no-digest-file) :hash hash :cas-hash cas-hash :form form :var var :ns-effect? ns-effect?) (fs/create-dirs config/cache-dir) - (let [introduced-var var] - (or (when cached-result? - (lookup-cached-result results-last-run introduced-var hash cas-hash visibility)) - (eval+cache! form hash digest-file introduced-var no-cache? visibility))))) + (-> (or (when cached-result? + (lookup-cached-result results-last-run var hash cas-hash visibility)) + (eval+cache! form hash digest-file var no-cache? visibility)) + (maybe-assign-viewer (meta form))))) #_(eval-file "notebooks/test123.clj") #_(eval-file "notebooks/how_clerk_works.clj") @@ -350,8 +361,7 @@ - `:bundle?` builds a single page app versus a folder with an html page for each notebook (defaults to `true`) - `:path-prefix` a prefix to urls - `:out-path` a relative path to a folder to contain the static pages (defaults to `\"public/build\"`) - - `:git/sha`, `:git/url` when both present, each page displays a link to `(str url \"blob\" sha path-to-notebook)` - " + - `:git/sha`, `:git/url` when both present, each page displays a link to `(str url \"blob\" sha path-to-notebook)`" [{:as opts :keys [paths out-path bundle? browse?] :or {paths clerk-docs out-path (str "public" fs/file-separator "build") From 3199c4543996604ffc89ef5f26dae29c4c38606d Mon Sep 17 00:00:00 2001 From: Martin Kavalar Date: Thu, 20 Jan 2022 09:04:31 +0100 Subject: [PATCH 3/5] Refactor, test & cleanup sample notebook --- .gitignore | 1 + notebooks/viewers_meta.clj | 26 +++++--------------------- src/nextjournal/clerk.clj | 23 +++++++---------------- src/nextjournal/clerk/view.clj | 19 +++++++++++++++++-- src/nextjournal/clerk/viewer.cljc | 13 +++++++++---- test/nextjournal/clerk_test.clj | 8 +++++++- 6 files changed, 46 insertions(+), 44 deletions(-) diff --git a/.gitignore b/.gitignore index 3cfb2813b..361e80bf1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .DS_Store .cache/ +.clerk/ .cpcache/ .lsp/ /node_modules/ diff --git a/notebooks/viewers_meta.clj b/notebooks/viewers_meta.clj index 5b202ae5f..6a40374aa 100644 --- a/notebooks/viewers_meta.clj +++ b/notebooks/viewers_meta.clj @@ -3,20 +3,8 @@ (ns ^:nextjournal.clerk/no-cache viewers-meta (:require [nextjournal.clerk :as clerk])) -;; Simple examples - -^{::clerk/viewer :html} -[:h1 "hi"] - -^{::clerk/viewer :html} -(def markup - [:h1 "hi"]) - -^{::clerk/viewer clerk/html} -(def markup-viewer-fn - [:h1 "hio"]) - ;; Clerk's viewer api has been based on functions like in the following example. + (def tabular-data (clerk/table {:col-1 ["a" "b" "c"] :col-2 ["a" "b" "c"]})) @@ -25,16 +13,12 @@ ;; If this isn't what you want because you depend on it downstream, you can alternatively use metadata to convey the viewer. -markup - -^{::clerk/viewer clerk/table} -{:col-1 ["a" "b" "c"] :col-2 ["a" "b" "c"]} - -^{::clerk/viewer :table} -{:col-1 ["a" "b" "c"] :col-2 ["a" "b" "c"]} - ^{::clerk/viewer clerk/table} (def tabular-data-untouched {:col-1 ["a" "b" "c"] :col-2 ["a" "b" "c"]}) (keys tabular-data-untouched) + +;; This also works on literals, not just on vars. Though you will less care about transformation in that case – as you'll not be holding a reference to it. As you see in the following example, you can also use keywords instead of functions. This is useful when you don't want to require Clerk. +^{::clerk/viewer :html} +[:h1 "Ohai Hiccup 👋"] diff --git a/src/nextjournal/clerk.clj b/src/nextjournal/clerk.clj index 96a66cb96..43c7303f8 100644 --- a/src/nextjournal/clerk.clj +++ b/src/nextjournal/clerk.clj @@ -108,17 +108,6 @@ (wrapped-with-metadata (cond-> result introduced-var var-from-def) visibility blob-id)))) -(defn maybe-assign-viewer [result {::keys [viewer]}] - (if-let [var (and viewer (get-in result [:nextjournal/value ::var-from-def]))] - (assoc result :nextjournal/value (if-let [viewer-fn (and (symbol? viewer) (resolve viewer))] - (viewer-fn @var) - {:nextjournal/value @var - :nextjournal/viewer viewer})) - result)) - -#_(eval-string "^{:nextjournal.clerk/viewer :html} (def markup [:h1 \"hi\"])") -#_(eval-string "^{:nextjournal.clerk/viewer nextjournal.clerk/html} (def markup [:h1 \"hi\"])") - (defn read+eval-cached [results-last-run ->hash doc-visibility codeblock] (let [{:keys [ns-effect? form var]} codeblock no-cache? (or ns-effect? @@ -130,17 +119,19 @@ visibility (if-let [fv (hashing/->visibility form)] fv doc-visibility) cached-result? (and (not no-cache?) cas-hash - (-> cas-hash ->cache-file fs/exists?))] + (-> cas-hash ->cache-file fs/exists?)) + viewer-on-form-meta (let [v (-> form meta ::viewer)] + (cond-> v (symbol? v) resolve))] #_(prn :cached? (cond no-cache? :no-cache cached-result? true cas-hash :no-cas-file :else :no-digest-file) :hash hash :cas-hash cas-hash :form form :var var :ns-effect? ns-effect?) (fs/create-dirs config/cache-dir) - (-> (or (when cached-result? - (lookup-cached-result results-last-run var hash cas-hash visibility)) - (eval+cache! form hash digest-file var no-cache? visibility)) - (maybe-assign-viewer (meta form))))) + (cond-> (or (when cached-result? + (lookup-cached-result results-last-run var hash cas-hash visibility)) + (eval+cache! form hash digest-file var no-cache? visibility)) + viewer-on-form-meta (assoc :nextjournal/viewer viewer-on-form-meta)))) #_(eval-file "notebooks/test123.clj") #_(eval-file "notebooks/how_clerk_works.clj") diff --git a/src/nextjournal/clerk/view.clj b/src/nextjournal/clerk/view.clj index 17b58da43..8d6bd4d9b 100644 --- a/src/nextjournal/clerk/view.clj +++ b/src/nextjournal/clerk/view.clj @@ -84,8 +84,23 @@ (update result :nextjournal/value (fn [data] (str "data:" content-type ";base64, " (.encodeToString (java.util.Base64/getEncoder) data))))) -(defn ->result [ns {:nextjournal/keys [value blob-id]} lazy-load?] - (let [described-result (v/describe value {:viewers (v/get-viewers ns (v/viewers value))}) + + +(defn apply-viewer-unwrapping-var-from-def [value viewer] + (let [value (if (get value :nextjournal.clerk/var-from-def) + (-> value :nextjournal.clerk/var-from-def deref) + value)] + (if (var? viewer) + (viewer value) + {:nextjournal/value value + :nextjournal/viewer viewer}))) + +#_(apply-viewer-unwrapping-var-from-def [:h1 "hi"] :html) +#_(apply-viewer-unwrapping-var-from-def [:h1 "hi"] (resolve 'nextjournal.clerk/html)) + +(defn ->result [ns {:nextjournal/keys [value blob-id viewer]} lazy-load?] + (let [value (cond-> value viewer (apply-viewer-unwrapping-var-from-def viewer)) + described-result (v/describe value {:viewers (v/get-viewers ns (v/viewers value))}) content-type (:nextjournal/content-type described-result)] (merge {:nextjournal/viewer :clerk/result :nextjournal/value (cond-> (try {:nextjournal/edn (->edn (cond-> described-result diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index 4198db201..5ccc4a109 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -71,10 +71,7 @@ (defn viewer "Returns the `:nextjournal/viewer` for a given wrapped value `x`, `nil` otherwise." [x] - (or (and (map? x) (:nextjournal/viewer x)) - (when-let [m (meta x)] - (:nextjournal.clerk/viewer m)))) - + (and (map? x) (:nextjournal/viewer x))) #_(viewer (with-viewer :code '(+ 1 2 3))) #_(viewer "123") @@ -278,6 +275,7 @@ #_(wrapped-with-viewer (with-viewer :elision {:remaining 10 :count 30 :offset 19})) #_(wrapped-with-viewer (with-viewer (->Form '(fn [name] (html [:<> "Hello " name]))) "James")) + (defn get-viewers "Returns all the viewers that apply in precendence of: optional local `viewers`, viewers set per `ns`, as well on the `:root`." ([ns] (get-viewers ns nil)) @@ -534,6 +532,13 @@ (def tex (partial with-viewer :latex)) (def hide-result (partial with-viewer :hide-result)) (def notebook (partial with-viewer :clerk/notebook)) + + +;; if with-viewer used meta, would be less obvious to see what's happening +;; same is true for moving stuff into a transform fn + + + (defn doc-url [path] (->viewer-eval (list 'v/doc-url path))) diff --git a/test/nextjournal/clerk_test.clj b/test/nextjournal/clerk_test.clj index 87bb19736..8d3e4fa8e 100644 --- a/test/nextjournal/clerk_test.clj +++ b/test/nextjournal/clerk_test.clj @@ -69,4 +69,10 @@ (testing "random expression doesn't get cached with no-cache" (is (not= (clerk/eval-string "(ns ^:nextjournal.clerk/no-cache my-random-test-ns) (java.util.UUID/randomUUID)") - (clerk/eval-string "(ns ^:nextjournal.clerk/no-cache my-random-test-ns) (java.util.UUID/randomUUID)"))))) + (clerk/eval-string "(ns ^:nextjournal.clerk/no-cache my-random-test-ns) (java.util.UUID/randomUUID)")))) + + (testing "assigning viewers from form meta" + (is (match? {:blocks [{:result {:nextjournal/viewer #'nextjournal.clerk/table}}]} + (clerk/eval-string "^{:nextjournal.clerk/viewer nextjournal.clerk/table} (def markup [:h1 \"hi\"])"))) + (is (match? {:blocks [{:result {:nextjournal/viewer :html}}]} + (clerk/eval-string "^{:nextjournal.clerk/viewer :html} (def markup [:h1 \"hi\"])"))))) From 71c53dac0363b475960478216d817ea4bf01a504 Mon Sep 17 00:00:00 2001 From: Martin Kavalar Date: Thu, 20 Jan 2022 09:06:37 +0100 Subject: [PATCH 4/5] cleanup --- src/nextjournal/clerk/viewer.cljc | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index 5ccc4a109..fc5d9934a 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -65,17 +65,18 @@ (:nextjournal/value x) x)) + #_(value (with-viewer :code '(+ 1 2 3))) #_(value 123) (defn viewer "Returns the `:nextjournal/viewer` for a given wrapped value `x`, `nil` otherwise." [x] - (and (map? x) (:nextjournal/viewer x))) + (when (map? x) + (:nextjournal/viewer x))) #_(viewer (with-viewer :code '(+ 1 2 3))) #_(viewer "123") -#_(viewer ^{:nextjournal.clerk/viewer :table} {:col-1 [1 2 3] :col-2 [1 2 3]}) (defn viewers "Returns the `:nextjournal/viewers` for a given wrapped value `x`, `nil` otherwise." @@ -275,7 +276,6 @@ #_(wrapped-with-viewer (with-viewer :elision {:remaining 10 :count 30 :offset 19})) #_(wrapped-with-viewer (with-viewer (->Form '(fn [name] (html [:<> "Hello " name]))) "James")) - (defn get-viewers "Returns all the viewers that apply in precendence of: optional local `viewers`, viewers set per `ns`, as well on the `:root`." ([ns] (get-viewers ns nil)) @@ -532,13 +532,6 @@ (def tex (partial with-viewer :latex)) (def hide-result (partial with-viewer :hide-result)) (def notebook (partial with-viewer :clerk/notebook)) - - -;; if with-viewer used meta, would be less obvious to see what's happening -;; same is true for moving stuff into a transform fn - - - (defn doc-url [path] (->viewer-eval (list 'v/doc-url path))) From a0bd665f0295f2fb1ebe437981a9edf337a4e45b Mon Sep 17 00:00:00 2001 From: Martin Kavalar Date: Thu, 20 Jan 2022 09:21:59 +0100 Subject: [PATCH 5/5] Tweak sample doc and add it to snapshot build --- notebooks/{viewers_meta.clj => viewer_api_meta.clj} | 8 ++++---- src/nextjournal/clerk.clj | 1 + src/nextjournal/clerk/viewer.cljc | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) rename notebooks/{viewers_meta.clj => viewer_api_meta.clj} (71%) diff --git a/notebooks/viewers_meta.clj b/notebooks/viewer_api_meta.clj similarity index 71% rename from notebooks/viewers_meta.clj rename to notebooks/viewer_api_meta.clj index 6a40374aa..80a8371eb 100644 --- a/notebooks/viewers_meta.clj +++ b/notebooks/viewer_api_meta.clj @@ -1,4 +1,4 @@ -;; # Viewers Meta +;; # Metadata-Based Viewer API ^{:nextjournal.clerk/visibility :hide-ns} (ns ^:nextjournal.clerk/no-cache viewers-meta (:require [nextjournal.clerk :as clerk])) @@ -8,15 +8,15 @@ (def tabular-data (clerk/table {:col-1 ["a" "b" "c"] :col-2 ["a" "b" "c"]})) -;; This isn't always what you want as it performs a tranformation of your data. +;; This isn't always what you want as it performs a transformation of your data. (keys tabular-data) -;; If this isn't what you want because you depend on it downstream, you can alternatively use metadata to convey the viewer. - +;; You can alternatively use metadata on the form to convey the viewer. ^{::clerk/viewer clerk/table} (def tabular-data-untouched {:col-1 ["a" "b" "c"] :col-2 ["a" "b" "c"]}) +;; And see that it remains untouched. This comes with the added benefit that changing a viewer does not require a recomputation. (keys tabular-data-untouched) ;; This also works on literals, not just on vars. Though you will less care about transformation in that case – as you'll not be holding a reference to it. As you see in the following example, you can also use keywords instead of functions. This is useful when you don't want to require Clerk. diff --git a/src/nextjournal/clerk.clj b/src/nextjournal/clerk.clj index 43c7303f8..6b404c891 100644 --- a/src/nextjournal/clerk.clj +++ b/src/nextjournal/clerk.clj @@ -315,6 +315,7 @@ "rule_30" "visibility" "viewer_api" + "viewer_api_meta" "viewers/html" "viewers/image" "viewers/markdown" diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index fc5d9934a..c8ee21085 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -65,7 +65,6 @@ (:nextjournal/value x) x)) - #_(value (with-viewer :code '(+ 1 2 3))) #_(value 123) @@ -75,6 +74,7 @@ (when (map? x) (:nextjournal/viewer x))) + #_(viewer (with-viewer :code '(+ 1 2 3))) #_(viewer "123")