From 0faad35a2bb98eda9aa59dc6b95bb1978f3fda71 Mon Sep 17 00:00:00 2001 From: Asko Nõmm Date: Tue, 7 Dec 2021 21:38:01 -0300 Subject: Pass renderers down to parsers for potential recursiveness and add quote block --- src/clarktown/core.clj | 130 ++---------------------- src/clarktown/parser.clj | 95 +++++++++++++++++ src/clarktown/parsers.clj | 32 ++++++ src/clarktown/parsers/bold.clj | 2 +- src/clarktown/parsers/empty_block.clj | 2 +- src/clarktown/parsers/heading_block.clj | 2 +- src/clarktown/parsers/horizontal_line_block.clj | 2 +- src/clarktown/parsers/inline_code.clj | 2 +- src/clarktown/parsers/italic.clj | 2 +- src/clarktown/parsers/link_and_image.clj | 2 +- src/clarktown/parsers/quote_block.clj | 23 +++++ src/clarktown/parsers/strikethrough.clj | 2 +- 12 files changed, 166 insertions(+), 130 deletions(-) create mode 100644 src/clarktown/parser.clj create mode 100644 src/clarktown/parsers.clj create mode 100644 src/clarktown/parsers/quote_block.clj (limited to 'src') diff --git a/src/clarktown/core.clj b/src/clarktown/core.clj index 7cf1e54..43db7c6 100644 --- a/src/clarktown/core.clj +++ b/src/clarktown/core.clj @@ -1,124 +1,7 @@ (ns clarktown.core (:require - [clojure.string :as string] - [clarktown.parsers.bold :as bold] - [clarktown.parsers.italic :as italic] - [clarktown.parsers.inline-code :as inline-code] - [clarktown.parsers.strikethrough :as strikethrough] - [clarktown.parsers.link-and-image :as link-and-image] - [clarktown.parsers.empty-block :as empty-block] - [clarktown.parsers.horizontal-line-block :as horizontal-line-block] - [clarktown.parsers.heading-block :as heading-block])) - - -(def default-parsers - [{:matcher empty-block/is? - :renderers [empty-block/render]} - {:matcher horizontal-line-block/is? - :renderers [horizontal-line-block/render]} - {:matcher heading-block/is? - :renderers [bold/render - italic/render - inline-code/render - strikethrough/render - link-and-image/render - heading-block/render]} - {:renderers [bold/render - italic/render - inline-code/render - strikethrough/render - link-and-image/render]}]) - - -(defn- stitch-code-blocks - "Since code blocks can span multiple blocks (a block is separated by - two line breaks from another block) , we need to stitch them together - into one block in order for a block parser to be able to do anything - with it." - [blocks] - (loop [stitched-blocks [] - code-block-started? false - blocks blocks] - (if (empty? blocks) - stitched-blocks - (let [block (first blocks)] - (if (and (string/starts-with? (string/trim block) "```") - (not (string/ends-with? (string/trim block) "```"))) - (recur (conj stitched-blocks block) - true - (drop 1 blocks)) - (if code-block-started? - (let [last-block (last stitched-blocks) - last-block-index (- (count stitched-blocks) 1)] - (if (string/ends-with? (string/trim block) "```") - (recur (assoc stitched-blocks last-block-index (str last-block "\n\n" block)) - false - (drop 1 blocks)) - (recur (assoc stitched-blocks last-block-index (str last-block "\n\n" block)) - true - (drop 1 blocks)))) - (recur (conj stitched-blocks block) - false - (drop 1 blocks)))))))) - - -(defn- find-parser-by-block - "Find a parser from `parsers` that matches the given `block`." - [parsers block] - (->> parsers - (filter - (fn [{:keys [matcher]}] - (matcher block))) - first)) - - -(defn- parse-block-with-known-parser - "Parses a given `block` with a known `parser`." - [parser block] - (loop [block block - renderers (:renderers parser)] - (if (empty? renderers) - block - (let [renderer (first renderers)] - (recur (renderer block) - (drop 1 renderers)))))) - - -(defn- parse-block-with-unknown-parsers - "Parses the given `block` with all the parsers that do not have - a matcher function, useful for any fallback parsing one might want - to do." - [parsers block] - (loop [block block - parsers (filter #(= nil (:matcher %)) parsers)] - (if (empty? parsers) - block - (loop [block block - renderers (:renderers (first parsers))] - (if (empty? renderers) - block - (let [renderer (first renderers)] - (recur (renderer block) - (drop 1 renderers)))))))) - - -(defn- parse-blocks - "Parses each individual Markdown block, given as `blocks`, with - the list of `parsers`." - [blocks parsers] - (for [block blocks] - (if-let [parser (find-parser-by-block parsers block)] - (parse-block-with-known-parser parser block) - (parse-block-with-unknown-parsers parsers block)))) - - -(defn- parse - "Parses given `markdown` with `parsers`." - [markdown parsers] - (let [blocks (-> (string/split markdown #"\n\n") - stitch-code-blocks) - parsed-blocks (parse-blocks blocks parsers)] - (string/join "" parsed-blocks))) + [clarktown.parser :as parser] + [clarktown.parsers :as parsers])) (defn render @@ -139,6 +22,9 @@ :renderers [(fn [block] ...) (fn [block] ...)]} ```" ([markdown] - (render markdown default-parsers)) - ([markdown parsers] - (parse markdown parsers))) + (render markdown parsers/parsers)) + ([markdown given-parsers] + (parser/parse markdown given-parsers))) + + +(render (slurp "/Users/asko/Documents/work/clarktown/quote-block.md")) diff --git a/src/clarktown/parser.clj b/src/clarktown/parser.clj new file mode 100644 index 0000000..53a96c7 --- /dev/null +++ b/src/clarktown/parser.clj @@ -0,0 +1,95 @@ +(ns clarktown.parser + (:require + [clojure.string :as string])) + + +(defn- stitch-code-blocks + "Since code blocks can span multiple blocks (a block is separated by + two line breaks from another block) , we need to stitch them together + into one block in order for a block parser to be able to do anything + with it." + [blocks] + (loop [stitched-blocks [] + code-block-started? false + blocks blocks] + (if (empty? blocks) + stitched-blocks + (let [block (first blocks)] + (if (and (string/starts-with? (string/trim block) "```") + (not (string/ends-with? (string/trim block) "```"))) + (recur (conj stitched-blocks block) + true + (drop 1 blocks)) + (if code-block-started? + (let [last-block (last stitched-blocks) + last-block-index (- (count stitched-blocks) 1)] + (if (string/ends-with? (string/trim block) "```") + (recur (assoc stitched-blocks last-block-index (str last-block "\n\n" block)) + false + (drop 1 blocks)) + (recur (assoc stitched-blocks last-block-index (str last-block "\n\n" block)) + true + (drop 1 blocks)))) + (recur (conj stitched-blocks block) + false + (drop 1 blocks)))))))) + + +(defn- find-parser-by-block + "Find a parser from `parsers` that matches the given `block`." + [parsers block] + (->> parsers + (filter + (fn [{:keys [matcher]}] + (when matcher + (matcher block)))) + first)) + + +(defn- parse-block-with-known-parser + "Parses a given `block` with a known `parser`." + [parser parsers block] + (loop [block block + renderers (:renderers parser)] + (if (empty? renderers) + block + (let [renderer (first renderers)] + (recur (renderer block parsers) + (drop 1 renderers)))))) + + +(defn- parse-block-with-unknown-parsers + "Parses the given `block` with all the parsers that do not have + a matcher function, useful for any fallback parsing one might want + to do." + [parsers block] + (loop [block block + parsers (filter #(= nil (:matcher %)) parsers)] + (if (empty? parsers) + block + (loop [block block + renderers (:renderers (first parsers))] + (if (empty? renderers) + block + (let [renderer (first renderers)] + (recur (renderer block parsers) + (drop 1 renderers)))))))) + + +(defn- parse-blocks + "Parses each individual Markdown block, given as `blocks`, with + the list of `parsers`." + [blocks parsers] + (for [block blocks] + (if-let [parser (find-parser-by-block parsers block)] + (parse-block-with-known-parser parser parsers block) + (parse-block-with-unknown-parsers parsers block)))) + + +(defn parse + "Parses given `markdown` with `parsers`." + [markdown parsers] + (let [blocks (-> (string/split markdown #"\n\n") + stitch-code-blocks) + parsed-blocks (parse-blocks blocks parsers)] + (string/join "" parsed-blocks))) diff --git a/src/clarktown/parsers.clj b/src/clarktown/parsers.clj new file mode 100644 index 0000000..fc8eb59 --- /dev/null +++ b/src/clarktown/parsers.clj @@ -0,0 +1,32 @@ +(ns clarktown.parsers + (:require + [clarktown.parsers.bold :as bold] + [clarktown.parsers.italic :as italic] + [clarktown.parsers.inline-code :as inline-code] + [clarktown.parsers.strikethrough :as strikethrough] + [clarktown.parsers.link-and-image :as link-and-image] + [clarktown.parsers.empty-block :as empty-block] + [clarktown.parsers.horizontal-line-block :as horizontal-line-block] + [clarktown.parsers.quote-block :as quote-block] + [clarktown.parsers.heading-block :as heading-block])) + + +(def parsers + [{:matcher empty-block/is? + :renderers [empty-block/render]} + {:matcher horizontal-line-block/is? + :renderers [horizontal-line-block/render]} + {:matcher heading-block/is? + :renderers [bold/render + italic/render + inline-code/render + strikethrough/render + link-and-image/render + heading-block/render]} + {:matcher quote-block/is? + :renderers [quote-block/render]} + {:renderers [bold/render + italic/render + inline-code/render + strikethrough/render + link-and-image/render]}]) diff --git a/src/clarktown/parsers/bold.clj b/src/clarktown/parsers/bold.clj index 5840a6e..296f002 100644 --- a/src/clarktown/parsers/bold.clj +++ b/src/clarktown/parsers/bold.clj @@ -5,7 +5,7 @@ (defn render "Renders all occuring bold text as bold." - [block] + [block _] (loop [block block matches (-> (re-seq #"\*\*.*?\*\*" block) distinct)] diff --git a/src/clarktown/parsers/empty_block.clj b/src/clarktown/parsers/empty_block.clj index 57d4655..f16bf6a 100644 --- a/src/clarktown/parsers/empty_block.clj +++ b/src/clarktown/parsers/empty_block.clj @@ -13,5 +13,5 @@ (defn render "Renders an empty block." - [block] + [block _] "") diff --git a/src/clarktown/parsers/heading_block.clj b/src/clarktown/parsers/heading_block.clj index 1bc1faf..84a5fdb 100644 --- a/src/clarktown/parsers/heading_block.clj +++ b/src/clarktown/parsers/heading_block.clj @@ -13,7 +13,7 @@ (defn render "Renders the heading block." - [block] + [block _] (let [single-line-block (-> (string/replace block #"\n" "") string/trim) size (-> (string/split single-line-block #" ") diff --git a/src/clarktown/parsers/horizontal_line_block.clj b/src/clarktown/parsers/horizontal_line_block.clj index 213b295..06997a6 100644 --- a/src/clarktown/parsers/horizontal_line_block.clj +++ b/src/clarktown/parsers/horizontal_line_block.clj @@ -12,5 +12,5 @@ (defn render "Renders the horizontal line block." - [_] + [_ _] "
") diff --git a/src/clarktown/parsers/inline_code.clj b/src/clarktown/parsers/inline_code.clj index b4323d7..c75adf4 100644 --- a/src/clarktown/parsers/inline_code.clj +++ b/src/clarktown/parsers/inline_code.clj @@ -5,7 +5,7 @@ (defn render "Renders all occuring inline code." - [block] + [block _] (loop [block block matches (-> (re-seq #"\`.*?\`" block) distinct)] diff --git a/src/clarktown/parsers/italic.clj b/src/clarktown/parsers/italic.clj index 5b90d12..d8c7c03 100644 --- a/src/clarktown/parsers/italic.clj +++ b/src/clarktown/parsers/italic.clj @@ -5,7 +5,7 @@ (defn render "Renders all occuring italic text as italic." - [block] + [block _] (loop [block block matches (-> (re-seq #"_.*?_" block) distinct)] diff --git a/src/clarktown/parsers/link_and_image.clj b/src/clarktown/parsers/link_and_image.clj index 5ef8857..7964448 100644 --- a/src/clarktown/parsers/link_and_image.clj +++ b/src/clarktown/parsers/link_and_image.clj @@ -5,7 +5,7 @@ (defn render "Renders all occuring links and images." - [block] + [block _] (loop [block block matches (-> (re-seq #"\!?\[(.*?)\]\((.*?)\)" block) distinct)] diff --git a/src/clarktown/parsers/quote_block.clj b/src/clarktown/parsers/quote_block.clj new file mode 100644 index 0000000..8b9c997 --- /dev/null +++ b/src/clarktown/parsers/quote_block.clj @@ -0,0 +1,23 @@ +(ns clarktown.parsers.quote-block + (:require + [clojure.string :as string] + [clarktown.parser :as parser])) + + +(defn is? + "Determines whether the given block is a quote block." + [block] + (-> (string/replace block #"\n" "") + string/trim + (string/starts-with? ">"))) + + +(defn render + "Renders a quote block." + [block parsers] + (let [matches (re-seq #">.*" block) + blocks (for [match matches] + (-> (subs match 1) + string/trim + (parser/parse parsers)))] + (str "
" (string/join "\n" blocks) "
"))) diff --git a/src/clarktown/parsers/strikethrough.clj b/src/clarktown/parsers/strikethrough.clj index add4f6b..31e4cc3 100644 --- a/src/clarktown/parsers/strikethrough.clj +++ b/src/clarktown/parsers/strikethrough.clj @@ -5,7 +5,7 @@ (defn render "Renders all occuring strikethrough text." - [block] + [block _] (loop [block block matches (-> (re-seq #"~~.*?~~" block) distinct)] -- cgit v1.2.3