diff --git a/README.md b/README.md index 1d52afbf..d325c004 100644 --- a/README.md +++ b/README.md @@ -93,14 +93,14 @@ We've built hiccup transformation in for convenience, but the same approach can This library is one of the building blocks of [Clerk](https://github.com/nextjournal/clerk) where it is used for rendering _literate fragments_. ```clojure -^{:nextjournal.clerk/viewer :markdown} +^{:nextjournal.clerk/viewer 'nextjournal.clerk.viewer/markdown-viewer} data ``` The transformation of markdown node types can be customised like this: ```clojure -^{:nextjournal.clerk/viewer :html} +^{:nextjournal.clerk/viewer 'nextjournal.clerk.viewer/html-viewer} (md.transform/->hiccup (assoc md.transform/default-hiccup-renderers ;; :doc specify a custom container for the whole doc diff --git a/deps.edn b/deps.edn index 66114b78..522dfb12 100644 --- a/deps.edn +++ b/deps.edn @@ -1,12 +1,12 @@ {:paths ["src" "resources"] :deps {applied-science/js-interop {:mvn/version "0.3.3"} org.clojure/data.json {:mvn/version "2.4.0"} - org.graalvm.js/js {:mvn/version "21.3.2.1"}} + org.graalvm.js/js {:mvn/version "22.3.1"}} :aliases {:nextjournal/clerk {:extra-paths ["notebooks"] - :extra-deps {io.github.nextjournal/clerk {:git/sha "60f2399dcdaf39f5d7bc2c213a8dde9a6e0081a9" + :extra-deps {io.github.nextjournal/clerk {:git/sha "711a1b2fae3c212d2c8c2323cf4d53c178766114" :exclusions [io.github.nextjournal/markdown]}} :jvm-opts ["-Dclojure.main.report=stderr" "-Dclerk.resource_manifest={\"/js/viewer.js\" \"js/viewer.js\"}"] ;; @@ -18,6 +18,7 @@ "notebooks/pandoc.clj" "notebooks/parsing_extensibility.clj" "notebooks/benchmarks.clj" + "notebooks/clerk_to_markdown.clj" "notebooks/tight_lists.clj"]}} :quiet @@ -39,7 +40,7 @@ :main-opts ["-m" "shadow.cljs.devtools.cli"] :jvm-opts ["-Dclerk.resource_manifest={\"/js/viewer.js\" \"http://localhost:8021/viewer.js\"}"] :extra-deps {io.github.nextjournal/clerk.render {:git/url "https://github.com/nextjournal/clerk" - :git/sha "bc137cb51ce98236f48d8dfcb420f6e9df3342c1" + :git/sha "711a1b2fae3c212d2c8c2323cf4d53c178766114" :deps/root "render"}}} :build diff --git a/notebooks/clerk_to_markdown.clj b/notebooks/clerk_to_markdown.clj new file mode 100644 index 00000000..5ab2c64e --- /dev/null +++ b/notebooks/clerk_to_markdown.clj @@ -0,0 +1,49 @@ +;; # 🔄 Clerk to Markdown and Back +(ns clerk_to_markdown + {:nextjournal.clerk/no-cache true} + (:require [clojure.string :as str] + [nextjournal.clerk :as clerk] + [nextjournal.clerk.parser :as clerk.parser] + [nextjournal.markdown.transform :as md.transform])) + +;; This notebook shows how to transform a Clojure namespace with Clerk style _literal fragments_ into a [markdown](https://daringfireball.net/projects/markdown/) file. + +^{::clerk/visibility {:result :hide}} +(def this-notebook "notebooks/clerk_to_markdown.clj") + +;; A clerk notebook is composed of blocks, of type `:markdown` and `:code`. With recent additions to the markdown library we can turn our markdown AST data back into markdown text. + +;; This function turns a Clerk block into a markdown string + +^{::clerk/visibility {:result :hide}} +(defn block->md [{:as block :keys [type text doc]}] + (case type + :code (str "```clojure\n" text "\n```\n\n") + :markdown (md.transform/->md doc))) + +;; now, to put everything together, parse this notebook with Clerk and emit markdown as follows. + +^{::clerk/viewer '(fn [s _] (nextjournal.clerk.viewer/html [:pre s]))} +(def as-markdown + (->> this-notebook + (clerk.parser/parse-file {:doc? true}) + :blocks + (map block->md) + (apply str))) + +;; We can go back to a clojure namespace as follows +(defn block->clj [{:as block :keys [type text doc]}] + (case type + :code (str "\n" text "\n") + :markdown (apply str + (interleave (repeat ";; ") + (str/split-lines (md.transform/->md doc)) + (repeat "\n"))))) + +^{::clerk/viewer '(fn [s _] (nextjournal.clerk.viewer/html [:pre s]))} +(->> (clerk.parser/parse-markdown-string {:doc? true} as-markdown) + :blocks + (map block->clj) + (apply str)) + +;; To learn more about clerk visit our [github page](https://github.com/nextjournal/clerk). diff --git a/notebooks/try.clj b/notebooks/try.clj index 1c2d3ba2..6ee32552 100644 --- a/notebooks/try.clj +++ b/notebooks/try.clj @@ -6,8 +6,8 @@ ^{::clerk/width :full ::clerk/visibility {:code :fold} ::clerk/viewer {:render-fn '(fn [_] - (v/html - (reagent/with-let + (nextjournal.clerk.viewer/html + (reagent.core/with-let [init-text "# 👋 Hello Markdown ```clojure id=xxyyzzww @@ -19,13 +19,13 @@ - [ ] _stuff_ here" text->state (fn [text] (as-> (md/parse text) parsed {:parsed parsed :hiccup (md.transform/->hiccup md.demo/renderers parsed)})) - !state (reagent/atom (text->state init-text)) + !state (reagent.core/atom (text->state init-text)) text-update! (fn [text] (reset! !state (text->state text)))] [:div.grid.grid-cols-2.m-10 [:div.m-2.p-2.text-xl.border-2.overflow-y-scroll.bg-slate-100 {:style {:height "20rem"}} [md.demo/editor {:doc-update text-update! :doc init-text}]] [:div.m-2.p-2.font-medium.overflow-y-scroll {:style {:height "20rem"}} [md.demo/inspect-expanded (:parsed @!state)]] [:div.m-2.p-2.overflow-x-scroll [md.demo/inspect-expanded (:hiccup @!state)]] - [:div.m-2.p-2.bg-slate-50.viewer-markdown [v/html (:hiccup @!state)]]])))}} + [:div.m-2.p-2.bg-slate-50.viewer-markdown [nextjournal.clerk.viewer/html (:hiccup @!state)]]])))}} (Object.) diff --git a/src/nextjournal/markdown.cljs b/src/nextjournal/markdown.cljs index 53b851d0..77f86365 100644 --- a/src/nextjournal/markdown.cljs +++ b/src/nextjournal/markdown.cljs @@ -7,7 +7,9 @@ (extend-type Token ILookup - (-lookup [this key] (j/get this key))) + (-lookup + ([this key] (j/get this key)) + ([this key not-found] (j/get this key not-found)))) (def tokenize md/tokenize) diff --git a/src/nextjournal/markdown/parser.cljc b/src/nextjournal/markdown/parser.cljc index 19465200..74b57131 100644 --- a/src/nextjournal/markdown/parser.cljc +++ b/src/nextjournal/markdown/parser.cljc @@ -135,10 +135,9 @@ ;; node constructors (defn node - [type content attrs top-level] + [type content keyvals] (cond-> {:type type :content content} - (seq attrs) (assoc :attrs attrs) - (seq top-level) (merge top-level))) + (seq keyvals) (merge keyvals))) (defn empty-text-node? [{text :text t :type}] (and (= :text t) (empty? text))) @@ -158,10 +157,9 @@ (defn open-node ([doc type] (open-node doc type {})) - ([doc type attrs] (open-node doc type attrs {})) - ([doc type attrs top-level] + ([doc type keyvals] (-> doc - (push-node (node type [] attrs top-level)) + (push-node (node type [] keyvals)) (update ::path into [:content -1])))) ;; after closing a node, document ::path will point at it @@ -305,7 +303,7 @@ end" doc) ;; blocks -(defmethod apply-token "heading_open" [doc token] (open-node doc :heading {} {:heading-level (hlevel token)})) +(defmethod apply-token "heading_open" [doc token] (open-node doc :heading (assoc (select-keys token [:markup]) :heading-level (hlevel token)))) (defmethod apply-token "heading_close" [doc {doc-level :level}] (let [{:as doc ::keys [path]} (close-node doc) doc' (assign-node-id+emoji doc) @@ -320,13 +318,13 @@ end" (defmethod apply-token "paragraph_open" [doc {:as _token :keys [hidden]}] (open-node doc (if hidden :plain :paragraph))) (defmethod apply-token "paragraph_close" [doc _token] (close-node doc)) -(defmethod apply-token "bullet_list_open" [doc {{:as attrs :keys [has-todos]} :attrs}] (open-node doc (if has-todos :todo-list :bullet-list) attrs)) +(defmethod apply-token "bullet_list_open" [doc {:as token :keys [attrs]}] (open-node doc (if (:has-todos attrs) :todo-list :bullet-list) (select-keys token [:attrs]))) (defmethod apply-token "bullet_list_close" [doc _token] (close-node doc)) -(defmethod apply-token "ordered_list_open" [doc {:keys [attrs]}] (open-node doc :numbered-list attrs)) +(defmethod apply-token "ordered_list_open" [doc token] (open-node doc :numbered-list (select-keys token [:attrs]))) (defmethod apply-token "ordered_list_close" [doc _token] (close-node doc)) -(defmethod apply-token "list_item_open" [doc {{:as attrs :keys [todo]} :attrs}] (open-node doc (if todo :todo-item :list-item) attrs)) +(defmethod apply-token "list_item_open" [doc {:as token :keys [attrs]}] (open-node doc (if (:todo attrs) :todo-item :list-item) (select-keys token [:attrs :markup]))) (defmethod apply-token "list_item_close" [doc _token] (close-node doc)) (defmethod apply-token "math_block" [doc {text :content}] (push-node doc (block-formula text))) @@ -341,15 +339,17 @@ end" (defmethod apply-token "tocBody" [doc _token] doc) ;; ignore body (defmethod apply-token "tocClose" [doc _token] (-> doc close-node (update-current dissoc :content))) -(defmethod apply-token "code_block" [doc {:as _token c :content}] +(defmethod apply-token "code_block" [doc {:as token :keys [content]}] (-> doc - (open-node :code) - (push-node (text-node c)) + (open-node :code (select-keys token [:markup])) + (push-node (text-node content)) close-node)) -(defmethod apply-token "fence" [doc {:as _token i :info c :content}] +(defmethod apply-token "fence" [doc {:as token :keys [info content]}] (-> doc - (open-node :code {} (assoc (parse-fence-info i) :info i)) - (push-node (text-node c)) + (open-node :code (-> (select-keys token [:markup]) + (assoc :info info) + (merge (parse-fence-info info)))) + (push-node (text-node content)) close-node)) ;; footnotes @@ -363,7 +363,7 @@ end" ;; consider an offset in case we're parsing multiple inputs into the same context (let [ref (+ (get-in* token [:meta :id]) footnote-offset) label (get-in* token [:meta :label])] - (open-node doc :footnote nil (cond-> {:ref ref} label (assoc :label label))))) + (open-node doc :footnote (cond-> {:ref ref} label (assoc :label label))))) (defmethod apply-token "footnote_close" [doc token] (close-node doc)) @@ -384,7 +384,7 @@ end" (defn footnote->sidenote [{:keys [ref label content]}] ;; this assumes the footnote container is a paragraph, won't work for lists - (node :sidenote (-> content first :content) nil (cond-> {:ref ref} label (assoc :label label)))) + (node :sidenote (-> content first :content) (cond-> {:ref ref} label (assoc :label label)))) (defn node-with-sidenote-refs [p-node] (loop [l (->zip p-node) refs []] @@ -457,11 +457,11 @@ And what. (defmethod apply-token "thead_close" [doc _token] (close-node doc)) (defmethod apply-token "tr_open" [doc _token] (open-node doc :table-row)) (defmethod apply-token "tr_close" [doc _token] (close-node doc)) -(defmethod apply-token "th_open" [doc token] (open-node doc :table-header (:attrs token))) +(defmethod apply-token "th_open" [doc token] (open-node doc :table-header (select-keys token [:attrs]))) (defmethod apply-token "th_close" [doc _token] (close-node doc)) (defmethod apply-token "tbody_open" [doc _token] (open-node doc :table-body)) (defmethod apply-token "tbody_close" [doc _token] (close-node doc)) -(defmethod apply-token "td_open" [doc token] (open-node doc :table-data (:attrs token))) +(defmethod apply-token "td_open" [doc token] (open-node doc :table-data (select-keys token [:attrs]))) (defmethod apply-token "td_close" [doc _token] (close-node doc)) (comment @@ -577,18 +577,25 @@ _this #should be a tag_, but this [_actually #foo shouldnt_](/bar/) is not." (defmethod apply-token "hardbreak" [doc _token] (push-node doc {:type :hardbreak})) ;; images -(defmethod apply-token "image" [doc {:keys [attrs children]}] (-> doc (open-node :image attrs) (apply-tokens children) close-node)) +(defmethod apply-token "image" [doc {:as token :keys [children]}] + (-> doc + (open-node :image (select-keys token [:attrs])) + (apply-tokens children) + close-node)) ;; marks -(defmethod apply-token "em_open" [doc _token] (open-node doc :em)) +(defmethod apply-token "em_open" [doc token] (open-node doc :em (select-keys token [:markup]))) (defmethod apply-token "em_close" [doc _token] (close-node doc)) -(defmethod apply-token "strong_open" [doc _token] (open-node doc :strong)) +(defmethod apply-token "strong_open" [doc token] (open-node doc :strong (select-keys token [:markup]))) (defmethod apply-token "strong_close" [doc _token] (close-node doc)) -(defmethod apply-token "s_open" [doc _token] (open-node doc :strikethrough)) +(defmethod apply-token "s_open" [doc token] (open-node doc :strikethrough (select-keys token [:markup]))) (defmethod apply-token "s_close" [doc _token] (close-node doc)) -(defmethod apply-token "link_open" [doc token] (open-node doc :link (:attrs token))) +(defmethod apply-token "link_open" [doc token] (open-node doc :link (select-keys token [:attrs]))) (defmethod apply-token "link_close" [doc _token] (close-node doc)) -(defmethod apply-token "code_inline" [doc {text :content}] (-> doc (open-node :monospace) (push-node (text-node text)) close-node)) +(defmethod apply-token "code_inline" [doc {:as token :keys [content]}] + (-> doc (open-node :monospace (select-keys token [:markup])) + (push-node (text-node content)) + close-node)) ;; html (ignored) (defmethod apply-token "html_inline" [doc _] doc) @@ -737,5 +744,6 @@ some final par" (into [] (comp (mapcat (partial tree-seq (comp seq :children) :children)) - (map #(select-keys % [:type :content :hidden :level :info :meta]))) + (map (comp #(into {} (filter (comp some? val)) %) + #(select-keys % [:type :content :hidden :level :info :attrs :markup :meta])))) tokens)) diff --git a/src/nextjournal/markdown/transform.cljc b/src/nextjournal/markdown/transform.cljc index c6a7fb4b..cf9b8d8b 100644 --- a/src/nextjournal/markdown/transform.cljc +++ b/src/nextjournal/markdown/transform.cljc @@ -1,6 +1,8 @@ (ns nextjournal.markdown.transform "transform markdown data as returned by `nextjournal.markdown/parse` into other formats, currently: - * hiccup") + * hiccup + * markdown" + (:require [clojure.string :as str])) ;; helpers (defn guard [pred val] (when (pred val) val)) @@ -9,6 +11,7 @@ text (apply str (map ->text content)))) +;; ## Hiccup Transform (defn hydrate-toc "Scans doc contents and replaces toc node placeholder with the toc node accumulated during parse." [{:as doc :keys [toc]}] @@ -205,3 +208,281 @@ par two" ;; wrap something around the default :paragraph (fn [{:as ctx d :default} node] [:div.p-container (d ctx node)])))) ) + +;; ## Text Transform +(defn write [ctx & strs] (update ctx ::buf into strs)) + +;; ctx -> node -> ctx +(defn write-node [ctx {:as node :keys [type]}] + (if-some [handler (get ctx type)] + (handler ctx node) + (throw (ex-info (str "unhandled node type: " type) {:node node})))) + +(defn write-child-nodes [ctx node] + (update (reduce write-node (update ctx ::parents conj (:type node)) (:content node)) + ::parents pop)) + +;; {node ->} str +(def new-line "\n") +(def block-end "\n\n") +(def code-fence "```") +(defn code-fence+info [_ {:keys [language]}] (str code-fence language new-line)) +(def tab "indent unit" " ") +(defn heading-marker [_ {:keys [heading-level]}] + (str (str/join (repeat heading-level "#")) " ")) +(defn node-markup [default] (fn [_ {:keys [markup]}] (or markup default))) + +;; handler -> handler +(defn ?->fn [m] (cond-> m (not (fn? m)) constantly)) +(defn before [bf f] (fn [ctx n] (f (write ctx ((?->fn bf) ctx n)) n))) +(defn after [af f] (fn [ctx n] + (let [ctx' (f ctx n)] + (write ctx' ((?->fn af) ctx' n))))) + +(defn block [f] (after block-end f)) + +;; nest children +(defn prepend-to-child-nodes [bf] (before bf write-child-nodes)) +(defn append-to-child-nodes [af] (after af write-child-nodes)) +(defn wrap-child-nodes [bf af] (after af (before bf write-child-nodes))) +(defn wrap-mark [default-markup] (wrap-child-nodes + (node-markup default-markup) + (node-markup default-markup))) + +(def top? (comp #{:doc} peek ::parents)) +(defn quote? [{::keys [parents]}] (some #{:blockquote} parents)) +(defn list-container [{::keys [parents]}] (some #{:bullet-list :numbered-list} parents)) +(defn write-list-padding [{:as ctx ::keys [parents]}] + (apply write ctx (repeat (dec (count (keep #{:bullet-list :numbered-list :todo-list} parents))) tab))) + +(defn write-list [ctx n] + (-> ctx + (write-child-nodes n) + (cond-> (top? ctx) (write new-line)))) + +(defn item-marker [{:as ctx ::keys [item-number]} {:keys [type markup attrs]}] + (if (= :todo-item type) + (str "- [" (if (:checked attrs) "x" " ") "] ") + (case (list-container ctx) + :bullet-list (str (or markup "*") " ") + :numbered-list (str item-number (or markup ".") " ")))) + +(defn write-list-item [{:as ctx ::keys [item-number]} n] + (-> ctx + (cond-> item-number (update ::item-number inc)) + (cond-> (quote? ctx) (write "> ")) + write-list-padding + (write (item-marker ctx n)) + (write-child-nodes n))) + +(defn write-footnote [ctx {:as node :keys [label ref]}] + (-> ctx (write "[^" (or label ref) "]: ") (write-child-nodes node) (write new-line))) + +(declare ->md) +(defn process-table-cell [ctx node] + (-> node (select-keys [:attrs]) (assoc :text (str/trim (->md (dissoc ctx ::buf) node))))) + +(defn write-row [col-widths ctx row] + (as-> ctx c + (write c "|") + (reduce-kv (fn [ctx i {:as cell :keys [text]}] + (as-> ctx c + (write c " ") + (write c text) + (apply write c (repeat (- (col-widths i) (count text)) " ")) + (write c " |"))) c (vec row)) + (write c new-line))) + +(defn write-head-line [col-widths ctx row] + (as-> ctx c + (write c "|") + (reduce-kv (fn [ctx i {{:keys [style]} :attrs}] + (as-> ctx c + (write c (if (#{"text-align:center" "text-align:left"} style) ":" "-")) + (apply write c (repeat (col-widths i) "-")) + (write c (if (#{"text-align:center" "text-align:right"} style) ":" "-") "|"))) c (vec row)) + (write c new-line))) + +(defn write-table [{:as ctx ::keys [table]}] + (def table table) + (let [[head & body :as rows] (:rows table) + column-widths (mapv (fn [i] (apply max (map (comp count :text #(nth % i)) rows))) + (range (count (first rows))))] + (as-> ctx c + (write-row column-widths c head) + (write-head-line column-widths c head) + (reduce (partial write-row column-widths) c body) + (write c new-line)))) + +;; ## Markdown Text Renderers +;; Dispatch on types, holds renderers +;; +;; { Type : Ctx -> Node -> Ctx } +;; +(def default-md-renderers + {:doc (block write-child-nodes) + :toc (fn [ctx n] ctx) ;; ignore toc + :text (fn [ctx {:keys [text]}] (write ctx text)) + :heading (block (prepend-to-child-nodes heading-marker)) + :ruler (block (fn [ctx _] (write ctx "---"))) + :paragraph (block + (prepend-to-child-nodes + (fn [ctx _] (when (quote? ctx) "> ")))) + :plain (append-to-child-nodes new-line) + :softbreak (fn [ctx _] + (-> ctx + (write new-line) + (cond-> (list-container ctx) (-> write-list-padding (write tab))) + (cond-> (quote? ctx) (write "> ")))) + :hardbreak (fn [ctx _] + (-> ctx + (write "\\") + (write new-line) + (cond-> (list-container ctx) (-> write-list-padding (write tab))) + (cond-> (quote? ctx) (write "> ")))) + :blockquote (block write-child-nodes) + + :formula (fn [ctx {:keys [text]}] (write ctx (str "$" text "$"))) + :block-formula (block (fn [ctx {:keys [text]}] (write ctx (str "$$" (str/trim text) "$$")))) + + ;; marks + :em (wrap-mark "_") + :strong (wrap-mark "**") + :monospace (wrap-mark "`") + :strikethrough (wrap-mark "~~") + :hashtag (fn [ctx {:keys [text]}] (write ctx (str \# text))) + :link (fn [ctx {:as n :keys [attrs]}] + (-> ctx + (write "[") + (write-child-nodes n) + (write "](" (:href attrs) ")"))) + :internal-link (fn [ctx {:keys [text]}] (write ctx "[[" text "]]")) + + :code (block (wrap-child-nodes code-fence+info code-fence)) + + ;; lists + :bullet-list write-list + :numbered-list (fn [ctx n] + (-> ctx + (assoc ::item-number (-> n :attrs (:start 1))) + (write-list n) + (dissoc ::item-number))) + :todo-list write-list + :list-item write-list-item + :todo-item write-list-item + + :image (fn [{:as ctx ::keys [parents]} {:as n :keys [attrs]}] + (-> ctx + (write "![") + (write-child-nodes n) + (write "](" (:src attrs) ")") + (cond-> (top? ctx) (write block-end)))) + + :footnote-ref (fn [ctx {:keys [ref label]}] (write ctx "[^" (or label ref) "]")) + + ;; tables + :table (fn [ctx n] (-> ctx (assoc ::table {:rows []}) (write-child-nodes n) write-table (dissoc ::table))) + :table-head write-child-nodes + :table-body write-child-nodes + :table-header write-child-nodes + :table-data write-child-nodes + :table-row (fn [ctx {:keys [content]}] + (update-in ctx [::table :rows] conj (map (partial process-table-cell ctx) content)))}) + +(defn ->md + ([doc] (->md default-md-renderers doc)) + ([ctx {:as doc :keys [footnotes]}] + (as-> ctx c + (write-node c doc) + (reduce write-footnote c (reverse footnotes)) + (str (str/trim (apply str (reverse (::buf c)))) + "\n")))) + +#_ (->md (nextjournal.markdown/parse "# Ahoi +this is *just* a __strong__ ~~text~~ with a $\\phi$ and a #hashtag + +this is an ![inline-image](/some/src) and a [_link_](/foo/bar) + +par with a sidenote at the end[^sidenote] and another[^sn2] somewhere + +```clojure +(+ 1 2) +``` + +$$ +\\int_a^b\\phi(t)dt +$$ + +* _this_ + + * sub1 + * sub2 some bla + bla bla + +* is not + + 2. nsub1 + 3. nsub2 + 4. nsub3 + +* thight + - [ ] undone + - [x] done + +* > and + > a nice + > quote + +![block *image*](/some/src) + +> so what +> is this + +1. one +2. two + +--- + +another + +| _col1_ | col2 | +|:-------------:|:------------------------| +| whatthasdasfd | hell | +| this is | insane as as as as as f | + +end + +[^sidenote]: Here a __description__ +[^sn2]: And some _other_ +")) + + +(comment + ;; nested marks + (->md (nextjournal.markdown/parse " +some *emph around a __strong__* bit")) + + + ;; preserving markup + (->md (nextjournal.markdown/parse " +Preserve Markup +--------------- + +- this _should_ and *could* +- look **the** __very same__ + +1) one +2) two +")) + + (nextjournal.markdown.parser/flatten-tokens (nextjournal.markdown/tokenize " +``` +fence +``` + +1) one +2) two + +- this _should_ and *could* +- look **the** __very same__ and ~~why~~ +"))) diff --git a/test/nextjournal/markdown/transform_test.cljc b/test/nextjournal/markdown/transform_test.cljc new file mode 100644 index 00000000..aa8f95e5 --- /dev/null +++ b/test/nextjournal/markdown/transform_test.cljc @@ -0,0 +1,72 @@ +(ns nextjournal.markdown.transform-test + (:require #?(:clj [clojure.test :refer :all] + :cljs [cljs.test :refer (deftest testing is)]) + [matcher-combinators.test] + [nextjournal.markdown :as md] + [nextjournal.markdown.transform :as md.transform])) + +(def test-text "# Ahoi +this is *just* a **strong** ~~text~~ with a $\\phi$ and a #hashtag + +this is an ![inline-image](/some/src) and a [_link_](/foo/bar) + +par with a sidenote at the end[^sidenote] and another[^sn2] somewhere + +```clojure +(+ 1 2) +``` + +$$\\int_a^b\\phi(t)dt$$ + +* _this_ + + - sub1 + - sub2 some bla + bla bla + +* is not + + 2. nsub1 + 3. nsub2 + 4. nsub3 + +* thight + - [ ] undone + - [x] done + +* > and + > a nice + > quote + +![block _image_](/some/src) + +> so what +> is this + +1) one +2) two + +--- + +another + +| _col1_ | col2 | +|:-------------:|:------------------------| +| whatthasdasfd | hell | +| this is | insane as as as as as f | + +> * one +> * two + +end + +[^sidenote]: Here a __description__ +[^sn2]: And some _other_ +") + +(deftest ->md + (let [doc (md/parse test-text)] + (is (= doc + (-> doc + md.transform/->md + md/parse))))) diff --git a/test/test_runner.clj b/test/test_runner.clj index 22e65216..d5b5c48a 100644 --- a/test/test_runner.clj +++ b/test/test_runner.clj @@ -1,6 +1,7 @@ (ns test-runner (:require [clojure.test] - [nextjournal.markdown-test])) + [nextjournal.markdown-test] + [nextjournal.markdown.transform-test])) (defn run [_] (let [{:keys [fail error]} (clojure.test/run-all-tests #"nextjournal\.markdown.*-test")]