From ff2ef755292f86878687bb1a151b233b569d0ee7 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Tue, 18 Oct 2022 18:25:44 +0200 Subject: [PATCH 01/17] First sketch of Clerk in Babashka --- bb-runtime.edn | 27 +++++++++++ bb/stubs/multihash/core.clj | 3 ++ bb/stubs/multihash/digest.clj | 3 ++ bb/stubs/nextjournal/beholder.clj | 4 ++ bb/stubs/nextjournal/clerk/analyzer.clj | 41 ++++++++++++++++ bb/stubs/nextjournal/markdown.clj | 3 ++ bb/stubs/nextjournal/markdown/parser.clj | 3 ++ bb/stubs/nextjournal/markdown/transform.clj | 4 ++ bb/stubs/taoensso/nippy.clj | 5 ++ resources/viewer-js-hash | 2 +- src/nextjournal/{clerk.clj => clerk.cljc} | 3 +- src/nextjournal/clerk/{eval.clj => eval.cljc} | 23 +++++---- src/nextjournal/clerk/sci_viewer.cljs | 2 + src/nextjournal/clerk/view.clj | 4 +- src/nextjournal/clerk/viewer.cljc | 48 +++++++++++-------- src/nextjournal/clerk/webserver.clj | 4 +- 16 files changed, 146 insertions(+), 33 deletions(-) create mode 100644 bb-runtime.edn create mode 100644 bb/stubs/multihash/core.clj create mode 100644 bb/stubs/multihash/digest.clj create mode 100644 bb/stubs/nextjournal/beholder.clj create mode 100644 bb/stubs/nextjournal/clerk/analyzer.clj create mode 100644 bb/stubs/nextjournal/markdown.clj create mode 100644 bb/stubs/nextjournal/markdown/parser.clj create mode 100644 bb/stubs/nextjournal/markdown/transform.clj create mode 100644 bb/stubs/taoensso/nippy.clj rename src/nextjournal/{clerk.clj => clerk.cljc} (99%) rename src/nextjournal/clerk/{eval.clj => eval.cljc} (91%) diff --git a/bb-runtime.edn b/bb-runtime.edn new file mode 100644 index 000000000..bd252391c --- /dev/null +++ b/bb-runtime.edn @@ -0,0 +1,27 @@ +{:min-bb-version "0.9.159" + :paths ["bb/stubs" "src" "notebooks" "resources"] + :deps {;; hash replacement? + io.replikativ/hasch {:mvn/version "0.3.7"} + weavejester/dependency {:mvn/version "0.2.1"} + ;; shiiiiet! + ;; io.github.nextjournal/markdown {:mvn/version "0.4.126"} + hiccup/hiccup {:mvn/version "2.0.0-alpha2"} + } + :tasks + {nrepl + {:requires ([babashka.fs :as fs] + [babashka.nrepl.server :as srv]) + + :task (do (srv/start-server! {:host "localhost" :port 1339}) + (spit ".nrepl-port" "1339") + (-> (Runtime/getRuntime) + (.addShutdownHook (Thread. (fn [] (fs/delete ".nrepl-port"))))) + (deref (promise)))} + + serve + {:task (clerk/serve! {:port 9999})} + + clerk + {#_ #_ :depends [nrepl #_ serve] + :parallel? true + :task (println (ns-publics 'hiccup2.core))}}} diff --git a/bb/stubs/multihash/core.clj b/bb/stubs/multihash/core.clj new file mode 100644 index 000000000..bf30c73ee --- /dev/null +++ b/bb/stubs/multihash/core.clj @@ -0,0 +1,3 @@ +(ns multihash.core) + +(defn base58 [h] "base58") diff --git a/bb/stubs/multihash/digest.clj b/bb/stubs/multihash/digest.clj new file mode 100644 index 000000000..c722b226e --- /dev/null +++ b/bb/stubs/multihash/digest.clj @@ -0,0 +1,3 @@ +(ns multihash.digest) + +(defn sha2-512 [o] "sha2") diff --git a/bb/stubs/nextjournal/beholder.clj b/bb/stubs/nextjournal/beholder.clj new file mode 100644 index 000000000..72b31f72e --- /dev/null +++ b/bb/stubs/nextjournal/beholder.clj @@ -0,0 +1,4 @@ +(ns nextjournal.beholder) + +(defn watch [cb & args] nil) +(defn stop [w] nil) diff --git a/bb/stubs/nextjournal/clerk/analyzer.clj b/bb/stubs/nextjournal/clerk/analyzer.clj new file mode 100644 index 000000000..2a007c793 --- /dev/null +++ b/bb/stubs/nextjournal/clerk/analyzer.clj @@ -0,0 +1,41 @@ +(ns nextjournal.clerk.analyzer + (:require [nextjournal.clerk.config :as config] + [edamame.core :as edamame])) + +(defn valuehash [value] "hash") +(defn hash-codeblock [_ {:keys [hash]}] hash) +(defn ->hash-str [value] "hash-str") +(defn deref? [form] false) +(defn hash [doc] doc) +(defn hash-deref-deps [doc _cell] doc) + +(defn build-graph [doc] + (-> doc + (assoc :->analysis-info (fn [x] {:form x})) + ;; TODO: read first form, create sci.lang.Namespace + (assoc :ns *ns*) + (update :blocks + (partial into [] (map (fn [{:as b :keys [type text]}] + (cond-> b + (= :code type) + (assoc :form + (edamame/parse-string text + {:all true + :readers *data-readers* + :read-cond :allow + :regex #(list `re-pattern %) + :features #{:clj} + ;; TODO: + #_#_:auto-resolve (auto-resolves (or *ns* (find-ns 'user)))}))))))))) + +(defn exceeds-bounded-count-limit? [x] + (reduce (fn [_ xs] + (try + (let [limit config/*bounded-count-limit*] + (if (and (seqable? xs) (<= limit (bounded-count limit xs))) + (reduced true) + false)) + (catch Exception _e + (reduced true)))) + false + (tree-seq seqable? seq x))) diff --git a/bb/stubs/nextjournal/markdown.clj b/bb/stubs/nextjournal/markdown.clj new file mode 100644 index 000000000..6bef596bd --- /dev/null +++ b/bb/stubs/nextjournal/markdown.clj @@ -0,0 +1,3 @@ +(ns nextjournal.markdown) + +(defn parse [md] {:type :doc :content md}) diff --git a/bb/stubs/nextjournal/markdown/parser.clj b/bb/stubs/nextjournal/markdown/parser.clj new file mode 100644 index 000000000..55a8c459b --- /dev/null +++ b/bb/stubs/nextjournal/markdown/parser.clj @@ -0,0 +1,3 @@ +(ns nextjournal.markdown.parser) + +(defn add-title+toc [doc] doc) diff --git a/bb/stubs/nextjournal/markdown/transform.clj b/bb/stubs/nextjournal/markdown/transform.clj new file mode 100644 index 000000000..28968dfc7 --- /dev/null +++ b/bb/stubs/nextjournal/markdown/transform.clj @@ -0,0 +1,4 @@ +(ns nextjournal.markdown.transform) + +(defn ->text [node] "text") +(defn table-alignment [attrs] {}) diff --git a/bb/stubs/taoensso/nippy.clj b/bb/stubs/taoensso/nippy.clj new file mode 100644 index 000000000..e9ce3e8e1 --- /dev/null +++ b/bb/stubs/taoensso/nippy.clj @@ -0,0 +1,5 @@ +(ns taoensso.nippy) + +(defn freezable? [x] false) +(defn freeze [x] x) +(defn thaw-from-file [file] "thawed") diff --git a/resources/viewer-js-hash b/resources/viewer-js-hash index 7a589706c..b39c9d5d7 100644 --- a/resources/viewer-js-hash +++ b/resources/viewer-js-hash @@ -1 +1 @@ -46k5SeDmWgR6seZFSVzwdiCJ7BEi \ No newline at end of file +HqSvyTYhQi6sTyU5EVEMH3u4Dn6 \ No newline at end of file diff --git a/src/nextjournal/clerk.clj b/src/nextjournal/clerk.cljc similarity index 99% rename from src/nextjournal/clerk.clj rename to src/nextjournal/clerk.cljc index 1613920d0..ecc39167a 100644 --- a/src/nextjournal/clerk.clj +++ b/src/nextjournal/clerk.cljc @@ -40,7 +40,8 @@ (throw (ex-info (str "`nextjournal.clerk/show!` cannot show `nil`.") {:file-or-ns file-or-ns})) - (or (symbol? file-or-ns) (instance? clojure.lang.Namespace file-or-ns)) + ;; TODO: sci.lang.Namespace + (or (symbol? file-or-ns) (instance? #?(:bb (type *ns*) :clj clojure.lang.Namespace) file-or-ns)) (or (some (fn [ext] (io/resource (str (str/replace (namespace-munge file-or-ns) "." "/") ext))) [".clj" ".cljc"]) diff --git a/src/nextjournal/clerk/eval.clj b/src/nextjournal/clerk/eval.cljc similarity index 91% rename from src/nextjournal/clerk/eval.clj rename to src/nextjournal/clerk/eval.cljc index 7898c7ef8..99723bab1 100644 --- a/src/nextjournal/clerk/eval.clj +++ b/src/nextjournal/clerk/eval.cljc @@ -11,8 +11,9 @@ [nextjournal.clerk.parser :as parser] [nextjournal.clerk.viewer :as v] [taoensso.nippy :as nippy]) - (:import (java.awt.image BufferedImage) - (javax.imageio ImageIO))) + #?(:bb (:import) + :clj (:import (java.awt.image BufferedImage) + (javax.imageio ImageIO)))) (comment (alter-var-root #'nippy/*freeze-serializable-allowlist* (fn [_] "allow-and-record")) @@ -20,9 +21,11 @@ (nippy/get-recorded-serializable-classes)) ;; nippy tweaks -(alter-var-root #'nippy/*thaw-serializable-allowlist* (fn [_] (conj nippy/default-thaw-serializable-allowlist "java.io.File" "clojure.lang.Var" "clojure.lang.Namespace"))) -(nippy/extend-freeze BufferedImage :java.awt.image.BufferedImage [x out] (ImageIO/write x "png" (ImageIO/createImageOutputStream out))) -(nippy/extend-thaw :java.awt.image.BufferedImage [in] (ImageIO/read in)) +#?(:bb nil + :clj (do + (alter-var-root #'nippy/*thaw-serializable-allowlist* (fn [_] (conj nippy/default-thaw-serializable-allowlist "java.io.File" "clojure.lang.Var" "clojure.lang.Namespace"))) + (nippy/extend-freeze BufferedImage :java.awt.image.BufferedImage [x out] (ImageIO/write x "png" (ImageIO/createImageOutputStream out))) + (nippy/extend-thaw :java.awt.image.BufferedImage [in] (ImageIO/read in)))) #_(-> [(clojure.java.io/file "notebooks") (find-ns 'user)] nippy/freeze nippy/thaw) @@ -107,6 +110,10 @@ #_(prn :freeze-error e) nil))) +;; NOTE: cannot add clojure.main to bb stubs +(defn ex-triage [m] #?(:bb m :clj (main/ex-triage m))) +(defn ex-str [m] #?(:bb (str m) :clj (main/ex-str m))) + (defn ^:private eval+cache! [{:keys [form var ns-effect? no-cache? freezable?] :as form-info} hash digest-file] (try (let [{:keys [result]} (time-ms (binding [config/*in-clerk* true] @@ -128,8 +135,8 @@ result)] (wrapped-with-metadata result blob-id))) (catch Throwable t - (let [triaged (main/ex-triage (Throwable->map t))] - (throw (ex-info (main/ex-str triaged) triaged)))))) + (let [triaged (ex-triage (Throwable->map t))] + (throw (ex-info (ex-str triaged) triaged)))))) (defn maybe-eval-viewers [{:as opts :nextjournal/keys [viewer viewers]}] (cond-> opts @@ -196,7 +203,7 @@ "Evaluates the given `parsed-doc` using the `in-memory-cache` and augments it with the results." [in-memory-cache parsed-doc] (let [{:as analyzed-doc :keys [ns]} (analyzer/build-graph parsed-doc)] - (binding [*ns* ns] + (binding [*ns* (or ns *ns*)] (-> analyzed-doc analyzer/hash (assoc :blob->result in-memory-cache) diff --git a/src/nextjournal/clerk/sci_viewer.cljs b/src/nextjournal/clerk/sci_viewer.cljs index 48ccb3a4e..4492af151 100644 --- a/src/nextjournal/clerk/sci_viewer.cljs +++ b/src/nextjournal/clerk/sci_viewer.cljs @@ -10,6 +10,7 @@ [goog.string :as gstring] [nextjournal.clerk.viewer :as viewer :refer [code md plotly tex table vl row col with-viewer with-viewers]] [nextjournal.clerk.parser :as clerk.parser] + [nextjournal.markdown :as markdown] [nextjournal.markdown.transform :as md.transform] [nextjournal.ui.components.icon :as icon] [nextjournal.ui.components.localstorage :as ls] @@ -752,6 +753,7 @@ 'col col 'html html-render 'md md + 'md->hiccup markdown/->hiccup 'plotly plotly 'row row 'table table diff --git a/src/nextjournal/clerk/view.clj b/src/nextjournal/clerk/view.clj index d584fe4cd..0e3efe28c 100644 --- a/src/nextjournal/clerk/view.clj +++ b/src/nextjournal/clerk/view.clj @@ -12,7 +12,7 @@ (-> (merge doc opts) v/notebook v/present)))) -#_(doc->viewer (nextjournal.clerk/eval-file "notebooks/hello.clj")) +#_(doc->viewer (nextjournal.clerk.eval/eval-file "notebooks/hello.clj")) #_(nextjournal.clerk/show! "notebooks/test.clj") #_(nextjournal.clerk/show! "notebooks/visibility.clj") @@ -56,7 +56,7 @@ window.ws_send = msg => ws.send(msg)")]])) [:head [:title (or (and current-path (-> state :path->doc (get current-path) v/->value :title)) "Clerk")] [:meta {:charset "UTF-8"}] - [:meta {:name "viewport" :content "width=device-width, initial-scale=1"}] + [:meta {:name "viewport" :content "width=device-width, initial-scale=1"}] (include-css+js)] [:body [:div#clerk-static-app] diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index c4e9f3c0b..f3560cbe5 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -14,7 +14,10 @@ [applied-science.js-interop :as j]]) [nextjournal.markdown :as md] [nextjournal.markdown.transform :as md.transform]) - #?(:clj (:import (com.pngencoder PngEncoder) + #?(:bb (:import (java.nio.file Files StandardOpenOption) + (java.util Base64) + (java.lang Throwable)) + :clj (:import (com.pngencoder PngEncoder) (clojure.lang IDeref) (java.lang Throwable) (java.awt.image BufferedImage) @@ -466,7 +469,8 @@ #?(:clj (defn datafy-scope [scope] (cond - (instance? clojure.lang.Namespace scope) {:namespace (-> scope str keyword)} + (instance? #?(:bb (type *ns*) :clj clojure.lang.Namespace) scope) + {:namespace (-> scope str keyword)} (keyword? scope) scope :else (throw (ex-info (str "Unsupported scope " scope) {:scope scope}))))) @@ -605,7 +609,8 @@ {:pred (fn [e] (instance? #?(:clj Throwable :cljs js/Error) e)) :name :error :render-fn 'v/throwable-viewer :transform-fn (comp mark-presented (update-val (comp demunge-ex-data datafy/datafy)))}) -(def buffered-image-viewer #?(:clj {:pred #(instance? BufferedImage %) +(def buffered-image-viewer #?(:bb {} + :clj {:pred #(instance? BufferedImage %) :transform-fn (fn [{image :nextjournal/value}] (let [w (.getWidth image) h (.getHeight image) @@ -620,15 +625,16 @@ :render-fn '(fn [blob] (v/html [:figure.flex.flex-col.items-center.not-prose [:img {:src (v/url-for blob)}]]))})) (def ideref-viewer - {:pred #(instance? IDeref %) - :transform-fn (update-val (fn [ideref] - (with-viewer :tagged-value - {:tag "object" - :value (vector (symbol (pr-str (type ideref))) - #?(:clj (with-viewer :number-hex (System/identityHashCode ideref))) - (if-let [deref-as-map (resolve 'clojure.core/deref-as-map)] - (deref-as-map ideref) - ideref))})))}) + #?(:bb {} :clj + {:pred #(instance? IDeref %) + :transform-fn (update-val (fn [ideref] + (with-viewer :tagged-value + {:tag "object" + :value (vector (symbol (pr-str (type ideref))) + #?(:clj (with-viewer :number-hex (System/identityHashCode ideref))) + (if-let [deref-as-map (resolve 'clojure.core/deref-as-map)] + (deref-as-map ideref) + ideref))})))})) (def regex-viewer {:pred #?(:clj (partial instance? java.util.regex.Pattern) :cljs regexp?) @@ -660,12 +666,16 @@ {:name :vega-lite :render-fn (quote v/vega-lite-viewer) :transform-fn mark-presented}) (def markdown-viewer - {:name :markdown :transform-fn (fn [wrapped-value] - (-> wrapped-value - mark-presented - (update :nextjournal/value #(cond->> % (string? %) md/parse)) - (update :nextjournal/viewers add-viewers markdown-viewers) - (with-md-viewer)))}) + (-> {:name :markdown} + #?(:bb (assoc :transform-fn (comp mark-presented (update-val :content))) + :clj (assoc :transform-fn + (fn [wrapped-value] + (-> wrapped-value + mark-presented + (update :nextjournal/value #(cond->> % (string? %) md/parse)) + (update :nextjournal/viewers add-viewers markdown-viewers) + (with-md-viewer))))) + #?(:bb (assoc :render-fn '(fn [str] (js/console.log :str str) (v/html (v/md->hiccup str))))))) (def code-viewer {:name :code :render-fn (quote v/code-viewer) :transform-fn (comp mark-presented (update-val (fn [v] (if (string? v) v (str/trim (with-out-str (pprint/pprint v)))))))}) @@ -1275,7 +1285,7 @@ ([viewers] (reset-viewers! *ns* viewers)) ([scope viewers] (assert (or (#{:default} scope) - #?(:clj (instance? clojure.lang.Namespace scope)))) + #?(:bb :default :clj (instance? clojure.lang.Namespace scope)))) (swap! !viewers assoc scope viewers))) (defn add-viewers! [viewers] diff --git a/src/nextjournal/clerk/webserver.clj b/src/nextjournal/clerk/webserver.clj index 17e384578..b4637fa91 100644 --- a/src/nextjournal/clerk/webserver.clj +++ b/src/nextjournal/clerk/webserver.clj @@ -3,14 +3,14 @@ [clojure.edn :as edn] [clojure.pprint :as pprint] [clojure.string :as str] - [lambdaisland.uri :as uri] [nextjournal.clerk.view :as view] [nextjournal.clerk.viewer :as v] [nextjournal.markdown :as md] [org.httpkit.server :as httpkit])) (def help-doc - {:blocks [{:type :markdown :doc (md/parse "Use `nextjournal.clerk/show!` to make your notebook appear…")}]}) + {:ns *ns* + :blocks [{:type :markdown :doc (md/parse "Use `nextjournal.clerk/show!` to make your notebook appear…")}]}) (defonce !clients (atom #{})) (defonce !doc (atom help-doc)) From 36ba59c08c409c4c12d90d0145232d793ee1386a Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Tue, 18 Oct 2022 18:47:43 +0200 Subject: [PATCH 02/17] Fix bb task --- bb-runtime.edn | 21 ++++++++------------- resources/viewer-js-hash | 2 +- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/bb-runtime.edn b/bb-runtime.edn index bd252391c..0bed14042 100644 --- a/bb-runtime.edn +++ b/bb-runtime.edn @@ -8,20 +8,15 @@ hiccup/hiccup {:mvn/version "2.0.0-alpha2"} } :tasks - {nrepl + {clerk {:requires ([babashka.fs :as fs] - [babashka.nrepl.server :as srv]) - + [babashka.nrepl.server :as srv] + [nextjournal.clerk :as clerk]) :task (do (srv/start-server! {:host "localhost" :port 1339}) (spit ".nrepl-port" "1339") + (clerk/serve! {:port 9999}) (-> (Runtime/getRuntime) - (.addShutdownHook (Thread. (fn [] (fs/delete ".nrepl-port"))))) - (deref (promise)))} - - serve - {:task (clerk/serve! {:port 9999})} - - clerk - {#_ #_ :depends [nrepl #_ serve] - :parallel? true - :task (println (ns-publics 'hiccup2.core))}}} + (.addShutdownHook (Thread. (fn [] + (clerk/halt!) + (fs/delete ".nrepl-port"))))) + (deref (promise)))}}} diff --git a/resources/viewer-js-hash b/resources/viewer-js-hash index b39c9d5d7..3625ba889 100644 --- a/resources/viewer-js-hash +++ b/resources/viewer-js-hash @@ -1 +1 @@ -HqSvyTYhQi6sTyU5EVEMH3u4Dn6 \ No newline at end of file +3E4A2XqsHVVWy5QvM6RCuu7ScjWw \ No newline at end of file From 066161f4e0375fe419b49c01e92dbe34e65b006d Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Tue, 18 Oct 2022 19:13:59 +0200 Subject: [PATCH 03/17] Require latest babashka --- bb-runtime.edn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bb-runtime.edn b/bb-runtime.edn index 0bed14042..6abe7ceee 100644 --- a/bb-runtime.edn +++ b/bb-runtime.edn @@ -1,4 +1,4 @@ -{:min-bb-version "0.9.159" +{:min-bb-version "1.0.164" :paths ["bb/stubs" "src" "notebooks" "resources"] :deps {;; hash replacement? io.replikativ/hasch {:mvn/version "0.3.7"} From ed9b56090c32c7963a9a02a6d002a0e73d146e7d Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Wed, 19 Oct 2022 09:23:33 +0200 Subject: [PATCH 04/17] clean bb deps --- bb-runtime.edn | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/bb-runtime.edn b/bb-runtime.edn index 6abe7ceee..43fadb4b7 100644 --- a/bb-runtime.edn +++ b/bb-runtime.edn @@ -1,12 +1,6 @@ {:min-bb-version "1.0.164" :paths ["bb/stubs" "src" "notebooks" "resources"] - :deps {;; hash replacement? - io.replikativ/hasch {:mvn/version "0.3.7"} - weavejester/dependency {:mvn/version "0.2.1"} - ;; shiiiiet! - ;; io.github.nextjournal/markdown {:mvn/version "0.4.126"} - hiccup/hiccup {:mvn/version "2.0.0-alpha2"} - } + :deps {hiccup/hiccup {:mvn/version "2.0.0-alpha2"}} :tasks {clerk {:requires ([babashka.fs :as fs] From baa9660a368d9a3ef37c0dea88d2928182a84203 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Wed, 19 Oct 2022 10:21:52 +0200 Subject: [PATCH 05/17] Rely on latest bb for namespace instance check --- resources/viewer-js-hash | 2 +- src/nextjournal/clerk.cljc | 3 +-- src/nextjournal/clerk/view.clj | 4 ++-- src/nextjournal/clerk/viewer.cljc | 10 +++++----- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/resources/viewer-js-hash b/resources/viewer-js-hash index 3625ba889..87ead369c 100644 --- a/resources/viewer-js-hash +++ b/resources/viewer-js-hash @@ -1 +1 @@ -3E4A2XqsHVVWy5QvM6RCuu7ScjWw \ No newline at end of file +QHueRFEbfs7jTLGUVfX7SnJvJoo \ No newline at end of file diff --git a/src/nextjournal/clerk.cljc b/src/nextjournal/clerk.cljc index ecc39167a..1613920d0 100644 --- a/src/nextjournal/clerk.cljc +++ b/src/nextjournal/clerk.cljc @@ -40,8 +40,7 @@ (throw (ex-info (str "`nextjournal.clerk/show!` cannot show `nil`.") {:file-or-ns file-or-ns})) - ;; TODO: sci.lang.Namespace - (or (symbol? file-or-ns) (instance? #?(:bb (type *ns*) :clj clojure.lang.Namespace) file-or-ns)) + (or (symbol? file-or-ns) (instance? clojure.lang.Namespace file-or-ns)) (or (some (fn [ext] (io/resource (str (str/replace (namespace-munge file-or-ns) "." "/") ext))) [".clj" ".cljc"]) diff --git a/src/nextjournal/clerk/view.clj b/src/nextjournal/clerk/view.clj index 0e3efe28c..d584fe4cd 100644 --- a/src/nextjournal/clerk/view.clj +++ b/src/nextjournal/clerk/view.clj @@ -12,7 +12,7 @@ (-> (merge doc opts) v/notebook v/present)))) -#_(doc->viewer (nextjournal.clerk.eval/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") @@ -56,7 +56,7 @@ window.ws_send = msg => ws.send(msg)")]])) [:head [:title (or (and current-path (-> state :path->doc (get current-path) v/->value :title)) "Clerk")] [:meta {:charset "UTF-8"}] - [:meta {:name "viewport" :content "width=device-width, initial-scale=1"}] + [:meta {:name "viewport" :content "width=device-width, initial-scale=1"}] (include-css+js)] [:body [:div#clerk-static-app] diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index f3560cbe5..82e1b4373 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -469,7 +469,7 @@ #?(:clj (defn datafy-scope [scope] (cond - (instance? #?(:bb (type *ns*) :clj clojure.lang.Namespace) scope) + (instance? clojure.lang.Namespace scope) {:namespace (-> scope str keyword)} (keyword? scope) scope :else (throw (ex-info (str "Unsupported scope " scope) {:scope scope}))))) @@ -667,15 +667,15 @@ (def markdown-viewer (-> {:name :markdown} - #?(:bb (assoc :transform-fn (comp mark-presented (update-val :content))) + #?(:bb (assoc :transform-fn (comp mark-presented (update-val :content)) + :render-fn '(fn [str] (js/console.log :str str) (v/html (v/md->hiccup str)))) :clj (assoc :transform-fn (fn [wrapped-value] (-> wrapped-value mark-presented (update :nextjournal/value #(cond->> % (string? %) md/parse)) (update :nextjournal/viewers add-viewers markdown-viewers) - (with-md-viewer))))) - #?(:bb (assoc :render-fn '(fn [str] (js/console.log :str str) (v/html (v/md->hiccup str))))))) + (with-md-viewer))))))) (def code-viewer {:name :code :render-fn (quote v/code-viewer) :transform-fn (comp mark-presented (update-val (fn [v] (if (string? v) v (str/trim (with-out-str (pprint/pprint v)))))))}) @@ -1285,7 +1285,7 @@ ([viewers] (reset-viewers! *ns* viewers)) ([scope viewers] (assert (or (#{:default} scope) - #?(:bb :default :clj (instance? clojure.lang.Namespace scope)))) + #?(:clj (instance? clojure.lang.Namespace scope)))) (swap! !viewers assoc scope viewers))) (defn add-viewers! [viewers] From 4fc352602793da1713287f9d3a1b9d026ffd28ff Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Wed, 19 Oct 2022 11:51:29 +0200 Subject: [PATCH 06/17] Move stubs to bb files --- bb/stubs/multihash/core.clj | 3 - bb/stubs/multihash/digest.clj | 3 - bb/stubs/nextjournal/beholder.clj | 4 - bb/stubs/nextjournal/clerk/analyzer.clj | 41 ----- bb/stubs/nextjournal/markdown.clj | 3 - bb/stubs/nextjournal/markdown/parser.clj | 3 - bb/stubs/taoensso/nippy.clj | 5 - resources/viewer-js-hash | 2 +- src/nextjournal/beholder.bb | 5 + src/nextjournal/{clerk.cljc => clerk.clj} | 0 src/nextjournal/clerk/analyzer.bb | 6 + src/nextjournal/clerk/builder.clj | 4 +- src/nextjournal/clerk/eval.bb | 149 ++++++++++++++++++ src/nextjournal/clerk/{eval.cljc => eval.clj} | 27 ++-- src/nextjournal/markdown.bb | 4 + src/nextjournal/markdown/parser.bb | 4 + .../nextjournal/markdown/transform.bb | 3 +- 17 files changed, 183 insertions(+), 83 deletions(-) delete mode 100644 bb/stubs/multihash/core.clj delete mode 100644 bb/stubs/multihash/digest.clj delete mode 100644 bb/stubs/nextjournal/beholder.clj delete mode 100644 bb/stubs/nextjournal/clerk/analyzer.clj delete mode 100644 bb/stubs/nextjournal/markdown.clj delete mode 100644 bb/stubs/nextjournal/markdown/parser.clj delete mode 100644 bb/stubs/taoensso/nippy.clj create mode 100644 src/nextjournal/beholder.bb rename src/nextjournal/{clerk.cljc => clerk.clj} (100%) create mode 100644 src/nextjournal/clerk/analyzer.bb create mode 100644 src/nextjournal/clerk/eval.bb rename src/nextjournal/clerk/{eval.cljc => eval.clj} (91%) create mode 100644 src/nextjournal/markdown.bb create mode 100644 src/nextjournal/markdown/parser.bb rename bb/stubs/nextjournal/markdown/transform.clj => src/nextjournal/markdown/transform.bb (50%) diff --git a/bb/stubs/multihash/core.clj b/bb/stubs/multihash/core.clj deleted file mode 100644 index bf30c73ee..000000000 --- a/bb/stubs/multihash/core.clj +++ /dev/null @@ -1,3 +0,0 @@ -(ns multihash.core) - -(defn base58 [h] "base58") diff --git a/bb/stubs/multihash/digest.clj b/bb/stubs/multihash/digest.clj deleted file mode 100644 index c722b226e..000000000 --- a/bb/stubs/multihash/digest.clj +++ /dev/null @@ -1,3 +0,0 @@ -(ns multihash.digest) - -(defn sha2-512 [o] "sha2") diff --git a/bb/stubs/nextjournal/beholder.clj b/bb/stubs/nextjournal/beholder.clj deleted file mode 100644 index 72b31f72e..000000000 --- a/bb/stubs/nextjournal/beholder.clj +++ /dev/null @@ -1,4 +0,0 @@ -(ns nextjournal.beholder) - -(defn watch [cb & args] nil) -(defn stop [w] nil) diff --git a/bb/stubs/nextjournal/clerk/analyzer.clj b/bb/stubs/nextjournal/clerk/analyzer.clj deleted file mode 100644 index 2a007c793..000000000 --- a/bb/stubs/nextjournal/clerk/analyzer.clj +++ /dev/null @@ -1,41 +0,0 @@ -(ns nextjournal.clerk.analyzer - (:require [nextjournal.clerk.config :as config] - [edamame.core :as edamame])) - -(defn valuehash [value] "hash") -(defn hash-codeblock [_ {:keys [hash]}] hash) -(defn ->hash-str [value] "hash-str") -(defn deref? [form] false) -(defn hash [doc] doc) -(defn hash-deref-deps [doc _cell] doc) - -(defn build-graph [doc] - (-> doc - (assoc :->analysis-info (fn [x] {:form x})) - ;; TODO: read first form, create sci.lang.Namespace - (assoc :ns *ns*) - (update :blocks - (partial into [] (map (fn [{:as b :keys [type text]}] - (cond-> b - (= :code type) - (assoc :form - (edamame/parse-string text - {:all true - :readers *data-readers* - :read-cond :allow - :regex #(list `re-pattern %) - :features #{:clj} - ;; TODO: - #_#_:auto-resolve (auto-resolves (or *ns* (find-ns 'user)))}))))))))) - -(defn exceeds-bounded-count-limit? [x] - (reduce (fn [_ xs] - (try - (let [limit config/*bounded-count-limit*] - (if (and (seqable? xs) (<= limit (bounded-count limit xs))) - (reduced true) - false)) - (catch Exception _e - (reduced true)))) - false - (tree-seq seqable? seq x))) diff --git a/bb/stubs/nextjournal/markdown.clj b/bb/stubs/nextjournal/markdown.clj deleted file mode 100644 index 6bef596bd..000000000 --- a/bb/stubs/nextjournal/markdown.clj +++ /dev/null @@ -1,3 +0,0 @@ -(ns nextjournal.markdown) - -(defn parse [md] {:type :doc :content md}) diff --git a/bb/stubs/nextjournal/markdown/parser.clj b/bb/stubs/nextjournal/markdown/parser.clj deleted file mode 100644 index 55a8c459b..000000000 --- a/bb/stubs/nextjournal/markdown/parser.clj +++ /dev/null @@ -1,3 +0,0 @@ -(ns nextjournal.markdown.parser) - -(defn add-title+toc [doc] doc) diff --git a/bb/stubs/taoensso/nippy.clj b/bb/stubs/taoensso/nippy.clj deleted file mode 100644 index e9ce3e8e1..000000000 --- a/bb/stubs/taoensso/nippy.clj +++ /dev/null @@ -1,5 +0,0 @@ -(ns taoensso.nippy) - -(defn freezable? [x] false) -(defn freeze [x] x) -(defn thaw-from-file [file] "thawed") diff --git a/resources/viewer-js-hash b/resources/viewer-js-hash index 87ead369c..58daa8e5c 100644 --- a/resources/viewer-js-hash +++ b/resources/viewer-js-hash @@ -1 +1 @@ -QHueRFEbfs7jTLGUVfX7SnJvJoo \ No newline at end of file +3iQsRk6fdHXvMb1dcpPuwPdat3ya \ No newline at end of file diff --git a/src/nextjournal/beholder.bb b/src/nextjournal/beholder.bb new file mode 100644 index 000000000..e6d96e0c9 --- /dev/null +++ b/src/nextjournal/beholder.bb @@ -0,0 +1,5 @@ +(ns nextjournal.beholder + "Babashka runtime no-op stubs") + +(defn watch [cb & args] nil) +(defn stop [w] nil) diff --git a/src/nextjournal/clerk.cljc b/src/nextjournal/clerk.clj similarity index 100% rename from src/nextjournal/clerk.cljc rename to src/nextjournal/clerk.clj diff --git a/src/nextjournal/clerk/analyzer.bb b/src/nextjournal/clerk/analyzer.bb new file mode 100644 index 000000000..3fbe94d7a --- /dev/null +++ b/src/nextjournal/clerk/analyzer.bb @@ -0,0 +1,6 @@ +(ns nextjournal.clerk.analyzer + "Babashka runtime no-op stubs") + +;; TODO: consider using this in eval +(defn valuehash [val] "valuehash") +(defn ->hash-str [val] (valuehash val)) diff --git a/src/nextjournal/clerk/builder.clj b/src/nextjournal/clerk/builder.clj index 126b50f3e..fa9d47efc 100644 --- a/src/nextjournal/clerk/builder.clj +++ b/src/nextjournal/clerk/builder.clj @@ -2,9 +2,7 @@ "Clerk's Static App Builder." (:require [babashka.fs :as fs] [clojure.java.browse :as browse] - [clojure.set :as set] [clojure.string :as str] - [nextjournal.clerk.analyzer :as analyzer] [nextjournal.clerk.builder-ui :as builder-ui] [nextjournal.clerk.eval :as eval] [nextjournal.clerk.parser :as parser] @@ -197,7 +195,7 @@ {state :result duration :time-ms} (eval/time-ms (mapv (comp (partial parser/parse-file {:doc? true}) :file) state)) _ (report-fn {:stage :parsed :state state :duration duration}) {state :result duration :time-ms} (eval/time-ms (reduce (fn [state doc] - (try (conj state (-> doc analyzer/build-graph analyzer/hash)) + (try (conj state (eval/analyze-doc doc)) (catch Exception e (reduced {:error e})))) [] diff --git a/src/nextjournal/clerk/eval.bb b/src/nextjournal/clerk/eval.bb new file mode 100644 index 000000000..bef482670 --- /dev/null +++ b/src/nextjournal/clerk/eval.bb @@ -0,0 +1,149 @@ +(ns nextjournal.clerk.eval + "Clerk's incremental evaluation with in-memory and disk-persisted caching layers." + (:require [babashka.fs :as fs] + [clojure.java.io :as io] + [edamame.core :as edamame] + [nextjournal.clerk.config :as config] + [nextjournal.clerk.parser :as parser] + [nextjournal.clerk.viewer :as v])) + +(defn wrapped-with-metadata [value hash] + (cond-> {:nextjournal/value value} + ;; TODO: maybe fix hash for blob serving + hash (assoc :nextjournal/blob-id (cond-> hash (not (string? hash)) str #_ multihash/base58)))) + +#_(wrap-with-blob-id :test "foo") + +(defn elapsed-ms [from] + (/ (double (- (. System (nanoTime)) from)) 1000000.0)) + +(defmacro time-ms + "Pure version of `clojure.core/time`. Returns a map with `:result` and `:time-ms` keys." + [expr] + `(let [start# (System/nanoTime) + ret# ~expr] + {:result ret# + :time-ms (elapsed-ms start#)})) + +(defn ^:private var-from-def [var] + (let [resolved-var (cond (var? var) + var + + (symbol? var) + (find-var var) + + :else + (throw (ex-info "Unable to resolve into a variable" {:data var})))] + {:nextjournal.clerk/var-from-def resolved-var})) + +(defn ^:private eval-form [{:keys [form var ns-effect? no-cache?]} hash] + (try + (let [{:keys [result]} (time-ms (binding [config/*in-clerk* true] + (eval form))) + result (if (and (nil? result) var (= 'defonce (first form))) + (find-var var) + result) + var-value (cond-> result (and var (var? result)) deref) + no-cache? (or ns-effect? no-cache? config/cache-disabled?)] + (let [blob-id (cond no-cache? "valuehash" #_#_ TODO?/valuehash (analyzer/->hash-str var-value) + (fn? var-value) nil + :else hash) + result (if var (var-from-def var) result)] + (wrapped-with-metadata result blob-id))) + (catch Throwable t + (throw (ex-info (ex-message t) (Throwable->map t)))))) + +(defn maybe-eval-viewers [{:as opts :nextjournal/keys [viewer viewers]}] + (cond-> opts + viewer + (update :nextjournal/viewer eval) + viewers + (update :nextjournal/viewers eval))) + +(defn read+eval-cached [{:as _doc :keys [blob->result]} {:as codeblock :keys [form vars var ns-effect? no-cache?]}] + (let [no-cache? (or ns-effect? no-cache?) + ;; TODO: hash for in-memory cache + hash (str (gensym)) + opts-from-form-meta (-> (meta form) + (select-keys [:nextjournal.clerk/viewer :nextjournal.clerk/viewers :nextjournal.clerk/width :nextjournal.clerk/opts]) + v/normalize-viewer-opts + maybe-eval-viewers)] + (cond-> (or (when-let [cached-result (and (not no-cache?) (get-in blob->result [hash :nextjournal/value]))] + (wrapped-with-metadata cached-result hash)) + (eval-form codeblock hash)) + (seq opts-from-form-meta) + (merge opts-from-form-meta)))) + +#_(show! "notebooks/scratch_cache.clj") + +#_(eval-file "notebooks/test123.clj") +#_(eval-file "notebooks/how_clerk_works.clj") + +;; no-op for public access from builder +(defn analyze-doc [doc] doc) + +(defn eval-analyzed-doc [{:as analyzed-doc :keys [->hash blocks]}] + (let [{:as evaluated-doc :keys [blob-ids]} + (reduce (fn [state {:as cell :keys [type]}] + (let [{:as result :nextjournal/keys [blob-id]} (when (= :code type) (read+eval-cached state cell))] + (cond-> (update state :blocks conj (cond-> cell result (assoc :result result))) + blob-id (update :blob-ids conj blob-id) + blob-id (assoc-in [:blob->result blob-id] result)))) + (assoc analyzed-doc :blocks [] :blob-ids #{}) + blocks)] + (-> evaluated-doc + (update :blob->result select-keys blob-ids) + (dissoc :blob-ids)))) + +(defn read-forms [doc] + (-> doc + ;; TODO: read first form, create sci.lang.Namespace and eval ns-effects + ;; TODO: restore var, vars + (assoc :ns *ns*) + (update :blocks + (partial into [] (map (fn [{:as b :keys [type text]}] + (cond-> b + (= :code type) + (assoc :form + (edamame/parse-string text + {:all true + :readers *data-readers* + :read-cond :allow + :regex #(list `re-pattern %) + :features #{:clj} + ;; TODO: + #_#_:auto-resolve (auto-resolves (or *ns* (find-ns 'user)))}))))))))) + +(defn +eval-results + "Evaluates the given `parsed-doc` using the `in-memory-cache` and augments it with the results." + [in-memory-cache parsed-doc] + (let [{:as doc :keys [ns]} (read-forms parsed-doc)] + (binding [*ns* (or ns *ns*)] + (-> doc + (assoc :blob->result in-memory-cache) + eval-analyzed-doc)))) + +(defn eval-doc + "Evaluates the given `doc`." + ([doc] (eval-doc {} doc)) + ([in-memory-cache doc] (+eval-results in-memory-cache doc))) + +(defn eval-file + "Reads given `file` (using `slurp`) and evaluates it." + ([file] (eval-file {} file)) + ([in-memory-cache file] + (->> file + (parser/parse-file {:doc? true}) + (eval-doc in-memory-cache)))) + +#_(eval-file "notebooks/hello.clj") +#_(eval-file "notebooks/rule_30.clj") +#_(eval-file "notebooks/visibility.clj") + +(defn eval-string + "Evaluated the given `code-string` using the optional `in-memory-cache` map." + ([code-string] (eval-string {} code-string)) + ([in-memory-cache code-string] + (eval-doc in-memory-cache (parser/parse-clojure-string {:doc? true} code-string)))) + +#_(eval-string "(+ 39 3)") diff --git a/src/nextjournal/clerk/eval.cljc b/src/nextjournal/clerk/eval.clj similarity index 91% rename from src/nextjournal/clerk/eval.cljc rename to src/nextjournal/clerk/eval.clj index 99723bab1..9b0541f9e 100644 --- a/src/nextjournal/clerk/eval.cljc +++ b/src/nextjournal/clerk/eval.clj @@ -11,9 +11,8 @@ [nextjournal.clerk.parser :as parser] [nextjournal.clerk.viewer :as v] [taoensso.nippy :as nippy]) - #?(:bb (:import) - :clj (:import (java.awt.image BufferedImage) - (javax.imageio ImageIO)))) + (:import (java.awt.image BufferedImage) + (javax.imageio ImageIO))) (comment (alter-var-root #'nippy/*freeze-serializable-allowlist* (fn [_] "allow-and-record")) @@ -21,11 +20,9 @@ (nippy/get-recorded-serializable-classes)) ;; nippy tweaks -#?(:bb nil - :clj (do - (alter-var-root #'nippy/*thaw-serializable-allowlist* (fn [_] (conj nippy/default-thaw-serializable-allowlist "java.io.File" "clojure.lang.Var" "clojure.lang.Namespace"))) - (nippy/extend-freeze BufferedImage :java.awt.image.BufferedImage [x out] (ImageIO/write x "png" (ImageIO/createImageOutputStream out))) - (nippy/extend-thaw :java.awt.image.BufferedImage [in] (ImageIO/read in)))) +(alter-var-root #'nippy/*thaw-serializable-allowlist* (fn [_] (conj nippy/default-thaw-serializable-allowlist "java.io.File" "clojure.lang.Var" "clojure.lang.Namespace"))) +(nippy/extend-freeze BufferedImage :java.awt.image.BufferedImage [x out] (ImageIO/write x "png" (ImageIO/createImageOutputStream out))) +(nippy/extend-thaw :java.awt.image.BufferedImage [in] (ImageIO/read in)) #_(-> [(clojure.java.io/file "notebooks") (find-ns 'user)] nippy/freeze nippy/thaw) @@ -110,10 +107,6 @@ #_(prn :freeze-error e) nil))) -;; NOTE: cannot add clojure.main to bb stubs -(defn ex-triage [m] #?(:bb m :clj (main/ex-triage m))) -(defn ex-str [m] #?(:bb (str m) :clj (main/ex-str m))) - (defn ^:private eval+cache! [{:keys [form var ns-effect? no-cache? freezable?] :as form-info} hash digest-file] (try (let [{:keys [result]} (time-ms (binding [config/*in-clerk* true] @@ -135,8 +128,8 @@ result)] (wrapped-with-metadata result blob-id))) (catch Throwable t - (let [triaged (ex-triage (Throwable->map t))] - (throw (ex-info (ex-str triaged) triaged)))))) + (let [triaged (main/ex-triage (Throwable->map t))] + (throw (ex-info (main/ex-str triaged) triaged)))))) (defn maybe-eval-viewers [{:as opts :nextjournal/keys [viewer viewers]}] (cond-> opts @@ -199,11 +192,14 @@ (update :blob->result select-keys blob-ids) (dissoc :blob-ids)))) +;; TODO: used in builder to drop analyzer dependency, cfr. below +(defn analyze-doc [doc] (-> doc analyzer/build-graph analyzer/hash)) + (defn +eval-results "Evaluates the given `parsed-doc` using the `in-memory-cache` and augments it with the results." [in-memory-cache parsed-doc] (let [{:as analyzed-doc :keys [ns]} (analyzer/build-graph parsed-doc)] - (binding [*ns* (or ns *ns*)] + (binding [*ns* ns] (-> analyzed-doc analyzer/hash (assoc :blob->result in-memory-cache) @@ -233,4 +229,3 @@ (eval-doc in-memory-cache (parser/parse-clojure-string {:doc? true} code-string)))) #_(eval-string "(+ 39 3)") - diff --git a/src/nextjournal/markdown.bb b/src/nextjournal/markdown.bb new file mode 100644 index 000000000..491da7063 --- /dev/null +++ b/src/nextjournal/markdown.bb @@ -0,0 +1,4 @@ +(ns nextjournal.markdown + "Babashka runtime stubs") + +(defn parse [md] {:type :doc :content md}) diff --git a/src/nextjournal/markdown/parser.bb b/src/nextjournal/markdown/parser.bb new file mode 100644 index 000000000..932d45342 --- /dev/null +++ b/src/nextjournal/markdown/parser.bb @@ -0,0 +1,4 @@ +(ns nextjournal.markdown.parser + "Babashka runtime no-op stubs") + +(defn add-title+toc [doc] doc) diff --git a/bb/stubs/nextjournal/markdown/transform.clj b/src/nextjournal/markdown/transform.bb similarity index 50% rename from bb/stubs/nextjournal/markdown/transform.clj rename to src/nextjournal/markdown/transform.bb index 28968dfc7..c4585c0a4 100644 --- a/bb/stubs/nextjournal/markdown/transform.clj +++ b/src/nextjournal/markdown/transform.bb @@ -1,4 +1,5 @@ -(ns nextjournal.markdown.transform) +(ns nextjournal.markdown.transform + "Babashka runtime stubs") (defn ->text [node] "text") (defn table-alignment [attrs] {}) From 1493d7be715f3e5a2d3ed13652132b60b49a91e0 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Wed, 19 Oct 2022 12:05:06 +0200 Subject: [PATCH 07/17] Pass cli args to bb task --- bb-runtime.edn | 5 +++-- resources/viewer-js-hash | 2 +- src/nextjournal/clerk/eval.bb | 6 ++---- src/nextjournal/clerk/viewer.cljc | 6 +++--- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/bb-runtime.edn b/bb-runtime.edn index 43fadb4b7..ce6651ce4 100644 --- a/bb-runtime.edn +++ b/bb-runtime.edn @@ -5,10 +5,11 @@ {clerk {:requires ([babashka.fs :as fs] [babashka.nrepl.server :as srv] - [nextjournal.clerk :as clerk]) + [nextjournal.clerk :as clerk] + [babashka.cli :as cli]) :task (do (srv/start-server! {:host "localhost" :port 1339}) (spit ".nrepl-port" "1339") - (clerk/serve! {:port 9999}) + (clerk/serve! (cli/parse-opts *command-line-args*)) (-> (Runtime/getRuntime) (.addShutdownHook (Thread. (fn [] (clerk/halt!) diff --git a/resources/viewer-js-hash b/resources/viewer-js-hash index 58daa8e5c..786f4b0ae 100644 --- a/resources/viewer-js-hash +++ b/resources/viewer-js-hash @@ -1 +1 @@ -3iQsRk6fdHXvMb1dcpPuwPdat3ya \ No newline at end of file +3DvNBfsJZstwCXFtQehkuSfLP5xH \ No newline at end of file diff --git a/src/nextjournal/clerk/eval.bb b/src/nextjournal/clerk/eval.bb index bef482670..02508bf32 100644 --- a/src/nextjournal/clerk/eval.bb +++ b/src/nextjournal/clerk/eval.bb @@ -1,8 +1,6 @@ (ns nextjournal.clerk.eval - "Clerk's incremental evaluation with in-memory and disk-persisted caching layers." - (:require [babashka.fs :as fs] - [clojure.java.io :as io] - [edamame.core :as edamame] + "Clerk's incremental evaluation (Babashka Edition) with in-memory caching layer." + (:require [edamame.core :as edamame] [nextjournal.clerk.config :as config] [nextjournal.clerk.parser :as parser] [nextjournal.clerk.viewer :as v])) diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index 82e1b4373..8aff6d5d0 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -469,8 +469,7 @@ #?(:clj (defn datafy-scope [scope] (cond - (instance? clojure.lang.Namespace scope) - {:namespace (-> scope str keyword)} + (instance? clojure.lang.Namespace scope) {:namespace (-> scope str keyword)} (keyword? scope) scope :else (throw (ex-info (str "Unsupported scope " scope) {:scope scope}))))) @@ -625,7 +624,8 @@ :render-fn '(fn [blob] (v/html [:figure.flex.flex-col.items-center.not-prose [:img {:src (v/url-for blob)}]]))})) (def ideref-viewer - #?(:bb {} :clj + #?(:bb {} + :clj {:pred #(instance? IDeref %) :transform-fn (update-val (fn [ideref] (with-viewer :tagged-value From 840659118fe2ec9f03576ed8a8e90ec9f328b7ad Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Wed, 19 Oct 2022 14:53:32 +0200 Subject: [PATCH 08/17] Fix add-viewers! and auto-resolve of ::alias keywords --- src/nextjournal/clerk/eval.bb | 49 +++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/src/nextjournal/clerk/eval.bb b/src/nextjournal/clerk/eval.bb index 02508bf32..b33dcc430 100644 --- a/src/nextjournal/clerk/eval.bb +++ b/src/nextjournal/clerk/eval.bb @@ -1,5 +1,6 @@ (ns nextjournal.clerk.eval "Clerk's incremental evaluation (Babashka Edition) with in-memory caching layer." + (:refer-clojure :exclude [read-string]) (:require [edamame.core :as edamame] [nextjournal.clerk.config :as config] [nextjournal.clerk.parser :as parser] @@ -34,7 +35,7 @@ (throw (ex-info "Unable to resolve into a variable" {:data var})))] {:nextjournal.clerk/var-from-def resolved-var})) -(defn ^:private eval-form [{:keys [form var ns-effect? no-cache?]} hash] +(defn ^:private eval-form [{:keys [form var no-cache?]} hash] (try (let [{:keys [result]} (time-ms (binding [config/*in-clerk* true] (eval form))) @@ -42,7 +43,7 @@ (find-var var) result) var-value (cond-> result (and var (var? result)) deref) - no-cache? (or ns-effect? no-cache? config/cache-disabled?)] + no-cache? (or no-cache? config/cache-disabled?)] (let [blob-id (cond no-cache? "valuehash" #_#_ TODO?/valuehash (analyzer/->hash-str var-value) (fn? var-value) nil :else hash) @@ -93,24 +94,34 @@ (update :blob->result select-keys blob-ids) (dissoc :blob-ids)))) +(defn read-string [s] + (edamame/parse-string s + {:all true + :readers *data-readers* + :read-cond :allow + :regex #(list `re-pattern %) + :features #{:clj} + :auto-resolve (as-> (ns-aliases (or *ns* (find-ns 'user))) $ + (zipmap (keys $) (map ns-name (vals $))) + (assoc $ :current (ns-name *ns*)))})) + (defn read-forms [doc] - (-> doc - ;; TODO: read first form, create sci.lang.Namespace and eval ns-effects - ;; TODO: restore var, vars - (assoc :ns *ns*) - (update :blocks - (partial into [] (map (fn [{:as b :keys [type text]}] - (cond-> b - (= :code type) - (assoc :form - (edamame/parse-string text - {:all true - :readers *data-readers* - :read-cond :allow - :regex #(list `re-pattern %) - :features #{:clj} - ;; TODO: - #_#_:auto-resolve (auto-resolves (or *ns* (find-ns 'user)))}))))))))) + (binding [*ns* *ns*] + (reduce (fn [doc {:as b :keys [type text]}] + (let [form (read-string text) + ns? (= 'ns (when (list? form) (first form)))] + (when ns? (eval form)) + (-> doc + (cond-> (and ns? (not (:ns doc))) (assoc :ns *ns*)) + (update :blocks conj + (cond-> b + (= :code type) (assoc :form form) + ns? (assoc :no-cache? true)))))) + (assoc doc :blocks []) + (:blocks doc)))) + +#_(read-forms + (parser/parse-file "notebooks/hello.clj")) (defn +eval-results "Evaluates the given `parsed-doc` using the `in-memory-cache` and augments it with the results." From e5ce7ab5f77327ea08cbde36cfcc6a49ccf01b5c Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Wed, 19 Oct 2022 16:00:08 +0200 Subject: [PATCH 09/17] Extract var from def --- resources/viewer-js-hash | 2 +- src/nextjournal/clerk/eval.bb | 20 +++++++++++--------- src/nextjournal/clerk/viewer.cljc | 2 +- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/resources/viewer-js-hash b/resources/viewer-js-hash index 786f4b0ae..abe47eb89 100644 --- a/resources/viewer-js-hash +++ b/resources/viewer-js-hash @@ -1 +1 @@ -3DvNBfsJZstwCXFtQehkuSfLP5xH \ No newline at end of file +3KYTPPtm8ocErseHCqJNzLEUsByx \ No newline at end of file diff --git a/src/nextjournal/clerk/eval.bb b/src/nextjournal/clerk/eval.bb index b33dcc430..7e6cc75ad 100644 --- a/src/nextjournal/clerk/eval.bb +++ b/src/nextjournal/clerk/eval.bb @@ -1,7 +1,8 @@ (ns nextjournal.clerk.eval "Clerk's incremental evaluation (Babashka Edition) with in-memory caching layer." (:refer-clojure :exclude [read-string]) - (:require [edamame.core :as edamame] + (:require [clojure.string :as str] + [edamame.core :as edamame] [nextjournal.clerk.config :as config] [nextjournal.clerk.parser :as parser] [nextjournal.clerk.viewer :as v])) @@ -73,15 +74,10 @@ (seq opts-from-form-meta) (merge opts-from-form-meta)))) -#_(show! "notebooks/scratch_cache.clj") - -#_(eval-file "notebooks/test123.clj") -#_(eval-file "notebooks/how_clerk_works.clj") - ;; no-op for public access from builder (defn analyze-doc [doc] doc) -(defn eval-analyzed-doc [{:as analyzed-doc :keys [->hash blocks]}] +(defn eval-analyzed-doc [{:as analyzed-doc :keys [blocks]}] (let [{:as evaluated-doc :keys [blob-ids]} (reduce (fn [state {:as cell :keys [type]}] (let [{:as result :nextjournal/keys [blob-id]} (when (= :code type) (read+eval-cached state cell))] @@ -105,18 +101,24 @@ (zipmap (keys $) (map ns-name (vals $))) (assoc $ :current (ns-name *ns*)))})) +(defn deflike? [form] (and (seq? form) (symbol? (first form)) (str/starts-with? (name (first form)) "def"))) +#_(deflike? (read-string "(def ^{:doc \"aloha\"} foo 123)")) +#_(deflike? (read-string "(def ^{:doc \"aloha\"} foo 123)")) + (defn read-forms [doc] (binding [*ns* *ns*] (reduce (fn [doc {:as b :keys [type text]}] (let [form (read-string text) - ns? (= 'ns (when (list? form) (first form)))] + ns? (= 'ns (when (list? form) (first form))) + var (when (and (deflike? form) (symbol? (second form))) (second form))] (when ns? (eval form)) (-> doc (cond-> (and ns? (not (:ns doc))) (assoc :ns *ns*)) (update :blocks conj (cond-> b (= :code type) (assoc :form form) - ns? (assoc :no-cache? true)))))) + ns? (assoc :no-cache? true) + var (assoc :var (symbol (name (ns-name *ns*)) (name var)))))))) (assoc doc :blocks []) (:blocks doc)))) diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index 8aff6d5d0..d69992dfe 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -668,7 +668,7 @@ (def markdown-viewer (-> {:name :markdown} #?(:bb (assoc :transform-fn (comp mark-presented (update-val :content)) - :render-fn '(fn [str] (js/console.log :str str) (v/html (v/md->hiccup str)))) + :render-fn '(fn [str] (v/html (v/md->hiccup str)))) :clj (assoc :transform-fn (fn [wrapped-value] (-> wrapped-value From a4b37384219ab0f76aa59b1aa5a214f80bb3810d Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Wed, 19 Oct 2022 16:26:49 +0200 Subject: [PATCH 10/17] Fix static builder in bb runtime --- src/nextjournal/clerk/eval.bb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/nextjournal/clerk/eval.bb b/src/nextjournal/clerk/eval.bb index 7e6cc75ad..850e3ce66 100644 --- a/src/nextjournal/clerk/eval.bb +++ b/src/nextjournal/clerk/eval.bb @@ -74,10 +74,7 @@ (seq opts-from-form-meta) (merge opts-from-form-meta)))) -;; no-op for public access from builder -(defn analyze-doc [doc] doc) - -(defn eval-analyzed-doc [{:as analyzed-doc :keys [blocks]}] +(defn eval-analyzed-doc [{:as analyzed-doc :keys [ns blocks]}] (let [{:as evaluated-doc :keys [blob-ids]} (reduce (fn [state {:as cell :keys [type]}] (let [{:as result :nextjournal/keys [blob-id]} (when (= :code type) (read+eval-cached state cell))] @@ -87,6 +84,7 @@ (assoc analyzed-doc :blocks [] :blob-ids #{}) blocks)] (-> evaluated-doc + (cond-> (not ns) (assoc :ns (find-ns 'user))) (update :blob->result select-keys blob-ids) (dissoc :blob-ids)))) @@ -125,6 +123,9 @@ #_(read-forms (parser/parse-file "notebooks/hello.clj")) +;; used in builder +(def analyze-doc read-forms) + (defn +eval-results "Evaluates the given `parsed-doc` using the `in-memory-cache` and augments it with the results." [in-memory-cache parsed-doc] From a642887da479ba7e56bbd0d79f36ca3a033eaccc Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Wed, 19 Oct 2022 16:45:07 +0200 Subject: [PATCH 11/17] Fix markdown viewer for cljs consumption --- resources/viewer-js-hash | 2 +- src/nextjournal/clerk/viewer.cljc | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/resources/viewer-js-hash b/resources/viewer-js-hash index abe47eb89..f53aff06c 100644 --- a/resources/viewer-js-hash +++ b/resources/viewer-js-hash @@ -1 +1 @@ -3KYTPPtm8ocErseHCqJNzLEUsByx \ No newline at end of file +3dJJyLHsUmEC71T4CnGbFunsr74R \ No newline at end of file diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index d69992dfe..c5288f888 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -667,15 +667,15 @@ (def markdown-viewer (-> {:name :markdown} - #?(:bb (assoc :transform-fn (comp mark-presented (update-val :content)) + #?(:bb (assoc :transform-fn (comp mark-presented (update-val #(cond-> % (map? %) :content))) :render-fn '(fn [str] (v/html (v/md->hiccup str)))) - :clj (assoc :transform-fn - (fn [wrapped-value] - (-> wrapped-value - mark-presented - (update :nextjournal/value #(cond->> % (string? %) md/parse)) - (update :nextjournal/viewers add-viewers markdown-viewers) - (with-md-viewer))))))) + :default (assoc :transform-fn + (fn [wrapped-value] + (-> wrapped-value + mark-presented + (update :nextjournal/value #(cond->> % (string? %) md/parse)) + (update :nextjournal/viewers add-viewers markdown-viewers) + (with-md-viewer))))))) (def code-viewer {:name :code :render-fn (quote v/code-viewer) :transform-fn (comp mark-presented (update-val (fn [v] (if (string? v) v (str/trim (with-out-str (pprint/pprint v)))))))}) From b6bd4c402b6ff831ca8d54f1b4d3e7d9e9bef5e1 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Wed, 19 Oct 2022 17:40:17 +0200 Subject: [PATCH 12/17] Add build bb task --- bb-runtime.edn | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/bb-runtime.edn b/bb-runtime.edn index ce6651ce4..94321aac5 100644 --- a/bb-runtime.edn +++ b/bb-runtime.edn @@ -1,8 +1,9 @@ {:min-bb-version "1.0.164" - :paths ["bb/stubs" "src" "notebooks" "resources"] - :deps {hiccup/hiccup {:mvn/version "2.0.0-alpha2"}} + :paths ["src" "notebooks" "resources"] + :deps {hiccup/hiccup {:mvn/version "2.0.0-alpha2"} + org.babashka/cli {:mvn/version "0.5.40"}} :tasks - {clerk + {dev {:requires ([babashka.fs :as fs] [babashka.nrepl.server :as srv] [nextjournal.clerk :as clerk] @@ -14,4 +15,10 @@ (.addShutdownHook (Thread. (fn [] (clerk/halt!) (fs/delete ".nrepl-port"))))) - (deref (promise)))}}} + (deref (promise)))} + + build + {:requires ([nextjournal.clerk :as clerk] + [babashka.cli :as cli]) + :task (let [spec (-> (resolve 'nextjournal.clerk/build!) meta :org.babashka/cli)] + (clerk/build! (cli/parse-opts *command-line-args* spec)))}}} From 7c95f89cf3771b8aa0a984fb7dcc56fbaf746c54 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Thu, 20 Oct 2022 13:02:14 +0200 Subject: [PATCH 13/17] Implement no-cache? --- src/nextjournal/clerk/eval.bb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/nextjournal/clerk/eval.bb b/src/nextjournal/clerk/eval.bb index 850e3ce66..28ad406f3 100644 --- a/src/nextjournal/clerk/eval.bb +++ b/src/nextjournal/clerk/eval.bb @@ -62,8 +62,7 @@ (defn read+eval-cached [{:as _doc :keys [blob->result]} {:as codeblock :keys [form vars var ns-effect? no-cache?]}] (let [no-cache? (or ns-effect? no-cache?) - ;; TODO: hash for in-memory cache - hash (str (gensym)) + hash (.encodeToString (java.util.Base64/getEncoder) (.getBytes (str form))) opts-from-form-meta (-> (meta form) (select-keys [:nextjournal.clerk/viewer :nextjournal.clerk/viewers :nextjournal.clerk/width :nextjournal.clerk/opts]) v/normalize-viewer-opts @@ -102,6 +101,14 @@ (defn deflike? [form] (and (seq? form) (symbol? (first form)) (str/starts-with? (name (first form)) "def"))) #_(deflike? (read-string "(def ^{:doc \"aloha\"} foo 123)")) #_(deflike? (read-string "(def ^{:doc \"aloha\"} foo 123)")) +(defn no-cache-from-meta [form] + (when (contains? (meta form) :nextjournal.clerk/no-cache) + (-> form meta :nextjournal.clerk/no-cache))) +(defn no-cache? [& subjects] (or (some no-cache-from-meta subjects) false)) +(defn deref? [form] + (and (seq? form) + (= (first form) `deref) + (= 2 (count form)))) (defn read-forms [doc] (binding [*ns* *ns*] @@ -115,7 +122,7 @@ (update :blocks conj (cond-> b (= :code type) (assoc :form form) - ns? (assoc :no-cache? true) + (or ns? (deref? form) (no-cache? form var *ns*)) (assoc :no-cache? true) var (assoc :var (symbol (name (ns-name *ns*)) (name var)))))))) (assoc doc :blocks []) (:blocks doc)))) From 995e2645487ba76c86175b1559526cf16e736a63 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Thu, 20 Oct 2022 13:08:52 +0200 Subject: [PATCH 14/17] Parse markdown via quickjs --- bb-runtime.edn | 20 ++++++++++++---- resources/viewer-js-hash | 2 +- src/nextjournal/clerk/viewer.cljc | 16 +++++-------- src/nextjournal/markdown.bb | 33 +++++++++++++++++++++++++-- src/nextjournal/markdown/parser.bb | 4 ---- src/nextjournal/markdown/transform.bb | 5 ---- 6 files changed, 53 insertions(+), 27 deletions(-) delete mode 100644 src/nextjournal/markdown/parser.bb delete mode 100644 src/nextjournal/markdown/transform.bb diff --git a/bb-runtime.edn b/bb-runtime.edn index 94321aac5..8b6d234c4 100644 --- a/bb-runtime.edn +++ b/bb-runtime.edn @@ -1,19 +1,29 @@ {:min-bb-version "1.0.164" :paths ["src" "notebooks" "resources"] :deps {hiccup/hiccup {:mvn/version "2.0.0-alpha2"} - org.babashka/cli {:mvn/version "0.5.40"}} + org.babashka/cli {:mvn/version "0.5.40"} + io.github.nextjournal/markdown {:mvn/version "0.4.126"} + io.github.nextjournal/clerk-slideshow {:git/sha "562f634494a1e1a9149ed78d5d39fd9486cc00ba"}} :tasks - {dev + {:init + (let [md-mod (slurp (clojure.java.io/resource "js/markdown.mjs"))] + ;; FIXME: hack for having qjs relative module imports work + (when-not (babashka.fs/exists? "js/markdown.mjs") + (println "Copying nextjournal.markdown ES module:" (count md-mod)) + (babashka.fs/create-dir "js") + (spit (clojure.java.io/file "js/markdown.mjs") (slurp (clojure.java.io/resource "js/markdown.mjs"))))) + + dev {:requires ([babashka.fs :as fs] [babashka.nrepl.server :as srv] - [nextjournal.clerk :as clerk] [babashka.cli :as cli]) :task (do (srv/start-server! {:host "localhost" :port 1339}) (spit ".nrepl-port" "1339") - (clerk/serve! (cli/parse-opts *command-line-args*)) + ;; can't require clerk before copying md module + ((requiring-resolve 'nextjournal.clerk/serve!) (cli/parse-opts *command-line-args*)) (-> (Runtime/getRuntime) (.addShutdownHook (Thread. (fn [] - (clerk/halt!) + ((requiring-resolve 'nextjournal.clerk/halt!)) (fs/delete ".nrepl-port"))))) (deref (promise)))} diff --git a/resources/viewer-js-hash b/resources/viewer-js-hash index f53aff06c..98e4fca0e 100644 --- a/resources/viewer-js-hash +++ b/resources/viewer-js-hash @@ -1 +1 @@ -3dJJyLHsUmEC71T4CnGbFunsr74R \ No newline at end of file +jTDHst3WuaZZ65Kbk7vW4w7315u \ No newline at end of file diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index c5288f888..aa2b157a3 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -666,16 +666,12 @@ {:name :vega-lite :render-fn (quote v/vega-lite-viewer) :transform-fn mark-presented}) (def markdown-viewer - (-> {:name :markdown} - #?(:bb (assoc :transform-fn (comp mark-presented (update-val #(cond-> % (map? %) :content))) - :render-fn '(fn [str] (v/html (v/md->hiccup str)))) - :default (assoc :transform-fn - (fn [wrapped-value] - (-> wrapped-value - mark-presented - (update :nextjournal/value #(cond->> % (string? %) md/parse)) - (update :nextjournal/viewers add-viewers markdown-viewers) - (with-md-viewer))))))) + {:name :markdown :transform-fn (fn [wrapped-value] + (-> wrapped-value + mark-presented + (update :nextjournal/value #(cond->> % (string? %) md/parse)) + (update :nextjournal/viewers add-viewers markdown-viewers) + (with-md-viewer)))}) (def code-viewer {:name :code :render-fn (quote v/code-viewer) :transform-fn (comp mark-presented (update-val (fn [v] (if (string? v) v (str/trim (with-out-str (pprint/pprint v)))))))}) diff --git a/src/nextjournal/markdown.bb b/src/nextjournal/markdown.bb index 491da7063..22ad1b263 100644 --- a/src/nextjournal/markdown.bb +++ b/src/nextjournal/markdown.bb @@ -1,4 +1,33 @@ (ns nextjournal.markdown - "Babashka runtime stubs") + "Babashka runtime stubs" + (:require [babashka.process :as p] + [clojure.data.json :as json] + [nextjournal.markdown.parser :as md.parser] + [clojure.string :as str])) -(defn parse [md] {:type :doc :content md}) +(defn escape [t] (-> t (str/replace "`" "\\`") (str/replace "'" "\\'"))) +(defn tokenize [text] + (some-> (babashka.process/shell {:out :string :err :string} + (str "qjs -e 'import(\"./js/markdown.mjs\").then((mod) => {print(mod.default.tokenizeJSON(`" (escape text) "`))})" + ".catch((e) => {import(\"std\").then((std) => { std.err.puts(\"cant find markdown module\"); std.exit(1)})})'")) + :out not-empty + (json/read-str {:key-fn keyword}))) + +(defn parse [md] (some-> md tokenize md.parser/parse)) + +(comment + (tokenize "# Hello") + (parse "# Hello +* `this` +* _is_ +* crazy as [hello](https://hell.is) + +$what$ + +--- +``` +and this is code +``` +") + (try (parse (slurp "notebooks/markdown.md")) + (catch Exception e (:err (ex-data e))))) diff --git a/src/nextjournal/markdown/parser.bb b/src/nextjournal/markdown/parser.bb deleted file mode 100644 index 932d45342..000000000 --- a/src/nextjournal/markdown/parser.bb +++ /dev/null @@ -1,4 +0,0 @@ -(ns nextjournal.markdown.parser - "Babashka runtime no-op stubs") - -(defn add-title+toc [doc] doc) diff --git a/src/nextjournal/markdown/transform.bb b/src/nextjournal/markdown/transform.bb deleted file mode 100644 index c4585c0a4..000000000 --- a/src/nextjournal/markdown/transform.bb +++ /dev/null @@ -1,5 +0,0 @@ -(ns nextjournal.markdown.transform - "Babashka runtime stubs") - -(defn ->text [node] "text") -(defn table-alignment [attrs] {}) From 865c088548139dbaca7f772081b7f322863ed304 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Thu, 20 Oct 2022 13:20:45 +0200 Subject: [PATCH 15/17] Check quickjs is available --- bb-runtime.edn | 2 ++ src/nextjournal/markdown.bb | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/bb-runtime.edn b/bb-runtime.edn index 8b6d234c4..243417086 100644 --- a/bb-runtime.edn +++ b/bb-runtime.edn @@ -8,6 +8,8 @@ {:init (let [md-mod (slurp (clojure.java.io/resource "js/markdown.mjs"))] ;; FIXME: hack for having qjs relative module imports work + (assert (= 0 (:exit (deref (babashka.process/process "which qjs")))) + "Quickjs needs to be installed (brew install quickjs)") (when-not (babashka.fs/exists? "js/markdown.mjs") (println "Copying nextjournal.markdown ES module:" (count md-mod)) (babashka.fs/create-dir "js") diff --git a/src/nextjournal/markdown.bb b/src/nextjournal/markdown.bb index 22ad1b263..ddfb03d5d 100644 --- a/src/nextjournal/markdown.bb +++ b/src/nextjournal/markdown.bb @@ -8,8 +8,8 @@ (defn escape [t] (-> t (str/replace "`" "\\`") (str/replace "'" "\\'"))) (defn tokenize [text] (some-> (babashka.process/shell {:out :string :err :string} - (str "qjs -e 'import(\"./js/markdown.mjs\").then((mod) => {print(mod.default.tokenizeJSON(`" (escape text) "`))})" - ".catch((e) => {import(\"std\").then((std) => { std.err.puts(\"cant find markdown module\"); std.exit(1)})})'")) + (str "qjs -e 'import(\"./js/markdown.mjs\").then((mod) => {print(mod.default.tokenizeJSON(`" (escape text) "`))})" + ".catch((e) => {import(\"std\").then((std) => { std.err.puts(\"cant find markdown module\"); std.exit(1)})})'")) :out not-empty (json/read-str {:key-fn keyword}))) From 38997c8e21e9c0ab1347cb16ea0bfe50d3d2eade Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Thu, 20 Oct 2022 14:22:01 +0200 Subject: [PATCH 16/17] Lazily copy markdown js module --- bb-runtime.edn | 18 ++++-------------- src/nextjournal/markdown.bb | 31 ++++++++++++++++++++++--------- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/bb-runtime.edn b/bb-runtime.edn index 243417086..dc40f93f1 100644 --- a/bb-runtime.edn +++ b/bb-runtime.edn @@ -5,27 +5,17 @@ io.github.nextjournal/markdown {:mvn/version "0.4.126"} io.github.nextjournal/clerk-slideshow {:git/sha "562f634494a1e1a9149ed78d5d39fd9486cc00ba"}} :tasks - {:init - (let [md-mod (slurp (clojure.java.io/resource "js/markdown.mjs"))] - ;; FIXME: hack for having qjs relative module imports work - (assert (= 0 (:exit (deref (babashka.process/process "which qjs")))) - "Quickjs needs to be installed (brew install quickjs)") - (when-not (babashka.fs/exists? "js/markdown.mjs") - (println "Copying nextjournal.markdown ES module:" (count md-mod)) - (babashka.fs/create-dir "js") - (spit (clojure.java.io/file "js/markdown.mjs") (slurp (clojure.java.io/resource "js/markdown.mjs"))))) - - dev + {dev {:requires ([babashka.fs :as fs] [babashka.nrepl.server :as srv] + [nextjournal.clerk :as clerk] [babashka.cli :as cli]) :task (do (srv/start-server! {:host "localhost" :port 1339}) (spit ".nrepl-port" "1339") - ;; can't require clerk before copying md module - ((requiring-resolve 'nextjournal.clerk/serve!) (cli/parse-opts *command-line-args*)) + (nextjournal.clerk/serve! (cli/parse-opts *command-line-args*)) (-> (Runtime/getRuntime) (.addShutdownHook (Thread. (fn [] - ((requiring-resolve 'nextjournal.clerk/halt!)) + (nextjournal.clerk/halt!) (fs/delete ".nrepl-port"))))) (deref (promise)))} diff --git a/src/nextjournal/markdown.bb b/src/nextjournal/markdown.bb index ddfb03d5d..0896a8e9d 100644 --- a/src/nextjournal/markdown.bb +++ b/src/nextjournal/markdown.bb @@ -1,21 +1,34 @@ (ns nextjournal.markdown "Babashka runtime stubs" - (:require [babashka.process :as p] + (:require [babashka.fs :as fs] + [babashka.process :as p] [clojure.data.json :as json] - [nextjournal.markdown.parser :as md.parser] - [clojure.string :as str])) + [clojure.java.io :as io] + [clojure.string :as str] + [nextjournal.markdown.parser :as md.parser])) + +(defn assert-quickjs! [] (assert (= 0 (:exit @(p/process '[which qjs]))) "QuickJS needs to be installed (brew install quickjs)")) +(def !md-mod-temp-dir (atom nil)) +(defn md-mod-temp-dir [] + (or @!md-mod-temp-dir + (let [tempdir (fs/create-temp-dir)] + (assert-quickjs!) + (spit (fs/file tempdir "markdown.mjs") (slurp (io/resource "js/markdown.mjs"))) + (reset! !md-mod-temp-dir (str tempdir))))) (defn escape [t] (-> t (str/replace "`" "\\`") (str/replace "'" "\\'"))) (defn tokenize [text] - (some-> (babashka.process/shell {:out :string :err :string} - (str "qjs -e 'import(\"./js/markdown.mjs\").then((mod) => {print(mod.default.tokenizeJSON(`" (escape text) "`))})" - ".catch((e) => {import(\"std\").then((std) => { std.err.puts(\"cant find markdown module\"); std.exit(1)})})'")) - :out not-empty - (json/read-str {:key-fn keyword}))) + (some-> (p/shell {:out :string :err :string :dir (md-mod-temp-dir)} + (str "qjs -e 'import(\"./markdown.mjs\").then((mod) => {print(mod.default.tokenizeJSON(`" (escape text) "`))})" + ".catch((e) => {import(\"std\").then((std) => { std.err.puts(\"cant find markdown module\"); std.exit(1)})})'")) + deref :out not-empty + (json/read-str {:key-fn keyword}))) -(defn parse [md] (some-> md tokenize md.parser/parse)) +(defn parse [md] {:type :doc :content []} (some-> md tokenize md.parser/parse)) (comment + (assert-quickjs!) + (md-mod-temp-dir) (tokenize "# Hello") (parse "# Hello * `this` From 10e95c4c498c0f55bb5277d60796da4745811ee4 Mon Sep 17 00:00:00 2001 From: Andrea Amantini Date: Thu, 20 Oct 2022 14:34:54 +0200 Subject: [PATCH 17/17] More escapes --- src/nextjournal/markdown.bb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/nextjournal/markdown.bb b/src/nextjournal/markdown.bb index 0896a8e9d..66111f039 100644 --- a/src/nextjournal/markdown.bb +++ b/src/nextjournal/markdown.bb @@ -16,7 +16,7 @@ (spit (fs/file tempdir "markdown.mjs") (slurp (io/resource "js/markdown.mjs"))) (reset! !md-mod-temp-dir (str tempdir))))) -(defn escape [t] (-> t (str/replace "`" "\\`") (str/replace "'" "\\'"))) +(defn escape [t] (-> t (str/replace "\\" "\\\\\\") (str/replace "`" "\\`") (str/replace "'" "\\'"))) (defn tokenize [text] (some-> (p/shell {:out :string :err :string :dir (md-mod-temp-dir)} (str "qjs -e 'import(\"./markdown.mjs\").then((mod) => {print(mod.default.tokenizeJSON(`" (escape text) "`))})" @@ -32,13 +32,11 @@ (tokenize "# Hello") (parse "# Hello * `this` -* _is_ +* _is_ Some $\\mathfrak{M}$ formula * crazy as [hello](https://hell.is) -$what$ - --- -``` +```clojure and this is code ``` ")