An extensible and modular zero-dependency, pure-Clojure Markdown parser.
[com.github.askonomm/clarktown "2.0.1"]
com.github.askonomm/clarktown {:mvn/version "2.0.1"}
(ns myapp.core
(:require [clarktown.core :as clarktown]))
(clarktown/render "**Hello, world!**") ; => <strong>Hello, world!</strong>
At its core, Clarktown is nothing more than a collection of parsers that collectively make up a Markdown parser. Those parsers are:
bold
code-block
empty-block
heading-block
horizontal-line-block
inline-code
italic
link-and-image
list-block
paragraph-block
quote-block
strikethrough
So whenever you call clarktown.core/render
, those parsers are applied to the content you give it, and are defined collectively in
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:
(clarktown.core/render "**Hello, world!**" [..parsers-go-here..])
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.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 content you give it into blocks. Each paragraph would be its own block, each quote or heading would be its own block, and so on, basically whenever you create a newline separation between text, that creates a new block.
And so if you create a parser that does something with the entirety of a block, you want it to have a matcher. A function that returns a boolean true
when it detects that it's the kind of block that the parser should run on. And then there's the renderer, the parser does its magical transformations
in the renderer function.
There are also inline parsers, such as for bold or italic text, and those do not need a matcher because they are intended to be mixed with block parsers as a supplement rather than the main thing. Case in point: code blocks. You want a code block to have a block-level parser that parses it, but you don't want a code block to be able to turn text bold or italic, so you wouldn't pass it those inline parsers, whereas to a quote block you would.
(defn is?
"Determines whether the given block is a X block or not."
[block]
true)
(defn render
"Renders the X block."
[block parsers]
"")
In the above example the is?
function is the matcher, and the render
function makes sure the given block
gets rendered into the sort of HTML that
it should.
You may also notice that the render
function takes in a second argument called parsers
, and that's the list of parsers passed down to
Clarktown, so if you needed to run the whole Clarktown on just a subset of your block or something, you could call clarktown.parser/parse
on that piece
and pass those parsers
to it. In most cases, you probably don't need to do that.
(defn render
"Renders all occurring italic text as italic."
[block _]
(loop [block block
matches (-> (re-seq #"_.*?_" block)
distinct)]
(if (empty? matches)
block
(let [match (first matches)
value (subs match 1 (- (count match) 1))
replacement (str "<em>" value "</em>")]
(recur (string/replace block match replacement)
(drop 1 matches))))))
In the above example there is no matcher function, just the render
function. This render function goes over the entire block
and turns all string instances
that match _some text goes here_
into <em>some text goes here</em>
.
Now that you have a new parser (or a set of ones) you can add it to Clarktown. Remember that the second argument of clarktown.core/render
takes in a
vector of maps, each map representing one collection. And so to register you could simply conj
to the default ones, like so:
(ns myapp.core
(:require
[clarktown.core :as clarktown]
[clarktown.renderers :refer [parsers]]))
(def my-parser
{:matcher heading-block/is?
:renderers [bold/render
italic/render
inline-code/render
strikethrough/render
link-and-image/render
heading-block/render]})
(clarktown/render "**Hello, world!**" (conj parsers my-parser))
And then my-parser
will run if the matcher returns true
, and it will run through all the renderer functions you give it, in order from top to bottom.
Can you improve this documentation? These fine people already did:
Asko Nõmm, Asko Nomm & Asko NõmmEdit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close