From 52203a49aa544b2c11c96445d8732893160c436b Mon Sep 17 00:00:00 2001 From: Asko Nõmm Date: Tue, 19 Apr 2022 17:50:19 +0300 Subject: WIP #16 Pretty much done. Needs more testing. And new documentation. --- README.md | 6 +- src/clarktown/core.clj | 151 +++++++++++++++- src/clarktown/correctors.clj | 17 ++ src/clarktown/correctors/atx_heading_block.clj | 19 ++ src/clarktown/correctors/code_block.clj | 27 +++ src/clarktown/matchers/code_block.clj | 10 ++ src/clarktown/matchers/empty_block.clj | 11 ++ src/clarktown/matchers/heading_block.clj | 29 +++ src/clarktown/matchers/horizontal_line_block.clj | 10 ++ src/clarktown/matchers/list_block.clj | 10 ++ src/clarktown/matchers/quote_block.clj | 11 ++ src/clarktown/parser.clj | 195 --------------------- src/clarktown/parsers.clj | 136 +++++++++----- src/clarktown/parsers/bold.clj | 18 -- src/clarktown/parsers/code_block.clj | 30 ---- src/clarktown/parsers/empty_block.clj | 17 -- src/clarktown/parsers/heading_block.clj | 72 -------- src/clarktown/parsers/horizontal_line_block.clj | 16 -- src/clarktown/parsers/inline_code.clj | 21 --- src/clarktown/parsers/italic.clj | 18 -- src/clarktown/parsers/link_and_image.clj | 27 --- src/clarktown/parsers/list_block.clj | 127 -------------- src/clarktown/parsers/paragraph_block.clj | 9 - src/clarktown/parsers/quote_block.clj | 24 --- src/clarktown/parsers/strikethrough.clj | 18 -- src/clarktown/renderers/bold.clj | 18 ++ src/clarktown/renderers/code_block.clj | 23 +++ src/clarktown/renderers/empty_block.clj | 7 + src/clarktown/renderers/heading_block.clj | 44 +++++ src/clarktown/renderers/horizontal_line_block.clj | 7 + src/clarktown/renderers/inline_code.clj | 21 +++ src/clarktown/renderers/italic.clj | 18 ++ src/clarktown/renderers/link_and_image.clj | 27 +++ src/clarktown/renderers/list_block.clj | 121 +++++++++++++ src/clarktown/renderers/paragraph_block.clj | 9 + src/clarktown/renderers/quote_block.clj | 16 ++ src/clarktown/renderers/strikethrough.clj | 18 ++ test/clarktown/matchers/empty_block_test.clj | 9 + .../matchers/horizontal_line_block_test.clj | 14 ++ test/clarktown/matchers/quote_block_test.clj | 11 ++ test/clarktown/parsers/bold_test.clj | 18 -- test/clarktown/parsers/code_block_test.clj | 15 -- test/clarktown/parsers/empty_block_test.clj | 14 -- test/clarktown/parsers/heading_block_test.clj | 44 ----- .../parsers/horizontal_line_block_test.clj | 21 --- test/clarktown/parsers/inline_code_test.clj | 14 -- test/clarktown/parsers/italic_test.clj | 18 -- test/clarktown/parsers/link_and_image_test.clj | 23 --- test/clarktown/parsers/quote_block_test.clj | 15 -- test/clarktown/parsers/strikethrough_test.clj | 14 -- test/clarktown/renderers/bold_test.clj | 18 ++ test/clarktown/renderers/code_block_test.clj | 15 ++ test/clarktown/renderers/empty_block_test.clj | 10 ++ test/clarktown/renderers/heading_block_test.clj | 44 +++++ .../renderers/horizontal_line_block_test.clj | 13 ++ test/clarktown/renderers/inline_code_test.clj | 14 ++ test/clarktown/renderers/italic_test.clj | 18 ++ test/clarktown/renderers/link_and_image_test.clj | 23 +++ test/clarktown/renderers/quote_block_test.clj | 10 ++ test/clarktown/renderers/strikethrough_test.clj | 14 ++ 60 files changed, 926 insertions(+), 841 deletions(-) create mode 100644 src/clarktown/correctors.clj create mode 100644 src/clarktown/correctors/atx_heading_block.clj create mode 100644 src/clarktown/correctors/code_block.clj create mode 100644 src/clarktown/matchers/code_block.clj create mode 100644 src/clarktown/matchers/empty_block.clj create mode 100644 src/clarktown/matchers/heading_block.clj create mode 100644 src/clarktown/matchers/horizontal_line_block.clj create mode 100644 src/clarktown/matchers/list_block.clj create mode 100644 src/clarktown/matchers/quote_block.clj delete mode 100644 src/clarktown/parser.clj delete mode 100644 src/clarktown/parsers/bold.clj delete mode 100644 src/clarktown/parsers/code_block.clj delete mode 100644 src/clarktown/parsers/empty_block.clj delete mode 100644 src/clarktown/parsers/heading_block.clj delete mode 100644 src/clarktown/parsers/horizontal_line_block.clj delete mode 100644 src/clarktown/parsers/inline_code.clj delete mode 100644 src/clarktown/parsers/italic.clj delete mode 100644 src/clarktown/parsers/link_and_image.clj delete mode 100644 src/clarktown/parsers/list_block.clj delete mode 100644 src/clarktown/parsers/paragraph_block.clj delete mode 100644 src/clarktown/parsers/quote_block.clj delete mode 100644 src/clarktown/parsers/strikethrough.clj create mode 100644 src/clarktown/renderers/bold.clj create mode 100644 src/clarktown/renderers/code_block.clj create mode 100644 src/clarktown/renderers/empty_block.clj create mode 100644 src/clarktown/renderers/heading_block.clj create mode 100644 src/clarktown/renderers/horizontal_line_block.clj create mode 100644 src/clarktown/renderers/inline_code.clj create mode 100644 src/clarktown/renderers/italic.clj create mode 100644 src/clarktown/renderers/link_and_image.clj create mode 100644 src/clarktown/renderers/list_block.clj create mode 100644 src/clarktown/renderers/paragraph_block.clj create mode 100644 src/clarktown/renderers/quote_block.clj create mode 100644 src/clarktown/renderers/strikethrough.clj create mode 100644 test/clarktown/matchers/empty_block_test.clj create mode 100644 test/clarktown/matchers/horizontal_line_block_test.clj create mode 100644 test/clarktown/matchers/quote_block_test.clj delete mode 100644 test/clarktown/parsers/bold_test.clj delete mode 100644 test/clarktown/parsers/code_block_test.clj delete mode 100644 test/clarktown/parsers/empty_block_test.clj delete mode 100644 test/clarktown/parsers/heading_block_test.clj delete mode 100644 test/clarktown/parsers/horizontal_line_block_test.clj delete mode 100644 test/clarktown/parsers/inline_code_test.clj delete mode 100644 test/clarktown/parsers/italic_test.clj delete mode 100644 test/clarktown/parsers/link_and_image_test.clj delete mode 100644 test/clarktown/parsers/quote_block_test.clj delete mode 100644 test/clarktown/parsers/strikethrough_test.clj create mode 100644 test/clarktown/renderers/bold_test.clj create mode 100644 test/clarktown/renderers/code_block_test.clj create mode 100644 test/clarktown/renderers/empty_block_test.clj create mode 100644 test/clarktown/renderers/heading_block_test.clj create mode 100644 test/clarktown/renderers/horizontal_line_block_test.clj create mode 100644 test/clarktown/renderers/inline_code_test.clj create mode 100644 test/clarktown/renderers/italic_test.clj create mode 100644 test/clarktown/renderers/link_and_image_test.clj create mode 100644 test/clarktown/renderers/quote_block_test.clj create mode 100644 test/clarktown/renderers/strikethrough_test.clj diff --git a/README.md b/README.md index 1c96c82..cce655c 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ At its core, Clarktown is nothing more than a collection of parsers that collect - `strikethrough` So whenever you call `clarktown.core/render`, those parsers are applied to the content you give it, and are defined collectively in -`clarktown.parsers/parsers`. If you want to remove certain parsers feel free to duplicate that vector with whatever combination of +`clarktown.renderers/parsers`. If you want to remove certain parsers feel free to duplicate that vector with whatever combination of parsers that works best for you, and pass it as the second parameter to the `clarktown.core/render` function, like so: ```clojure @@ -45,7 +45,7 @@ parsers that works best for you, and pass it as the second parameter to the `cla ## Create your own parsers As Clarktown is modular, you can easily create your own custom parsers as well. To see how the parsers are made, I really recommend -checking any of the existing parsers you can find in the `clarktown.parsers/*` namespaces and how they are used in the `clarktown.parsers/parsers` variable, +checking any of the existing parsers you can find in the `clarktown.renderers/*` namespaces and how they are used in the `clarktown.renderers/parsers` variable, but overall the idea is very simple: there are (potential) matchers, and renderers. However, before we get into matchers and renderers, let me quickly explain how Clarktown does its thing. Clarktown splits the entire Markdown @@ -112,7 +112,7 @@ vector of maps, each map representing one collection. And so to register you cou (ns myapp.core (:require [clarktown.core :as clarktown] - [clarktown.parsers :refer [parsers]])) + [clarktown.renderers :refer [parsers]])) (def my-parser {:matcher heading-block/is? diff --git a/src/clarktown/core.clj b/src/clarktown/core.clj index a6ed42c..cabd6e6 100644 --- a/src/clarktown/core.clj +++ b/src/clarktown/core.clj @@ -1,7 +1,145 @@ (ns clarktown.core (:require - [clarktown.parser :as parser] - [clarktown.parsers :as parsers])) + [clojure.string :as string] + [clarktown.parsers :as parsers] + [clarktown.correctors :as correctors])) + + +(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- correct-block-separations + "Corrects block separations and adds newlines above or + below a block where needed." + [correctors lines] + (->> lines + (map-indexed + (fn [index line] + (let [add-line-above? (some #(true? (% lines line index)) (:empty-line-above? correctors)) + add-line-below? (some #(true? (% lines line index)) (:empty-line-below? correctors))] + (cond + ; If code block starts but there is no empty newline + ; above, let's fix that + (and add-line-above? + (not add-line-below?)) + (str \newline line) + + ; If the code block ends, but there is no empty newline + ; below, let's fix that. + (and add-line-below? + (not add-line-above?)) + (str line \newline) + + ; If the code block needs a newline both above and below, + ; let's fix that. + (and add-line-above? + add-line-below?) + (str \newline line \newline) + + ; otherwise is what it is + :else line)))))) + + +(defn- correct-markdown + "Corrects invalid Markdown for the parser." + [markdown given-correctors] + (let [lines (string/split-lines markdown)] + (->> lines + (correct-block-separations (:block-separations given-correctors)) + (string/join \newline)))) + + +(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 + (recur (loop [block block + renderers (:renderers (first parsers))] + (if (empty? renderers) + block + (let [renderer (first renderers)] + (recur (renderer block parsers) + (drop 1 renderers))))) + (drop 1 parsers))))) + + +(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)] + (->> (string/trim block) + (parse-block-with-known-parser parser parsers)) + (->> (string/trim block) + (parse-block-with-unknown-parsers parsers))))) + + +(defn parse + "Parses given `markdown` with `parsers`." + [markdown given-parsers given-correctors] + (let [blocks (-> (correct-markdown markdown given-correctors) + (string/split #"\n\n") + stitch-code-blocks) + parsed-blocks (parse-blocks blocks given-parsers)] + (string/join "\n\n" parsed-blocks))) (defn render @@ -22,9 +160,8 @@ :renderers [(fn [block] ...) (fn [block] ...)]} ```" ([markdown] - (render markdown parsers/parsers)) + (render markdown parsers/default-parsers)) ([markdown given-parsers] - (parser/parse markdown given-parsers))) - -(comment - (render (slurp "./test.md"))) \ No newline at end of file + (render markdown given-parsers correctors/default-correctors)) + ([markdown given-parsers given-correctors] + (parse markdown given-parsers given-correctors))) \ No newline at end of file diff --git a/src/clarktown/correctors.clj b/src/clarktown/correctors.clj new file mode 100644 index 0000000..ba49879 --- /dev/null +++ b/src/clarktown/correctors.clj @@ -0,0 +1,17 @@ +(ns clarktown.correctors + (:require + [clarktown.correctors.code-block :as code-block] + [clarktown.correctors.atx-heading-block :as atx-heading-block])) + + +(def block-separation-correctors + {:empty-line-above? + [code-block/empty-line-above? + atx-heading-block/empty-line-above?] + :empty-line-below? + [code-block/empty-line-below? + atx-heading-block/empty-line-below?]}) + + +(def default-correctors + {:block-separations block-separation-correctors}) \ No newline at end of file diff --git a/src/clarktown/correctors/atx_heading_block.clj b/src/clarktown/correctors/atx_heading_block.clj new file mode 100644 index 0000000..a792572 --- /dev/null +++ b/src/clarktown/correctors/atx_heading_block.clj @@ -0,0 +1,19 @@ +(ns clarktown.correctors.atx-heading-block + (:require + [clojure.string :as string])) + + +(defn empty-line-above? + [lines line index] + (and (string/starts-with? (string/trim line) "#") + (> index 0) + (not (= (-> (nth lines (- index 1)) + string/trim) "")))) + + +(defn empty-line-below? + [lines line index] + (and (string/starts-with? (string/trim line) "#") + (< index (- (count lines) 1)) + (not (= (-> (nth lines (+ index 1)) + string/trim) "")))) \ No newline at end of file diff --git a/src/clarktown/correctors/code_block.clj b/src/clarktown/correctors/code_block.clj new file mode 100644 index 0000000..e767390 --- /dev/null +++ b/src/clarktown/correctors/code_block.clj @@ -0,0 +1,27 @@ +(ns clarktown.correctors.code-block + (:require + [clojure.string :as string])) + + +(defn empty-line-above? + [lines line index] + (and (= (string/trim line) "```") + (> index 0) + (->> (take index lines) + (filter #(= (string/trim %) "```")) + count + odd?) + (not (= (-> (nth lines (- index 1)) + string/trim) "")))) + + +(defn empty-line-below? + [lines line index] + (and (= (string/trim line) "```") + (< index (- (count lines) 1)) + (->> (take index lines) + (filter #(= (string/trim %) "```")) + count + even?) + (not (= (-> (nth lines (+ index 1)) + string/trim) "")))) \ No newline at end of file diff --git a/src/clarktown/matchers/code_block.clj b/src/clarktown/matchers/code_block.clj new file mode 100644 index 0000000..655c951 --- /dev/null +++ b/src/clarktown/matchers/code_block.clj @@ -0,0 +1,10 @@ +(ns clarktown.matchers.code-block + (:require + [clojure.string :as string])) + + +(defn match? + "Determines whether we're dealing with a code block." + [block] + (and (string/starts-with? block "```") + (string/ends-with? block "```"))) \ No newline at end of file diff --git a/src/clarktown/matchers/empty_block.clj b/src/clarktown/matchers/empty_block.clj new file mode 100644 index 0000000..cc7b7f4 --- /dev/null +++ b/src/clarktown/matchers/empty_block.clj @@ -0,0 +1,11 @@ +(ns clarktown.matchers.empty-block + (:require + [clojure.string :as string])) + + +(defn match? + "Determines if the current block is an empty block or not." + [block] + (-> (string/replace block #"\n" "") + string/trim + string/blank?)) \ No newline at end of file diff --git a/src/clarktown/matchers/heading_block.clj b/src/clarktown/matchers/heading_block.clj new file mode 100644 index 0000000..2295f26 --- /dev/null +++ b/src/clarktown/matchers/heading_block.clj @@ -0,0 +1,29 @@ +(ns clarktown.matchers.heading-block + (:require + [clojure.string :as string])) + + +(defn is-atx-heading? + "Determines whether the given block is a atx heading." + [block] + (-> (string/replace block #"\n" "") + string/trim + (string/starts-with? "#"))) + + +(defn is-settext-heading? + "Determines whether the given block is a settext heading." + [block] + (let [lines (-> (string/split-lines block)) + chars (-> (last lines) + string/trim + (string/split #""))] + (and (> (count lines) 1) + (every? #{"-" "="} chars)))) + + +(defn match? + "Determines whether the given block is a heading block." + [block] + (or (is-atx-heading? block) + (is-settext-heading? block))) \ No newline at end of file diff --git a/src/clarktown/matchers/horizontal_line_block.clj b/src/clarktown/matchers/horizontal_line_block.clj new file mode 100644 index 0000000..a2dde6e --- /dev/null +++ b/src/clarktown/matchers/horizontal_line_block.clj @@ -0,0 +1,10 @@ +(ns clarktown.matchers.horizontal-line-block + (:require + [clojure.string :as string])) + + +(defn match? + "Determines whether the given block is a horizontal line block." + [block] + (or (= "***" (string/trim block)) + (= "---" (string/trim block)))) \ No newline at end of file diff --git a/src/clarktown/matchers/list_block.clj b/src/clarktown/matchers/list_block.clj new file mode 100644 index 0000000..a37b06e --- /dev/null +++ b/src/clarktown/matchers/list_block.clj @@ -0,0 +1,10 @@ +(ns clarktown.matchers.list-block + (:require + [clojure.string :as string])) + + +(defn match? + "Determines whether we're dealing with a list block or not." + [block] + (->> (string/trim block) + (re-matches #"(?s)^(\d\.\s|\*{1}\s|\-{1}\s).*$"))) \ No newline at end of file diff --git a/src/clarktown/matchers/quote_block.clj b/src/clarktown/matchers/quote_block.clj new file mode 100644 index 0000000..230b561 --- /dev/null +++ b/src/clarktown/matchers/quote_block.clj @@ -0,0 +1,11 @@ +(ns clarktown.matchers.quote-block + (:require + [clojure.string :as string])) + + +(defn match? + "Determines whether the given block is a quote block." + [block] + (-> (string/replace block #"\n" "") + string/trim + (string/starts-with? ">"))) \ No newline at end of file diff --git a/src/clarktown/parser.clj b/src/clarktown/parser.clj deleted file mode 100644 index ecee37d..0000000 --- a/src/clarktown/parser.clj +++ /dev/null @@ -1,195 +0,0 @@ -(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- needs-empty-line-above? - "Determines whether the current line needs an empty line correction - above." - [lines line index] - (cond - ; code block - (and (= (string/trim line) "```") - (> index 0) - (->> (take index lines) - (filter #(= (string/trim %) "```")) - count - odd?) - (not (= (-> (nth lines (- index 1)) - string/trim) ""))) - true - - ; ATX heading block - (and (string/starts-with? (string/trim line) "#") - (> index 0) - (not (= (-> (nth lines (- index 1)) - string/trim) ""))) - true - - - ; everything else stays normal - :else false)) - - -(defn- needs-empty-line-below? - "Determines whether the current line needs an empty line correction - below." - [lines line index] - (cond - ; code block - (and (= (string/trim line) "```") - (< index (- (count lines) 1)) - (->> (take index lines) - (filter #(= (string/trim %) "```")) - count - even?) - (not (= (-> (nth lines (+ index 1)) - string/trim) ""))) - true - - ; ATX heading block - (and (string/starts-with? (string/trim line) "#") - (< index (- (count lines) 1)) - (not (= (-> (nth lines (+ index 1)) - string/trim) ""))) - true - - ; everything else stays normal - :else false)) - - -(defn- correct-block-separations - "Corrects block separations and adds newlines above or - below a block where needed." - [lines] - (->> lines - (map-indexed - (fn [index line] - (let [add-line-above? (needs-empty-line-above? lines line index) - add-line-below? (needs-empty-line-below? lines line index)] - (cond - ; If code block starts but there is no empty newline - ; above, let's fix that - (and add-line-above? - (not add-line-below?)) - (str \newline line) - - ; If the code block ends, but there is no empty newline - ; below, let's fix that. - (and add-line-below? - (not add-line-above?)) - (str line \newline) - - ; If the code block needs a newline both above and below, - ; let's fix that. - (and add-line-above? - add-line-below?) - (str \newline line \newline) - - ; otherwise is what it is - :else line)))))) - - -(defn- correct-markdown - "Corrects invalid Markdown for the parser." - [markdown] - (let [lines (string/split-lines markdown)] - (->> lines - correct-block-separations - (string/join \newline)))) - - -(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 - (recur (loop [block block - renderers (:renderers (first parsers))] - (if (empty? renderers) - block - (let [renderer (first renderers)] - (recur (renderer block parsers) - (drop 1 renderers))))) - (drop 1 parsers))))) - - -(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)] - (->> (string/trim block) - (parse-block-with-known-parser parser parsers)) - (->> (string/trim block) - (parse-block-with-unknown-parsers parsers))))) - - -(defn parse - "Parses given `markdown` with `parsers`." - [markdown parsers] - (let [blocks (-> (correct-markdown markdown) - (string/split #"\n\n") - stitch-code-blocks) - parsed-blocks (parse-blocks blocks parsers)] - (string/join "\n\n" parsed-blocks))) diff --git a/src/clarktown/parsers.clj b/src/clarktown/parsers.clj index cd909b3..77c9794 100644 --- a/src/clarktown/parsers.clj +++ b/src/clarktown/parsers.clj @@ -1,45 +1,95 @@ (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] - [clarktown.parsers.code-block :as code-block] - [clarktown.parsers.list-block :as list-block] - [clarktown.parsers.paragraph-block :as paragraph-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 [link-and-image/render - bold/render - italic/render - inline-code/render - strikethrough/render - heading-block/render]} - {:matcher quote-block/is? - :renderers [quote-block/render]} - {:matcher code-block/is? - :renderers [code-block/render]} - {:matcher list-block/is? - :renderers [link-and-image/render - bold/render - italic/render - inline-code/render - strikethrough/render - list-block/render]} - {:renderers [link-and-image/render - bold/render - italic/render - inline-code/render - strikethrough/render - paragraph-block/render]}]) + [clarktown.matchers.empty-block] + [clarktown.renderers.empty-block] + [clarktown.matchers.horizontal-line-block] + [clarktown.renderers.horizontal-line-block] + [clarktown.matchers.heading-block] + [clarktown.renderers.heading-block] + [clarktown.matchers.quote-block] + [clarktown.renderers.quote-block] + [clarktown.matchers.code-block] + [clarktown.renderers.code-block] + [clarktown.matchers.list-block] + [clarktown.renderers.list-block] + [clarktown.renderers.paragraph-block] + [clarktown.renderers.link-and-image] + [clarktown.renderers.bold] + [clarktown.renderers.italic] + [clarktown.renderers.inline-code] + [clarktown.renderers.strikethrough])) + + +(def + ^{:doc "Detects, parses and renders a empty block."} + empty-block-parser + {:matcher clarktown.matchers.empty-block/match? + :renderers [clarktown.renderers.empty-block/render]}) + + +(def + ^{:doc "Detects, parses and renders a horizontal line block."} + horizontal-line-block-parser + {:matcher clarktown.matchers.horizontal-line-block/match? + :renderers [clarktown.renderers.horizontal-line-block/render]}) + + +(def + ^{:doc "Detects, parses and renders a heading block."} + heading-block-parser + {:matcher clarktown.matchers.heading-block/match? + :renderers [clarktown.renderers.link-and-image/render + clarktown.renderers.bold/render + clarktown.renderers.italic/render + clarktown.renderers.inline-code/render + clarktown.renderers.strikethrough/render + clarktown.renderers.heading-block/render]}) + + +(def + ^{:doc "Detects, parses and renders a quote block."} + quote-block-parser + {:matcher clarktown.matchers.quote-block/match? + :renderers [clarktown.renderers.quote-block/render]}) + + +(def + ^{:doc "Detects, parses and renders a code block."} + code-block-parser + {:matcher clarktown.matchers.code-block/match? + :renderers [clarktown.renderers.code-block/render]}) + + +(def + ^{:doc "Detects, parses and renders a list block."} + list-block-parser + {:matcher clarktown.matchers.list-block/match? + :renderers [clarktown.renderers.link-and-image/render + clarktown.renderers.bold/render + clarktown.renderers.italic/render + clarktown.renderers.inline-code/render + clarktown.renderers.strikethrough/render + clarktown.renderers.list-block/render]}) + + +(def + ^{:doc "Parses and renders a quote block."} + paragraph-block-parser + {:renderers [clarktown.renderers.link-and-image/render + clarktown.renderers.bold/render + clarktown.renderers.italic/render + clarktown.renderers.inline-code/render + clarktown.renderers.strikethrough/render + clarktown.renderers.paragraph-block/render]}) + + +(def + ^{:doc "A set of default parsers."} + default-parsers + [empty-block-parser + horizontal-line-block-parser + heading-block-parser + quote-block-parser + code-block-parser + list-block-parser + paragraph-block-parser]) diff --git a/src/clarktown/parsers/bold.clj b/src/clarktown/parsers/bold.clj deleted file mode 100644 index 79579bf..0000000 --- a/src/clarktown/parsers/bold.clj +++ /dev/null @@ -1,18 +0,0 @@ -(ns clarktown.parsers.bold - (:require - [clojure.string :as string])) - - -(defn render - "Renders all occurring bold text as bold." - [block _] - (loop [block block - matches (-> (re-seq #"(\*{2}|\_{2})[^\*|\_](.*?)[^\*|\_](\*{2}|\_{2})" block) - distinct)] - (if (empty? matches) - block - (let [match (ffirst matches) - value (subs match 2 (- (count match) 2)) - replacement (str "" value "")] - (recur (string/replace block match replacement) - (drop 1 matches)))))) diff --git a/src/clarktown/parsers/code_block.clj b/src/clarktown/parsers/code_block.clj deleted file mode 100644 index c6ecfea..0000000 --- a/src/clarktown/parsers/code_block.clj +++ /dev/null @@ -1,30 +0,0 @@ -(ns clarktown.parsers.code-block - (:require - [clojure.string :as string])) - - -(defn is? - "Determines whether we're dealing with a code block." - [block] - (and (string/starts-with? block "```") - (string/ends-with? block "```"))) - - -(defn render - "Renders the code block." - [block _] - (let [language (->> block - (re-find #"\`\`\`(\w+)") - second) - lines (string/split-lines block) - block* (->> (next lines) - (take (- (count lines) 2)) - (string/join \newline)) - code (-> block* - (string/replace #"&" "&") - (string/replace #"<" "<") - (string/replace #">" ">") - string/trim)] - (if language - (str "
" code "
") - (str "
" code "
")))) diff --git a/src/clarktown/parsers/empty_block.clj b/src/clarktown/parsers/empty_block.clj deleted file mode 100644 index 0ed5a08..0000000 --- a/src/clarktown/parsers/empty_block.clj +++ /dev/null @@ -1,17 +0,0 @@ -(ns clarktown.parsers.empty-block - (:require - [clojure.string :as string])) - - -(defn is? - "Determines if the current block is an empty block or not." - [block] - (-> (string/replace block #"\n" "") - string/trim - string/blank?)) - - -(defn render - "Renders an empty block." - [_ _] - "") diff --git a/src/clarktown/parsers/heading_block.clj b/src/clarktown/parsers/heading_block.clj deleted file mode 100644 index def2394..0000000 --- a/src/clarktown/parsers/heading_block.clj +++ /dev/null @@ -1,72 +0,0 @@ -(ns clarktown.parsers.heading-block - (:require - [clojure.string :as string])) - - -(defn is-hashbang-heading? - "Determines whether the given block is a hashbang heading." - [block] - (-> (string/replace block #"\n" "") - string/trim - (string/starts-with? "#"))) - - -(defn is-settext-heading? - "Determines whether the given block is a settext heading." - [block] - (let [lines (-> (string/split-lines block)) - chars (-> (last lines) - string/trim - (string/split #""))] - (and (> (count lines) 1) - (every? #{"-" "="} chars)))) - - -(defn is? - "Determines whether the given block is a heading block." - [block] - (or (is-hashbang-heading? block) - (is-settext-heading? block))) - - -(defn render-hashbang-heading - "Renders the hashbang heading block." - [block] - (let [single-line-block (-> (string/replace block #"\n" "") - string/trim) - size (-> (string/split single-line-block #" ") - first - string/trim - count) - value (->> (string/split single-line-block #" ") - next - (string/join " ") - string/trim)] - (str "" value ""))) - - -(defn render-settext-heading - "Renders the settext heading block." - [block] - (let [lines (string/split-lines block) - value (->> (split-at (- (count lines) 1) lines) - first - (string/join "\n")) - h1? (= "=" (-> (last lines) - string/trim - (string/split #"") - first))] - (if h1? - (str "

" value "

") - (str "

" value "

")))) - - -(render-settext-heading "Hello world\nAnd you too\n===") - - -(defn render - "Renders the heading block." - [block _] - (if (is-hashbang-heading? block) - (render-hashbang-heading block) - (render-settext-heading block))) diff --git a/src/clarktown/parsers/horizontal_line_block.clj b/src/clarktown/parsers/horizontal_line_block.clj deleted file mode 100644 index b1d1e05..0000000 --- a/src/clarktown/parsers/horizontal_line_block.clj +++ /dev/null @@ -1,16 +0,0 @@ -(ns clarktown.parsers.horizontal-line-block - (:require - [clojure.string :as string])) - - -(defn is? - "Determines whether the given block is a horizontal line block." - [block] - (or (= "***" (string/trim block)) - (= "---" (string/trim block)))) - - -(defn render - "Renders the horizontal line block." - [_ _] - "
") diff --git a/src/clarktown/parsers/inline_code.clj b/src/clarktown/parsers/inline_code.clj deleted file mode 100644 index 1de73bf..0000000 --- a/src/clarktown/parsers/inline_code.clj +++ /dev/null @@ -1,21 +0,0 @@ -(ns clarktown.parsers.inline-code - (:require - [clojure.string :as string])) - - -(defn render - "Renders all occurring inline code." - [block _] - (loop [block block - matches (-> (re-seq #"\`.*?\`" block) - distinct)] - (if (empty? matches) - block - (let [match (first matches) - value (-> (subs match 1 (- (count match) 1)) - (string/replace #"&" "&") - (string/replace #"<" "<") - (string/replace #">" ">")) - replacement (str "" value "")] - (recur (string/replace block match replacement) - (drop 1 matches)))))) diff --git a/src/clarktown/parsers/italic.clj b/src/clarktown/parsers/italic.clj deleted file mode 100644 index 915a017..0000000 --- a/src/clarktown/parsers/italic.clj +++ /dev/null @@ -1,18 +0,0 @@ -(ns clarktown.parsers.italic - (:require - [clojure.string :as string])) - - -(defn render - "Renders all occurring italic text as italic." - [block _] - (loop [block block - matches (-> (re-seq #"(\*{1,}?|\_{1,}?)(.*?)(\*{1,}?|\_{1,}?)" block) - distinct)] - (if (empty? matches) - block - (let [match (ffirst matches) - value (subs match 1 (- (count match) 1)) - replacement (str "" value "")] - (recur (string/replace block match replacement) - (drop 1 matches)))))) diff --git a/src/clarktown/parsers/link_and_image.clj b/src/clarktown/parsers/link_and_image.clj deleted file mode 100644 index 3be2efe..0000000 --- a/src/clarktown/parsers/link_and_image.clj +++ /dev/null @@ -1,27 +0,0 @@ -(ns clarktown.parsers.link-and-image - (:require - [clojure.string :as string])) - - -(defn encode-href - [href] - (-> href - (string/replace "_" "_"))) - - -(defn render - "Renders all occurring links and images." - [block _] - (loop [block block - matches (-> (re-seq #"\!?\[([a-zA-Z0-9\-\.\,]+( [a-zA-Z0-9\-\.\,]+)*)\]\((.*?)\)" block) - distinct)] - (if (empty? matches) - block - (let [[whole-match label _ href] (first matches) - image? (string/starts-with? whole-match "!") - image (str "\""") - link (str "" label "")] - (recur (if image? - (string/replace block whole-match image) - (string/replace block whole-match link)) - (drop 1 matches)))))) \ No newline at end of file diff --git a/src/clarktown/parsers/list_block.clj b/src/clarktown/parsers/list_block.clj deleted file mode 100644 index 52f955f..0000000 --- a/src/clarktown/parsers/list_block.clj +++ /dev/null @@ -1,127 +0,0 @@ -(ns clarktown.parsers.list-block - (:require - [clojure.string :as string])) - - -(defn is? - "Determines whether we're dealing with a list block or not." - [block] - (->> (string/trim block) - (re-matches #"(?s)^(\d\.\s|\*{1}\s|\-{1}\s).*$"))) - - -(defn string->indent-n - "Returns the indentation count from left of `str`, which must be - in spaces and not tabs." - [str] - (count (take-while #{\space} str))) - - -(defn compose-items-with-indent-guides - "Composes a vector of maps from given `block` that adds a unique - ID to each line as well as its `indent-n` which is used later - on to determine hierarchies. " - [block] - (->> (string/split-lines block) - (mapv - (fn [line] - {:id (random-uuid) - :indent-n (string->indent-n line) - :value (-> line - string/trim)})))) - - -(defn find-parent-id - "Assuming a 1-level `items`, will attempt to find the parent `id` - of the item at given `index`. Will return `nil` otherwise." - [items index] - (let [indent-n-at-index (:indent-n (nth items index))] - (-> (->> (split-at index items) - first - reverse - (remove #(or (> (:indent-n %) indent-n-at-index) - (= (:indent-n %) indent-n-at-index))) - first - :id)))) - - -(defn compose-items-with-parents - "Composes a 1-level list of items from `block` and adds parent - information to each if they belong to another item. The result - of this is used to build the final data tree." - [block] - (let [items (compose-items-with-indent-guides block)] - (->> items - (map-indexed - (fn [index line] - (merge line {:parent (find-parent-id items index)})))))) - - -(defn add-to-parent - "Recursively scans `items`, which can be multiple levels deep, - and tries to find a home for `item` according to its parent ID." - [items item] - (->> items - (mapv - (fn [i] - (if (= (:id i) (:parent item)) - (if (:items i) - (assoc i :items (concat (:items i) [item])) - (assoc i :items [item])) - (if (:items i) - (assoc i :items (add-to-parent (:items i) item)) - i)))))) - - -(defn compose-item-tree - "Given a `block`, composes a data representation of it based on - the indentation of each line." - [block] - (loop [result [] - items (compose-items-with-parents block)] - (if (empty? items) - result - (let [item (first items) - parent (:parent item) - new-item {:id (:id item) - :value (:value item)}] - (recur (if parent - (add-to-parent result item) - (concat result [new-item])) - (drop 1 items)))))) - - -(defn render-items - "Renders an ordered/un-ordered list hierarchy from given `items`." - [items] - (loop [result "" - inner-items items] - (if (empty? inner-items) - (if (or (string/starts-with? (:value (first items)) "*") - (string/starts-with? (:value (first items)) "-")) - (str "") - (str "
    " result "
")) - (let [inner-item (first inner-items) - value (cond - ; * unordered list - (string/starts-with? (:value inner-item) "*") - (-> (string/replace-first (:value inner-item) "*" "") - string/trim) - ; - unordered list - (string/starts-with? (:value inner-item) "-") - (-> (string/replace-first (:value inner-item) "-" "") - string/trim) - :else - (-> (string/replace-first (:value inner-item) #"\d\." "") - string/trim))] - (recur (if (:items inner-item) - (str result "
  • " value (render-items (:items inner-item)) "
  • ") - (str result "
  • " value "
  • ")) - (drop 1 inner-items)))))) - - -(defn render - "Renders the list block" - [block _] - (-> (compose-item-tree block) - (render-items))) \ No newline at end of file diff --git a/src/clarktown/parsers/paragraph_block.clj b/src/clarktown/parsers/paragraph_block.clj deleted file mode 100644 index 3a7c633..0000000 --- a/src/clarktown/parsers/paragraph_block.clj +++ /dev/null @@ -1,9 +0,0 @@ -(ns clarktown.parsers.paragraph-block - (:require - [clojure.string :as string])) - - -(defn render - "Renders the paragraph block." - [block _] - (str "

    " (string/trim block) "

    ")) diff --git a/src/clarktown/parsers/quote_block.clj b/src/clarktown/parsers/quote_block.clj deleted file mode 100644 index ff2dda8..0000000 --- a/src/clarktown/parsers/quote_block.clj +++ /dev/null @@ -1,24 +0,0 @@ -(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)) - (string/join "\n")) - block (parser/parse blocks parsers)] - (str "
    " block "
    "))) diff --git a/src/clarktown/parsers/strikethrough.clj b/src/clarktown/parsers/strikethrough.clj deleted file mode 100644 index 6f03152..0000000 --- a/src/clarktown/parsers/strikethrough.clj +++ /dev/null @@ -1,18 +0,0 @@ -(ns clarktown.parsers.strikethrough - (:require - [clojure.string :as string])) - - -(defn render - "Renders all occurring strikethrough text." - [block _] - (loop [block block - matches (-> (re-seq #"~~.*?~~" block) - distinct)] - (if (empty? matches) - block - (let [match (first matches) - value (subs match 2 (- (count match) 2)) - replacement (str "" value "")] - (recur (string/replace block match replacement) - (drop 1 matches)))))) diff --git a/src/clarktown/renderers/bold.clj b/src/clarktown/renderers/bold.clj new file mode 100644 index 0000000..1ce7f84 --- /dev/null +++ b/src/clarktown/renderers/bold.clj @@ -0,0 +1,18 @@ +(ns clarktown.renderers.bold + (:require + [clojure.string :as string])) + + +(defn render + "Renders all occurring bold text as bold." + [block _] + (loop [block block + matches (-> (re-seq #"(\*{2}|\_{2})[^\*|\_](.*?)[^\*|\_](\*{2}|\_{2})" block) + distinct)] + (if (empty? matches) + block + (let [match (ffirst matches) + value (subs match 2 (- (count match) 2)) + replacement (str "" value "")] + (recur (string/replace block match replacement) + (drop 1 matches)))))) diff --git a/src/clarktown/renderers/code_block.clj b/src/clarktown/renderers/code_block.clj new file mode 100644 index 0000000..2bed4e6 --- /dev/null +++ b/src/clarktown/renderers/code_block.clj @@ -0,0 +1,23 @@ +(ns clarktown.renderers.code-block + (:require + [clojure.string :as string])) + + +(defn render + "Renders the code block." + [block _] + (let [language (->> block + (re-find #"\`\`\`(\w+)") + second) + lines (string/split-lines block) + block* (->> (next lines) + (take (- (count lines) 2)) + (string/join \newline)) + code (-> block* + (string/replace #"&" "&") + (string/replace #"<" "<") + (string/replace #">" ">") + string/trim)] + (if language + (str "
    " code "
    ") + (str "
    " code "
    ")))) diff --git a/src/clarktown/renderers/empty_block.clj b/src/clarktown/renderers/empty_block.clj new file mode 100644 index 0000000..66e819e --- /dev/null +++ b/src/clarktown/renderers/empty_block.clj @@ -0,0 +1,7 @@ +(ns clarktown.renderers.empty-block) + + +(defn render + "Renders an empty block." + [_ _] + "") diff --git a/src/clarktown/renderers/heading_block.clj b/src/clarktown/renderers/heading_block.clj new file mode 100644 index 0000000..f953d0a --- /dev/null +++ b/src/clarktown/renderers/heading_block.clj @@ -0,0 +1,44 @@ +(ns clarktown.renderers.heading-block + (:require + [clojure.string :as string] + [clarktown.matchers.heading-block :as matcher])) + + +(defn render-atx-heading + "Renders the hashbang heading block." + [block] + (let [single-line-block (-> (string/replace block #"\n" "") + string/trim) + size (-> (string/split single-line-block #" ") + first + string/trim + count) + value (->> (string/split single-line-block #" ") + next + (string/join " ") + string/trim)] + (str "" value ""))) + + +(defn render-settext-heading + "Renders the settext heading block." + [block] + (let [lines (string/split-lines block) + value (->> (split-at (- (count lines) 1) lines) + first + (string/join "\n")) + h1? (= "=" (-> (last lines) + string/trim + (string/split #"") + first))] + (if h1? + (str "

    " value "

    ") + (str "

    " value "

    ")))) + + +(defn render + "Renders the heading block." + [block _] + (if (matcher/is-atx-heading? block) + (render-atx-heading block) + (render-settext-heading block))) diff --git a/src/clarktown/renderers/horizontal_line_block.clj b/src/clarktown/renderers/horizontal_line_block.clj new file mode 100644 index 0000000..f141e5f --- /dev/null +++ b/src/clarktown/renderers/horizontal_line_block.clj @@ -0,0 +1,7 @@ +(ns clarktown.renderers.horizontal-line-block) + + +(defn render + "Renders the horizontal line block." + [_ _] + "
    ") diff --git a/src/clarktown/renderers/inline_code.clj b/src/clarktown/renderers/inline_code.clj new file mode 100644 index 0000000..29593a8 --- /dev/null +++ b/src/clarktown/renderers/inline_code.clj @@ -0,0 +1,21 @@ +(ns clarktown.renderers.inline-code + (:require + [clojure.string :as string])) + + +(defn render + "Renders all occurring inline code." + [block _] + (loop [block block + matches (-> (re-seq #"\`.*?\`" block) + distinct)] + (if (empty? matches) + block + (let [match (first matches) + value (-> (subs match 1 (- (count match) 1)) + (string/replace #"&" "&") + (string/replace #"<" "<") + (string/replace #">" ">")) + replacement (str "" value "")] + (recur (string/replace block match replacement) + (drop 1 matches)))))) diff --git a/src/clarktown/renderers/italic.clj b/src/clarktown/renderers/italic.clj new file mode 100644 index 0000000..a1568f6 --- /dev/null +++ b/src/clarktown/renderers/italic.clj @@ -0,0 +1,18 @@ +(ns clarktown.renderers.italic + (:require + [clojure.string :as string])) + + +(defn render + "Renders all occurring italic text as italic." + [block _] + (loop [block block + matches (-> (re-seq #"(\*{1,}?|\_{1,}?)(.*?)(\*{1,}?|\_{1,}?)" block) + distinct)] + (if (empty? matches) + block + (let [match (ffirst matches) + value (subs match 1 (- (count match) 1)) + replacement (str "" value "")] + (recur (string/replace block match replacement) + (drop 1 matches)))))) diff --git a/src/clarktown/renderers/link_and_image.clj b/src/clarktown/renderers/link_and_image.clj new file mode 100644 index 0000000..ea4a006 --- /dev/null +++ b/src/clarktown/renderers/link_and_image.clj @@ -0,0 +1,27 @@ +(ns clarktown.renderers.link-and-image + (:require + [clojure.string :as string])) + + +(defn encode-href + [href] + (-> href + (string/replace "_" "_"))) + + +(defn render + "Renders all occurring links and images." + [block _] + (loop [block block + matches (-> (re-seq #"\!?\[([a-zA-Z0-9\-\.\,]+( [a-zA-Z0-9\-\.\,]+)*)\]\((.*?)\)" block) + distinct)] + (if (empty? matches) + block + (let [[whole-match label _ href] (first matches) + image? (string/starts-with? whole-match "!") + image (str "\""") + link (str "" label "")] + (recur (if image? + (string/replace block whole-match image) + (string/replace block whole-match link)) + (drop 1 matches)))))) \ No newline at end of file diff --git a/src/clarktown/renderers/list_block.clj b/src/clarktown/renderers/list_block.clj new file mode 100644 index 0000000..2a40b06 --- /dev/null +++ b/src/clarktown/renderers/list_block.clj @@ -0,0 +1,121 @@ +(ns clarktown.renderers.list-block + (:require + [clojure.string :as string])) + + +(defn string->indent-n + "Returns the indentation count from left of `str`, which must be + in spaces and not tabs." + [str] + (count (take-while #{\space} str))) + + +(defn compose-items-with-indent-guides + "Composes a vector of maps from given `block` that adds a unique + ID to each line as well as its `indent-n` which is used later + on to determine hierarchies. " + [block] + (->> (string/split-lines block) + (mapv + (fn [line] + {:id (random-uuid) + :indent-n (string->indent-n line) + :value (-> line + string/trim)})))) + + +(defn find-parent-id + "Assuming a 1-level `items`, will attempt to find the parent `id` + of the item at given `index`. Will return `nil` otherwise." + [items index] + (let [indent-n-at-index (:indent-n (nth items index))] + (-> (->> (split-at index items) + first + reverse + (remove #(or (> (:indent-n %) indent-n-at-index) + (= (:indent-n %) indent-n-at-index))) + first + :id)))) + + +(defn compose-items-with-parents + "Composes a 1-level list of items from `block` and adds parent + information to each if they belong to another item. The result + of this is used to build the final data tree." + [block] + (let [items (compose-items-with-indent-guides block)] + (->> items + (map-indexed + (fn [index line] + (merge line {:parent (find-parent-id items index)})))))) + + +(defn add-to-parent + "Recursively scans `items`, which can be multiple levels deep, + and tries to find a home for `item` according to its parent ID." + [items item] + (->> items + (mapv + (fn [i] + (if (= (:id i) (:parent item)) + (if (:items i) + (assoc i :items (concat (:items i) [item])) + (assoc i :items [item])) + (if (:items i) + (assoc i :items (add-to-parent (:items i) item)) + i)))))) + + +(defn compose-item-tree + "Given a `block`, composes a data representation of it based on + the indentation of each line." + [block] + (loop [result [] + items (compose-items-with-parents block)] + (if (empty? items) + result + (let [item (first items) + parent (:parent item) + new-item {:id (:id item) + :value (:value item)}] + (recur (if parent + (add-to-parent result item) + (concat result [new-item])) + (drop 1 items)))))) + + +(defn render-items + "Renders an ordered/un-ordered list hierarchy from given `items`." + [items] + (loop [result "" + inner-items items] + (if (empty? inner-items) + (if (or (string/starts-with? (:value (first items)) "*") + (string/starts-with? (:value (first items)) "-")) + (str "") + (str "
      " result "
    ")) + (let [inner-item (first inner-items) + value (cond + ; * unordered list + (string/starts-with? (:value inner-item) "*") + (-> (string/replace-first (:value inner-item) "*" "") + string/trim) + ; - unordered list + (string/starts-with? (:value inner-item) "-") + (-> (string/replace-first (:value inner-item) "-" "") + string/trim) + ; ordered list + :else + (-> (string/replace-first (:value inner-item) #"\d\." "") + string/trim))] + (recur (if (:items inner-item) + (str result "
  • " value (render-items (:items inner-item)) "
  • ") + (str result "
  • " value "
  • ")) + (drop 1 inner-items)))))) + + +(defn render + "Renders the list block" + [block _] + (-> (compose-item-tree block) + (render-items))) \ No newline at end of file diff --git a/src/clarktown/renderers/paragraph_block.clj b/src/clarktown/renderers/paragraph_block.clj new file mode 100644 index 0000000..c7bec22 --- /dev/null +++ b/src/clarktown/renderers/paragraph_block.clj @@ -0,0 +1,9 @@ +(ns clarktown.renderers.paragraph-block + (:require + [clojure.string :as string])) + + +(defn render + "Renders the paragraph block." + [block _] + (str "

    " (string/trim block) "

    ")) diff --git a/src/clarktown/renderers/quote_block.clj b/src/clarktown/renderers/quote_block.clj new file mode 100644 index 0000000..ee30635 --- /dev/null +++ b/src/clarktown/renderers/quote_block.clj @@ -0,0 +1,16 @@ +(ns clarktown.renderers.quote-block + (:require + [clojure.string :as string] + [clarktown.parser :as parser])) + + +(defn render + "Renders a quote block." + [block parsers] + (let [matches (re-seq #">.*" block) + blocks (->> (for [match matches] + (-> (subs match 1) + string/trim)) + (string/join "\n")) + block (parser/parse blocks parsers)] + (str "
    " block "
    "))) diff --git a/src/clarktown/renderers/strikethrough.clj b/src/clarktown/renderers/strikethrough.clj new file mode 100644 index 0000000..8e124a0 --- /dev/null +++ b/src/clarktown/renderers/strikethrough.clj @@ -0,0 +1,18 @@ +(ns clarktown.renderers.strikethrough + (:require + [clojure.string :as string])) + + +(defn render + "Renders all occurring strikethrough text." + [block _] + (loop [block block + matches (-> (re-seq #"~~.*?~~" block) + distinct)] + (if (empty? matches) + block + (let [match (first matches) + value (subs match 2 (- (count match) 2)) + replacement (str "" value "")] + (recur (string/replace block match replacement) + (drop 1 matches)))))) diff --git a/test/clarktown/matchers/empty_block_test.clj b/test/clarktown/matchers/empty_block_test.clj new file mode 100644 index 0000000..8fe83e4 --- /dev/null +++ b/test/clarktown/matchers/empty_block_test.clj @@ -0,0 +1,9 @@ +(ns clarktown.matchers.empty-block-test + (:require + [clojure.test :refer [deftest testing is]] + [clarktown.matchers.empty-block :as empty-block])) + +(deftest empty-block-matcher-test + (testing "Checking an empty block" + (is (true? (empty-block/match? ""))) + (is (true? (empty-block/match? " "))))) \ No newline at end of file diff --git a/test/clarktown/matchers/horizontal_line_block_test.clj b/test/clarktown/matchers/horizontal_line_block_test.clj new file mode 100644 index 0000000..c3402d2 --- /dev/null +++ b/test/clarktown/matchers/horizontal_line_block_test.clj @@ -0,0 +1,14 @@ +(ns clarktown.matchers.horizontal-line-block-test + (:require + [clojure.test :refer [deftest testing is]] + [clarktown.matchers.horizontal-line-block :as horizontal-line-block])) + + +(deftest horizontal-line-block-matcher-test + (testing "Is a horizontal line block" + (is (true? (horizontal-line-block/match? "***"))) + (is (true? (horizontal-line-block/match? " ***"))) + (is (false? (horizontal-line-block/match? "Test *** 123"))) + (is (true? (horizontal-line-block/match? "---"))) + (is (true? (horizontal-line-block/match? " ---"))) + (is (false? (horizontal-line-block/match? "Test --- 123"))))) \ No newline at end of file diff --git a/test/clarktown/matchers/quote_block_test.clj b/test/clarktown/matchers/quote_block_test.clj new file mode 100644 index 0000000..05288d6 --- /dev/null +++ b/test/clarktown/matchers/quote_block_test.clj @@ -0,0 +1,11 @@ +(ns clarktown.matchers.quote-block-test + (:require + [clojure.test :refer [deftest testing is]] + [clarktown.matchers.quote-block :as quote-block])) + + +(deftest quote-block-block-matcher-test + (testing "Checking a quote block" + (is (true? (quote-block/match? "> Test"))) + (is (true? (quote-block/match? " > Test"))) + (is (true? (quote-block/match? ">"))))) \ No newline at end of file diff --git a/test/clarktown/parsers/bold_test.clj b/test/clarktown/parsers/bold_test.clj deleted file mode 100644 index a082d41..0000000 --- a/test/clarktown/parsers/bold_test.clj +++ /dev/null @@ -1,18 +0,0 @@ -(ns clarktown.parsers.bold-test - (:require - [clojure.test :refer [deftest testing is]] - [clarktown.parsers.bold :as bold])) - - -(deftest bold-test - (testing "Creating bold text with two surrounding asterisk characters" - (is (= "This is bold." - (bold/render "**This is bold.**" nil)))) - - (testing "Creating bold text with two surrounding underscore characters" - (is (= "This is bold." - (bold/render "__This is bold.__" nil)))) - - (testing "Creating bold text with both underscores and asterisks mixed" - (is (= "Hi, my name is John, what is your name?" - (bold/render "Hi, my name is **John**, what is __your name?__" nil))))) \ No newline at end of file diff --git a/test/clarktown/parsers/code_block_test.clj b/test/clarktown/parsers/code_block_test.clj deleted file mode 100644 index 8b1113d..0000000 --- a/test/clarktown/parsers/code_block_test.clj +++ /dev/null @@ -1,15 +0,0 @@ -(ns clarktown.parsers.code-block-test - (:require - [clojure.test :refer [deftest testing is]] - [clojure.java.io :as io] - [clarktown.parsers.code-block :as code-block])) - - -(deftest code-block-test - (testing "Code block with language specification" - (is (= (slurp (io/file (io/resource "test/parsers/code_block_result.html"))) - (code-block/render (slurp (io/file (io/resource "test/parsers/code_block.md"))) nil)))) - - (testing "Code block with NO language specification" - (is (= (slurp (io/file (io/resource "test/parsers/code_block_no_language_result.html"))) - (code-block/render (slurp (io/file (io/resource "test/parsers/code_block_no_language.md"))) nil))))) \ No newline at end of file diff --git a/test/clarktown/parsers/empty_block_test.clj b/test/clarktown/parsers/empty_block_test.clj deleted file mode 100644 index a8d89c4..0000000 --- a/test/clarktown/parsers/empty_block_test.clj +++ /dev/null @@ -1,14 +0,0 @@ -(ns clarktown.parsers.empty-block-test - (:require - [clojure.test :refer [deftest testing is]] - [clarktown.parsers.empty-block :as empty-block])) - - -(deftest empty-block-test - (testing "Rendering an empty block" - (is (= (empty-block/render "" nil) - ""))) - - (testing "Checking an empty block" - (is (true? (empty-block/is? ""))) - (is (true? (empty-block/is? " "))))) diff --git a/test/clarktown/parsers/heading_block_test.clj b/test/clarktown/parsers/heading_block_test.clj deleted file mode 100644 index 9bfff4f..0000000 --- a/test/clarktown/parsers/heading_block_test.clj +++ /dev/null @@ -1,44 +0,0 @@ -(ns clarktown.parsers.heading-block-test - (:require - [clojure.test :refer [deftest testing is]] - [clarktown.parsers.heading-block :as heading-block])) - - -(deftest hashbang-heading-test - (testing "Hashbang heading block that's a H1" - (is (= "

    This is a heading block.

    " - (heading-block/render "# This is a heading block." nil)))) - - (testing "Hashbang heading block that's a H2" - (is (= "

    This is a heading block.

    " - (heading-block/render "## This is a heading block." nil)))) - - (testing "Hashbang heading block that's a H3" - (is (= "

    This is a heading block.

    " - (heading-block/render "### This is a heading block." nil)))) - - (testing "Hashbang heading block that's a H4" - (is (= "

    This is a heading block.

    " - (heading-block/render "#### This is a heading block." nil)))) - - (testing "Hashbang heading block that's a H5" - (is (= "
    This is a heading block.
    " - (heading-block/render "##### This is a heading block." nil))))) - - -(deftest settext-heading-text - (testing "Settext heading block that's a H1" - (is (= "

    This is a heading block.

    " - (heading-block/render "This is a heading block.\n=========" nil)))) - - (testing "Settext heading block that's a H1 spanning multiple lines" - (is (= "

    This is a \nheading block spanning multiple lines.

    " - (heading-block/render "This is a \nheading block spanning multiple lines.\n========" nil)))) - - (testing "Settext heading block that's a H2" - (is (= "

    This is a heading block.

    " - (heading-block/render "This is a heading block.\n---------" nil)))) - - (testing "Settext heading block that's a H2 spanning multiple lines" - (is (= "

    This is a \nheading block spanning multiple lines.

    " - (heading-block/render "This is a \nheading block spanning multiple lines.\n--------" nil))))) \ No newline at end of file diff --git a/test/clarktown/parsers/horizontal_line_block_test.clj b/test/clarktown/parsers/horizontal_line_block_test.clj deleted file mode 100644 index 21617b6..0000000 --- a/test/clarktown/parsers/horizontal_line_block_test.clj +++ /dev/null @@ -1,21 +0,0 @@ -(ns clarktown.parsers.horizontal-line-block-test - (:require - [clojure.test :refer [deftest testing is]] - [clarktown.parsers.horizontal-line-block :as horizontal-line-block])) - - -(deftest horizontal-line-block-test - (testing "Creating a horizontal line" - (is (= "
    " - (horizontal-line-block/render "***" nil))) - - (is (= "
    " - (horizontal-line-block/render "---" nil)))) - - (testing "Is a horizontal line block" - (is (true? (horizontal-line-block/is? "***"))) - (is (true? (horizontal-line-block/is? " ***"))) - (is (false? (horizontal-line-block/is? "Test *** 123"))) - (is (true? (horizontal-line-block/is? "---"))) - (is (true? (horizontal-line-block/is? " ---"))) - (is (false? (horizontal-line-block/is? "Test --- 123"))))) \ No newline at end of file diff --git a/test/clarktown/parsers/inline_code_test.clj b/test/clarktown/parsers/inline_code_test.clj deleted file mode 100644 index 028c4b7..0000000 --- a/test/clarktown/parsers/inline_code_test.clj +++ /dev/null @@ -1,14 +0,0 @@ -(ns clarktown.parsers.inline-code-test - (:require - [clojure.test :refer [deftest testing is]] - [clarktown.parsers.inline-code :as inline-code])) - - -(deftest inline-code-test - (testing "Creating inline code text" - (is (= "This is inline code." - (inline-code/render "`This is inline code.`" nil)))) - - (testing "Creating inline-code text in the middle of regular text" - (is (= "This is regular text, mixed with some inline code., and it's great." - (inline-code/render "This is regular text, mixed with `some inline code.`, and it's great." nil))))) \ No newline at end of file diff --git a/test/clarktown/parsers/italic_test.clj b/test/clarktown/parsers/italic_test.clj deleted file mode 100644 index 8ab1369..0000000 --- a/test/clarktown/parsers/italic_test.clj +++ /dev/null @@ -1,18 +0,0 @@ -(ns clarktown.parsers.italic-test - (:require - [clojure.test :refer [deftest testing is]] - [clarktown.parsers.italic :as italic])) - - -(deftest italic-test - (testing "Creating italic text with one surrounding asterisk character" - (is (= "This is italic." - (italic/render "*This is italic.*" nil)))) - - (testing "Creating italic text with one surrounding underscore character" - (is (= "This is italic." - (italic/render "_This is italic._" nil)))) - - (testing "Creating italic text with both underscores and asterisks mixed" - (is (= "Hi, my name is John, what is your name?" - (italic/render "Hi, my name is *John*, what is _your name?_" nil))))) \ No newline at end of file diff --git a/test/clarktown/parsers/link_and_image_test.clj b/test/clarktown/parsers/link_and_image_test.clj deleted file mode 100644 index 348a8f9..0000000 --- a/test/clarktown/parsers/link_and_image_test.clj +++ /dev/null @@ -1,23 +0,0 @@ -(ns clarktown.parsers.link-and-image-test - (:require - [clojure.test :refer [deftest testing is]] - [clarktown.parsers.link-and-image :as link-and-image])) - - -(deftest link-test - (testing "Creating a link" - (is (= (link-and-image/render "[This is a link](https://example.com)" nil) - "This is a link")) - - (is (= (link-and-image/render "[This-is-a-link](https://example.com)" nil) - "This-is-a-link")) - - (is (= (link-and-image/render "[x] [label](link)" nil) - "[x] label")) - - (is (= (link-and-image/render "[ ] [label](link)" nil) - "[ ] label"))) - - (testing "Creating an image" - (is (= (link-and-image/render "![This is an image](https://example.com)" nil) - "\"This")))) \ No newline at end of file diff --git a/test/clarktown/parsers/quote_block_test.clj b/test/clarktown/parsers/quote_block_test.clj deleted file mode 100644 index 94553cf..0000000 --- a/test/clarktown/parsers/quote_block_test.clj +++ /dev/null @@ -1,15 +0,0 @@ -(ns clarktown.parsers.quote-block-test - (:require - [clojure.test :refer [deftest testing is]] - [clarktown.parsers.quote-block :as quote-block])) - - -(deftest quote-block-block-test - (testing "Creating a quote block line" - (is (= (quote-block/render "> First line\n> second line" nil) - "
    First line\nsecond line
    "))) - - (testing "Checking a quote block" - (is (true? (quote-block/is? "> Test"))) - (is (true? (quote-block/is? " > Test"))) - (is (true? (quote-block/is? ">"))))) \ No newline at end of file diff --git a/test/clarktown/parsers/strikethrough_test.clj b/test/clarktown/parsers/strikethrough_test.clj deleted file mode 100644 index fdf6188..0000000 --- a/test/clarktown/parsers/strikethrough_test.clj +++ /dev/null @@ -1,14 +0,0 @@ -(ns clarktown.parsers.strikethrough-test - (:require - [clojure.test :refer [deftest testing is]] - [clarktown.parsers.strikethrough :as strikethrough])) - - -(deftest strikethrough-test - (testing "Creating strikethrough text" - (is (= (strikethrough/render "~~This is strikethrough text.~~" nil) - "This is strikethrough text."))) - - (testing "Creating strikethrough text mixed with regular text" - (is (= (strikethrough/render "Some other text, ~~This is strikethrough text.~~ And more text." nil) - "Some other text, This is strikethrough text. And more text.")))) \ No newline at end of file diff --git a/test/clarktown/renderers/bold_test.clj b/test/clarktown/renderers/bold_test.clj new file mode 100644 index 0000000..fba0ea6 --- /dev/null +++ b/test/clarktown/renderers/bold_test.clj @@ -0,0 +1,18 @@ +(ns clarktown.renderers.bold-test + (:require + [clojure.test :refer [deftest testing is]] + [clarktown.renderers.bold :as bold])) + + +(deftest bold-renderer-test + (testing "Creating bold text with two surrounding asterisk characters" + (is (= "This is bold." + (bold/render "**This is bold.**" nil)))) + + (testing "Creating bold text with two surrounding underscore characters" + (is (= "This is bold." + (bold/render "__This is bold.__" nil)))) + + (testing "Creating bold text with both underscores and asterisks mixed" + (is (= "Hi, my name is John, what is your name?" + (bold/render "Hi, my name is **John**, what is __your name?__" nil))))) \ No newline at end of file diff --git a/test/clarktown/renderers/code_block_test.clj b/test/clarktown/renderers/code_block_test.clj new file mode 100644 index 0000000..37c701b --- /dev/null +++ b/test/clarktown/renderers/code_block_test.clj @@ -0,0 +1,15 @@ +(ns clarktown.renderers.code-block-test + (:require + [clojure.test :refer [deftest testing is]] + [clojure.java.io :as io] + [clarktown.renderers.code-block :as code-block])) + + +(deftest code-block-renderer-test + (testing "Code block with language specification" + (is (= (slurp (io/file (io/resource "test/parsers/code_block_result.html"))) + (code-block/render (slurp (io/file (io/resource "test/parsers/code_block.md"))) nil)))) + + (testing "Code block with NO language specification" + (is (= (slurp (io/file (io/resource "test/parsers/code_block_no_language_result.html"))) + (code-block/render (slurp (io/file (io/resource "test/parsers/code_block_no_language.md"))) nil))))) \ No newline at end of file diff --git a/test/clarktown/renderers/empty_block_test.clj b/test/clarktown/renderers/empty_block_test.clj new file mode 100644 index 0000000..35fb902 --- /dev/null +++ b/test/clarktown/renderers/empty_block_test.clj @@ -0,0 +1,10 @@ +(ns clarktown.renderers.empty-block-test + (:require + [clojure.test :refer [deftest testing is]] + [clarktown.renderers.empty-block :as empty-block])) + + +(deftest empty-block-renderer-test + (testing "Rendering an empty block" + (is (= (empty-block/render "" nil) + "")))) diff --git a/test/clarktown/renderers/heading_block_test.clj b/test/clarktown/renderers/heading_block_test.clj new file mode 100644 index 0000000..9c3386f --- /dev/null +++ b/test/clarktown/renderers/heading_block_test.clj @@ -0,0 +1,44 @@ +(ns clarktown.renderers.heading-block-test + (:require + [clojure.test :refer [deftest testing is]] + [clarktown.renderers.heading-block :as heading-block])) + + +(deftest atx-heading-renderer-test + (testing "Hashbang heading block that's a H1" + (is (= "

    This is a heading block.

    " + (heading-block/render "# This is a heading block." nil)))) + + (testing "Hashbang heading block that's a H2" + (is (= "

    This is a heading block.

    " + (heading-block/render "## This is a heading block." nil)))) + + (testing "Hashbang heading block that's a H3" + (is (= "

    This is a heading block.

    " + (heading-block/render "### This is a heading block." nil)))) + + (testing "Hashbang heading block that's a H4" + (is (= "

    This is a heading block.

    " + (heading-block/render "#### This is a heading block." nil)))) + + (testing "Hashbang heading block that's a H5" + (is (= "
    This is a heading block.
    " + (heading-block/render "##### This is a heading block." nil))))) + + +(deftest settext-heading-renderer-text + (testing "Settext heading block that's a H1" + (is (= "

    This is a heading block.

    " + (heading-block/render "This is a heading block.\n=========" nil)))) + + (testing "Settext heading block that's a H1 spanning multiple lines" + (is (= "

    This is a \nheading block spanning multiple lines.

    " + (heading-block/render "This is a \nheading block spanning multiple lines.\n========" nil)))) + + (testing "Settext heading block that's a H2" + (is (= "

    This is a heading block.

    " + (heading-block/render "This is a heading block.\n---------" nil)))) + + (testing "Settext heading block that's a H2 spanning multiple lines" + (is (= "

    This is a \nheading block spanning multiple lines.

    " + (heading-block/render "This is a \nheading block spanning multiple lines.\n--------" nil))))) \ No newline at end of file diff --git a/test/clarktown/renderers/horizontal_line_block_test.clj b/test/clarktown/renderers/horizontal_line_block_test.clj new file mode 100644 index 0000000..db72682 --- /dev/null +++ b/test/clarktown/renderers/horizontal_line_block_test.clj @@ -0,0 +1,13 @@ +(ns clarktown.renderers.horizontal-line-block-test + (:require + [clojure.test :refer [deftest testing is]] + [clarktown.renderers.horizontal-line-block :as horizontal-line-block])) + + +(deftest horizontal-line-block-renderer-test + (testing "Creating a horizontal line" + (is (= (horizontal-line-block/render "***" nil) + "
    ")) + + (is (= (horizontal-line-block/render "---" nil) + "
    ")))) diff --git a/test/clarktown/renderers/inline_code_test.clj b/test/clarktown/renderers/inline_code_test.clj new file mode 100644 index 0000000..2071b7f --- /dev/null +++ b/test/clarktown/renderers/inline_code_test.clj @@ -0,0 +1,14 @@ +(ns clarktown.renderers.inline-code-test + (:require + [clojure.test :refer [deftest testing is]] + [clarktown.renderers.inline-code :as inline-code])) + + +(deftest inline-code-renderer-test + (testing "Creating inline code text" + (is (= "This is inline code." + (inline-code/render "`This is inline code.`" nil)))) + + (testing "Creating inline-code text in the middle of regular text" + (is (= "This is regular text, mixed with some inline code., and it's great." + (inline-code/render "This is regular text, mixed with `some inline code.`, and it's great." nil))))) \ No newline at end of file diff --git a/test/clarktown/renderers/italic_test.clj b/test/clarktown/renderers/italic_test.clj new file mode 100644 index 0000000..29e7811 --- /dev/null +++ b/test/clarktown/renderers/italic_test.clj @@ -0,0 +1,18 @@ +(ns clarktown.renderers.italic-test + (:require + [clojure.test :refer [deftest testing is]] + [clarktown.renderers.italic :as italic])) + + +(deftest italic-renderer-test + (testing "Creating italic text with one surrounding asterisk character" + (is (= "This is italic." + (italic/render "*This is italic.*" nil)))) + + (testing "Creating italic text with one surrounding underscore character" + (is (= "This is italic." + (italic/render "_This is italic._" nil)))) + + (testing "Creating italic text with both underscores and asterisks mixed" + (is (= "Hi, my name is John, what is your name?" + (italic/render "Hi, my name is *John*, what is _your name?_" nil))))) \ No newline at end of file diff --git a/test/clarktown/renderers/link_and_image_test.clj b/test/clarktown/renderers/link_and_image_test.clj new file mode 100644 index 0000000..aa821e3 --- /dev/null +++ b/test/clarktown/renderers/link_and_image_test.clj @@ -0,0 +1,23 @@ +(ns clarktown.renderers.link-and-image-test + (:require + [clojure.test :refer [deftest testing is]] + [clarktown.renderers.link-and-image :as link-and-image])) + + +(deftest link-renderer-test + (testing "Creating a link" + (is (= (link-and-image/render "[This is a link](https://example.com)" nil) + "This is a link")) + + (is (= (link-and-image/render "[This-is-a-link](https://example.com)" nil) + "This-is-a-link")) + + (is (= (link-and-image/render "[x] [label](link)" nil) + "[x] label")) + + (is (= (link-and-image/render "[ ] [label](link)" nil) + "[ ] label"))) + + (testing "Creating an image" + (is (= (link-and-image/render "![This is an image](https://example.com)" nil) + "\"This")))) \ No newline at end of file diff --git a/test/clarktown/renderers/quote_block_test.clj b/test/clarktown/renderers/quote_block_test.clj new file mode 100644 index 0000000..33a7495 --- /dev/null +++ b/test/clarktown/renderers/quote_block_test.clj @@ -0,0 +1,10 @@ +(ns clarktown.renderers.quote-block-test + (:require + [clojure.test :refer [deftest testing is]] + [clarktown.renderers.quote-block :as quote-block])) + + +(deftest quote-block-block-renderer-test + (testing "Creating a quote block line" + (is (= (quote-block/render "> First line\n> second line" nil) + "
    First line\nsecond line
    ")))) \ No newline at end of file diff --git a/test/clarktown/renderers/strikethrough_test.clj b/test/clarktown/renderers/strikethrough_test.clj new file mode 100644 index 0000000..55493e0 --- /dev/null +++ b/test/clarktown/renderers/strikethrough_test.clj @@ -0,0 +1,14 @@ +(ns clarktown.renderers.strikethrough-test + (:require + [clojure.test :refer [deftest testing is]] + [clarktown.renderers.strikethrough :as strikethrough])) + + +(deftest strikethrough-renderer-test + (testing "Creating strikethrough text" + (is (= (strikethrough/render "~~This is strikethrough text.~~" nil) + "This is strikethrough text."))) + + (testing "Creating strikethrough text mixed with regular text" + (is (= (strikethrough/render "Some other text, ~~This is strikethrough text.~~ And more text." nil) + "Some other text, This is strikethrough text. And more text.")))) \ No newline at end of file -- cgit v1.2.3