From 20afc215c8729866ce0287b62b1a999295997e70 Mon Sep 17 00:00:00 2001 From: Martin Kavalar Date: Thu, 27 Jan 2022 10:45:56 +0100 Subject: [PATCH] Viewer API: support setting `:nextjournal/width` For both functional and metadata api. --- notebooks/viewer_api.clj | 3 ++ src/nextjournal/clerk.clj | 58 ++++++++++++++++++------------- src/nextjournal/clerk/view.clj | 11 +++--- src/nextjournal/clerk/viewer.cljc | 26 +++++++------- test/nextjournal/clerk_test.clj | 21 ++++++++++- 5 files changed, 76 insertions(+), 43 deletions(-) diff --git a/notebooks/viewer_api.clj b/notebooks/viewer_api.clj index d9ba8fe89..a145d4012 100644 --- a/notebooks/viewer_api.clj +++ b/notebooks/viewer_api.clj @@ -21,6 +21,9 @@ ;; Alternatively you can pass it an HTML string. (clerk/html "Never forget.") +;; ### 🔢 Tables +;; The table viewer api take a number of formats. Each viewer also takes an optional map as a first argument for customization. +(clerk/table {::clerk/width :full} (into (sorted-map) (map (fn [c] [(keyword (str c)) (shuffle (range 5))])) "abcdefghiklmno")) ;; ### 📑 Markdown ;; The Markdown viewer is useful for programmatically generated markdown. diff --git a/src/nextjournal/clerk.clj b/src/nextjournal/clerk.clj index 314b0ea89..a4a485870 100644 --- a/src/nextjournal/clerk.clj +++ b/src/nextjournal/clerk.clj @@ -116,6 +116,10 @@ result)] (wrapped-with-metadata result visibility blob-id)))) +(defn maybe-resolve-viewer [{:as opts :nextjournal/keys [viewer]}] + (cond-> opts + (symbol? viewer) + (update :nextjournal/viewer resolve))) (defn read+eval-cached [results-last-run ->hash doc-visibility codeblock] (let [{:keys [ns-effect? form var]} codeblock @@ -129,8 +133,10 @@ cached-result? (and (not no-cache?) cas-hash (-> cas-hash ->cache-file fs/exists?)) - viewer-on-form-meta (let [v (-> form meta ::viewer)] - (cond-> v (symbol? v) resolve))] + opts-from-form-meta (-> (meta form) + (select-keys [::viewer ::width]) + v/normalize-viewer-opts + maybe-resolve-viewer)] #_(prn :cached? (cond no-cache? :no-cache cached-result? true cas-hash :no-cas-file @@ -140,19 +146,19 @@ (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)))) + (seq opts-from-form-meta) + (merge opts-from-form-meta)))) #_(eval-file "notebooks/test123.clj") #_(eval-file "notebooks/how_clerk_works.clj") #_(read+eval-cached {} {} #{:show} "(subs (slurp \"/usr/share/dict/words\") 0 1000)") -(defn clear-cache! - ([] - (if (fs/exists? config/cache-dir) - (do - (fs/delete-tree config/cache-dir) - (prn :cache-dir/deleted config/cache-dir)) - (prn :cache-dir/does-not-exist config/cache-dir)))) +(defn clear-cache! [] + (if (fs/exists? config/cache-dir) + (do + (fs/delete-tree config/cache-dir) + (prn :cache-dir/deleted config/cache-dir)) + (prn :cache-dir/does-not-exist config/cache-dir))) (defn blob->result [blocks] @@ -162,14 +168,14 @@ #_(blob->result @nextjournal.clerk.webserver/!doc) (defn +eval-results [results-last-run parsed-doc] - (let [{:as info :keys [doc]} (hashing/build-graph parsed-doc) ;; TODO: clarify that this returns an analyzed doc - ->hash (hashing/hash info) - {:keys [blocks visibility]} doc - blocks (into [] (map (fn [{:as cell :keys [type]}] - (cond-> cell - (= :code type) - (assoc :result (read+eval-cached results-last-run ->hash visibility cell))))) blocks)] - (assoc parsed-doc :blocks blocks :blob->result (blob->result blocks) :ns *ns*))) +(let [{:as info :keys [doc]} (hashing/build-graph parsed-doc) ;; TODO: clarify that this returns an analyzed doc + ->hash (hashing/hash info) + {:keys [blocks visibility]} doc + blocks (into [] (map (fn [{:as cell :keys [type]}] + (cond-> cell + (= :code type) + (assoc :result (read+eval-cached results-last-run ->hash visibility cell))))) blocks)] + (assoc parsed-doc :blocks blocks :blob->result (blob->result blocks) :ns *ns*))) (defn parse-file [file] (hashing/parse-file {:doc? true} file)) @@ -245,13 +251,15 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; public viewer api -(def md v/md) -(def plotly v/plotly) -(def vl v/vl) -(def tex v/tex) -(def notebook v/notebook) -(def html v/html) -(def code v/code) + +;; these are refercing vars for convience when working at the REPL +(def md #'v/md) +(def plotly #'v/plotly) +(def vl #'v/vl) +(def tex #'v/tex) +(def notebook #'v/notebook) +(def html #'v/html) +(def code #'v/code) (def table #'v/table) (def use-headers #'v/use-headers) (def hide-result #'v/hide-result) diff --git a/src/nextjournal/clerk/view.clj b/src/nextjournal/clerk/view.clj index 258cd07cf..23fce73e0 100644 --- a/src/nextjournal/clerk/view.clj +++ b/src/nextjournal/clerk/view.clj @@ -106,8 +106,9 @@ base64-encode-value) described-result)) -(defn ->result [ns {:nextjournal/keys [value blob-id]} lazy-load?] - (let [described-result (extract-blobs lazy-load? blob-id (v/describe value {:viewers (v/get-viewers ns (v/viewers value))}))] +(defn ->result [ns {:as result :nextjournal/keys [value blob-id]} lazy-load?] + (let [described-result (extract-blobs lazy-load? blob-id (v/describe value {:viewers (v/get-viewers ns (v/viewers value))})) + opts-from-form-meta (select-keys result [:nextjournal/width])] (merge {:nextjournal/viewer :clerk/result :nextjournal/value (cond-> (try {:nextjournal/edn (->edn described-result)} (catch Throwable _e @@ -118,8 +119,8 @@ lazy-load? (assoc :nextjournal/fetch-opts {:blob-id blob-id} :nextjournal/hash (->hash-str [blob-id (selected-viewers described-result)])))} - - (dissoc described-result :nextjournal/value :nextjournal/viewer)))) + (dissoc described-result :nextjournal/value :nextjournal/viewer) + opts-from-form-meta))) #_(nextjournal.clerk/show! "notebooks/hello.clj") #_(nextjournal.clerk/show! "notebooks/viewers/image.clj") @@ -173,7 +174,7 @@ v/notebook (cond-> ns (assoc :scope (v/datafy-scope ns)))))) -#_(meta (doc->viewer (nextjournal.clerk/eval-file "notebooks/hello.clj"))) +#_(doc->viewer (nextjournal.clerk/eval-file "notebooks/hello.clj")) #_(nextjournal.clerk/show! "notebooks/test.clj") #_(nextjournal.clerk/show! "notebooks/visibility.clj") diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index f4baa3e56..6ddf420de 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -2,6 +2,7 @@ (:require [clojure.string :as str] [clojure.pprint :as pprint] [clojure.datafy :as datafy] + [clojure.set :as set] [clojure.walk :as w] #?@(:clj [[clojure.repl :refer [demunge]] [nextjournal.clerk.config :as config]] @@ -205,8 +206,8 @@ {:name :plotly :render-fn (quote v/plotly-viewer) :fetch-fn fetch-all} {:name :vega-lite :render-fn (quote v/vega-lite-viewer) :fetch-fn fetch-all} {:name :markdown :render-fn (quote v/markdown-viewer) :fetch-fn fetch-all} - {:name :code :render-fn (quote v/code-viewer) :fetch-fn fetch-all} - {:name :code-folded :render-fn (quote v/foldable-code-viewer) :fetch-fn fetch-all} + {:name :code :render-fn (quote v/code-viewer) :fetch-fn fetch-all :transform-fn #(let [v (value %)] (if (string? v) v (with-out-str (pprint/pprint v))))} + {:name :code-folded :render-fn (quote v/foldable-code-viewer) :fetch-fn fetch-all :transform-fn #(let [v (value %)] (if (string? v) v (with-out-str (pprint/pprint v))))} {:name :reagent :render-fn (quote v/reagent-viewer) :fetch-fn fetch-all} {:name :eval! :render-fn (constantly 'nextjournal.clerk.viewer/set-viewers!)} {:name :table :render-fn (quote v/table-viewer) :fetch-opts {:n 5} @@ -538,6 +539,10 @@ #_(registration? (set-viewers! [])) #_(nextjournal.clerk/show! "notebooks/viewers/vega.clj") +(defn normalize-viewer-opts [opts] + (set/rename-keys opts {:nextjournal.clerk/viewer :nextjournal/viewer + :nextjournal.clerk/width :nextjournal/width})) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; public api @@ -545,11 +550,12 @@ "Wraps given " ([viewer x] (with-viewer viewer {} x)) ([viewer opts x] - (merge opts (-> x - wrap-value - (assoc :nextjournal/viewer (cond-> viewer - (or (list? viewer) (symbol? viewer)) - ->viewer-fn)))))) + (merge (normalize-viewer-opts opts) + (-> x + wrap-value + (assoc :nextjournal/viewer (cond-> viewer + (or (list? viewer) (symbol? viewer)) + ->viewer-fn)))))) #_(with-viewer :latex "x^2") #_(with-viewer '#(v/html [:h3 "Hello " % "!"]) "x^2") @@ -576,8 +582,4 @@ (def notebook (partial with-viewer :clerk/notebook)) (defn doc-url [path] (->viewer-eval (list 'v/doc-url path))) - -(defn code [x] - (with-viewer :code (if (string? x) x (with-out-str (pprint/pprint x))))) - -#_(code '(+ 1 2)) +(def code (partial with-viewer :code)) diff --git a/test/nextjournal/clerk_test.clj b/test/nextjournal/clerk_test.clj index fea327eb4..1d2eb18db 100644 --- a/test/nextjournal/clerk_test.clj +++ b/test/nextjournal/clerk_test.clj @@ -5,7 +5,8 @@ [clojure.test :refer :all] [matcher-combinators.test :refer [match?]] [nextjournal.clerk :as clerk] - [nextjournal.clerk.hashing :as hashing]) + [nextjournal.clerk.hashing :as hashing] + [nextjournal.clerk.view :as view]) (:import (java.io File))) (deftest url-canonicalize @@ -81,3 +82,21 @@ (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\"])"))))) + +(deftest eval-string+doc->viewer + (testing "assigns the correct width from form meta" + (is (match? [{:nextjournal/width :full} + {:nextjournal/width :wide}] + (-> "^{:nextjournal.clerk/visibility :hide} (ns clerk-test-width) + +^{:nextjournal.clerk/viewer :table :nextjournal.clerk/width :full} +(def dataset + [[1 2] [3 4]]) + +^{:nextjournal.clerk/viewer :html :nextjournal.clerk/width :wide} +[:div.bg-red-200 [:h1 \"Wide Hiccup\"]] +" + clerk/eval-string + view/doc->viewer + :nextjournal/value + :blocks)))))