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 From a178a0b1867cb5194a1a2cd4f6e01ac261bfa4b7 Mon Sep 17 00:00:00 2001 From: Asko Nõmm Date: Thu, 21 Apr 2022 01:05:58 +0300 Subject: Use "default" prefix for these --- src/clarktown/correctors.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/clarktown/correctors.clj b/src/clarktown/correctors.clj index ba49879..d97defa 100644 --- a/src/clarktown/correctors.clj +++ b/src/clarktown/correctors.clj @@ -4,7 +4,7 @@ [clarktown.correctors.atx-heading-block :as atx-heading-block])) -(def block-separation-correctors +(def default-block-separation-correctors {:empty-line-above? [code-block/empty-line-above? atx-heading-block/empty-line-above?] @@ -14,4 +14,4 @@ (def default-correctors - {:block-separations block-separation-correctors}) \ No newline at end of file + {:block-separations default-block-separation-correctors}) \ No newline at end of file -- cgit v1.2.3 From 04821d8be5d773153718948454c864495704f67b Mon Sep 17 00:00:00 2001 From: Asko Nõmm Date: Thu, 21 Apr 2022 19:13:02 +0300 Subject: Send correctors to each render as well --- src/clarktown/core.clj | 143 +-------------------- src/clarktown/correctors.clj | 8 +- src/clarktown/engine.clj | 140 ++++++++++++++++++++ src/clarktown/renderers/bold.clj | 2 +- src/clarktown/renderers/code_block.clj | 2 +- src/clarktown/renderers/empty_block.clj | 2 +- src/clarktown/renderers/heading_block.clj | 2 +- src/clarktown/renderers/horizontal_line_block.clj | 2 +- src/clarktown/renderers/inline_code.clj | 2 +- src/clarktown/renderers/italic.clj | 2 +- src/clarktown/renderers/link_and_image.clj | 2 +- src/clarktown/renderers/list_block.clj | 4 +- src/clarktown/renderers/paragraph_block.clj | 2 +- src/clarktown/renderers/quote_block.clj | 6 +- src/clarktown/renderers/strikethrough.clj | 2 +- test/clarktown/renderers/bold_test.clj | 6 +- test/clarktown/renderers/code_block_test.clj | 4 +- test/clarktown/renderers/empty_block_test.clj | 2 +- test/clarktown/renderers/heading_block_test.clj | 18 +-- .../renderers/horizontal_line_block_test.clj | 4 +- test/clarktown/renderers/inline_code_test.clj | 4 +- test/clarktown/renderers/italic_test.clj | 6 +- test/clarktown/renderers/link_and_image_test.clj | 10 +- test/clarktown/renderers/quote_block_test.clj | 2 +- test/clarktown/renderers/strikethrough_test.clj | 4 +- 25 files changed, 194 insertions(+), 187 deletions(-) create mode 100644 src/clarktown/engine.clj diff --git a/src/clarktown/core.clj b/src/clarktown/core.clj index cabd6e6..a624275 100644 --- a/src/clarktown/core.clj +++ b/src/clarktown/core.clj @@ -1,147 +1,10 @@ (ns clarktown.core (:require - [clojure.string :as string] + [clarktown.engine :as engine] [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 "Renders the given `markdown` into a consumable HTML form. Optionally, a second argument can be passed that is made out of a vector of parsers. @@ -155,7 +18,7 @@ argument, which is a given Markdown block. An example parser: - ``` + ```clojure {:matcher (fn [block] ...) :renderers [(fn [block] ...) (fn [block] ...)]} ```" @@ -164,4 +27,4 @@ ([markdown given-parsers] (render markdown given-parsers correctors/default-correctors)) ([markdown given-parsers given-correctors] - (parse markdown given-parsers given-correctors))) \ No newline at end of file + (engine/render markdown given-parsers given-correctors))) \ No newline at end of file diff --git a/src/clarktown/correctors.clj b/src/clarktown/correctors.clj index d97defa..14d82ca 100644 --- a/src/clarktown/correctors.clj +++ b/src/clarktown/correctors.clj @@ -4,7 +4,9 @@ [clarktown.correctors.atx-heading-block :as atx-heading-block])) -(def default-block-separation-correctors +(def + ^{:doc "The default block separation correctors."} + default-block-separation-correctors {:empty-line-above? [code-block/empty-line-above? atx-heading-block/empty-line-above?] @@ -13,5 +15,7 @@ atx-heading-block/empty-line-below?]}) -(def default-correctors +(def + ^{:doc "The default correctors."} + default-correctors {:block-separations default-block-separation-correctors}) \ No newline at end of file diff --git a/src/clarktown/engine.clj b/src/clarktown/engine.clj new file mode 100644 index 0000000..17e5867 --- /dev/null +++ b/src/clarktown/engine.clj @@ -0,0 +1,140 @@ +(ns clarktown.engine + (: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- 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 given-parsers given-correctors block] + (loop [block block + renderers (:renderers parser)] + (if (empty? renderers) + block + (let [renderer (first renderers)] + (recur (renderer block given-parsers given-correctors) + (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." + [given-parsers given-correctors block] + (loop [block block + parsers (filter #(= nil (:matcher %)) given-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 given-correctors) + (drop 1 renderers))))) + (drop 1 parsers))))) + + +(defn- parse-blocks + "Parses each individual Markdown block, given as `blocks`, with + the list of `parsers`." + [blocks given-parsers given-correctors] + (for [block blocks] + (if-let [parser (find-parser-by-block given-parsers block)] + (->> (string/trim block) + (parse-block-with-known-parser parser given-parsers given-correctors)) + (->> (string/trim block) + (parse-block-with-unknown-parsers given-parsers given-correctors))))) + + +(defn render + "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 given-correctors)] + (string/join "\n\n" parsed-blocks))) \ No newline at end of file diff --git a/src/clarktown/renderers/bold.clj b/src/clarktown/renderers/bold.clj index 1ce7f84..64d6137 100644 --- a/src/clarktown/renderers/bold.clj +++ b/src/clarktown/renderers/bold.clj @@ -5,7 +5,7 @@ (defn render "Renders all occurring bold text as bold." - [block _] + [block _ _] (loop [block block matches (-> (re-seq #"(\*{2}|\_{2})[^\*|\_](.*?)[^\*|\_](\*{2}|\_{2})" block) distinct)] diff --git a/src/clarktown/renderers/code_block.clj b/src/clarktown/renderers/code_block.clj index 2bed4e6..184e90e 100644 --- a/src/clarktown/renderers/code_block.clj +++ b/src/clarktown/renderers/code_block.clj @@ -5,7 +5,7 @@ (defn render "Renders the code block." - [block _] + [block _ _] (let [language (->> block (re-find #"\`\`\`(\w+)") second) diff --git a/src/clarktown/renderers/empty_block.clj b/src/clarktown/renderers/empty_block.clj index 66e819e..84df1fb 100644 --- a/src/clarktown/renderers/empty_block.clj +++ b/src/clarktown/renderers/empty_block.clj @@ -3,5 +3,5 @@ (defn render "Renders an empty block." - [_ _] + [_ _ _] "") diff --git a/src/clarktown/renderers/heading_block.clj b/src/clarktown/renderers/heading_block.clj index f953d0a..4da9bda 100644 --- a/src/clarktown/renderers/heading_block.clj +++ b/src/clarktown/renderers/heading_block.clj @@ -38,7 +38,7 @@ (defn render "Renders the heading block." - [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 index f141e5f..14e5d8a 100644 --- a/src/clarktown/renderers/horizontal_line_block.clj +++ b/src/clarktown/renderers/horizontal_line_block.clj @@ -3,5 +3,5 @@ (defn render "Renders the horizontal line block." - [_ _] + [_ _ _] "
    ") diff --git a/src/clarktown/renderers/inline_code.clj b/src/clarktown/renderers/inline_code.clj index 29593a8..e8c298f 100644 --- a/src/clarktown/renderers/inline_code.clj +++ b/src/clarktown/renderers/inline_code.clj @@ -5,7 +5,7 @@ (defn render "Renders all occurring inline code." - [block _] + [block _ _] (loop [block block matches (-> (re-seq #"\`.*?\`" block) distinct)] diff --git a/src/clarktown/renderers/italic.clj b/src/clarktown/renderers/italic.clj index a1568f6..970364e 100644 --- a/src/clarktown/renderers/italic.clj +++ b/src/clarktown/renderers/italic.clj @@ -5,7 +5,7 @@ (defn render "Renders all occurring italic text as italic." - [block _] + [block _ _] (loop [block block matches (-> (re-seq #"(\*{1,}?|\_{1,}?)(.*?)(\*{1,}?|\_{1,}?)" block) distinct)] diff --git a/src/clarktown/renderers/link_and_image.clj b/src/clarktown/renderers/link_and_image.clj index ea4a006..e61503e 100644 --- a/src/clarktown/renderers/link_and_image.clj +++ b/src/clarktown/renderers/link_and_image.clj @@ -11,7 +11,7 @@ (defn render "Renders all occurring links and images." - [block _] + [block _ _] (loop [block block matches (-> (re-seq #"\!?\[([a-zA-Z0-9\-\.\,]+( [a-zA-Z0-9\-\.\,]+)*)\]\((.*?)\)" block) distinct)] diff --git a/src/clarktown/renderers/list_block.clj b/src/clarktown/renderers/list_block.clj index 2a40b06..27ca72a 100644 --- a/src/clarktown/renderers/list_block.clj +++ b/src/clarktown/renderers/list_block.clj @@ -116,6 +116,6 @@ (defn render "Renders the list block" - [block _] + [block _ _] (-> (compose-item-tree block) - (render-items))) \ No newline at end of file + (render-items))) diff --git a/src/clarktown/renderers/paragraph_block.clj b/src/clarktown/renderers/paragraph_block.clj index c7bec22..0ab3788 100644 --- a/src/clarktown/renderers/paragraph_block.clj +++ b/src/clarktown/renderers/paragraph_block.clj @@ -5,5 +5,5 @@ (defn render "Renders the paragraph block." - [block _] + [block _ _] (str "

    " (string/trim block) "

    ")) diff --git a/src/clarktown/renderers/quote_block.clj b/src/clarktown/renderers/quote_block.clj index ee30635..1a302f9 100644 --- a/src/clarktown/renderers/quote_block.clj +++ b/src/clarktown/renderers/quote_block.clj @@ -1,16 +1,16 @@ (ns clarktown.renderers.quote-block (:require [clojure.string :as string] - [clarktown.parser :as parser])) + [clarktown.engine :as engine])) (defn render "Renders a quote block." - [block parsers] + [block parsers correctors] (let [matches (re-seq #">.*" block) blocks (->> (for [match matches] (-> (subs match 1) string/trim)) (string/join "\n")) - block (parser/parse blocks parsers)] + block (engine/render blocks parsers correctors)] (str "
    " block "
    "))) diff --git a/src/clarktown/renderers/strikethrough.clj b/src/clarktown/renderers/strikethrough.clj index 8e124a0..133be47 100644 --- a/src/clarktown/renderers/strikethrough.clj +++ b/src/clarktown/renderers/strikethrough.clj @@ -5,7 +5,7 @@ (defn render "Renders all occurring strikethrough text." - [block _] + [block _ _] (loop [block block matches (-> (re-seq #"~~.*?~~" block) distinct)] diff --git a/test/clarktown/renderers/bold_test.clj b/test/clarktown/renderers/bold_test.clj index fba0ea6..28d9da8 100644 --- a/test/clarktown/renderers/bold_test.clj +++ b/test/clarktown/renderers/bold_test.clj @@ -7,12 +7,12 @@ (deftest bold-renderer-test (testing "Creating bold text with two surrounding asterisk characters" (is (= "This is bold." - (bold/render "**This is bold.**" nil)))) + (bold/render "**This is bold.**" nil nil)))) (testing "Creating bold text with two surrounding underscore characters" (is (= "This is bold." - (bold/render "__This is bold.__" nil)))) + (bold/render "__This is bold.__" nil 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 + (bold/render "Hi, my name is **John**, what is __your name?__" nil 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 index 37c701b..c5779be 100644 --- a/test/clarktown/renderers/code_block_test.clj +++ b/test/clarktown/renderers/code_block_test.clj @@ -8,8 +8,8 @@ (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)))) + (code-block/render (slurp (io/file (io/resource "test/parsers/code_block.md"))) nil 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 + (code-block/render (slurp (io/file (io/resource "test/parsers/code_block_no_language.md"))) nil 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 index 35fb902..da0fc7c 100644 --- a/test/clarktown/renderers/empty_block_test.clj +++ b/test/clarktown/renderers/empty_block_test.clj @@ -6,5 +6,5 @@ (deftest empty-block-renderer-test (testing "Rendering an empty block" - (is (= (empty-block/render "" nil) + (is (= (empty-block/render "" nil nil) "")))) diff --git a/test/clarktown/renderers/heading_block_test.clj b/test/clarktown/renderers/heading_block_test.clj index 9c3386f..2196422 100644 --- a/test/clarktown/renderers/heading_block_test.clj +++ b/test/clarktown/renderers/heading_block_test.clj @@ -7,38 +7,38 @@ (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)))) + (heading-block/render "# This is a heading block." nil nil)))) (testing "Hashbang heading block that's a H2" (is (= "

    This is a heading block.

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

    This is a heading block.

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

    This is a heading block.

    " - (heading-block/render "#### This is a heading block." nil)))) + (heading-block/render "#### This is a heading block." nil nil)))) (testing "Hashbang heading block that's a H5" (is (= "
    This is a heading block.
    " - (heading-block/render "##### This is a heading block." nil))))) + (heading-block/render "##### This is a heading block." nil 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)))) + (heading-block/render "This is a heading block.\n=========" nil 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)))) + (heading-block/render "This is a \nheading block spanning multiple lines.\n========" nil 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)))) + (heading-block/render "This is a heading block.\n---------" nil 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 + (heading-block/render "This is a \nheading block spanning multiple lines.\n--------" nil 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 index db72682..9b23607 100644 --- a/test/clarktown/renderers/horizontal_line_block_test.clj +++ b/test/clarktown/renderers/horizontal_line_block_test.clj @@ -6,8 +6,8 @@ (deftest horizontal-line-block-renderer-test (testing "Creating a horizontal line" - (is (= (horizontal-line-block/render "***" nil) + (is (= (horizontal-line-block/render "***" nil nil) "
    ")) - (is (= (horizontal-line-block/render "---" nil) + (is (= (horizontal-line-block/render "---" nil nil) "
    ")))) diff --git a/test/clarktown/renderers/inline_code_test.clj b/test/clarktown/renderers/inline_code_test.clj index 2071b7f..b1b277e 100644 --- a/test/clarktown/renderers/inline_code_test.clj +++ b/test/clarktown/renderers/inline_code_test.clj @@ -7,8 +7,8 @@ (deftest inline-code-renderer-test (testing "Creating inline code text" (is (= "This is inline code." - (inline-code/render "`This is inline code.`" nil)))) + (inline-code/render "`This is inline code.`" nil 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 + (inline-code/render "This is regular text, mixed with `some inline code.`, and it's great." nil nil))))) \ No newline at end of file diff --git a/test/clarktown/renderers/italic_test.clj b/test/clarktown/renderers/italic_test.clj index 29e7811..e85ee36 100644 --- a/test/clarktown/renderers/italic_test.clj +++ b/test/clarktown/renderers/italic_test.clj @@ -7,12 +7,12 @@ (deftest italic-renderer-test (testing "Creating italic text with one surrounding asterisk character" (is (= "This is italic." - (italic/render "*This is italic.*" nil)))) + (italic/render "*This is italic.*" nil nil)))) (testing "Creating italic text with one surrounding underscore character" (is (= "This is italic." - (italic/render "_This is italic._" nil)))) + (italic/render "_This is italic._" nil 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 + (italic/render "Hi, my name is *John*, what is _your name?_" nil 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 index aa821e3..d17e070 100644 --- a/test/clarktown/renderers/link_and_image_test.clj +++ b/test/clarktown/renderers/link_and_image_test.clj @@ -6,18 +6,18 @@ (deftest link-renderer-test (testing "Creating a link" - (is (= (link-and-image/render "[This is a link](https://example.com)" nil) + (is (= (link-and-image/render "[This is a link](https://example.com)" nil nil) "This is a link")) - (is (= (link-and-image/render "[This-is-a-link](https://example.com)" nil) + (is (= (link-and-image/render "[This-is-a-link](https://example.com)" nil nil) "This-is-a-link")) - (is (= (link-and-image/render "[x] [label](link)" nil) + (is (= (link-and-image/render "[x] [label](link)" nil nil) "[x] label")) - (is (= (link-and-image/render "[ ] [label](link)" nil) + (is (= (link-and-image/render "[ ] [label](link)" nil nil) "[ ] label"))) (testing "Creating an image" - (is (= (link-and-image/render "![This is an image](https://example.com)" nil) + (is (= (link-and-image/render "![This is an image](https://example.com)" nil 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 index 33a7495..1065a85 100644 --- a/test/clarktown/renderers/quote_block_test.clj +++ b/test/clarktown/renderers/quote_block_test.clj @@ -6,5 +6,5 @@ (deftest quote-block-block-renderer-test (testing "Creating a quote block line" - (is (= (quote-block/render "> First line\n> second line" nil) + (is (= (quote-block/render "> First line\n> second line" nil 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 index 55493e0..cf08fc9 100644 --- a/test/clarktown/renderers/strikethrough_test.clj +++ b/test/clarktown/renderers/strikethrough_test.clj @@ -6,9 +6,9 @@ (deftest strikethrough-renderer-test (testing "Creating strikethrough text" - (is (= (strikethrough/render "~~This is strikethrough text.~~" nil) + (is (= (strikethrough/render "~~This is strikethrough text.~~" nil 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) + (is (= (strikethrough/render "Some other text, ~~This is strikethrough text.~~ And more text." nil nil) "Some other text, This is strikethrough text. And more text.")))) \ No newline at end of file -- cgit v1.2.3 From 77b065ba5cd57d4a02b0cae68d74412d1234554c Mon Sep 17 00:00:00 2001 From: Asko Nõmm Date: Fri, 22 Apr 2022 14:34:18 +0300 Subject: Make code more narrow --- src/clarktown/core.clj | 2 +- src/clarktown/engine.clj | 56 +++++++++++++++++++++++++----------------------- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/src/clarktown/core.clj b/src/clarktown/core.clj index a624275..2a3e3a1 100644 --- a/src/clarktown/core.clj +++ b/src/clarktown/core.clj @@ -27,4 +27,4 @@ ([markdown given-parsers] (render markdown given-parsers correctors/default-correctors)) ([markdown given-parsers given-correctors] - (engine/render markdown given-parsers given-correctors))) \ No newline at end of file + (engine/render markdown given-parsers given-correctors))) diff --git a/src/clarktown/engine.clj b/src/clarktown/engine.clj index 17e5867..2dc9089 100644 --- a/src/clarktown/engine.clj +++ b/src/clarktown/engine.clj @@ -39,32 +39,34 @@ "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)))))) + (let [above-correctors (:empty-line-above? correctors) + below-correctors (:empty-line-below? correctors)] + (map-indexed + (fn [index line] + (let [add-line-above? (some #(true? (% lines line index)) above-correctors) + add-line-below? (some #(true? (% lines line index)) 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))) + lines))) (defn- correct-markdown @@ -137,4 +139,4 @@ (string/split #"\n\n") stitch-code-blocks) parsed-blocks (parse-blocks blocks given-parsers given-correctors)] - (string/join "\n\n" parsed-blocks))) \ No newline at end of file + (string/join "\n\n" parsed-blocks))) -- cgit v1.2.3 From ee7996bcdd2010c314c8efd17372bdbb8d24a8bd Mon Sep 17 00:00:00 2001 From: Asko Nõmm Date: Fri, 22 Apr 2022 14:34:31 +0300 Subject: Remove parent info from child items --- src/clarktown/renderers/list_block.clj | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/clarktown/renderers/list_block.clj b/src/clarktown/renderers/list_block.clj index 27ca72a..8632643 100644 --- a/src/clarktown/renderers/list_block.clj +++ b/src/clarktown/renderers/list_block.clj @@ -57,13 +57,15 @@ (->> 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)))))) + (let [new-item {:id (:id item) + :value (:value item)}] + (if (= (:id i) (:parent item)) + (if (:items i) + (assoc i :items (concat (:items i) [new-item])) + (assoc i :items [new-item])) + (if (:items i) + (assoc i :items (add-to-parent (:items i) item)) + i))))))) (defn compose-item-tree -- cgit v1.2.3 From c3cbae915e1858c2e0b4c013205753fd6e1f548e Mon Sep 17 00:00:00 2001 From: Asko Nõmm Date: Fri, 22 Apr 2022 22:09:01 +0300 Subject: Implement Kaocha --- bin/kaocha | 2 ++ project.clj | 2 ++ 2 files changed, 4 insertions(+) create mode 100755 bin/kaocha diff --git a/bin/kaocha b/bin/kaocha new file mode 100755 index 0000000..243e0bc --- /dev/null +++ b/bin/kaocha @@ -0,0 +1,2 @@ +#!/usr/bin/env sh - +lein kaocha "$@" diff --git a/project.clj b/project.clj index 5bbc717..26129a9 100644 --- a/project.clj +++ b/project.clj @@ -8,4 +8,6 @@ :dependencies [[org.clojure/clojure "1.11.1"]] :plugins [[lein-auto "0.1.3"] [lein-cloverage "1.2.3"]] + :profiles {:kaocha {:dependencies [[lambdaisland/kaocha "1.65.1029"]]}} + :aliases {"kaocha" ["with-profile" "+kaocha" "run" "-m" "kaocha.runner"]} :repl-options {:init-ns clarktown.core}) -- cgit v1.2.3 From 2b530b7c4e53596ce17e24f3fab42ab2f3c52ba8 Mon Sep 17 00:00:00 2001 From: Asko Nõmm Date: Fri, 22 Apr 2022 22:09:09 +0300 Subject: Implement tests for correctors --- src/clarktown/correctors/code_block.clj | 24 +++++++------- .../correctors/atx_heading_block_test.clj | 30 ++++++++++++++++++ test/clarktown/correctors/code_block_test.clj | 37 ++++++++++++++++++++++ 3 files changed, 79 insertions(+), 12 deletions(-) create mode 100644 test/clarktown/correctors/atx_heading_block_test.clj create mode 100644 test/clarktown/correctors/code_block_test.clj diff --git a/src/clarktown/correctors/code_block.clj b/src/clarktown/correctors/code_block.clj index e767390..fcda19b 100644 --- a/src/clarktown/correctors/code_block.clj +++ b/src/clarktown/correctors/code_block.clj @@ -5,23 +5,23 @@ (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) "")))) + (let [occurences (->> (take index lines) + (filter #(string/starts-with? (string/trim %) "```")) + count)] + (and (string/starts-with? (string/trim line) "```") + (> index 0) + (even? occurences) + (not (= (-> (nth lines (- index 1)) + string/trim) ""))))) (defn empty-line-below? [lines line index] - (and (= (string/trim line) "```") + (and (string/starts-with? (string/trim line) "```") (< index (- (count lines) 1)) (->> (take index lines) - (filter #(= (string/trim %) "```")) + (filter #(string/starts-with? (string/trim %) "```")) count - even?) + odd?) (not (= (-> (nth lines (+ index 1)) - string/trim) "")))) \ No newline at end of file + string/trim) "")))) diff --git a/test/clarktown/correctors/atx_heading_block_test.clj b/test/clarktown/correctors/atx_heading_block_test.clj new file mode 100644 index 0000000..3ece304 --- /dev/null +++ b/test/clarktown/correctors/atx_heading_block_test.clj @@ -0,0 +1,30 @@ +(ns clarktown.correctors.atx-heading-block-test + (:require + [clojure.test :refer [deftest testing is]] + [clarktown.correctors.atx-heading-block :as corrector])) + + +(deftest atx-heading-block-corrector + (testing "Empty line above" + (let [line "# Hello" + lines ["Some text goes here" line] + index 1] + (is (true? (corrector/empty-line-above? lines line index))))) + + (testing "No empty line above" + (let [line "# Hello" + lines ["Some text goes here" "\n" line] + index 2] + (is (false? (corrector/empty-line-above? lines line index))))) + + (testing "Empty line below" + (let [line "# Hello" + lines [line "Some text goes here"] + index 0] + (is (true? (corrector/empty-line-below? lines line index))))) + + (testing "No empty line below" + (let [line "# Hello" + lines [line "\n" "Some text goes here"] + index 0] + (is (false? (corrector/empty-line-below? lines line index)))))) diff --git a/test/clarktown/correctors/code_block_test.clj b/test/clarktown/correctors/code_block_test.clj new file mode 100644 index 0000000..16711da --- /dev/null +++ b/test/clarktown/correctors/code_block_test.clj @@ -0,0 +1,37 @@ +(ns clarktown.correctors.code-block-test + (:require + [clojure.test :refer [deftest testing is]] + [clarktown.correctors.code-block :as corrector])) + + +(deftest code-block-corrector + (testing "Empty line above" + (let [line "```clojure" + lines ["Some text goes here" line "some code here" "```"] + index 1] + (is (true? (corrector/empty-line-above? lines line index))))) + + (testing "No empty line above" + (let [line "```" + lines ["Some text goes here" "\n" line "some code" "```"] + index 2] + (is (false? (corrector/empty-line-above? lines line index))))) + + (testing "Empty line below" + (let [line "```" + lines ["Some text goes here" line "some code" line "some text"] + index 3] + (is (true? (corrector/empty-line-below? lines line index))))) + + (testing "No empty line below" + (let [line "```" + lines ["Some text goes here" line "some code" line "\n" "some text"] + index 3] + (is (false? (corrector/empty-line-below? lines line index))))) + + (testing "No empty line below when ending with code block" + (let [line "```" + lines ["Some text goes here" line "some code" line] + index 3] + (is (false? (corrector/empty-line-below? lines line index)))))) + -- cgit v1.2.3 From 1cf289f67927c7e06cba818c00383ce1aae8ad67 Mon Sep 17 00:00:00 2001 From: Asko Nõmm Date: Sat, 23 Apr 2022 01:33:27 +0300 Subject: No need for `let` here --- src/clarktown/correctors/code_block.clj | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/clarktown/correctors/code_block.clj b/src/clarktown/correctors/code_block.clj index fcda19b..9b2f0f3 100644 --- a/src/clarktown/correctors/code_block.clj +++ b/src/clarktown/correctors/code_block.clj @@ -5,14 +5,14 @@ (defn empty-line-above? [lines line index] - (let [occurences (->> (take index lines) - (filter #(string/starts-with? (string/trim %) "```")) - count)] - (and (string/starts-with? (string/trim line) "```") - (> index 0) - (even? occurences) - (not (= (-> (nth lines (- index 1)) - string/trim) ""))))) + (and (string/starts-with? (string/trim line) "```") + (> index 0) + (->> (take index lines) + (filter #(string/starts-with? (string/trim %) "```")) + count + even?) + (not (= (-> (nth lines (- index 1)) + string/trim) "")))) (defn empty-line-below? -- cgit v1.2.3 From 30432011eefdbf529021dcb329278c28ff72bc9b Mon Sep 17 00:00:00 2001 From: Asko Nõmm Date: Sat, 23 Apr 2022 01:38:38 +0300 Subject: Improve list block renderer --- src/clarktown/renderers/list_block.clj | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/clarktown/renderers/list_block.clj b/src/clarktown/renderers/list_block.clj index 8632643..0c07aaa 100644 --- a/src/clarktown/renderers/list_block.clj +++ b/src/clarktown/renderers/list_block.clj @@ -20,8 +20,7 @@ (fn [line] {:id (random-uuid) :indent-n (string->indent-n line) - :value (-> line - string/trim)})))) + :value (string/trim line)})))) (defn find-parent-id @@ -57,8 +56,7 @@ (->> items (mapv (fn [i] - (let [new-item {:id (:id item) - :value (:value item)}] + (let [new-item (select-keys item [:id :value])] (if (= (:id i) (:parent item)) (if (:items i) (assoc i :items (concat (:items i) [new-item])) @@ -78,8 +76,7 @@ result (let [item (first items) parent (:parent item) - new-item {:id (:id item) - :value (:value item)}] + new-item (select-keys item [:id :value])] (recur (if parent (add-to-parent result item) (concat result [new-item])) -- cgit v1.2.3 From 905e923ede7f0ef433c40561b478cf33a8766700 Mon Sep 17 00:00:00 2001 From: Asko Nõmm Date: Sat, 23 Apr 2022 01:41:28 +0300 Subject: Kaocha updates --- .github/workflows/tests.yml | 2 +- tests.edn | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 tests.edn diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 937395a..1a97df8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -28,4 +28,4 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} - name: "Run tests" - run: lein test + run: bin/kaocha diff --git a/tests.edn b/tests.edn new file mode 100644 index 0000000..9d8d845 --- /dev/null +++ b/tests.edn @@ -0,0 +1 @@ +#kaocha/v1 {} -- cgit v1.2.3 From dc921cbe1c786995a8670efd9f5556ee9df064f9 Mon Sep 17 00:00:00 2001 From: Asko Nõmm Date: Sat, 23 Apr 2022 02:42:52 +0300 Subject: Add docstrings --- src/clarktown/correctors/atx_heading_block.clj | 12 +++++++++++- src/clarktown/correctors/code_block.clj | 10 ++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/clarktown/correctors/atx_heading_block.clj b/src/clarktown/correctors/atx_heading_block.clj index a792572..9ff789e 100644 --- a/src/clarktown/correctors/atx_heading_block.clj +++ b/src/clarktown/correctors/atx_heading_block.clj @@ -4,6 +4,11 @@ (defn empty-line-above? + "Determines whether there's a need for an empty new line + above the `line` at the current `index`. In the case of a + ATX heading block that starts with the `#` character, if + there's no empty newline above, we need to create one, and + so this function must then return `true`." [lines line index] (and (string/starts-with? (string/trim line) "#") (> index 0) @@ -12,8 +17,13 @@ (defn empty-line-below? + "Determines whether there's a need for an empty new line + below the `line` at the current `index`. In the case of a + ATX heading block that starts with the `#` character, if + there's no empty newline below, we need to create one, and + so this function must then return `true`." [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 + string/trim) "")))) diff --git a/src/clarktown/correctors/code_block.clj b/src/clarktown/correctors/code_block.clj index 9b2f0f3..73989fe 100644 --- a/src/clarktown/correctors/code_block.clj +++ b/src/clarktown/correctors/code_block.clj @@ -4,6 +4,11 @@ (defn empty-line-above? + "Determines whether there's a need for an empty new line + above the `line` at the current `index`. In the case of a + code block, which starts with three backticks (```), if there's + no empty newline above, we need to create one, and so this + function must then return `true`." [lines line index] (and (string/starts-with? (string/trim line) "```") (> index 0) @@ -16,6 +21,11 @@ (defn empty-line-below? + "Determines whether there's a need for an empty new line + below the `line` at the current `index`. In the case of a + code block, which ends with three backticks (```), if there's + no empty newline above, we need to create one, and so this + function must then return `true`." [lines line index] (and (string/starts-with? (string/trim line) "```") (< index (- (count lines) 1)) -- cgit v1.2.3